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.
| Option | Result for a numbered item |
|---|---|
Text(), or TextWithOptions with WithNumbering: false | Overview |
WithNumbering: true | 1.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.goIf 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