Home

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

FieldTypeDescription
rootslist[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
      - Japan

flatten()

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.

FieldTypeDescription
heading_idintStable identifier for this heading. Accepted anywhere a node is — e.g. level(parent=heading_id) or chunks_under(heading_id).
textstrThe heading text.
levelint1-based depth in the tree (1 = top-level heading).
heading_typestrThe 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_numberint | None1-based physical page the heading starts on. None when unknown.
page_labelstr | NonePrinted page label where the heading starts, e.g. "iv", "A-6". None when no label was detected.
chunk_idslist[str]IDs of chunks directly under this heading — not its subsections. Use chunks_under to include descendants.
block_idslist[str]IDs of blocks directly under this heading's section.
childrenlist[HierarchyNode]Child heading nodes nested directly under this one.
pathlist[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_typeExample
scheduleSchedule — The Criminal Code
formFORM 10-K
bookBook Two - Real Rights
volumeVolume 1: sections 1–5
appendixAppendix B: Methodology
annexAnnex A – Form of Agreement
exhibitExhibit A — Certificate of Incorporation
attachmentAttachment 2 Frequency Chart
amendmentAmendment No. 5
endnoteEndnote 4 — Amendment history

Within-document — the divisions that structure the body itself.

heading_typeExample
titleTitle 26 — Internal Revenue Code
subtitleSUBTITLE I — INCOME TAXES
partPart II — Other Information
subpartSubpart B — Procedures
chapterCHAPTER 3
subchapterSubchapter B — Offerings
divisionDivision 301 — Serious drugs and precursors
subdivisionSubdivision B — Interference with telecommunications
sectionSection 4.2(a) Indemnification
subsectionSubsection 1 Supply of Services
section_abbreviatedSec. 10033 — Filing Requirements
section_symbol§ 1.101 Definitions
itemItem 1A. Risk Factors
noteNote 3 — Revenue Recognition
articleArticle IV – Term
proposalPROPOSAL 3: ADJOURNMENT OF THE MEETING
ruleRule 10b-5 — Employment of Manipulative Devices

Captions — figure and table captions, treated as headings so their content stays attached to them.

heading_typeExample
figureFigure 3.2: System Architecture
tableTable 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_typeExample
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_typeExample
numbered_heading10.2 Revenue, 1. Introduction, 1 - Introduction
roman_numbered_headingII. Background, II - Background

Alpha — letter-led prefixes, optionally combined with numbers.

heading_typeExample
alpha_dotted_numbered_headingA.1 Tasks, A1.2.3 Overview
alpha_numbered_headingA1. Tasks, A1 Tasks, A1 - Tasks
alpha_headingA 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 only

Size 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.