24
Reading the document's structure
ClauseTree(body) Clause parse_clause(p) iter_references(text) outline(tree, body_chars=240) render_outline(tree, body_chars=400, clauses=None) detect_strategy(body, tree=None) iter_blocks(body, tree=None) render_document(body, tree=None) segment_document(body, budget_tokens=25_000, strategy="auto", tree=None) Read-only: nothing here writes a revision.
examples/24_structure_and_segments.py — ran in 0.33s · exit 0
Console output
============================================================================
24 · Structure and segments
============================================================================
--- ClauseTree — nested by level, in body order ---
54 clauses in 12 sections
1 Definitions
1.1 “Authorized Users” means Customer’s employees and independen
1.2 “Customer Data” means all electronic data, text, files, or o
1.3 “Documentation” means Provider’s user guides and technical d
1.4 “Order Form” means an ordering document specifying the Servi
1.5 “Subscription Term” means the period of Customer’s subscript
2 License Grant and Restrictions
2.1 Grant of Rights
2.2 Restrictions
2.3 Reservation of Rights
3 Fees and Payment
3.1 Fees
3.2 Invoicing
3.3 Late Payment
3.4 Taxes
--- Clause — every field ---
label = '3.2'
number = (3, 2)
span = (0, 3)
title = 'Invoicing'
level = 2
inserted = False
moved = False
text = '3.2 Invoicing. Provider shall invoice Customer annually'
body = 'Invoicing. Provider shall invoice Customer annually in a' <- number stripped
parent = '3'
children = []
--- lookup and traversal ---
tree.get('10.2') -> Liability Cap
walk from 10 -> ['10', '10.1', '10.2', '10.3']
duplicates -> none
--- outline / render_outline — the summary view ---
outline() gives dicts: ['clause', 'level', 'title', 'text']
render_outline(body_chars=50):
1 Definitions
1.1 “Authorized Users” means Customer’s employees and
1.2 “Customer Data” means all electronic data, text, f
1.3 “Documentation” means Provider’s user guides and t
1.4 “Order Form” means an ordering document specifying
1.5 “Subscription Term” means the period of Customer’s
...but it truncates, which is why the reviewer gets render_document instead
--- iter_references — every cross-reference in a piece of text ---
[27:31] 10.2 Section
[62:65] 4.1 Sections
[70:73] 4.2 Sections
--- detect_strategy — which structural signal this file carries ---
clauses (clause numbers > outlineLvl > Heading styles > numPr > typography > windows)
--- iter_blocks — every paragraph and table, in reading order ---
86 blocks: 84 paragraphs, 2 tables
6 para level=0 - Provider and Customer are each a “Party” and
7 para level=1 1 1. Definitions
8 para level=2 1.1 1.1 “Authorized Users” means Customer’s emp
9 para level=2 1.2 1.2 “Customer Data” means all electronic da
65 table table_index=0 Signature | Date
--- render_document — the whole thing, untruncated ---
88 lines, 12403 chars
includes the exhibits a clause-only view drops:
Exhibit A — Order Form Summary
Exhibit B — Service Level Agreement
Exhibit C — Data Processing Addendum (Summary)
--- segment_document — request-sized pieces, cut on structure ---
budget=25000 -> 1 segment(s): [3045]
budget=1500 -> 3 segment(s): [1281, 1491, 272]
budget=800 -> 5 segment(s): [709, 792, 698, 690, 156]
--- DocSegment — every field ---
id = 'S01'
index = 1
strategy = 'clauses'
title = '3. Fees and Payment'
chars = 3168
approx_tokens = 792
labels = ['3', '3.1', '3.2', '3.3', '3.4', '4', '4.1', '4.2'] ...
tables = []
blocks = 19
--- strategy= — force a segmentation signal ---
strategy=auto -> 5 segment(s), reported as 'clauses'
strategy=clauses -> 5 segment(s), reported as 'clauses'
strategy=headings -> 5 segment(s), reported as 'headings'
strategy=windows -> 4 segment(s), reported as 'windows'
--- split() — halve a segment at a structural boundary ---
3045 tok -> [1840, 1204]
used when a model truncates: the fix is a smaller span, but only if
there is somewhere safe to cut
Source
"""24 · Reading the document's structure.
ClauseTree(body) Clause parse_clause(p) iter_references(text)
outline(tree, body_chars=240) render_outline(tree, body_chars=400, clauses=None)
detect_strategy(body, tree=None)
iter_blocks(body, tree=None)
render_document(body, tree=None)
segment_document(body, budget_tokens=25_000, strategy="auto", tree=None)
Read-only: nothing here writes a revision.
"""
import docx
from _shared import SOURCE, banner, section
from docx_redline import (
ClauseTree,
detect_strategy,
iter_blocks,
iter_references,
outline,
render_document,
render_outline,
segment_document,
)
banner("24 · Structure and segments")
body = docx.Document(SOURCE).element.body
tree = ClauseTree(body)
section("ClauseTree — nested by level, in body order")
print(f" {len(tree.all())} clauses in {len(tree.sections)} sections")
for section_clause in tree.sections[:3]:
print(f" {section_clause.label:<5} {section_clause.title}")
for child in section_clause.children:
print(f" {child.label:<6} {child.title or child.body[:44]}")
section("Clause — every field")
clause = tree.get("3.2")
for field in ("label", "number", "span", "title", "level", "inserted", "moved"):
print(f" {field:<9} = {getattr(clause, field)!r}")
print(f" {'text':<9} = {clause.text[:56]!r}")
print(f" {'body':<9} = {clause.body[:56]!r} <- number stripped")
print(f" {'parent':<9} = {clause.parent.label if clause.parent else None!r}")
print(f" {'children':<9} = {[c.label for c in clause.children]}")
section("lookup and traversal")
print(" tree.get('10.2') ->", tree.get("10.2").title)
print(" walk from 10 ->", [c.label for c in tree.get("10").walk()])
print(" duplicates ->", tree.duplicates or "none")
section("outline / render_outline — the summary view")
rows = outline(tree, body_chars=60)
print(" outline() gives dicts:", list(rows[0]))
print(" render_outline(body_chars=50):")
print("\n".join(" " + line for line in render_outline(tree, body_chars=50).splitlines()[:6]))
print(" ...but it truncates, which is why the reviewer gets render_document instead")
section("iter_references — every cross-reference in a piece of text")
text = tree.get("10.3").text if tree.get("10.3") else ""
for start, end, kind, number in iter_references(
"The limitations in Section 10.2 shall not apply, and Sections 4.1 and 4.2 survive."
):
print(f" [{start}:{end}] {kind} {number}")
section("detect_strategy — which structural signal this file carries")
print(
" ",
detect_strategy(body),
" (clause numbers > outlineLvl > Heading styles > numPr > typography > windows)",
)
section("iter_blocks — every paragraph and table, in reading order")
blocks = iter_blocks(body, tree)
print(
f" {len(blocks)} blocks: "
f"{sum(1 for b in blocks if b.kind == 'para')} paragraphs, "
f"{sum(1 for b in blocks if b.kind == 'table')} tables"
)
for block in blocks[6:10]:
print(
f" {block.ordinal:>3} {block.kind:<6} level={block.level} "
f"{(block.clause.label if block.clause else '-'):<6} {block.text[:44]}"
)
table = next(b for b in blocks if b.kind == "table")
print(
f" {table.ordinal:>3} {table.kind:<6} table_index={table.table_index} "
f"{table.text.splitlines()[0]}"
)
section("render_document — the whole thing, untruncated")
rendered = render_document(body, tree)
print(f" {len(rendered.splitlines())} lines, {len(rendered)} chars")
print(" includes the exhibits a clause-only view drops:")
for line in rendered.splitlines():
if line.strip().startswith("Exhibit"):
print(" ", line.strip()[:64])
section("segment_document — request-sized pieces, cut on structure")
for budget in (25_000, 1_500, 800):
segments = segment_document(body, budget_tokens=budget)
print(
f" budget={budget:<6} -> {len(segments)} segment(s): {[s.approx_tokens for s in segments]}"
)
section("DocSegment — every field")
seg = segment_document(body, budget_tokens=800)[1]
for field in ("id", "index", "strategy", "title", "chars", "approx_tokens"):
print(f" {field:<14} = {getattr(seg, field)!r}")
print(f" {'labels':<14} = {sorted(seg.labels)[:8]} ...")
print(f" {'tables':<14} = {seg.tables}")
print(f" {'blocks':<14} = {len(seg.blocks)}")
section("strategy= — force a segmentation signal")
for strategy in ("auto", "clauses", "headings", "windows"):
segments = segment_document(body, budget_tokens=800, strategy=strategy)
print(
f" strategy={strategy:<9} -> {len(segments)} segment(s), "
f"reported as {segments[0].strategy!r}"
)
section("split() — halve a segment at a structural boundary")
seg = segment_document(body, budget_tokens=25_000)[0]
halves = seg.split()
print(
" ",
f"{seg.approx_tokens} tok -> {[h.approx_tokens for h in halves] if halves else 'indivisible'}",
)
print(" used when a model truncates: the fix is a smaller span, but only if")
print(" there is somewhere safe to cut")
What it wrote
This example prints its result rather than saving a document — read the
console output beside it.