Skip to content

Commit 03eaab2

Browse files
authored
Document structure roles (#46)
* Explanation of document structure roles * Category and role properties * Map document roles for each supported parser
1 parent 009a470 commit 03eaab2

5 files changed

Lines changed: 223 additions & 9 deletions

File tree

docs/explanation/document-roles.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Document Structure
2+
3+
Documents and pages are composed of logical parts, e.g. headings, footnotes, tables, images, headers, and footers, that describe *what content is*, rather than *how it is visually presented*. These logical parts are independent of layout, styling, or rendering medium.
4+
5+
We refer to this logical classification as **document structure**, or more generally as **document semantics**. In this context, *semantics* describe the role a piece of content plays within the document, as defined by the [W3C Web Accessibility Initiative definition of semantics](https://www.w3.org/TR/wai-aria/#dfn-semantics).
6+
7+
Following the terminology of the [W3C Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/standards-guidelines/aria/), each logical part of a document is assigned a **role**.
8+
Roles prefixed with `doc-` identify **document structure roles**, while other roles describe **in-page or interactive structures**.
9+
10+
Document structure roles describe elements that organize and contextualize content within a page or publication. These roles are typically **non-interactive** and convey structural meaning rather than behavior.
11+
12+
**Roles** are mutuated from [Accessible Rich Internet Applications (WAI-ARIA)](https://www.w3.org/TR/wai-aria/) and the [Digital Publishing WAI-ARIA Module](https://www.w3.org/TR/dpub-aria-1.1/), specifically suited for structural divisions of long-form documents.
13+
14+
Because many document parsers and extraction tools use inconsistent or undocumented classification schemes, we maintain explicit **mappings from parser-specific categories to WAI-ARIA roles**. For this reason, we use the `role` field (rather than `category`) to represent document structure semantics in a standardized way. It is important to note that parsers might not support all roles.
15+
16+
17+
## Page level roles
18+
19+
Page level roles applies to parts of a page (although some of them can span across pages). Page roles are a subset of [WAI-ARIA Document Structure Roles](https://www.w3.org/TR/wai-aria/#document_structure_roles)[^1].
20+
21+
[^1]: The term document structure might confuse you as ARIA roles comes from accessibility in a HTML document, so document in that context refers to an element of the markup language containing content that assistive technology users may want to browse in a reading mode.
22+
23+
- **blockquote**: A section of content quoted from another source.
24+
- **caption**: Visible content that names and may describe a figure or a table.
25+
- **definition**: Content that provides the meaning or explanation of a term.
26+
- **deletion**: Content that has been removed or marked as deleted from the document.
27+
- **emphasis**: Text that is emphasized to convey stress or importance in spoken or written language.
28+
- **figure**: A self-contained unit of content, such as an image, diagram, code example, or illustration, often referenced from the main text.
29+
- **generic**: Content that does not convey a specific semantic role beyond grouping or containment.
30+
- **heading**: A label or title that introduces and describes the topic of a section.
31+
- **insertion**: Content that has been added or inserted into the document.
32+
- **list**: A collection of related items presented in a sequential or grouped form.
33+
- **listitem**: A single item within a list.
34+
- **math**: Mathematical expressions or formulas represented in a structured form.
35+
- **paragraph**: A distinct block of text that presents a single idea or unit of discourse.
36+
- **row**: A horizontal grouping of cells within a table or grid.
37+
- **strong**: Text of strong importance, seriousness, or urgency.
38+
- **subscript**: Text displayed below the baseline, typically used in mathematical or chemical notation.
39+
- **superscript**: Text displayed above the baseline, commonly used for exponents, references, or annotations.
40+
- **table**: A structured arrangement of data organized into rows and columns.
41+
42+
43+
44+
45+
46+
## Document level roles
47+
48+
Document level roles applies to parts of a document. Document roles are the same as defined in the [Digital Publishing WAI-ARIA Module](https://www.w3.org/TR/dpub-aria-1.1/).
49+
50+
51+
- **doc-abstract**: A short summary of the principal ideas, concepts, and conclusions of the work, or of a section or excerpt within it.
52+
- **doc-acknowledgments**: A section or statement that acknowledges significant contributions by persons, organizations, governments, and other entities to the realization of the work.
53+
- **doc-afterword**: A closing statement from the author or a person of importance, typically providing insight into how the content came to be written, its significance, or related events that have transpired since its timeline.
54+
- **doc-appendix**: A section of supplemental information located after the primary content that informs the content but is not central to it.
55+
- **doc-backlink**: A link that allows the user to return to a related location in the content (e.g., from a footnote to its reference or from a glossary definition to where a term is used).
56+
- **doc-bibliography**: A list of external references cited in the work, which could be to print or digital sources.
57+
- **doc-biblioref**: A reference to a bibliography entry.
58+
- **doc-chapter**: A major thematic section of content in a work.
59+
- **doc-colophon**: A short section of production notes particular to the edition (e.g., describing the typeface used), often located at the end of a work.
60+
- **doc-conclusion**: A concluding section or statement that summarizes the work or wraps up the narrative.
61+
- **doc-cover**: An image that sets the mood or tone for the work and typically includes the title and author.
62+
- **doc-credit**: An acknowledgment of the source of integrated content from third-party sources, such as photos. Typically identifies the creator, copyright, and any restrictions on reuse.
63+
- **doc-credits**: A collection of credits.
64+
- **doc-dedication**: An inscription at the front of the work, typically addressed in tribute to one or more persons close to the author.
65+
- **doc-endnotes**: A collection of notes at the end of a work or a section within it.
66+
- **doc-epigraph**: A quotation set at the start of the work or a section that establishes the theme or sets the mood.
67+
- **doc-epilogue**: A concluding section of narrative that wraps up or comments on the actions and events of the work, typically from a future perspective.
68+
- **doc-errata**: A set of corrections discovered after initial publication of the work, sometimes referred to as corrigenda.
69+
- **doc-example**: An illustration of a key concept of the work, such as a code listing, case study or problem.
70+
- **doc-footnote**: Ancillary information, such as a citation or commentary, that provides additional context to a referenced passage of text.
71+
- **doc-foreword**: An introductory section that precedes the work, typically not written by the author of the work.
72+
- **doc-glossary**: A brief dictionary of new, uncommon, or specialized terms used in the content.
73+
- **doc-glossref**: A reference to a glossary definition.
74+
- **doc-index**: A navigational aid that provides a detailed list of links to key subjects, names and other important topics covered in the work.
75+
- **doc-introduction**: A preliminary section that typically introduces the scope or nature of the work.
76+
- **doc-noteref**: A reference to a footnote or endnote, typically appearing as a superscripted number or symbol in the main body of text.
77+
- **doc-notice**: Notifies the user of consequences that might arise from an action or event. Examples include warnings, cautions and dangers.
78+
- **doc-pagebreak**: A separator denoting the position before which a break occurs between two contiguous pages in a statically paginated version of the content.
79+
- **doc-pagefooter**: A section of text appearing at the bottom of a page that provides context about the current work and location within it. The page footer is distinct from the body text and normally follows a repeating template that contains (possibly truncated) items such as the document title, current section, author name(s), and page number.
80+
- **doc-pageheader**: A section of text appearing at the top of a page that provides context about the current work and location within it. The page header is distinct from the body text and normally follows a repeating template that contains (possibly truncated) items such as the document title, current section, author name(s), and page number.
81+
- **doc-pagelist**: A navigational aid that provides a list of links to the page breaks in the content.
82+
- **doc-part**: A major structural division in a work that contains a set of related sections dealing with a particular subject, narrative arc, or similar encapsulated theme.
83+
- **doc-preface**: An introductory section that precedes the work, typically written by the author of the work.
84+
- **doc-prologue**: An introductory section that sets the background to a work, typically part of the narrative.
85+
- **doc-pullquote**: A distinctively placed or highlighted quotation from the current content designed to draw attention to a topic or highlight a key point.
86+
- **doc-qna**: A section of content structured as a series of questions and answers, such as an interview or list of frequently asked questions.
87+
- **doc-subtitle**: An explanatory or alternate title for the work, or a section or component within it.
88+
- **doc-tip**: Helpful information that clarifies some aspect of the content or assists in its comprehension.
89+
- **doc-toc**: A navigational aid that provides an ordered list of links to the major sectional headings in the content. A table of contents could cover an entire work or only a smaller section of it.
90+
91+
92+
Considering that in ARIA title comes from the HTML document we decided to add a non-standard role `doc-title` to represent the document title.
93+
94+

src/parxy_core/drivers/landingai.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,41 @@
1818
from parxy_core.models import Document, Metadata, TextBlock, Page, BoundingBox
1919
from parxy_core.utils import safe_json_dumps
2020

21+
# Mapping from LandingAI ADE chunk types to WAI-ARIA document structure roles
22+
# See docs/explanation/document-roles.md for role definitions
23+
LANDINGAI_TO_ROLE: dict[str, str] = {
24+
'text': 'paragraph',
25+
'table': 'table',
26+
'marginalia': 'generic', # Mixed content in margins - too generic to map precisely
27+
'figure': 'figure',
28+
'logo': 'figure', # DPT-2 only: logos are visual elements
29+
'card': 'figure', # DPT-2 only: ID cards, driver licenses
30+
'attestation': 'figure', # DPT-2 only: signatures, stamps, seals
31+
'scan_code': 'figure', # DPT-2 only: QR codes, barcodes
32+
# Footer variants
33+
'page-footer': 'doc-pagefooter',
34+
'page_footer': 'doc-pagefooter',
35+
'footer': 'doc-pagefooter',
36+
'page-number': 'doc-pagefooter',
37+
# Footnote variants
38+
'footnote': 'doc-footnote',
39+
'note': 'doc-footnote',
40+
'endnote': 'doc-endnotes',
41+
'annotation': 'doc-footnote',
42+
'footer-note': 'doc-footnote',
43+
# Heading variants
44+
'heading': 'heading',
45+
'title': 'doc-title',
46+
'subtitle': 'doc-subtitle',
47+
'section': 'heading',
48+
'chapter': 'doc-chapter',
49+
# Header variants
50+
'page-header': 'doc-pageheader',
51+
'page_header': 'doc-pageheader',
52+
'page-heading': 'doc-pageheader',
53+
'header': 'doc-pageheader',
54+
}
55+
2156

2257
class LandingAIADEDriver(Driver):
2358
def _initialize_driver(self):
@@ -134,7 +169,8 @@ def landingaiade_to_parxy(parsed_data: ParseResponse) -> Document:
134169
chunk_text = chunk.markdown
135170

136171
page_text_parts.append(chunk_text)
137-
chunk_type = chunk.type
172+
category = chunk.type
173+
role = LANDINGAI_TO_ROLE.get(category, 'generic') if category else 'generic'
138174

139175
# Get bounding box from first grounding
140176
bbox = None
@@ -147,9 +183,10 @@ def landingaiade_to_parxy(parsed_data: ParseResponse) -> Document:
147183
# Create the appropriate block type
148184
block = TextBlock(
149185
type='text',
186+
role=role,
150187
bbox=bbox,
151188
page=page,
152-
category=chunk_type,
189+
category=category,
153190
text=chunk_text,
154191
source_data=chunk.model_dump(),
155192
)

src/parxy_core/drivers/llamaparse.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,41 @@
2626
FileNotFoundException,
2727
)
2828

29+
# Mapping from LlamaParse types to WAI-ARIA document structure roles
30+
# See docs/explanation/document-roles.md for role definitions
31+
LLAMAPARSE_TO_ROLE: dict[str, str] = {
32+
'text': 'paragraph',
33+
'table': 'table',
34+
'tables': 'table',
35+
'figure': 'figure',
36+
'figures': 'figure',
37+
'list': 'list',
38+
'lists': 'list',
39+
# Footer variants
40+
'footer': 'doc-pagefooter',
41+
'page-footer': 'doc-pagefooter',
42+
'page_footer': 'doc-pagefooter',
43+
'page-number': 'doc-pagefooter',
44+
# Footnote variants
45+
'footnote': 'doc-footnote',
46+
'note': 'doc-footnote',
47+
'endnote': 'doc-endnotes',
48+
'annotation': 'doc-footnote',
49+
'footer-note': 'doc-footnote',
50+
# Heading variants
51+
'heading': 'heading',
52+
'title': 'doc-title',
53+
'titles': 'heading',
54+
'subtitle': 'doc-subtitle',
55+
'section': 'heading',
56+
'chapter': 'doc-chapter',
57+
# Header variants
58+
'page-header': 'doc-pageheader',
59+
'page_header': 'doc-pageheader',
60+
'page-heading': 'doc-pageheader',
61+
'header': 'doc-pageheader',
62+
}
63+
2964
_credits_per_parsing_mode = {
3065
# Minimum credits per parsing mode as deduced from https://developers.llamaindex.ai/python/cloud/general/pricing/
3166
'accurate': 3, # equivalent to Parse page with LLM as observed in their dashboard
@@ -453,9 +488,12 @@ def _convert_text_block(text_block: PageItem, page_number: int) -> TextBlock:
453488
text_value = text_block.value if text_block.value else ''
454489
if text_value == 'NO_CONTENT_HERE':
455490
text_value = ''
491+
category = text_block.type
492+
role = LLAMAPARSE_TO_ROLE.get(category, 'generic') if category else 'generic'
456493
return TextBlock(
457494
type='text',
458-
category=text_block.type,
495+
role=role,
496+
category=category,
459497
level=text_block.lvl,
460498
text=text_value,
461499
bbox=bbox,

src/parxy_core/drivers/pdfact.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,50 @@
2121
from parxy_core.models.config import PdfActConfig
2222
from parxy_core.tracing.utils import trace_with_output
2323

24+
# Mapping from PdfAct categories to WAI-ARIA document structure roles
25+
# See docs/explanation/document-roles.md for role definitions
26+
PDFACT_TO_ROLE: dict[str, str] = {
27+
'abstract': 'doc-abstract',
28+
'acknowledgments': 'doc-acknowledgments',
29+
'affiliation': 'generic',
30+
'appendix': 'doc-appendix',
31+
'authors': 'generic',
32+
'body': 'paragraph',
33+
'caption': 'caption',
34+
'categories': 'generic',
35+
'figure': 'figure',
36+
'formula': 'math',
37+
'general-terms': 'generic',
38+
'itemize-item': 'listitem',
39+
'keywords': 'generic',
40+
'other': 'generic',
41+
'reference': 'doc-biblioref',
42+
'table': 'table',
43+
'toc': 'doc-toc',
44+
# Footer variants
45+
'footer': 'doc-pagefooter',
46+
'page-footer': 'doc-pagefooter',
47+
'page_footer': 'doc-pagefooter',
48+
'page-number': 'doc-pagefooter',
49+
# Footnote variants
50+
'footnote': 'doc-footnote',
51+
'note': 'doc-footnote',
52+
'endnote': 'doc-endnotes',
53+
'annotation': 'doc-footnote',
54+
'footer-note': 'doc-footnote',
55+
# Heading variants
56+
'heading': 'heading',
57+
'title': 'doc-title',
58+
'subtitle': 'doc-subtitle',
59+
'section': 'heading',
60+
'chapter': 'doc-chapter',
61+
# Header variants
62+
'page-header': 'doc-pageheader',
63+
'page_header': 'doc-pageheader',
64+
'page-heading': 'doc-pageheader',
65+
'header': 'doc-pageheader',
66+
}
67+
2468

2569
class PdfActDriver(Driver):
2670
"""PdfAct service driver.
@@ -287,6 +331,7 @@ def _convert_text_block(
287331
data = text_block.get('paragraph')
288332
text = data.get('text', '')
289333
category = data.get('role') if 'role' in data else None
334+
role = PDFACT_TO_ROLE.get(category, 'generic') if category else 'generic'
290335
positions = data.get('positions', [])
291336

292337
# Convert font and color
@@ -308,6 +353,7 @@ def _convert_text_block(
308353
bbox = _convert_bbox([pos for pos in positions if pos['page'] == page])
309354
return TextBlock(
310355
type='text',
356+
role=role,
311357
bbox=bbox,
312358
style=style,
313359
page=page,

src/parxy_core/models/models.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,16 @@ def isEmpty(self) -> bool:
6060

6161
class Block(BaseModel, ABC):
6262
type: str
63+
role: Optional[str] = 'generic'
64+
"""Document Structure role recognized for this block"""
6365
bbox: Optional[BoundingBox] = None
6466
page: Optional[int] = None
6567
source_data: Optional[dict[str, Any]] = None
68+
category: Optional[str] = None
69+
"""Category attributed to this block by the parser"""
6670

6771

68-
class TextBlock(BaseModel):
69-
type: str
70-
bbox: Optional[BoundingBox] = None
71-
page: Optional[int] = None
72-
source_data: Optional[dict[str, Any]] = None
73-
category: Optional[str] = None
72+
class TextBlock(Block):
7473
style: Optional[Style] = None
7574
level: Optional[int] = None
7675
lines: Optional[List[Line]] = None

0 commit comments

Comments
 (0)