Skip to content
Find and Edit Content

Find and Edit Content

These guides are about documents that already exist. Building one from nothing is a matter of calling AddParagraph in the right order; changing one means first finding the part you want, which is a different problem.

doc.Paragraphs() and doc.Tables() each answer half of it, and neither preserves the ordering between them. doc.Nodes() returns both in document order behind one handle, document.Node:

nodes := doc.Nodes()

headings := nodes.FindNodeByStyleName("heading 1")
nodes.ReplaceText("{{NAME}}", "Alice")

nodes has to be a variable. Nodes is returned by value and every method on it takes a pointer receiver, so doc.Nodes().ReplaceText(...) does not compile.

The shape of the tree

The top level is the document body: paragraphs and tables in order, with structured document tag and custom XML wrappers already unwrapped. A table’s Children are the paragraphs in its cells; a paragraph’s Children are its runs. A table nested in a cell is a child of the outer table, not a top-level node. When the body carries section properties, one extra node wrapping them is appended at the end.

Node.X() returns the element behind the handle, and it is worth knowing all three cases before writing a type switch:

switch t := node.X().(type) {
case *document.Paragraph:
case *document.Table:
case document.Run:
}

Run is a value, not a pointer, and Node’s own type comment mentions only paragraphs and tables, so a switch written from that comment misses runs silently. Run nodes appear only when you ask FindNodeByCondition for them.

Choosing a finder

FindNodeByStyleId and FindNodeByStyleName select on style, which is how you find headings. Watch the argument: Word’s Heading 1 has the id Heading1 and the name heading 1, lowercase, and both are matched exactly.

FindNodeByText and FindNodeByRegexp select on content. Both work off Node.Text(), which puts a newline after each run, so a sentence Word split across runs does not read back as one string.

FindNodeByCondition takes a predicate for anything else, usually a type test. It is the only finder that does not always recurse: its second argument decides whether children are tested too. Passing false restricts the search to the level you called it on, which is what you want for document-level structure. Passing true flattens every descendant into the same result slice, runs included.

Reading, writing, deleting

A Node has Text() for reading, ReplaceText and ReplaceTextByRegexp for substitution, and Remove() for deletion. Clear() looks like it belongs in that list and does not; it detaches the node’s own reference and leaves the document alone.

Replacement and deletion both work through the same handles, which is why a find-and-replace across a document reaches text inside table cells without any special casing, and why splitting a document at its headings or recombining parts of two files is a short program rather than a schema exercise.

Where to look

GuideCovers
Find ContentWalking the node tree and identifying paragraphs and tables.
Edit a DocumentOpening a document and changing its existing content.
Find and Replace TextLiteral and regular expression substitution.
Remove ContentDeleting elements, for example every table.
Split by HeadingUsing a heading style as a divider and writing one file per section.
Copy between DocumentsTaking elements from two files into a new one.
Merge DocumentsAppending whole documents together.
Last updated on