HierarchyTree
result.hierarchy is a HierarchyTree — a navigable tree of the document's headings, extracted deterministically from the document itself. It's the structure that lets an agent inspect a long document's outline first, decide which sections are relevant, and pull only those — instead of feeding the whole thing into context.
The tree holds a list of top-level HierarchyNodes in roots; each node carries its children recursively.
result = docslicer.parse_document("report.pdf")
# Print the whole outline
print(result.hierarchy.to_outline())
# Walk the top-level sections
for node in result.hierarchy.level(1):
print(node.text, "→", len(result.chunks_under(node)), "chunks")Fields
| Field | Type | Description |
|---|---|---|
roots | list[HierarchyNode] | The top-level heading nodes. Each node nests its subsections under children, so the whole tree hangs off this list. |
Iterating the tree (for node in tree) yields the roots, and len(tree) is the number of roots.
Methods
level(n, parent=None)
Return every node at depth n (1-based; 1 = top-level headings), each with its path populated. Pass parent — a HierarchyNode or a heading_id — to restrict the results to descendants of that node, the typical pattern for drilling into one section.
l1 = result.hierarchy.level(1) # all top-level headings
# Level-2 headings under a specific parent — pass a node or its heading_id
section = l1[0]
l2_under = result.hierarchy.level(2, parent=section)
l2_under = result.hierarchy.level(2, parent=section.heading_id) # equivalent
# All level-2 headings, each carrying its ancestor context
l2 = result.hierarchy.level(2)
# l2[0].path == ["Part I – Business"] ← ancestor texts
# l2[0].text == "Item 1A. Risk Factors"find_heading(text, case_sensitive=False)
Return all nodes whose text contains text (case-insensitive by default), each with its path populated. result.find_heading(...) is a convenience shortcut for result.hierarchy.find_heading(...).
nodes = result.hierarchy.find_heading("Risk Factors") # case-insensitive
exact = result.hierarchy.find_heading("Item 1A", case_sensitive=True)to_outline()
Render the tree as an indented plain-text outline (two spaces per level, one heading per line).
print(result.hierarchy.to_outline())- PART II
- Item 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of Equity Securities
- Holders
- Purchases of Equity Securities by the Issuer and Affiliated Purchasers
- Company Stock Performance
- Item 6. [Reserved]
- Item 7. Management's Discussion and Analysis of Financial Condition and Results of Operations
- Product, Service and Software Announcements
- Fiscal Period
- Macroeconomic Conditions
- Tariffs and Other Measures
- Segment Operating Performance
- Americas
- Europe
- Greater China
- Japanflatten()
Return every node in the tree as a flat list, in document (depth-first) order.
all_headings = result.hierarchy.flatten()to_dict(minimal=False)
Serialize the tree to a list of node dicts (the same structure save() writes). With minimal=True, each node collapses to just its text and children — a compact nested outline, ideal for handing an LLM the document's structure without the bookkeeping fields:
[
{"text": "PART II", "children": [
{"text": "Item 5. Market for Registrant’s Common Equity, Related Stockholder Matters and Issuer Purchases of Equity Securities", "children": [
{"text": "Holders"},
{"text": "Purchases of Equity Securities by the Issuer and Affiliated Purchasers"},
{"text": "Company Stock Performance"}
]},
{"text": "Item 6. [Reserved]"},
{"text": "Item 7. Management’s Discussion and Analysis of Financial Condition and Results of Operations", "children": [
{"text": "Product, Service and Software Announcements"},
{"text": "Fiscal Period"},
{"text": "Macroeconomic Conditions"},
{"text": "Tariffs and Other Measures"},
{"text": "Segment Operating Performance", "children": [
{"text": "Americas"},
{"text": "Europe"},
{"text": "Greater China"},
{"text": "Japan"}
]}
]}
]}
]The default (minimal=False) carries every field. Here's the Item 7 node from that full form — note heading_type: "item", the page_number (22) / page_label ("21") divergence, and how chunk_ids and block_ids land on different nodes (two sibling subsections omitted for brevity, and the first node's block_ids abbreviated):
{
"heading_id": 64,
"text": "Item 7. Management’s Discussion and Analysis of Financial Condition and Results of Operations",
"level": 2,
"heading_type": "item",
"page_number": 22,
"page_label": "21",
"chunk_ids": ["c26115db-eb49-4d35-8b54-c6cd6944a102"],
"children": [
{
"heading_id": 65,
"text": "Product, Service and Software Announcements",
"level": 3,
"heading_type": "free_form",
"page_number": 22,
"page_label": "21",
"chunk_ids": [],
"children": [],
"block_ids": ["285", "286", "287", "…"]
},
{
"heading_id": 68,
"text": "Tariffs and Other Measures",
"level": 3,
"heading_type": "free_form",
"page_number": 23,
"page_label": "22",
"chunk_ids": ["77e18389-6d28-4a82-899d-13a01112d6d4"],
"children": [],
"block_ids": ["310", "311"]
},
{
"heading_id": 69,
"text": "Segment Operating Performance",
"level": 3,
"heading_type": "free_form",
"page_number": 23,
"page_label": "22",
"chunk_ids": [],
"children": [
{"heading_id": 70, "text": "Americas", "level": 4, "heading_type": "free_form", "page_number": 23, "page_label": "22", "chunk_ids": [], "children": [], "block_ids": ["315", "316"]},
{"heading_id": 71, "text": "Europe", "level": 4, "heading_type": "free_form", "page_number": 23, "page_label": "22", "chunk_ids": [], "children": [], "block_ids": ["317", "318"]},
{"heading_id": 72, "text": "Greater China", "level": 4, "heading_type": "free_form", "page_number": 23, "page_label": "22", "chunk_ids": [], "children": [], "block_ids": ["319", "320"]},
{"heading_id": 73, "text": "Japan", "level": 4, "heading_type": "free_form", "page_number": 23, "page_label": "22", "chunk_ids": [], "children": [], "block_ids": ["321", "322", "323", "324", "325", "326"]}
],
"block_ids": ["312", "313", "314"]
}
]
}save(path, minimal=False, indent=2)
Write the serialized tree to a JSON file. minimal=True writes the compact text/children form.
save_outline(path)
Write the to_outline() text to a file.
result.hierarchy.save("hierarchy.json", minimal=True)
result.hierarchy.save_outline("outline.txt")HierarchyNode
Each node in the tree is a HierarchyNode — one heading plus everything known about its section. Nodes are what level(), find_heading(), and flatten() return, and what the retrieval methods on ParseResult (chunks_under, blocks_under, tables_under) take as input.
| Field | Type | Description |
|---|---|---|
heading_id | int | Stable identifier for this heading. Accepted anywhere a node is — e.g. level(parent=heading_id) or chunks_under(heading_id). |
text | str | The heading text. |
level | int | 1-based depth in the tree (1 = top-level heading). |
heading_type | str | The detected scheme of the heading — free_form for plain unnumbered headings (the default), or one of the recognised styles such as item, section, numbered_heading, or double_parens. See Heading types for the full list. |
page_number | int | None | 1-based physical page the heading starts on. None when unknown. |
page_label | str | None | Printed page label where the heading starts, e.g. "iv", "A-6". None when no label was detected. |
chunk_ids | list[str] | IDs of chunks directly under this heading — not its subsections. Use chunks_under to include descendants. |
block_ids | list[str] | IDs of blocks directly under this heading's section. |
children | list[HierarchyNode] | Child heading nodes nested directly under this one. |
path | list[str] | Ancestor heading texts from the root down to this node. Populated when the node comes from level() or find_heading(); an empty list otherwise. |
Node methods
to_dict(minimal=False) — serialize the node (and its children) to a dict; minimal=True keeps only text and children.
HierarchyNode.from_dict(d) — reconstruct a node from a dict produced by to_dict().
Heading types
heading_type is the scheme DocSlicer recognised for a heading. Detection is deterministic — each heading's leading text is matched against a fixed set of patterns — so the value is one of a closed vocabulary. The families below group the full set; for the conceptual picture of how heading type feeds the tree, see Hierarchy & navigation.
Anything that doesn't match a recognised pattern is free_form — a plain bold or styled heading with no structural prefix. This is the most common type in ordinary prose documents.
Every pattern is tested against the heading, and the longest match wins — so (a)(1) Definitions is double_parens, not single_parens_single_alpha. Ties are broken by the order below, which is the order the patterns are declared in.
Named — structural keywords, common in legal, SEC, and government documents. Matched case-sensitively (both Section and SECTION, but not section).
Document-level — the outermost divisions, usually one heading per document or per exhibit.
| heading_type | Example |
|---|---|
schedule | Schedule — The Criminal Code |
form | FORM 10-K |
book | Book Two - Real Rights |
volume | Volume 1: sections 1–5 |
appendix | Appendix B: Methodology |
annex | Annex A – Form of Agreement |
exhibit | Exhibit A — Certificate of Incorporation |
attachment | Attachment 2 Frequency Chart |
amendment | Amendment No. 5 |
endnote | Endnote 4 — Amendment history |
Within-document — the divisions that structure the body itself.
| heading_type | Example |
|---|---|
title | Title 26 — Internal Revenue Code |
subtitle | SUBTITLE I — INCOME TAXES |
part | Part II — Other Information |
subpart | Subpart B — Procedures |
chapter | CHAPTER 3 |
subchapter | Subchapter B — Offerings |
division | Division 301 — Serious drugs and precursors |
subdivision | Subdivision B — Interference with telecommunications |
section | Section 4.2(a) Indemnification |
subsection | Subsection 1 Supply of Services |
section_abbreviated | Sec. 10033 — Filing Requirements |
section_symbol | § 1.101 Definitions |
item | Item 1A. Risk Factors |
note | Note 3 — Revenue Recognition |
article | Article IV – Term |
proposal | PROPOSAL 3: ADJOURNMENT OF THE MEETING |
rule | Rule 10b-5 — Employment of Manipulative Devices |
Captions — figure and table captions, treated as headings so their content stays attached to them.
| heading_type | Example |
|---|---|
figure | Figure 3.2: System Architecture |
table | Table 1 — Summary Statistics |
part and item also match their plural forms (Parts I and II, Items 1 and 2). table excludes Table of Contents, which is detected as a TOC heading instead.
Parenthetical — bracketed counters, including the multi-level forms common in statutes and contracts.
| heading_type | Example |
|---|---|
single_parens_numeric | (28) Financial Instruments |
single_parens_roman | (iv) Termination |
single_parens_single_alpha | (a) Exhibits |
single_parens_double_alpha | (aa) Conditions |
single_parens_triple_alpha | (ccc) Special Provisions |
single_parens_quadruple_alpha | (zzzz) Miscellaneous |
double_parens | (a)(1) Definitions |
triple_parens | (a)(1)(i) Special Provisions |
quadruple_parens | (a)(1)(i)(A) Additional Terms |
quintuple_parens | (a)(1)(i)(A)(2) Exceptions |
Numbered — leading numeric or roman prefixes. The numeric depth sets the nesting depth (1. < 1.2 < 1.2.3). Four-digit years (2024 Results) and bare number runs are excluded, so a heading needs at least one letter to qualify.
| heading_type | Example |
|---|---|
numbered_heading | 10.2 Revenue, 1. Introduction, 1 - Introduction |
roman_numbered_heading | II. Background, II - Background |
Alpha — letter-led prefixes, optionally combined with numbers.
| heading_type | Example |
|---|---|
alpha_dotted_numbered_heading | A.1 Tasks, A1.2.3 Overview |
alpha_numbered_heading | A1. Tasks, A1 Tasks, A1 - Tasks |
alpha_heading | A Additional, A. Additional, A - Additional |
Examples
Drill from a section into its content
# Find a section, list its subsections, then pull one subsection's chunks
section = result.find_heading("Financial Statements")[0]
for node in result.hierarchy.level(2, parent=section):
print(node.text, f"(p.{node.page_number})")
note = result.find_heading("Financial Instruments")[0]
chunks = result.chunks_under(note) # includes subsections
direct = result.chunks_under(note, recursive=False) # this heading onlySize every top-level section
for node in result.hierarchy.level(1):
n = len(result.chunks_under(node))
print(f"{node.text}: {n} chunks")See ParseResult for the full set of *_under and page-based retrieval methods.