Skip to content
Extract Text with List Numbering

Extract Text with List Numbering

A numbered paragraph in a Word document does not contain its number. The paragraph stores only a w:numPr pointing at a numbering definition, and Word paints “1.”, “2.”, “a)” from that definition when it lays the page out. Nothing in the run text says which item this is. So plain extraction returns the item content with no marker in front of it, and a nine-item procedure comes back as nine unnumbered lines.

ExtractTextOptions.WithNumbering puts them back by counting the items itself.

OptionResult for a numbered item
Text(), or TextWithOptions with WithNumbering: falseOverview
WithNumbering: true1.Overview
WithNumbering: true, NumberingIndent: " "1. Overview

NumberingIndent is the separator written between the marker and the text, and it defaults to empty, which glues them together. Set it to a space or a tab unless you are parsing the output yourself.

Extracting with markers

doc, err := document.Open("numbered_list.docx")
if err != nil {
    panic(err)
}
defer doc.Close()

text := doc.ExtractText().TextWithOptions(document.ExtractTextOptions{
    WithNumbering:   true,
    NumberingIndent: " ",
})
fmt.Println(text)

Everything else about the extraction is unchanged, so Items and the formatting information described in Extract Text from a Document are still there. WithNumbering only affects the flattened string.

What it costs

The numbering is reconstructed, not read. ExtractText walks Document.Paragraphs() and resolves each paragraph’s numbering definition, then walks the whole paragraph list a second time to build a per-level occurrence count for the document. TextWithOptions then scans that recorded list once per paragraph it emits, looking for the entry whose paragraph pointer matches, and increments a counter keyed on abstract numbering id and level.

Two consequences. The work happens inside ExtractText, so a document with thousands of paragraphs pays for the numbering analysis even when you go on to call plain Text(). And the per-paragraph lookup is a linear scan of a list as long as the document, so extraction time grows faster than the document does. For a report or a contract this is invisible; for a very long generated document it is worth measuring before it surprises you.

Limitations

Bullets get nothing. WithNumbering formats a marker through the level’s numFmt, and the bullet format has no case in that formatter, so a bulleted item comes back with the NumberingIndent in front of it and no bullet character. Only list formats that produce a number or a letter - decimal, roman, alphabetic - are reconstructed. See Bullets and Numbering for how the definitions themselves are built.

Numbering inherited from a paragraph style is not reconstructed. The lookup reads w:numPr from the paragraph’s own properties only. A paragraph that gets its numbering from the style it references, which is how Word’s built-in list styles work, is treated as not numbered and comes back bare.

TextWithOptions is not repeatable on the same DocText. The counters it consumes live on the DocText and are decremented as markers are emitted, so a second call on the same value returns the text with no numbering at all. Call ExtractText again for each render you need.

Multi-level markers double their separator. A level whose lvlText is %1.%2. renders as 1..1. rather than 1.1., and a three-level %1.%2.%3. renders as 1.4..1.. The extra character comes from how the template is split before substitution. Strip it out downstream if the exact marker matters.

A level’s w:start is ignored. Counting always begins at 1 for the first item at each level, so a list defined to start at 5 extracts as if it started at 1.

Run the example

The example opens numbered_list.docx, prints every extracted item the same way the plain extraction example does, then prints the flattened text with WithNumbering and a single-space indent.

git clone https://github.com/unidoc/unioffice-examples.git
cd unioffice-examples/document/text_extraction_with_numbering
go run main.go

If this is your first time using UniOffice, follow the getting started guide to create an API key and set up your development environment.

View the full source

Sample output

The tail of the run, showing the reconstructed markers and the doubled separator on the nested levels:

FLATTENED:
Video provides a powerful way to help you prove your point.
1. Overview
1..1. Dr. Johnson's Vitae
You can also type a keyword.
1..2. Team List
John
Bob
Bill
Frank
1..3. Motivations
Lorem ipsum dolor sit amet.
1..4. Benefits
Nunc viverra imperdiet enim.
1.4..1. Aaa
1.4..2. Bbb
1.4..3. CCC
2. Synopsis
Last updated on