InDesign Scripting: Why are Endnotes so Hard to Decipher?
June 03, 2023 | Tips | en
Endnotes were introduced in InDesign 13. Unlike footnotes, which are rendered in virtual containers attached to the host TextFrame
, endnotes flow into extra containers of the EndnoteTextFrame
class (a subclass of the TextFrame
object).
However, the Endnote
component is only browsable from Stories
(or Tables
, or Cells
) that own endnote references. For example, if myStory
denotes the main story of your document, myStory.endnotes
collects the associated set of Endnotes
. But if you select a particular reference (or some Text
supposed to contain one or several endnote references), you have no access to the underlying Endnote
instances. Indeed, Text.endnotes
is not implemented—while Text.footnotes
is.
On the other hand, because both footnote and endnote numbers are based on the special character U+0004
, there seems to be no direct way to discriminate them within a sample text:
It follows that the only method of extracting and parsing endnotes involves a global traversal of all objects that point to them. It's this strategy that I implemented in the $$.Dom.Endnote module of IdExtenso. In that example, the desired feature is only to get the numeral of the incoming Endnote
specifier, so there's no need to dissect the remote content of the note(s). In some InDesign scripts, however, you want to tackle the problem and understand how endnotes work behind the scenes.
Endnotes versus EndnoteRanges
The Endnote
component behaves like a proxy regarding text data. In this respect it resembles the Footnote
object (the former being a little simpler though). Basically, from a given Endnote
myEnd, you can access Text
collections like myEnd.texts
, .characters
, .words
, .insertionPoints
, .textStyleRanges
… Strangely, the object does not expose .paragraphs
or .contents
properties, or anything that involves PageItems
. Perhaps the reasoning adopted by the API designers is this: in contrast to footnotes, endnotes are rendered in another place (remote textframe) and then require a specific link to a distinct entity.
That distinct entity is the object EndnoteRange
, returned by the property myEnd.endnoteTextRange
. The job of this component is to decouple the logical object (Endnote
) from its physical realization in the layout. But surprisingly, EndnoteRanges
are not the kind of things you might actually select (like Words
or TextStyleRanges
). Although they only occur in EndnoteTextFrames
, there is no direct connection from such a frame and the endnote ranges it contains.
So, here again, some contortion is needed to extract the information, typically using the code myEndnoteFrame.texts[0].endnoteRanges
(which, by the way, tends to erroneously return an extra range that actually belongs to the next threaded frame!)
The big problem with EndnoteRanges
is that they can be mixed with non-endnote text. The square brackets ⎡ and ⎦ shown in the below figure reflect special characters that InDesign inserts in the story to keep track of the entry/exit points of a range. Thus we can identify that the part highlighted in yellow escapes the legal content of the endnotes:
Also, as you can see, the endnote #44 has lost its number and its paragraph break. The content of an EndnoteTextFrame
is freely editable because it is, in the background, managed exactly like a traditional text container. And a very unfortunate choice was made to encode the opening and closing brackets: both rely on the very same Unicode character, U+FEFF
(ZERO WIDTH NO-BREAK SPACE). Which means that you cannot analyze a fragment of text in order to extract its particular ranges, without having an exact count of all the markers that preceded it in the entire “endnote” story.
Note. — There is no actual EndnoteStory object, but you can check whether a specific Story
has its isEndnoteStory
property turned on.
In summary
The Endnotes
collection embodies the references from the source text (the one that points to the endnotes), while the EndnoteRanges
collection reflects the same data but from the perspective of the story that articulates the endnote content. A round-trip gateway makes it possible to go from an Endnote
instance to the corresponding EndnoteRange
, using the respective properties endnoteTextRange
vs. sourceEndnote
.
I haven't detailed the few properties exposed by the EndnoteRange
class. They help recover start and end indices (pure integers) in the endnote story, so real InsertionPoints
could be regenerated in order to make sense of the concrete text ranges. This approach works great as long as you parse the endnotes of the entire document.
But the API doesn't offer more local pointers: EndnoteRanges
are not easily translatable into actual Text from a partial selection (or from an EndnoteTextFrame
) while U+FEFF
markers create ambiguities which complicate the task. A very similar problem arises on the side of the source text, since the markers U+0004
both denote Footnote
and Endnote
references.