Inspect Existing Notes
Adding a note to a document you opened uses the same two calls as building one from scratch, but you are working around content that is already there. Before anchoring anything it is worth knowing what the file holds, because the note ids in a document Word wrote are not the numbers printed on the page and the count you get back is not the count you can see.
| What you want | Call |
|---|---|
| Whether the document has a notes part at all | doc.HasFootnotes(), doc.HasEndnotes() |
| Every entry in that part, separators included | doc.Footnotes(), doc.Endnotes() |
| One note by id | doc.Footnote(id), doc.Endnote(id) |
| The anchor points in the body | run.IsFootnote(), run.IsEndnote() |
The first two are what you reach for when reporting on a file. The last is what you reach for when you need the paragraph a note is attached to, since removal and any edit to the mark itself happen through the paragraph, not the note.
Inspecting, then adding
doc, err := document.Open("footnotes_endnotes.docx")
if err != nil {
log.Fatalf("error opening document: %s", err)
}
defer doc.Close()
if doc.HasFootnotes() {
fmt.Printf("Document has %02d footnotes.\n", len(doc.Footnotes()))
}
para := doc.Paragraphs()[0]
para.AddFootnote("Anchored at the end of the first paragraph.")
doc.SaveToFile("out.docx")The HasFootnotes guard is doing real work. Footnotes() reads the part
without checking whether it exists, so on a document with no footnotes the call
panics rather than returning an empty slice. The same holds for Endnotes().
The count that comes back includes the separator and continuation separator entries, which are definitions of the rule Word draws above the notes rather than notes themselves. Run this against the sample file and it reports four footnotes and three endnotes for a document containing two footnotes and one endnote.
AddFootnote works the same on an opened document as on a new one. If the file
already has a footnotes part the new note takes an id one past the highest one
in it, so nothing collides; if it does not, the part, the styles and the
separators are created first. Either way the ids already in the file are left
alone, which is what keeps the existing marks pointing where they did.
Where the mark lands
At the end of the paragraph, always. AddFootnote appends a run, so on a
paragraph you did not build the mark goes after the last run in it, not next to
the word you had in mind. Ordering the calls while building the paragraph is the
only positioning control there is.
InsertRunBefore and InsertRunAfter do not help here. They place an empty run
at a chosen point, but there is no public call that turns a run into an anchor;
the element carrying the note id is written only by AddFootnote and
AddEndnote. Pointing a mark at text in the middle of an existing paragraph
means rebuilding the paragraph in the order you want.
Limitations
doc.Paragraphs() walks the body and the paragraphs inside table cells,
including nested tables, so a note anchored in a table is found by a scan and
renumbered when another note is removed. Headers and footers are not in that
walk. Their paragraphs come from Header.Paragraphs() and Footer.Paragraphs()
and are outside every document-level scan, which means a mark placed there is
invisible to the code that keeps ids consistent.
IsFootnote and IsEndnote look only at the first inner element of a run. A
reference produced by AddFootnote is the only thing in its run, so it is
always found, but a run from another producer that carries the reference behind
some other content is reported as not being a note.
There is no call that moves an existing mark or retargets it at a different note. Deleting it and adding a new one is the only route.
Run the example
The example opens footnotes_endnotes.docx and reports how many footnote and
endnote entries it holds, guarding each read with HasFootnotes and
HasEndnotes. It writes no file; the counts go to stdout, and they include the
two separator entries in each part.
git clone https://github.com/unidoc/unioffice-examples.git
cd unioffice-examples/document/endnotes_footnotes
go run main.goIf this is your first time using UniOffice, follow the getting started guide to create an API key and set up your development environment.