11
ParagraphIndex — the stable integer address space
ParagraphIndex(rl, include_tables=True) len / iter / index[pid] -> ParagraphRef .clauses .paragraph(pid) .find(needle, ignore_case) .render(para_ids, with_clause_labels, skip_empty) .manifest() .fingerprint() .locate(...) .apply(...) .refresh() Clause numbers are the right address for a lawyer and the wrong one for a machine. Ids are positions in rl.paragraphs() and survive every text edit.
examples/11_paragraph_index.py — ran in 0.25s · exit 0
Console output
============================================================================
11 · ParagraphIndex
============================================================================
--- the index itself ---
92 paragraphs, 54 numbered clauses
fingerprint: 902892107d00df8a
--- ParagraphRef — every field ---
para_id = 19
text = '3.2 Invoicing. Provider shall invoice Customer annually i'
style = None
numbered = False
in_table = False
table_index = None
clause_label = '3.2'
level = 2
is_empty = False
--- a paragraph inside a table ---
[65] in_table=True table_index=0 text='Signature'
--- include_tables=False — table cells drop out of the address space ---
with tables : 92
without tables: 84
--- find(needle, ignore_case) — folded on both sides ---
'thirty (30) days' -> [19, 25, 26, 89]
'("Agreement")' straight -> [3] <- the document has curly quotes
'PROVIDER' ignore_case -> [4, 6, 10, 14, 16, 19] ...
--- paragraph(pid) — the live python-docx object ---
Paragraph | 3.2 Invoicing. Provider shall invoice Customer annu
--- render() — the cacheable prefix a model reads ---
<<clause 3>>
[17] 3. Fees and Payment
<<clause 3.1>>
[18] 3.1 Fees. Customer shall pay the fees set forth in the applicable Order Form (“Fees”). Unless otherwise specified, Fees are quoted and payable in U.S. dollars.
<<clause 3.2>>
[19] 3.2 Invoicing. Provider shall invoice Customer annually in advance unless a different billing frequency is specified in the Order Form. Payment is due within thirty (30) days of the invoice date.
<<clause 3.3>>
[20] 3.3 Late Payment. Amounts not paid when due shall accrue interest at the lesser of 1.5% per month or the maximum rate permitted by applicable law, and Provider may suspend access to the Services for accounts more than fifteen (15) days past due, upon prior written notice.
--- render options ---
with_clause_labels=False:
[18] 3.1 Fees. Customer shall pay the fees set forth in the applicable Order Form (“Fees”). Unless otherwise specified, Fees are quoted and payable in U.S. dollars.
[19] 3.2 Invoicing. Provider shall invoice Customer annually in advance unless a different billing frequency is specified in the Order Form. Payment is due within thirty (30) days of the invoice date.
skip_empty=True -> 198 lines
skip_empty=False -> 202 lines
--- manifest() — routing input, one line per clause ---
1 paras 7-7 Definitions
1.1 paras 8-8 “Authorized Users” means Customer’s employees and independen
1.2 paras 9-9 “Customer Data” means all electronic data, text, files, or o
1.3 paras 10-10 “Documentation” means Provider’s user guides and technical d
1.4 paras 11-11 “Order Form” means an ordering document specifying the Servi
1.5 paras 12-12 “Subscription Term” means the period of Customer’s subscript
--- locate() — resolve a quote without applying anything ---
hit : [(159, 175)]
every hit: [(0, 8), (128, 136)]
refusal : Rejection.TARGET_NOT_FOUND
--- refresh() — re-index after structural work ---
ids survive a text edit: thin forty-five (45) days of the invoice date.
after inserting a paragraph, len(index) is stale: 92
refresh() -> 93 paragraphs, new fingerprint 70feecc52601472b
Source
"""11 · ParagraphIndex — the stable integer address space.
ParagraphIndex(rl, include_tables=True)
len / iter / index[pid] -> ParagraphRef
.clauses .paragraph(pid) .find(needle, ignore_case)
.render(para_ids, with_clause_labels, skip_empty)
.manifest() .fingerprint() .locate(...) .apply(...) .refresh()
Clause numbers are the right address for a lawyer and the wrong one for a
machine. Ids are positions in rl.paragraphs() and survive every text edit.
"""
from _shared import banner, fresh, section
from docx_redline import ParagraphIndex, RedlineEdit
banner("11 · ParagraphIndex")
rl = fresh()
index = ParagraphIndex(rl)
section("the index itself")
print(f" {len(index)} paragraphs, {len(index.clauses)} numbered clauses")
print(f" fingerprint: {index.fingerprint()}")
section("ParagraphRef — every field")
ref = index[19]
for field in (
"para_id",
"text",
"style",
"numbered",
"in_table",
"table_index",
"clause_label",
"level",
"is_empty",
):
value = getattr(ref, field)
print(
f" {field:<13} = {str(value)[:58]!r}" if field == "text" else f" {field:<13} = {value!r}"
)
section("a paragraph inside a table")
tabled = next(r for r in index if r.in_table)
print(
f" [{tabled.para_id}] in_table={tabled.in_table} table_index={tabled.table_index} "
f"text={tabled.text!r}"
)
section("include_tables=False — table cells drop out of the address space")
print(" with tables :", len(ParagraphIndex(fresh())))
print(" without tables:", len(ParagraphIndex(fresh(), include_tables=False)))
section("find(needle, ignore_case) — folded on both sides")
print(" 'thirty (30) days' ->", index.find("thirty (30) days"))
print(
" '(\"Agreement\")' straight ->",
index.find('("Agreement")'),
" <- the document has curly quotes",
)
print(" 'PROVIDER' ignore_case ->", index.find("PROVIDER", ignore_case=True)[:6], "...")
section("paragraph(pid) — the live python-docx object")
print(" ", type(index.paragraph(19)).__name__, "|", rl.text_of(index.paragraph(19))[:52])
section("render() — the cacheable prefix a model reads")
print(index.render(para_ids=range(17, 21)))
section("render options")
print(" with_clause_labels=False:")
print(" ", index.render(para_ids=range(18, 20), with_clause_labels=False).replace("\n", "\n "))
print(f" skip_empty=True -> {len(index.render().splitlines())} lines")
print(f" skip_empty=False -> {len(index.render(skip_empty=False).splitlines())} lines")
section("manifest() — routing input, one line per clause")
print("\n".join(" " + line for line in index.manifest().splitlines()[:6]))
section("locate() — resolve a quote without applying anything")
print(" hit :", index.locate(19, "thirty (30) days"))
print(" every hit:", index.locate(6, "Provider", occurrence=0))
print(" refusal :", index.locate(19, "not in this paragraph"))
section("refresh() — re-index after structural work")
index.apply([RedlineEdit(19, "thirty (30) days", "forty-five (45) days")])
print(" ids survive a text edit:", index[19].text[-46:])
rl.insert_paragraph_after(rl.find_paragraph(contains="3.4 Taxes"), "3.5 Currency.")
print(" after inserting a paragraph, len(index) is stale:", len(index))
index.refresh()
print(" refresh() ->", len(index), "paragraphs, new fingerprint", index.fingerprint())
What it wrote
This example prints its result rather than saving a document — read the
console output beside it.