all2md.ast
Abstract Syntax Tree (AST) module for document representation.
This module provides a complete Abstract Syntax Tree representation for markdown documents, enabling separation of document parsing from markdown rendering. The AST approach allows for:
Multiple markdown flavor support (CommonMark, GFM, etc.)
Document structure validation and manipulation
Improved testability and maintainability
Bidirectional conversion support
The module consists of several components:
nodes: AST node classes representing document structure
visitors: Visitor pattern implementation for AST traversal
serialization: JSON serialization and deserialization of AST structures
transforms: AST transformation utilities (cloning, filtering, rewriting)
builder: Helper classes for constructing complex AST structures
Examples
Basic usage:
>>> from all2md.ast import Document, Heading, Paragraph, Text
>>> from all2md.renderers.markdown import MarkdownRenderer
>>>
>>> # Create AST
>>> doc = Document(children=[
... Heading(level=1, content=[Text(content="Title")]),
... Paragraph(content=[Text(content="Hello world")])
... ])
>>>
>>> # Render to markdown
>>> renderer = MarkdownRenderer()
>>> markdown = renderer.render_to_string(doc)
- class all2md.ast.Node
Bases:
ABCBase class for all AST nodes.
All document nodes inherit from this base class and support the visitor pattern for traversal and rendering.
- Parameters:
metadata (dict, default = empty dict) – Arbitrary metadata associated with this node
source_location (SourceLocation or None, default = None) – Information about where this node came from in the source
- metadata: dict[str, Any]
- source_location: SourceLocation | None
- abstractmethod accept(visitor: Any) Any
Accept a visitor for processing this node.
- Parameters:
visitor (Any) – A visitor object with visit_* methods
- Returns:
Result from the visitor’s processing
- Return type:
Any
- class all2md.ast.SourceLocation
Bases:
objectSource location information for AST nodes.
Preserves information about where a node originated in the source document, useful for debugging, error messages, and round-trip conversions.
- Parameters:
format (str) – Source format (e.g., ‘pdf’, ‘html’, ‘docx’)
page (int or None, default = None) – Page number in source document (for paginated formats)
line (int or None, default = None) – Line number in source document (for text formats)
column (int or None, default = None) – Column number in source document
element_id (str or None, default = None) – Source element identifier (e.g., HTML element ID)
metadata (dict, default = empty dict) – Additional format-specific location information
- format: str
- page: int | None = None
- line: int | None = None
- column: int | None = None
- element_id: str | None = None
- metadata: dict[str, Any]
- __init__(format: str, page: int | None = None, line: int | None = None, column: int | None = None, element_id: str | None = None, metadata: dict[str, ~typing.Any]=<factory>) None
- class all2md.ast.Document
Bases:
NodeRoot document node containing all other nodes.
The Document node represents the complete document and contains a list of block-level children.
- Parameters:
children (list of Node, default = empty list) – Block-level nodes in the document
metadata (dict, default = empty dict) – Document-level metadata (title, author, etc.)
source_location (SourceLocation or None, default = None) – Source location information
- children: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this document.
- Parameters:
visitor (Any) – A visitor object with visit_document method
- Returns:
Result from visitor.visit_document(self)
- Return type:
Any
- add_section_after(target: str | int, new_section: Section | Document, case_sensitive: bool = False) Document
Add a new section after the specified target section.
- Parameters:
- Returns:
New document with the section inserted
- Return type:
- Raises:
ValueError – If target section is not found
Examples
>>> from all2md.ast.sections import Section >>> from all2md.ast.nodes import Heading, Paragraph, Text >>> new_section = Section( ... heading=Heading(level=2, content=[Text("New Section")]), ... content=[Paragraph(content=[Text("Content")])], ... level=2, start_index=0, end_index=0 ... ) >>> updated_doc = doc.add_section_after("Introduction", new_section)
- add_section_before(target: str | int, new_section: Section | Document, case_sensitive: bool = False) Document
Add a new section before the specified target section.
- Parameters:
- Returns:
New document with the section inserted
- Return type:
- Raises:
ValueError – If target section is not found
Examples
>>> from all2md.ast.sections import Section >>> from all2md.ast.nodes import Heading, Paragraph, Text >>> new_section = Section( ... heading=Heading(level=2, content=[Text("Preface")]), ... content=[Paragraph(content=[Text("Introduction text")])], ... level=2, start_index=0, end_index=0 ... ) >>> updated_doc = doc.add_section_before("Chapter 1", new_section)
- remove_section(target: str | int, case_sensitive: bool = False) Document
Remove a section from the document.
- Parameters:
target (str or int) – Heading text or section index to remove
case_sensitive (bool, default = False) – Whether text matching is case-sensitive (for string targets)
- Returns:
New document with the section removed
- Return type:
- Raises:
ValueError – If target section is not found
Examples
>>> updated_doc = doc.remove_section("Obsolete Section") >>> updated_doc = doc.remove_section(2) # Remove third section
- replace_section(target: str | int, new_content: Section | Document | list[Node], case_sensitive: bool = False) Document
Replace a section with new content.
- Parameters:
- Returns:
New document with the section replaced
- Return type:
- Raises:
ValueError – If target section is not found
Examples
- Replace with a new section:
>>> from all2md.ast.sections import Section >>> from all2md.ast.nodes import Heading, Paragraph, Text >>> new_section = Section( ... heading=Heading(level=2, content=[Text("Updated")]), ... content=[Paragraph(content=[Text("New content")])], ... level=2, start_index=0, end_index=0 ... ) >>> updated_doc = doc.replace_section("Old Section", new_section)
- Replace with custom nodes:
>>> new_nodes = [ ... Heading(level=2, content=[Text("New Heading")]), ... Paragraph(content=[Text("Content here")]) ... ] >>> updated_doc = doc.replace_section(0, new_nodes)
- insert_into_section(target: str | int, content: Node | list[Node], position: Literal['start', 'end', 'after_heading'] = 'end', case_sensitive: bool = False) Document
Insert content into an existing section.
- Parameters:
target (str or int) – Heading text or section index to insert into
position ({"start", "end", "after_heading"}, default = "end") – Where to insert the content within the section
case_sensitive (bool, default = False) – Whether text matching is case-sensitive (for string targets)
- Returns:
New document with content inserted
- Return type:
- Raises:
ValueError – If target section is not found
Notes
“start”: Insert at the beginning of the section content (before all content)
“after_heading”: Insert immediately after the heading (same as “start”)
“end”: Insert at the end of the section content (after all content)
Examples
>>> from all2md.ast.nodes import Paragraph, Text >>> new_para = Paragraph(content=[Text("Additional info")]) >>> updated_doc = doc.insert_into_section("Methods", new_para, position="end")
- __init__(children: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Heading
Bases:
NodeHeading node (h1-h6).
Represents a document heading with a level from 1 to 6 and inline content.
- Parameters:
level (int) – Heading level (1-6, where 1 is most important)
content (list of Node, default = empty list) – Inline nodes representing heading text
metadata (dict, default = empty dict) – Heading metadata
source_location (SourceLocation or None, default = None) – Source location information
- level: int
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this heading.
- Parameters:
visitor (Any) – A visitor object with visit_heading method
- Returns:
Result from visitor.visit_heading(self)
- Return type:
Any
- __init__(level: int, content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Paragraph
Bases:
NodeParagraph node containing inline content.
Represents a paragraph of text with inline formatting.
- Parameters:
content (list of Node, default = empty list) – Inline nodes representing paragraph content
metadata (dict, default = empty dict) – Paragraph metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this paragraph.
- Parameters:
visitor (Any) – A visitor object with visit_paragraph method
- Returns:
Result from visitor.visit_paragraph(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.CodeBlock
Bases:
NodeCode block node with optional language specification.
Represents a fenced or indented code block.
- Parameters:
content (str) – Code content (not parsed as markdown)
language (str or None, default = None) – Programming language for syntax highlighting
fence_char (str, default = '`') – Character used for fencing (` or ~)
fence_length (int, default = 3) – Number of fence characters
metadata (dict, default = empty dict) – Code block metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: str
- language: str | None = None
- fence_char: str = '`'
- fence_length: int = 3
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this code block.
- Parameters:
visitor (Any) – A visitor object with visit_code_block method
- Returns:
Result from visitor.visit_code_block(self)
- Return type:
Any
- __init__(content: str, language: str | None = None, fence_char: str = '`', fence_length: int = 3, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.BlockQuote
Bases:
NodeBlock quote node containing other block elements.
Represents a quoted section of content.
- Parameters:
children (list of Node, default = empty list) – Block-level nodes in the quote
metadata (dict, default = empty dict) – Block quote metadata
source_location (SourceLocation or None, default = None) – Source location information
- children: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this block quote.
- Parameters:
visitor (Any) – A visitor object with visit_block_quote method
- Returns:
Result from visitor.visit_block_quote(self)
- Return type:
Any
- __init__(children: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.List
Bases:
NodeList node (ordered or unordered).
Represents an ordered (numbered) or unordered (bulleted) list.
- Parameters:
ordered (bool) – True for ordered lists, False for unordered
items (list of ListItem, default = empty list) – List items
start (int, default = 1) – Starting number for ordered lists
tight (bool, default = True) – Whether list is tight (no blank lines between items)
metadata (dict, default = empty dict) – List metadata
source_location (SourceLocation or None, default = None) – Source location information
- ordered: bool
- items: list[ListItem]
- start: int = 1
- tight: bool = True
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this list.
- Parameters:
visitor (Any) – A visitor object with visit_list method
- Returns:
Result from visitor.visit_list(self)
- Return type:
Any
- __init__(ordered: bool, items: list[ListItem] = <factory>, start: int = 1, tight: bool = True, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.ListItem
Bases:
NodeList item node containing block content.
Represents a single item in a list, which can contain paragraphs, nested lists, or other block elements.
- Parameters:
children (list of Node, default = empty list) – Block-level nodes in the list item
task_status ({'checked', 'unchecked'} or None, default = None) – For task lists (GFM extension)
metadata (dict, default = empty dict) – List item metadata
source_location (SourceLocation or None, default = None) – Source location information
- children: list[Node]
- task_status: Literal['checked', 'unchecked'] | None = None
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this list item.
- Parameters:
visitor (Any) – A visitor object with visit_list_item method
- Returns:
Result from visitor.visit_list_item(self)
- Return type:
Any
- __init__(children: list[Node] = <factory>, task_status: Literal['checked', 'unchecked'] | None=None, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Table
Bases:
NodeTable node with optional header and alignment.
Represents a table structure (GFM extension).
- Parameters:
rows (list of TableRow, default = empty list) – Table rows (excluding header)
header (TableRow or None, default = None) – Optional header row
alignments (list, default = empty list) – Column alignments (‘left’, ‘center’, ‘right’, or None)
caption (str or None, default = None) – Optional table caption
metadata (dict, default = empty dict) – Table metadata
source_location (SourceLocation or None, default = None) – Source location information
- rows: list[TableRow]
- header: TableRow | None = None
- alignments: list[Literal['left', 'center', 'right'] | None]
- caption: str | None = None
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this table.
- Parameters:
visitor (Any) – A visitor object with visit_table method
- Returns:
Result from visitor.visit_table(self)
- Return type:
Any
- __init__(rows: list[TableRow] = <factory>, header: TableRow | None = None, alignments: list[~typing.Literal['left', 'center', 'right'] | None]=<factory>, caption: str | None = None, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.TableRow
Bases:
NodeTable row node containing cells.
Represents a single row in a table.
- Parameters:
cells (list of TableCell, default = empty list) – Cells in this row
is_header (bool, default = False) – Whether this is a header row
metadata (dict, default = empty dict) – Row metadata
source_location (SourceLocation or None, default = None) – Source location information
- cells: list[TableCell]
- is_header: bool = False
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this table row.
- Parameters:
visitor (Any) – A visitor object with visit_table_row method
- Returns:
Result from visitor.visit_table_row(self)
- Return type:
Any
- __init__(cells: list[TableCell] = <factory>, is_header: bool = False, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.TableCell
Bases:
NodeTable cell node with optional span and alignment.
Represents a single cell in a table.
- Parameters:
content (list of Node, default = empty list) – Inline content of the cell
colspan (int, default = 1) – Number of columns this cell spans
rowspan (int, default = 1) – Number of rows this cell spans
alignment ({'left', 'center', 'right'} or None, default = None) – Cell alignment
metadata (dict, default = empty dict) – Cell metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- colspan: int = 1
- rowspan: int = 1
- alignment: Literal['left', 'center', 'right'] | None = None
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this table cell.
- Parameters:
visitor (Any) – A visitor object with visit_table_cell method
- Returns:
Result from visitor.visit_table_cell(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, colspan: int = 1, rowspan: int = 1, alignment: Literal['left', 'center', 'right'] | None=None, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.ThematicBreak
Bases:
NodeThematic break node (horizontal rule).
Represents a thematic break/horizontal rule.
- Parameters:
metadata (dict, default = empty dict) – Break metadata
source_location (SourceLocation or None, default = None) – Source location information
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this thematic break.
- Parameters:
visitor (Any) – A visitor object with visit_thematic_break method
- Returns:
Result from visitor.visit_thematic_break(self)
- Return type:
Any
- __init__(metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.HTMLBlock
Bases:
NodeRaw HTML block node.
Represents a block of raw HTML content. This node preserves HTML as-is without modification or sanitization.
- Parameters:
content (str) – Raw HTML content
metadata (dict, default = empty dict) – HTML block metadata
source_location (SourceLocation or None, default = None) – Source location information
Warning
Security: Raw HTML content is preserved without sanitization. Renderers MUST sanitize user-provided HTML content before rendering to prevent XSS attacks and other security vulnerabilities. For strict security contexts, consider using ValidationVisitor with allow_raw_html=False to reject documents containing raw HTML.
See also
ValidationVisitorAST validator with optional HTML rejection
- content: str
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this HTML block.
- Parameters:
visitor (Any) – A visitor object with visit_html_block method
- Returns:
Result from visitor.visit_html_block(self)
- Return type:
Any
- __init__(content: str, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Comment
Bases:
NodeBlock-level comment node.
Represents a standalone comment block that appears at the block level. Comments can originate from HTML comments, LaTeX comments, code comments, or other format-specific comment mechanisms.
The node uses metadata to store variant information such as comment type, author, date, identifier, and other format-specific attributes.
- Parameters:
content (str) – Comment text content
metadata (dict, default = empty dict) – Comment metadata. Common metadata keys: - ‘comment_type’: str - Type of comment (‘html’, ‘docx_review’, ‘latex’, ‘code’, ‘generic’) - ‘author’: str - Comment author name - ‘date’: str - Comment timestamp - ‘identifier’: str - Comment ID for linking/referencing - ‘label’: str - Comment label/number - ‘range_start’: str - Start of commented text range - ‘range_end’: str - End of commented text range
source_location (SourceLocation or None, default = None) – Source location information
Examples
- HTML block comment:
>>> Comment( ... content="This section needs review", ... metadata={'comment_type': 'html'} ... )
- DOCX reviewer comment at document end:
>>> Comment( ... content="Please verify these numbers", ... metadata={ ... 'comment_type': 'docx_review', ... 'author': 'Jane Smith', ... 'date': '2025-01-20', ... 'identifier': 'comment2', ... 'label': '2' ... } ... )
- LaTeX comment:
>>> Comment( ... content="TODO: Add more examples here", ... metadata={'comment_type': 'latex'} ... )
- content: str
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this comment block.
- Parameters:
visitor (Any) – A visitor object with visit_comment method
- Returns:
Result from visitor.visit_comment(self)
- Return type:
Any
- __init__(content: str, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.CommentInline
Bases:
NodeInline comment node.
Represents an inline comment that appears within the text flow. Comments can originate from HTML comments, DOCX reviewer comments, or other format-specific comment mechanisms.
The node uses metadata to store variant information such as comment type, author, date, identifier, and other format-specific attributes.
- Parameters:
content (str) – Comment text content
metadata (dict, default = empty dict) – Comment metadata. Common metadata keys: - ‘comment_type’: str - Type of comment (‘html’, ‘docx_review’, ‘latex’, ‘code’, ‘generic’) - ‘author’: str - Comment author name - ‘date’: str - Comment timestamp - ‘identifier’: str - Comment ID for linking/referencing - ‘label’: str - Comment label/number - ‘range_start’: str - Start of commented text range - ‘range_end’: str - End of commented text range
source_location (SourceLocation or None, default = None) – Source location information
Examples
- HTML comment:
>>> CommentInline( ... content="TODO: Fix this logic", ... metadata={'comment_type': 'html'} ... )
- DOCX reviewer comment:
>>> CommentInline( ... content="This needs clarification", ... metadata={ ... 'comment_type': 'docx_review', ... 'author': 'John Doe', ... 'date': '2025-01-20', ... 'identifier': 'comment1' ... } ... )
- content: str
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this inline comment.
- Parameters:
visitor (Any) – A visitor object with visit_comment_inline method
- Returns:
Result from visitor.visit_comment_inline(self)
- Return type:
Any
- __init__(content: str, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Text
Bases:
NodePlain text node.
Represents plain text content without formatting.
- Parameters:
content (str) – Text content
metadata (dict, default = empty dict) – Text metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: str
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this text.
- Parameters:
visitor (Any) – A visitor object with visit_text method
- Returns:
Result from visitor.visit_text(self)
- Return type:
Any
- __init__(content: str, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Emphasis
Bases:
NodeEmphasis (italic) node.
Represents emphasized (typically italic) text.
- Parameters:
content (list of Node, default = empty list) – Inline nodes with emphasis
metadata (dict, default = empty dict) – Emphasis metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this emphasis.
- Parameters:
visitor (Any) – A visitor object with visit_emphasis method
- Returns:
Result from visitor.visit_emphasis(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Strong
Bases:
NodeStrong (bold) node.
Represents strong (typically bold) text.
- Parameters:
content (list of Node, default = empty list) – Inline nodes with strong emphasis
metadata (dict, default = empty dict) – Strong metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this strong emphasis.
- Parameters:
visitor (Any) – A visitor object with visit_strong method
- Returns:
Result from visitor.visit_strong(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Code
Bases:
NodeInline code node.
Represents inline code (monospace text).
- Parameters:
content (str) – Code content
metadata (dict, default = empty dict) – Code metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: str
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this inline code.
- Parameters:
visitor (Any) – A visitor object with visit_code method
- Returns:
Result from visitor.visit_code(self)
- Return type:
Any
- __init__(content: str, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Link
Bases:
NodeLink node.
Represents a hyperlink with optional title.
- Parameters:
url (str) – Link destination URL
content (list of Node, default = empty list) – Inline nodes representing link text
title (str or None, default = None) – Optional link title (tooltip)
metadata (dict, default = empty dict) – Link metadata
source_location (SourceLocation or None, default = None) – Source location information
- url: str
- content: list[Node]
- title: str | None = None
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this link.
- Parameters:
visitor (Any) – A visitor object with visit_link method
- Returns:
Result from visitor.visit_link(self)
- Return type:
Any
- __init__(url: str, content: list[Node] = <factory>, title: str | None = None, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Image
Bases:
NodeImage node.
Represents an embedded image.
- Parameters:
url (str) – Image source URL or data URI
alt_text (str, default = '') – Alternative text description
title (str or None, default = None) – Optional image title
width (int or None, default = None) – Optional width in pixels
height (int or None, default = None) – Optional height in pixels
metadata (dict, default = empty dict) – Image metadata
source_location (SourceLocation or None, default = None) – Source location information
- url: str
- alt_text: str = ''
- title: str | None = None
- width: int | None = None
- height: int | None = None
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this image.
- Parameters:
visitor (Any) – A visitor object with visit_image method
- Returns:
Result from visitor.visit_image(self)
- Return type:
Any
- __init__(url: str, alt_text: str = '', title: str | None = None, width: int | None = None, height: int | None = None, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.LineBreak
Bases:
NodeLine break node.
Represents a line break (hard or soft).
- Parameters:
soft (bool, default = False) – True for soft breaks (newline in source), False for hard breaks
metadata (dict, default = empty dict) – Line break metadata
source_location (SourceLocation or None, default = None) – Source location information
- soft: bool = False
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this line break.
- Parameters:
visitor (Any) – A visitor object with visit_line_break method
- Returns:
Result from visitor.visit_line_break(self)
- Return type:
Any
- __init__(soft: bool = False, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Strikethrough
Bases:
NodeStrikethrough node (GFM extension).
Represents strikethrough text.
- Parameters:
content (list of Node, default = empty list) – Inline nodes with strikethrough
metadata (dict, default = empty dict) – Strikethrough metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this strikethrough.
- Parameters:
visitor (Any) – A visitor object with visit_strikethrough method
- Returns:
Result from visitor.visit_strikethrough(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Mark
Bases:
NodeMark (highlight) node (non-standard extension).
Represents highlighted/marked text, as produced by the
==text==syntax of pymdownx.mark (Material for MkDocs). Renders to<mark>in HTML and to==text==in flavors that support it.- Parameters:
content (list of Node, default = empty list) – Inline nodes that are highlighted
metadata (dict, default = empty dict) – Mark metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this mark.
- Parameters:
visitor (Any) – A visitor object with visit_mark method
- Returns:
Result from visitor.visit_mark(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Underline
Bases:
NodeUnderline node (non-standard extension).
Represents underlined text.
- Parameters:
content (list of Node, default = empty list) – Inline nodes with underline
metadata (dict, default = empty dict) – Underline metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this underline.
- Parameters:
visitor (Any) – A visitor object with visit_underline method
- Returns:
Result from visitor.visit_underline(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Superscript
Bases:
NodeSuperscript node (non-standard extension).
Represents superscript text.
- Parameters:
content (list of Node, default = empty list) – Inline nodes in superscript
metadata (dict, default = empty dict) – Superscript metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this superscript.
- Parameters:
visitor (Any) – A visitor object with visit_superscript method
- Returns:
Result from visitor.visit_superscript(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.Subscript
Bases:
NodeSubscript node (non-standard extension).
Represents subscript text.
- Parameters:
content (list of Node, default = empty list) – Inline nodes in subscript
metadata (dict, default = empty dict) – Subscript metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this subscript.
- Parameters:
visitor (Any) – A visitor object with visit_subscript method
- Returns:
Result from visitor.visit_subscript(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.HTMLInline
Bases:
NodeInline HTML node.
Represents inline HTML content. This node preserves HTML as-is without modification or sanitization.
- Parameters:
content (str) – Raw HTML content
metadata (dict, default = empty dict) – HTML metadata
source_location (SourceLocation or None, default = None) – Source location information
Warning
Security: Raw HTML content is preserved without sanitization. Renderers MUST sanitize user-provided HTML content before rendering to prevent XSS attacks and other security vulnerabilities. For strict security contexts, consider using ValidationVisitor with allow_raw_html=False to reject documents containing raw HTML.
See also
ValidationVisitorAST validator with optional HTML rejection
- content: str
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this inline HTML.
- Parameters:
visitor (Any) – A visitor object with visit_html_inline method
- Returns:
Result from visitor.visit_html_inline(self)
- Return type:
Any
- __init__(content: str, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.FootnoteReference
Bases:
NodeFootnote reference node (inline).
Represents an inline reference to a footnote, typically rendered as [^id]. This is used with flavors that support footnotes (e.g., MultiMarkdown, Pandoc).
- Parameters:
identifier (str) – Footnote identifier (e.g., “1”, “note1”)
metadata (dict, default = empty dict) – Footnote reference metadata
source_location (SourceLocation or None, default = None) – Source location information
- identifier: str
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this footnote reference.
- Parameters:
visitor (Any) – A visitor object with visit_footnote_reference method
- Returns:
Result from visitor.visit_footnote_reference(self)
- Return type:
Any
- __init__(identifier: str, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.FootnoteDefinition
Bases:
NodeFootnote definition node (block).
Represents a footnote definition, typically rendered as [^id]: content. Used with MultiMarkdown, Pandoc, Kramdown, and MarkdownPlus flavors.
- Parameters:
identifier (str) – Footnote identifier matching a FootnoteReference
content (list of Node, default = empty list) – Block-level content of the footnote
metadata (dict, default = empty dict) – Footnote definition metadata
source_location (SourceLocation or None, default = None) – Source location information
- identifier: str
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this footnote definition.
- Parameters:
visitor (Any) – A visitor object with visit_footnote_definition method
- Returns:
Result from visitor.visit_footnote_definition(self)
- Return type:
Any
- __init__(identifier: str, content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.MathInline
Bases:
NodeInline math node.
Represents inline mathematical content, typically rendered with $ delimiters. Supported by GFM, Pandoc, Kramdown, and MarkdownPlus flavors.
- Parameters:
content (str) – LaTeX math content (without delimiters)
notation ({"latex", "mathml", "html"}, default "latex") – Format of the primary math representation stored in
content.representations (dict, default = empty dict) – Additional representations keyed by notation. The primary notation is automatically registered when missing.
metadata (dict, default = empty dict) – Math metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: str
- notation: Literal['latex', 'mathml', 'html'] = 'latex'
- representations: dict[Literal['latex', 'mathml', 'html'], str]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this inline math.
- Parameters:
visitor (Any) – A visitor object with visit_math_inline method
- Returns:
Result from visitor.visit_math_inline(self)
- Return type:
Any
- get_preferred_representation(preferred: Literal['latex', 'mathml', 'html']) tuple[str, Literal['latex', 'mathml', 'html']]
Return math content for the requested representation with fallback.
- Parameters:
preferred ({"latex", "mathml", "html"}) – Requested representation for rendering
- Returns:
Math content and notation actually provided
- Return type:
tuple[str, MathNotation]
- __init__(content: str, notation: ~typing.Literal['latex', 'mathml', 'html'] = 'latex', representations: dict[~typing.Literal['latex', 'mathml', 'html'], str] = <factory>, metadata: dict[str, ~typing.Any] = <factory>, source_location: ~all2md.ast.nodes.SourceLocation | None = None) None
- class all2md.ast.MathBlock
Bases:
NodeMath block node.
Represents a block of mathematical content, typically rendered with $$ delimiters. Supported by GFM, MultiMarkdown, Pandoc, Kramdown, and MarkdownPlus flavors.
- Parameters:
content (str) – LaTeX math content (without delimiters)
notation ({"latex", "mathml", "html"}, default "latex") – Format of the primary math representation stored in
content.representations (dict, default = empty dict) – Additional representations keyed by notation. The primary notation is automatically registered when missing.
metadata (dict, default = empty dict) – Math block metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: str
- notation: Literal['latex', 'mathml', 'html'] = 'latex'
- representations: dict[Literal['latex', 'mathml', 'html'], str]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this math block.
- Parameters:
visitor (Any) – A visitor object with visit_math_block method
- Returns:
Result from visitor.visit_math_block(self)
- Return type:
Any
- get_preferred_representation(preferred: Literal['latex', 'mathml', 'html']) tuple[str, Literal['latex', 'mathml', 'html']]
Return math content for the requested representation with fallback.
- __init__(content: str, notation: ~typing.Literal['latex', 'mathml', 'html'] = 'latex', representations: dict[~typing.Literal['latex', 'mathml', 'html'], str] = <factory>, metadata: dict[str, ~typing.Any] = <factory>, source_location: ~all2md.ast.nodes.SourceLocation | None = None) None
- class all2md.ast.DefinitionList
Bases:
NodeDefinition list node (block).
Represents a definition list containing terms and their descriptions. Supported by MultiMarkdown, Pandoc, Kramdown, and MarkdownPlus flavors.
- Parameters:
items (list of tuple, default = empty list) – List of (DefinitionTerm, list[DefinitionDescription]) tuples
metadata (dict, default = empty dict) – Definition list metadata
source_location (SourceLocation or None, default = None) – Source location information
- items: list[tuple[DefinitionTerm, list[DefinitionDescription]]]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this definition list.
- Parameters:
visitor (Any) – A visitor object with visit_definition_list method
- Returns:
Result from visitor.visit_definition_list(self)
- Return type:
Any
- __init__(items: list[tuple[~all2md.ast.nodes.DefinitionTerm, list[~all2md.ast.nodes.DefinitionDescription]]]=<factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.DefinitionTerm
Bases:
NodeDefinition term node (block).
Represents a term in a definition list.
- Parameters:
content (list of Node, default = empty list) – Inline content of the term
metadata (dict, default = empty dict) – Term metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this definition term.
- Parameters:
visitor (Any) – A visitor object with visit_definition_term method
- Returns:
Result from visitor.visit_definition_term(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- class all2md.ast.DefinitionDescription
Bases:
NodeDefinition description node (block).
Represents a description/definition in a definition list.
- Parameters:
content (list of Node, default = empty list) – Block-level content of the description
metadata (dict, default = empty dict) – Description metadata
source_location (SourceLocation or None, default = None) – Source location information
- content: list[Node]
- metadata: dict[str, Any]
- source_location: SourceLocation | None = None
- accept(visitor: Any) Any
Accept a visitor for processing this definition description.
- Parameters:
visitor (Any) – A visitor object with visit_definition_description method
- Returns:
Result from visitor.visit_definition_description(self)
- Return type:
Any
- __init__(content: list[Node] = <factory>, metadata: dict[str, ~typing.Any]=<factory>, source_location: SourceLocation | None = None) None
- all2md.ast.get_node_children(node: Node) list[Node]
Get all child nodes from a node.
This is a helper function for visitor pattern implementations that need to traverse the AST. It returns a list of all child nodes regardless of the node type.
- Parameters:
node (Node) – The node to get children from
- Returns:
List of child nodes (empty list if node has no children)
- Return type:
list of Node
Examples
>>> heading = Heading(level=1, content=[Text("Hello"), Strong(content=[Text("world")])]) >>> children = get_node_children(heading) >>> len(children) 2
- all2md.ast.replace_node_children(node: Node, new_children: list[Node]) Node
Create a copy of a node with replaced children.
This is a helper function for transformer pattern implementations. It creates a new node of the same type with the children replaced.
- Parameters:
- Returns:
New node with replaced children
- Return type:
- Raises:
ValueError – If the node type doesn’t support children, if children are of the wrong type, or if the structure is invalid
Notes
- Special handling for Table nodes:
The function inspects new_children to determine header vs body rows. The first TableRow with is_header=True becomes the table header. All other rows become body rows. This allows: - Adding a header: Include a TableRow with is_header=True - Removing a header: Omit rows with is_header=True - Preserving structure: Set is_header appropriately on rows
All new_children must be TableRow instances, or ValueError is raised.
Examples
- Replace heading content:
>>> heading = Heading(level=1, content=[Text("Hello")]) >>> new_heading = replace_node_children(heading, [Text("Goodbye")]) >>> new_heading.content[0].content 'Goodbye'
- Add header to table:
>>> table = Table(rows=[TableRow(cells=[...], is_header=False)]) >>> header = TableRow(cells=[...], is_header=True) >>> new_table = replace_node_children(table, [header] + table.rows) >>> new_table.header is not None True
- class all2md.ast.NodeVisitor
Bases:
ABCAbstract base class for AST node visitors.
Subclasses should implement visit_* methods for each node type they want to process. The visitor pattern allows algorithms to be separated from the node structure.
All visit methods should accept a node and return Any (typically None for side-effect visitors, or accumulated results for transforming visitors).
Examples
Simple visitor that counts nodes:
>>> class NodeCounter(NodeVisitor): ... def __init__(self): ... self.count = 0 ... ... def generic_visit(self, node): ... self.count += 1 ... # Visit children if they exist ... if hasattr(node, 'children'): ... for child in node.children: ... child.accept(self) ... >>> counter = NodeCounter() >>> document.accept(counter) >>> print(counter.count)
- abstractmethod visit_document(node: Document) Any
Visit a Document node.
- Parameters:
node (Document) – The document node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_heading(node: Heading) Any
Visit a Heading node.
- Parameters:
node (Heading) – The heading node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_paragraph(node: Paragraph) Any
Visit a Paragraph node.
- Parameters:
node (Paragraph) – The paragraph node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_code_block(node: CodeBlock) Any
Visit a CodeBlock node.
- Parameters:
node (CodeBlock) – The code block node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_block_quote(node: BlockQuote) Any
Visit a BlockQuote node.
- Parameters:
node (BlockQuote) – The block quote node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_list(node: List) Any
Visit a List node.
- Parameters:
node (List) – The list node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_list_item(node: ListItem) Any
Visit a ListItem node.
- Parameters:
node (ListItem) – The list item node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_table(node: Table) Any
Visit a Table node.
- Parameters:
node (Table) – The table node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_table_row(node: TableRow) Any
Visit a TableRow node.
- Parameters:
node (TableRow) – The table row node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_table_cell(node: TableCell) Any
Visit a TableCell node.
- Parameters:
node (TableCell) – The table cell node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_thematic_break(node: ThematicBreak) Any
Visit a ThematicBreak node.
- Parameters:
node (ThematicBreak) – The thematic break node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_html_block(node: HTMLBlock) Any
Visit an HTMLBlock node.
- Parameters:
node (HTMLBlock) – The HTML block node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_comment(node: Comment) Any
Visit a Comment node (block-level).
- Parameters:
node (Comment) – The comment block node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_text(node: Text) Any
Visit a Text node.
- Parameters:
node (Text) – The text node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_emphasis(node: Emphasis) Any
Visit an Emphasis node.
- Parameters:
node (Emphasis) – The emphasis node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_strong(node: Strong) Any
Visit a Strong node.
- Parameters:
node (Strong) – The strong node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_code(node: Code) Any
Visit a Code node.
- Parameters:
node (Code) – The code node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_link(node: Link) Any
Visit a Link node.
- Parameters:
node (Link) – The link node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_image(node: Image) Any
Visit an Image node.
- Parameters:
node (Image) – The image node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_line_break(node: LineBreak) Any
Visit a LineBreak node.
- Parameters:
node (LineBreak) – The line break node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_strikethrough(node: Strikethrough) Any
Visit a Strikethrough node.
- Parameters:
node (Strikethrough) – The strikethrough node to visit
- Returns:
Result of processing this node
- Return type:
Any
- visit_mark(node: Mark) Any
Visit a Mark (highlight) node.
Unlike most visit methods this is concrete rather than abstract so that visitors predating the Mark node (e.g. format renderers that do not have a dedicated highlight syntax) degrade gracefully: the default simply visits the node’s children, emitting their content unwrapped rather than dropping it. Visitors that support highlighting should override this method.
- Parameters:
node (Mark) – The mark node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_underline(node: Underline) Any
Visit an Underline node.
- Parameters:
node (Underline) – The underline node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_superscript(node: Superscript) Any
Visit a Superscript node.
- Parameters:
node (Superscript) – The superscript node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_subscript(node: Subscript) Any
Visit a Subscript node.
- Parameters:
node (Subscript) – The subscript node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_html_inline(node: HTMLInline) Any
Visit an HTMLInline node.
- Parameters:
node (HTMLInline) – The inline HTML node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_comment_inline(node: CommentInline) Any
Visit a CommentInline node (inline).
- Parameters:
node (CommentInline) – The inline comment node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_footnote_reference(node: FootnoteReference) Any
Visit a FootnoteReference node.
- Parameters:
node (FootnoteReference) – The footnote reference node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_math_inline(node: MathInline) Any
Visit a MathInline node.
- Parameters:
node (MathInline) – The inline math node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_footnote_definition(node: FootnoteDefinition) Any
Visit a FootnoteDefinition node.
- Parameters:
node (FootnoteDefinition) – The footnote definition node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_definition_list(node: DefinitionList) Any
Visit a DefinitionList node.
- Parameters:
node (DefinitionList) – The definition list node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_definition_term(node: DefinitionTerm) Any
Visit a DefinitionTerm node.
- Parameters:
node (DefinitionTerm) – The definition term node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_definition_description(node: DefinitionDescription) Any
Visit a DefinitionDescription node.
- Parameters:
node (DefinitionDescription) – The definition description node to visit
- Returns:
Result of processing this node
- Return type:
Any
- abstractmethod visit_math_block(node: MathBlock) Any
Visit a MathBlock node.
- Parameters:
node (MathBlock) – The math block node to visit
- Returns:
Result of processing this node
- Return type:
Any
- generic_visit(node: Node) Any
Fallback visitor for unhandled node types.
This method is called when no specific visit_* method exists. The default implementation does nothing but can be overridden.
- Parameters:
node (Node) – The node to visit
- Returns:
Result of processing (default: None)
- Return type:
Any
- class all2md.ast.ValidationVisitor
Bases:
NodeVisitorVisitor that validates AST structure.
This visitor checks for structural issues in the AST, such as: - Invalid nesting (e.g., block nodes inside inline nodes) - Missing required fields - Invalid field values - Presence of raw HTML content (when disallowed) - Block/inline containment rules (in strict mode) - URL scheme validation (in strict mode)
- Parameters:
strict (bool, default = True) – Whether to raise errors on validation failures
allow_raw_html (bool, default = False) – Whether to allow HTMLBlock and HTMLInline nodes. By default (False), any raw HTML content will trigger a validation error for security. Set to True only when you trust the HTML source and need to preserve raw HTML content. This follows security best practices of default-deny for potentially unsafe content.
Notes
- Security Considerations:
Raw HTML in documents can pose security risks (XSS attacks, code injection). This validator defaults to rejecting HTML to enforce security best practices. Only set allow_raw_html=True when: - You trust the document source - HTML will be sanitized before rendering - You’re working in a trusted/non-web context
Examples
- Strict validation (rejects HTML by default):
>>> validator = ValidationVisitor(strict=True) >>> doc.accept(validator) # Raises ValueError if HTML present
- Allow HTML from trusted sources:
>>> validator = ValidationVisitor(strict=True, allow_raw_html=True) >>> doc.accept(validator) # Permits HTMLBlock/HTMLInline nodes
Initialize the validator with strictness and HTML policy.
- Parameters:
strict (bool, default = True) – Whether to raise errors immediately on validation failures
allow_raw_html (bool, default = False) – Whether to allow raw HTML nodes (security: default deny)
- INLINE_NODES = frozenset({<class 'all2md.ast.nodes.Code'>, <class 'all2md.ast.nodes.Emphasis'>, <class 'all2md.ast.nodes.FootnoteReference'>, <class 'all2md.ast.nodes.HTMLInline'>, <class 'all2md.ast.nodes.Image'>, <class 'all2md.ast.nodes.LineBreak'>, <class 'all2md.ast.nodes.Link'>, <class 'all2md.ast.nodes.Mark'>, <class 'all2md.ast.nodes.MathInline'>, <class 'all2md.ast.nodes.Strikethrough'>, <class 'all2md.ast.nodes.Strong'>, <class 'all2md.ast.nodes.Subscript'>, <class 'all2md.ast.nodes.Superscript'>, <class 'all2md.ast.nodes.Text'>, <class 'all2md.ast.nodes.Underline'>})
- BLOCK_NODES = frozenset({<class 'all2md.ast.nodes.BlockQuote'>, <class 'all2md.ast.nodes.CodeBlock'>, <class 'all2md.ast.nodes.DefinitionDescription'>, <class 'all2md.ast.nodes.DefinitionList'>, <class 'all2md.ast.nodes.DefinitionTerm'>, <class 'all2md.ast.nodes.Document'>, <class 'all2md.ast.nodes.FootnoteDefinition'>, <class 'all2md.ast.nodes.HTMLBlock'>, <class 'all2md.ast.nodes.Heading'>, <class 'all2md.ast.nodes.List'>, <class 'all2md.ast.nodes.ListItem'>, <class 'all2md.ast.nodes.MathBlock'>, <class 'all2md.ast.nodes.Paragraph'>, <class 'all2md.ast.nodes.Table'>, <class 'all2md.ast.nodes.TableCell'>, <class 'all2md.ast.nodes.TableRow'>, <class 'all2md.ast.nodes.ThematicBreak'>})
- __init__(strict: bool = True, allow_raw_html: bool = False)
Initialize the validator with strictness and HTML policy.
- Parameters:
strict (bool, default = True) – Whether to raise errors immediately on validation failures
allow_raw_html (bool, default = False) – Whether to allow raw HTML nodes (security: default deny)
- visit_document(node: Document) None
Validate a Document node.
- visit_heading(node: Heading) None
Validate a Heading node.
- visit_paragraph(node: Paragraph) None
Validate a Paragraph node.
- visit_code_block(node: CodeBlock) None
Validate a CodeBlock node.
- visit_block_quote(node: BlockQuote) None
Validate a BlockQuote node.
- visit_list(node: List) None
Validate a List node.
- visit_list_item(node: ListItem) None
Validate a ListItem node.
- visit_table(node: Table) None
Validate a Table node.
- visit_table_row(node: TableRow) None
Validate a TableRow node.
- visit_table_cell(node: TableCell) None
Validate a TableCell node.
- visit_thematic_break(node: ThematicBreak) None
Validate a ThematicBreak node.
- visit_html_block(node: HTMLBlock) None
Validate an HTMLBlock node.
Checks if raw HTML is allowed based on the allow_raw_html setting.
- visit_comment(node: Comment) None
Validate a Comment node (block-level).
Comments are informational and generally safe, but we validate that they have content.
- visit_text(node: Text) None
Validate a Text node.
- visit_emphasis(node: Emphasis) None
Validate an Emphasis node.
- visit_strong(node: Strong) None
Validate a Strong node.
- visit_code(node: Code) None
Validate a Code node.
- visit_link(node: Link) None
Validate a Link node.
- visit_image(node: Image) None
Validate an Image node.
- visit_line_break(node: LineBreak) None
Validate a LineBreak node.
- visit_strikethrough(node: Strikethrough) None
Validate a Strikethrough node.
- visit_mark(node: Mark) None
Validate a Mark (highlight) node.
- visit_underline(node: Underline) None
Validate an Underline node.
- visit_superscript(node: Superscript) None
Validate a Superscript node.
- visit_subscript(node: Subscript) None
Validate a Subscript node.
- visit_html_inline(node: HTMLInline) None
Validate an HTMLInline node.
Checks if raw HTML is allowed based on the allow_raw_html setting.
- visit_comment_inline(node: CommentInline) None
Validate a CommentInline node (inline).
Comments are informational and generally safe, but we validate that they have content.
- visit_footnote_reference(node: FootnoteReference) None
Validate a FootnoteReference node.
- visit_math_inline(node: MathInline) None
Validate a MathInline node.
- visit_footnote_definition(node: FootnoteDefinition) None
Validate a FootnoteDefinition node.
- visit_definition_list(node: DefinitionList) None
Validate a DefinitionList node.
- visit_definition_term(node: DefinitionTerm) None
Validate a DefinitionTerm node.
- visit_definition_description(node: DefinitionDescription) None
Validate a DefinitionDescription node.
- visit_math_block(node: MathBlock) None
Validate a MathBlock node.
- class all2md.ast.ListBuilder
Bases:
objectHelper for building nested list structures.
This class manages the complexity of creating properly nested lists, handling level transitions and list type changes automatically.
- Parameters:
root (Document or None, default = None) – Root document to append lists to. If None, creates a new Document.
allow_placeholders (bool, default = False) – If True, allows creation of placeholder list items when nesting without a parent item. Placeholder items will be marked with metadata {“placeholder”: True}. If False, raises ValueError on invalid nesting.
default_start (int, default = 1) – Default starting number for ordered lists
default_tight (bool, default = True) – Default tight spacing for lists (True means no blank lines between items)
Examples
>>> builder = ListBuilder() >>> builder.add_item(level=1, ordered=False, content=[Text("Item 1")]) >>> builder.add_item(level=2, ordered=False, content=[Text("Nested")]) >>> builder.add_item(level=1, ordered=False, content=[Text("Item 2")]) >>> doc = builder.get_document()
Initialize the list builder with an optional root document.
- __init__(root: Document | None = None, allow_placeholders: bool = False, default_start: int = 1, default_tight: bool = True)
Initialize the list builder with an optional root document.
- add_item(level: int, ordered: bool, content: list[Node], task_status: Literal['checked', 'unchecked'] | None = None, start: int | None = None, tight: bool | None = None) None
Add a list item at the specified nesting level.
- Parameters:
level (int) – Nesting level (1 is top-level, 2 is nested once, etc.)
ordered (bool) – True for ordered lists, False for unordered
content (list of Node) – Block content for the list item
task_status ({'checked', 'unchecked'} or None, default = None) – Optional task list status
start (int or None, default = None) – Starting number for ordered lists. If None, uses default_start. Only applies when a new list is created.
tight (bool or None, default = None) – Tight spacing for lists. If None, uses default_tight. Only applies when a new list is created.
- Raises:
ValueError – If level is less than 1, or if allow_placeholders=False and nesting is attempted without a parent item
- class all2md.ast.TableBuilder
Bases:
objectHelper for building table structures.
This class simplifies table construction by handling row and cell management, alignment tracking, and header designation.
- Parameters:
has_header (bool, default = False) – Whether the first row should be designated as a header
Examples
>>> builder = TableBuilder(has_header=True) >>> builder.add_row([Text("Name"), Text("Age")], is_header=True) >>> builder.add_row([Text("Alice"), Text("30")]) >>> builder.add_row([Text("Bob"), Text("25")]) >>> table = builder.get_table()
Initialize the table builder with optional header flag.
- __init__(has_header: bool = False)
Initialize the table builder with optional header flag.
- add_row(cells: Sequence[str | Node | Sequence[Node]], is_header: bool = False, alignments: list[Literal['left', 'center', 'right'] | None] | None = None) None
Add a row to the table.
This method accepts a flexible cell specification allowing mixed types per cell. Each cell can be a plain string, a single Node, or a sequence of inline Node objects.
- Parameters:
cells (Sequence of str, Node, or Sequence of Node) –
Cell contents where each cell can be: - A plain string (e.g., “Hello”) - A single Node (e.g., Text(“Hello”)) - A sequence of inline nodes (e.g., [Text(“Hello”), Strong(content=[Text(“world”)])])
Mixed types are supported per-cell. An empty sequence creates an empty row.
is_header (bool, default False) – Whether this is a header row. If has_header=True and no header exists yet, the first row added will automatically be treated as a header regardless of this parameter.
alignments (list of Alignment or None, optional) – Column alignments (only used for header rows). Length should match the number of cells. If not specified and this is a header row, all alignments will be set to None (default alignment).
Examples
[“Name”, “Age”] # All strings [Text(“Name”), Text(“Age”)] # All single nodes [[Text(“Name”)], [Text(“Age”)]] # All node sequences [“Name”, [Text(“Age: “), Strong(content=[Text(“30”)])]] # Mixed [Text(“Name”), [Text(“Age: “), Strong(content=[Text(“30”)])]] # Mixed
Notes
The flexible typing allows for ergonomic table building while maintaining type safety. The implementation normalizes all inputs to TableCell objects internally.
- set_caption(caption: str) None
Set the table caption.
- Parameters:
caption (str) – Table caption text
- set_column_alignment(column_index: int, alignment: Literal['left', 'center', 'right'] | None) None
Set alignment for a specific column.
- Parameters:
column_index (int) – Zero-based column index
alignment ({'left', 'center', 'right'} or None) – Column alignment
- class all2md.ast.DocumentBuilder
Bases:
objectHelper for building complete documents.
This class provides a fluent interface for constructing documents with multiple block-level elements. It supports method chaining for ergonomic document construction.
Examples
Basic usage with headings and paragraphs:
>>> builder = DocumentBuilder() >>> builder.add_heading(1, [Text("Title")]) >>> builder.add_paragraph([Text("Content")]) >>> doc = builder.get_document()
Using method chaining:
>>> doc = (DocumentBuilder() ... .add_heading(1, [Text("My Document")]) ... .add_paragraph([Text("Introduction paragraph.")]) ... .add_code_block("print('Hello, world!')", language="python") ... .add_thematic_break() ... .get_document())
Adding complex structures:
>>> builder = DocumentBuilder() >>> builder.add_block_quote([Paragraph(content=[Text("Quote text")])]) >>> builder.add_list([ListItem(children=[Paragraph(content=[Text("Item 1")])])], ordered=False) >>> builder.add_table( ... rows=[TableRow(cells=[TableCell(content=[Text("Cell")])])], ... header=TableRow(cells=[TableCell(content=[Text("Header")])]) ... ) >>> doc = builder.get_document()
Adding multiple nodes at once:
>>> nodes = [ ... Heading(level=1, content=[Text("Title")]), ... Paragraph(content=[Text("Paragraph 1")]), ... Paragraph(content=[Text("Paragraph 2")]) ... ] >>> builder = DocumentBuilder() >>> builder.add_nodes(nodes) >>> doc = builder.get_document()
Initialize the document builder with an empty children list.
- __init__() None
Initialize the document builder with an empty children list.
- add_node(node: Node) DocumentBuilder
Add a node to the document.
- Parameters:
node (Node) – Block-level node to add
- Returns:
Self for method chaining
- Return type:
- add_heading(level: int, content: list[Node]) DocumentBuilder
Add a heading to the document.
- Parameters:
level (int) – Heading level (1-6)
content (list of Node) – Inline content
- Returns:
Self for method chaining
- Return type:
- add_paragraph(content: list[Node]) DocumentBuilder
Add a paragraph to the document.
- Parameters:
content (list of Node) – Inline content
- Returns:
Self for method chaining
- Return type:
- add_code_block(content: str, language: str | None = None) DocumentBuilder
Add a code block to the document.
- Parameters:
content (str) – Code content
language (str or None, default = None) – Programming language
- Returns:
Self for method chaining
- Return type:
- add_thematic_break() DocumentBuilder
Add a thematic break to the document.
- Returns:
Self for method chaining
- Return type:
- add_block_quote(children: list[Node]) DocumentBuilder
Add a block quote to the document.
- Parameters:
children (list of Node) – Block-level content for the quote
- Returns:
Self for method chaining
- Return type:
- add_list(items: list[ListItem], ordered: bool = False, start: int = 1, tight: bool = True) DocumentBuilder
Add a list to the document.
- Parameters:
items (list of ListItem) – List items
ordered (bool, default = False) – True for ordered list, False for unordered
start (int, default = 1) – Starting number for ordered lists
tight (bool, default = True) – Tight spacing (no blank lines between items)
- Returns:
Self for method chaining
- Return type:
- add_table(rows: list[TableRow], header: TableRow | None = None, alignments: list[Literal['left', 'center', 'right'] | None] | None = None, caption: str | None = None) DocumentBuilder
Add a table to the document.
- Parameters:
- Returns:
Self for method chaining
- Return type:
- add_html_block(content: str) DocumentBuilder
Add an HTML block to the document.
- Parameters:
content (str) – Raw HTML content
- Returns:
Self for method chaining
- Return type:
- add_footnote_definition(identifier: str, content: list[Node]) DocumentBuilder
Add a footnote definition to the document.
- Parameters:
identifier (str) – Footnote identifier/label
content (list of Node) – Block-level content for the footnote
- Returns:
Self for method chaining
- Return type:
- add_definition_list(items: list[tuple[DefinitionTerm, list[DefinitionDescription]]]) DocumentBuilder
Add a definition list to the document.
- Parameters:
items (list of tuple) – List of (term, descriptions) tuples
- Returns:
Self for method chaining
- Return type:
- add_math_block(content: str, notation: Literal['latex', 'mathml', 'html'] = 'latex') DocumentBuilder
Add a math block to the document.
- Parameters:
content (str) – Math expression content
notation ({"latex", "mathml", "html"}, default = "latex") – Math notation system
- Returns:
Self for method chaining
- Return type:
- add_nodes(nodes: list[Node]) DocumentBuilder
Add multiple nodes to the document at once.
- Parameters:
nodes (list of Node) – Block-level nodes to add
- Returns:
Self for method chaining
- Return type:
Examples
>>> builder = DocumentBuilder() >>> builder.add_nodes([ ... Heading(level=1, content=[Text("Title")]), ... Paragraph(content=[Text("Content")]) ... ])
- all2md.ast.ast_to_dict(node: Node | SourceLocation) dict[str, Any]
Convert an AST node to a dictionary representation.
- Parameters:
node (Node or SourceLocation) – The AST node to convert
- Returns:
Dictionary representation of the node
- Return type:
dict
Examples
>>> from all2md.ast import Text >>> text = Text(content="Hello") >>> ast_to_dict(text) {'node_type': 'Text', 'content': 'Hello', 'metadata': {}, 'source_location': None}
- all2md.ast.dict_to_ast(data: dict[str, Any], strict_mode: bool = True) Node | SourceLocation
Convert a dictionary representation back to an AST node.
- Parameters:
data (dict) – Dictionary representation of a node
strict_mode (bool, default True) – If True, raise ValueError on unknown node types. If False, skip unknown nodes (useful for forward compatibility).
- Returns:
Reconstructed AST node
- Return type:
- Raises:
ValueError – If the dictionary contains an unknown node type and strict_mode is True
Examples
>>> data = {'node_type': 'Text', 'content': 'Hello', 'metadata': {}, 'source_location': None} >>> node = dict_to_ast(data) >>> print(node.content) Hello
- all2md.ast.ast_to_json(node: Node, indent: int | None = None) str
Serialize an AST node to JSON string with schema versioning.
The serialized JSON includes a schema_version field to enable migration paths for future AST evolution. Unicode characters are preserved without escape sequences for improved readability.
- Parameters:
node (Node) – The AST node to serialize
indent (int or None, default = None) – Number of spaces for indentation (None for compact format)
- Returns:
JSON string representation with schema version
- Return type:
str
Notes
- The output format includes a schema_version field:
{“schema_version”: 1, “node_type”: “…”, …}
Examples
>>> from all2md.ast import Document, Paragraph, Text >>> doc = Document(children=[ ... Paragraph(content=[Text(content="Hello")]) ... ]) >>> json_str = ast_to_json(doc, indent=2) >>> print(json_str)
- all2md.ast.json_to_ast(json_str: str, validate_schema: bool = True, strict_mode: bool = True) Node
Deserialize a JSON string to an AST node.
This function handles schema versioning to support future AST evolution. If no schema_version is present in the JSON (for backward compatibility), it assumes version 1.
- Parameters:
json_str (str) – JSON string representation
validate_schema (bool, default True) – If True, validate schema version and raise errors on unsupported versions. If False, skip schema validation (useful for forward compatibility).
strict_mode (bool, default True) – If True, raise ValueError on unknown node types or attributes. If False, log warnings and skip unknown elements (useful for forward compatibility).
- Returns:
Reconstructed AST node
- Return type:
- Raises:
ValueError – If JSON is invalid, contains unknown node types, or has an unsupported schema version (when validate_schema=True or strict_mode=True)
json.JSONDecodeError – If JSON string is malformed
Notes
Backward compatibility is maintained for JSON without schema_version field.
Examples
>>> json_str = '{"schema_version": 1, "node_type": "Text", "content": "Hello"}' >>> node = json_to_ast(json_str) >>> print(node.content) Hello
Optional validation allows forward compatibility:
>>> # With validation disabled, can load newer schema versions >>> node = json_to_ast(future_json, validate_schema=False, strict_mode=False)
- class all2md.ast.NodeTransformer
Bases:
NodeVisitorBase class for transforming AST nodes.
Subclasses should implement visit_* methods that return modified nodes or None to remove nodes. The transformer creates a new AST with the transformations applied.
Examples
>>> class UppercaseTransformer(NodeTransformer): ... def visit_text(self, node): ... return Text(content=node.content.upper()) >>> >>> transformer = UppercaseTransformer() >>> new_doc = transformer.transform(doc)
- visit_block_quote(node: BlockQuote) BlockQuote
Transform a BlockQuote node.
- visit_thematic_break(node: ThematicBreak) ThematicBreak
Transform a ThematicBreak node.
- visit_strikethrough(node: Strikethrough) Strikethrough
Transform a Strikethrough node.
- visit_superscript(node: Superscript) Superscript
Transform a Superscript node.
- visit_html_inline(node: HTMLInline) HTMLInline
Transform an HTMLInline node.
- visit_comment_inline(node: CommentInline) CommentInline
Transform a CommentInline node (inline).
- visit_footnote_reference(node: FootnoteReference) FootnoteReference
Transform a FootnoteReference node.
- visit_math_inline(node: MathInline) MathInline
Transform a MathInline node.
- visit_footnote_definition(node: FootnoteDefinition) FootnoteDefinition
Transform a FootnoteDefinition node.
- visit_definition_list(node: DefinitionList) DefinitionList
Transform a DefinitionList node.
- visit_definition_term(node: DefinitionTerm) DefinitionTerm
Transform a DefinitionTerm node.
- visit_definition_description(node: DefinitionDescription) DefinitionDescription
Transform a DefinitionDescription node.
- class all2md.ast.NodeCollector
Bases:
NodeVisitorVisitor that collects nodes matching a condition.
- Parameters:
predicate (callable or None, default = None) – Function that takes a node and returns True to collect it
Initialize the collector with an optional predicate function.
- __init__(predicate: Callable[[Node], bool] | None = None)
Initialize the collector with an optional predicate function.
- visit_document(node: Document) None
Visit a Document node.
- visit_heading(node: Heading) None
Visit a Heading node.
- visit_paragraph(node: Paragraph) None
Visit a Paragraph node.
- visit_code_block(node: CodeBlock) None
Visit a CodeBlock node.
- visit_block_quote(node: BlockQuote) None
Visit a BlockQuote node.
- visit_list(node: List) None
Visit a List node.
- visit_list_item(node: ListItem) None
Visit a ListItem node.
- visit_table(node: Table) None
Visit a Table node.
- visit_table_row(node: TableRow) None
Visit a TableRow node.
- visit_table_cell(node: TableCell) None
Visit a TableCell node.
- visit_thematic_break(node: ThematicBreak) None
Visit a ThematicBreak node.
- visit_html_block(node: HTMLBlock) None
Visit an HTMLBlock node.
- visit_comment(node: Comment) None
Visit a Comment node (block-level).
- visit_text(node: Text) None
Visit a Text node.
- visit_emphasis(node: Emphasis) None
Visit an Emphasis node.
- visit_strong(node: Strong) None
Visit a Strong node.
- visit_code(node: Code) None
Visit a Code node.
- visit_link(node: Link) None
Visit a Link node.
- visit_image(node: Image) None
Visit an Image node.
- visit_line_break(node: LineBreak) None
Visit a LineBreak node.
- visit_strikethrough(node: Strikethrough) None
Visit a Strikethrough node.
- visit_mark(node: Mark) None
Visit a Mark (highlight) node.
- visit_underline(node: Underline) None
Visit an Underline node.
- visit_superscript(node: Superscript) None
Visit a Superscript node.
- visit_subscript(node: Subscript) None
Visit a Subscript node.
- visit_html_inline(node: HTMLInline) None
Visit an HTMLInline node.
- visit_comment_inline(node: CommentInline) None
Visit a CommentInline node (inline).
- visit_footnote_reference(node: FootnoteReference) None
Visit a FootnoteReference node.
- visit_math_inline(node: MathInline) None
Visit a MathInline node.
- visit_footnote_definition(node: FootnoteDefinition) None
Visit a FootnoteDefinition node.
- visit_definition_list(node: DefinitionList) None
Visit a DefinitionList node.
- visit_definition_term(node: DefinitionTerm) None
Visit a DefinitionTerm node.
- visit_definition_description(node: DefinitionDescription) None
Visit a DefinitionDescription node.
- visit_math_block(node: MathBlock) None
Visit a MathBlock node.
- all2md.ast.clone_node(node: Node) Node
Create a deep copy of an AST node.
Examples
>>> cloned_doc = clone_node(doc) >>> cloned_doc is doc # False False
- all2md.ast.extract_nodes(doc: Document, node_type: Type[Node] | None = None) list[Node]
Extract all nodes of a specific type from a document.
- Parameters:
- Returns:
All matching nodes
- Return type:
list of Node
Examples
>>> headings = extract_nodes(doc, Heading) >>> images = extract_nodes(doc, Image)
- all2md.ast.filter_nodes(doc: Document, predicate: Callable[[Node], bool]) Document
Filter nodes from a document based on a condition.
- Parameters:
doc (Document) – Document to filter
predicate (callable) – Function that takes a node and returns True to keep it
- Returns:
New document with filtered nodes
- Return type:
Notes
The root Document node is always preserved, regardless of the predicate. Only the children of the Document are filtered according to the predicate.
Examples
- Remove all images:
>>> filtered_doc = filter_nodes(doc, lambda n: not isinstance(n, Image))
- Keep only headings and paragraphs:
>>> filtered_doc = filter_nodes(doc, lambda n: isinstance(n, (Heading, Paragraph)))
- all2md.ast.transform_nodes(doc: Document, transformer: NodeTransformer) Document
Apply a transformation visitor to a document.
- Parameters:
doc (Document) – Document to transform
transformer (NodeTransformer) – Transformer to apply
- Returns:
Transformed document
- Return type:
Examples
>>> transformer = HeadingLevelTransformer(offset=1) >>> new_doc = transform_nodes(doc, transformer)
- all2md.ast.merge_documents(docs: list[Document], metadata_merger: Callable[[dict[str, Any], dict[str, Any]], dict[str, Any]] | None = None) Document
Merge multiple documents into a single document.
- Parameters:
docs (list of Document) – Documents to merge
metadata_merger (callable or None, default = None) –
Optional function to customize metadata merging. Takes (existing_metadata, new_metadata) and returns merged metadata dict. If None, uses last-write-wins strategy where later documents overwrite earlier ones for duplicate keys.
Common strategies provided: - last_write_wins_merger: Later values overwrite (default behavior) - first_write_wins_merger: Earlier values are preserved - merge_lists_merger: Concatenate list values, last-wins for others
- Returns:
Merged document with all children and combined metadata
- Return type:
Examples
Basic merge with default last-write-wins:
>>> merged = merge_documents([doc1, doc2, doc3])
Preserve first document’s metadata values:
>>> merged = merge_documents([doc1, doc2], metadata_merger=first_write_wins_merger)
Concatenate list-valued metadata:
>>> merged = merge_documents([doc1, doc2], metadata_merger=merge_lists_merger)
Custom merger:
>>> def custom_merger(existing, new): ... # Custom logic here ... return merged_dict >>> merged = merge_documents([doc1, doc2], metadata_merger=custom_merger)
Notes
The default behavior (when metadata_merger=None) uses last-write-wins strategy, meaning later documents’ metadata values overwrite earlier ones for duplicate keys.
- all2md.ast.last_write_wins_merger(existing: dict[str, Any], new: dict[str, Any]) dict[str, Any]
Merge metadata with last-write-wins strategy (default).
Later document’s metadata values overwrite earlier ones for duplicate keys.
- Parameters:
existing (dict) – Existing accumulated metadata
new (dict) – New metadata to merge in
- Returns:
Merged metadata dictionary
- Return type:
dict
Examples
>>> existing = {"author": "Alice", "version": "1.0"} >>> new = {"version": "2.0", "date": "2025-01-01"} >>> last_write_wins_merger(existing, new) {"author": "Alice", "version": "2.0", "date": "2025-01-01"}
- all2md.ast.first_write_wins_merger(existing: dict[str, Any], new: dict[str, Any]) dict[str, Any]
Merge metadata with first-write-wins strategy.
Earlier document’s metadata values are preserved for duplicate keys.
- Parameters:
existing (dict) – Existing accumulated metadata
new (dict) – New metadata to merge in
- Returns:
Merged metadata dictionary
- Return type:
dict
Examples
>>> existing = {"author": "Alice", "version": "1.0"} >>> new = {"version": "2.0", "date": "2025-01-01"} >>> first_write_wins_merger(existing, new) {"author": "Alice", "version": "1.0", "date": "2025-01-01"}
- all2md.ast.merge_lists_merger(existing: dict[str, Any], new: dict[str, Any]) dict[str, Any]
Merge metadata with list concatenation for list values.
When both existing and new have list values for the same key, concatenate them. For non-list values, uses last-write-wins strategy.
- Parameters:
existing (dict) – Existing accumulated metadata
new (dict) – New metadata to merge in
- Returns:
Merged metadata dictionary
- Return type:
dict
Examples
>>> existing = {"tags": ["python", "ast"], "version": "1.0"} >>> new = {"tags": ["markdown"], "version": "2.0"} >>> merge_lists_merger(existing, new) {"tags": ["python", "ast", "markdown"], "version": "2.0"}
- class all2md.ast.HeadingLevelTransformer
Bases:
NodeTransformerTransformer that adjusts heading levels by an offset.
- Parameters:
offset (int) – Amount to shift heading levels (can be negative)
min_level (int, default = 1) – Minimum allowed heading level
max_level (int, default = 6) – Maximum allowed heading level
Examples
>>> # Increase all heading levels by 1 >>> transformer = HeadingLevelTransformer(offset=1) >>> new_doc = transformer.transform(doc)
Initialize the transform with offset and level constraints.
- __init__(offset: int, min_level: int = 1, max_level: int = 6)
Initialize the transform with offset and level constraints.
- class all2md.ast.LinkRewriter
Bases:
NodeTransformerTransformer that rewrites link URLs.
- Parameters:
url_mapper (callable) – Function that takes a URL string and returns a new URL string
validate_urls (bool, default = True) – Whether to validate URLs after rewriting for dangerous schemes. When True, raises ValueError if the url_mapper produces a URL with a dangerous scheme (javascript:, vbscript:, etc.). This provides defense-in-depth against accidentally creating unsafe URLs.
- Raises:
ValueError – If validate_urls=True and url_mapper produces a URL with a dangerous or unrecognized scheme
Examples
>>> # Convert relative links to absolute >>> def make_absolute(url): ... if url.startswith('/'): ... return f'https://example.com{url}' ... return url >>> transformer = LinkRewriter(make_absolute) >>> new_doc = transformer.transform(doc)
>>> # Disable validation if you need to generate non-standard URLs >>> transformer = LinkRewriter(my_mapper, validate_urls=False)
Notes
- Security Considerations:
By default (validate_urls=True), this transformer validates that the url_mapper does not produce dangerous URLs. This prevents accidental creation of XSS vectors like javascript: or data:text/html URLs. Only disable validation if you have a specific need and understand the security implications.
Initialize the transform with a URL mapping function.
- Parameters:
url_mapper (callable) – Function that takes a URL string and returns a new URL string
validate_urls (bool, default = True) – Whether to validate URLs after rewriting for dangerous schemes
- __init__(url_mapper: Callable[[str], str], validate_urls: bool = True)
Initialize the transform with a URL mapping function.
- Parameters:
url_mapper (callable) – Function that takes a URL string and returns a new URL string
validate_urls (bool, default = True) – Whether to validate URLs after rewriting for dangerous schemes
- class all2md.ast.TextReplacer
Bases:
NodeTransformerTransformer that replaces text patterns.
- Parameters:
pattern (str) – Pattern to search for
replacement (str) – Replacement string
use_regex (bool, default = False) – Whether to use regex matching
- Raises:
ValueError – If use_regex=True and pattern is not a valid regular expression
SecurityError – If use_regex=True and pattern contains dangerous constructs that could lead to ReDoS (Regular Expression Denial of Service) attacks
Examples
>>> # Replace all occurrences of "foo" with "bar" >>> transformer = TextReplacer("foo", "bar") >>> new_doc = transformer.transform(doc)
>>> # Use regex for pattern matching >>> transformer = TextReplacer(r"\\d+", "NUMBER", use_regex=True) >>> new_doc = transformer.transform(doc)
Notes
For security reasons, when
use_regex=True, this transform validates user-supplied regex patterns to prevent ReDoS attacks. Patterns with nested quantifiers or excessive backtracking potential are rejected. Seevalidate_user_regex_pattern()for details on what patterns are considered safe.Initialize the transform with pattern and replacement.
- Parameters:
pattern (str) – Pattern to search for (literal string or regex)
replacement (str) – Replacement string
use_regex (bool, default = False) – Whether to use regex matching
- Raises:
ValueError – If use_regex=True and pattern is not a valid regular expression
SecurityError – If use_regex=True and pattern contains dangerous constructs
- __init__(pattern: str, replacement: str, use_regex: bool = False)
Initialize the transform with pattern and replacement.
- Parameters:
pattern (str) – Pattern to search for (literal string or regex)
replacement (str) – Replacement string
use_regex (bool, default = False) – Whether to use regex matching
- Raises:
ValueError – If use_regex=True and pattern is not a valid regular expression
SecurityError – If use_regex=True and pattern contains dangerous constructs
- all2md.ast.extract_text(node_or_nodes: Node | list[Node], joiner: str = ' ') str
Extract plain text from a node or list of nodes.
This function recursively traverses the AST and concatenates all Text node content, joining text parts with the specified joiner string. It uses get_node_children() to properly handle all node types including List, Table, and other complex structures.
- Parameters:
- Returns:
Concatenated text content from all Text nodes
- Return type:
str
Notes
- Spacing Behavior:
The joiner is applied at each level of the AST hierarchy when combining child nodes. This can result in extra spaces when Text nodes already contain whitespace at their boundaries. For example:
>>> para = Paragraph(content=[ ... Text(content="This is "), # trailing space ... Strong(content=[Text(content="bold")]), ... Text(content=" text.") # leading space ... ]) >>> extract_text(para) 'This is bold text.' # Note the double spaces
This occurs because: 1. Text content is preserved exactly (including trailing/leading spaces) 2. The joiner adds spacing between nodes at each nesting level 3. These can combine to create multiple consecutive spaces
Workarounds: - Use joiner=”” and rely on spaces within Text nodes: extract_text(node, joiner=””) - Post-process with regex: re.sub(r’\s+’, ‘ ‘, extract_text(node)).strip() - Normalize Text nodes before extraction to trim whitespace
This behavior is intentional to preserve the exact text content and provide consistent separation at structural boundaries.
Examples
Extract text with space joiner (default):
>>> from all2md.ast import Paragraph, Text, Strong >>> para = Paragraph(content=[ ... Text(content="This is "), ... Strong(content=[Text(content="bold")]), ... Text(content=" text.") ... ]) >>> extract_text(para) 'This is bold text.' # Note: double spaces preserved
Extract text with no joiner (preserves only Text content):
>>> from all2md.ast import Heading, Text >>> heading = Heading(level=2, content=[Text(content="My Heading")]) >>> extract_text(heading.content, joiner="") 'My Heading'
Extract text from a list of nodes:
>>> from all2md.ast import Text, Emphasis >>> nodes = [Text(content="Hello"), Emphasis(content=[Text(content="world")])] >>> extract_text(nodes) 'Hello world'
Extract text from complex structures (List, Table):
>>> from all2md.ast import List, ListItem, Text, Paragraph >>> lst = List(ordered=False, items=[ ... ListItem(children=[Paragraph(content=[Text(content="Item 1")])]), ... ListItem(children=[Paragraph(content=[Text(content="Item 2")])]) ... ]) >>> extract_text(lst) 'Item 1 Item 2'
Normalize whitespace in extracted text:
>>> import re >>> result = extract_text(para) >>> normalized = re.sub(r'\\s+', ' ', result).strip() >>> normalized 'This is bold text.'
- class all2md.ast.Section
Bases:
objectRepresents a document section with heading and content.
A section consists of a heading node and all content nodes that follow it until the next heading of the same or higher level.
- Parameters:
heading (Heading) – The heading node for this section
content (list of Node) – All nodes between this heading and the next same-or-higher level heading
level (int) – Heading level (1-6)
start_index (int) – Index of the heading in the parent document’s children list
end_index (int) – Exclusive end index (one past the last content node)
Examples
>>> section = Section( ... heading=Heading(level=1, content=[Text("Title")]), ... content=[Paragraph(content=[Text("Content")])], ... level=1, ... start_index=0, ... end_index=2 ... )
- heading: Heading
- content: list[Node]
- level: int = 1
- start_index: int = 0
- end_index: int = 0
- to_document() Document
Convert this section to a standalone document.
- Returns:
New document containing the heading and content
- Return type:
Examples
>>> doc = section.to_document() >>> len(doc.children) 2
- get_heading_text() str
Extract plain text from the heading.
- Returns:
Plain text content of the heading
- Return type:
str
Examples
>>> section.get_heading_text() 'Introduction'
- all2md.ast.get_all_sections(doc: Document, min_level: int = 1, max_level: int = 6) list[Section]
Extract all sections from a document.
- Parameters:
doc (Document) – Document to extract sections from
min_level (int, default = 1) – Minimum heading level to include (1 is highest)
max_level (int, default = 6) – Maximum heading level to include (6 is lowest)
- Returns:
All sections in document order
- Return type:
list of Section
Notes
A section is defined as a heading plus all content until the next same-or-higher level heading. Content before the first heading is not included in any section (see get_preamble).
Examples
>>> sections = get_all_sections(doc) >>> for section in sections: ... print(f"Level {section.level}: {section.get_heading_text()}")
- Get only top-level sections:
>>> top_sections = get_all_sections(doc, min_level=1, max_level=1)
- all2md.ast.get_preamble(doc: Document) list[Node]
Get all content before the first heading.
- Parameters:
doc (Document) – Document to extract preamble from
- Returns:
All nodes before the first heading (empty if document starts with heading)
- Return type:
list of Node
Examples
>>> preamble = get_preamble(doc) >>> if preamble: ... print(f"Found {len(preamble)} preamble nodes")
- all2md.ast.parse_section_ranges(section_spec: str, total_sections: int) list[int]
Parse section range specification into list of 0-based section indices.
Supports various formats (all 1-indexed input): - “1-3” -> [0, 1, 2] - “5” -> [4] - “10-” -> [9, 10, …, total_sections-1] - “1-3,5,10-” -> combined ranges - “5-3” -> [2, 3, 4] (automatically swaps to “3-5”)
Reversed ranges (where start > end) are automatically corrected by swapping the values. For example, “10-5” is treated as “5-10”.
- Parameters:
section_spec (str) – Section range specification (1-based section numbers)
total_sections (int) – Total number of sections in document
- Returns:
Sorted list of 0-based section indices
- Return type:
list of int
Examples
>>> parse_section_ranges("1-3,5", 10) [0, 1, 2, 4] >>> parse_section_ranges("8-", 10) [7, 8, 9] >>> parse_section_ranges("10-5", 10) [4, 5, 6, 7, 8, 9]
- all2md.ast.query_sections(doc: Document, spec: str | int | list[int] | Callable[[Section], bool] | None = None, *, level: int | None = None, min_level: int = 1, max_level: int = 6, case_sensitive: bool = False) list[Section]
Universal section query function with flexible parameters.
This function consolidates multiple section-finding functions into one powerful interface that handles querying by name, index, pattern, predicate, or level filtering.
- Parameters:
doc (Document) – Document to search
spec (str, int, list[int], callable, or None) – Query specification: - None: return all sections (respects level filters) - str: heading text or pattern (“Introduction”, “Chapter*”) - int: single section index (0-based) - list[int]: multiple section indices (0-based) - callable: predicate function(section) -> bool
level (int or None, default = None) – If specified, only return sections at this exact level (1-6)
min_level (int, default = 1) – Minimum heading level to include
max_level (int, default = 6) – Maximum heading level to include
case_sensitive (bool, default = False) – Whether text matching is case-sensitive (for string specs)
- Returns:
Matching sections in document order
- Return type:
list of Section
- Raises:
ValueError – If spec is invalid or indices are out of range
IndexError – If a specified index is out of range
Examples
- Find all sections:
>>> sections = query_sections(doc)
- Find by exact heading text:
>>> sections = query_sections(doc, "Introduction")
- Find by pattern (wildcards):
>>> sections = query_sections(doc, "Chapter*")
- Find by single index:
>>> sections = query_sections(doc, 0) # First section
- Find by multiple indices:
>>> sections = query_sections(doc, [0, 2, 4])
- Find by predicate:
>>> sections = query_sections(doc, lambda s: len(s.content) > 5)
- Find by level:
>>> sections = query_sections(doc, level=2)
- Find by level range:
>>> sections = query_sections(doc, min_level=2, max_level=3)
- Combined filters:
>>> sections = query_sections(doc, "Chapter*", level=1)
- all2md.ast.count_sections(doc: Document, level: int | None = None) int
Count the number of sections in a document.
- Parameters:
doc (Document) – Document to count sections in
level (int or None, default = None) – If specified, only count sections at this level
- Returns:
Number of sections
- Return type:
int
Examples
>>> total_sections = count_sections(doc) >>> top_level = count_sections(doc, level=1)
- all2md.ast.find_heading(doc: Document, text: str, level: int | None = None, case_sensitive: bool = False) tuple[int, Heading] | None
Find a heading node in the document.
This function finds heading nodes directly, not sections. Use query_sections() if you want to find sections (heading + content).
- Parameters:
doc (Document) – Document to search
text (str) – Text to search for
level (int or None, default = None) – If specified, only match headings at this level
case_sensitive (bool, default = False) – Whether to perform case-sensitive matching
- Returns:
Tuple of (index, heading_node) if found, None otherwise
- Return type:
tuple of (int, Heading) or None
Examples
>>> result = find_heading(doc, "Introduction") >>> if result: ... idx, heading = result ... print(f"Found at index {idx}, level {heading.level}")
- all2md.ast.extract_sections(doc: Document, spec: str | int | list[int], *, case_sensitive: bool = False, combine: bool = True, separator: Node | None = None) Document
Extract sections from document using flexible specification.
This function provides powerful section extraction with support for ranges, wildcards, multiple sections, and fuzzy matching with helpful suggestions.
- Parameters:
doc (Document) – Source document
spec (str or int or list of int) – Extraction specification: - Name: “Introduction” (exact match) - Pattern: “Intro*”, “Results” (uses fnmatch) - Single index: 0 or “#:1” (0-based or 1-based with #:) - Range: “#:1-3”, “#:3-” (1-based, inclusive) - Multiple: “#:1,3,5” or [0, 2, 4] (1-based or 0-based list)
case_sensitive (bool, default False) – Whether name matching is case-sensitive
combine (bool, default True) – If True, combine sections into single document with separators. If False, return document with just the first match.
separator (Node or None, default None) – Node to insert between sections (default: ThematicBreak)
- Returns:
Document with extracted sections
- Return type:
- Raises:
ValueError – If spec is invalid or no sections match
Examples
- Extract by name:
>>> extracted = extract_sections(doc, "Introduction")
- Extract with wildcards:
>>> extracted = extract_sections(doc, "Chapter*")
- Extract by range:
>>> extracted = extract_sections(doc, "#:1-3")
- Extract multiple specific sections:
>>> extracted = extract_sections(doc, "#:1,3,5") >>> extracted = extract_sections(doc, [0, 2, 4])
- Extract without separators:
>>> extracted = extract_sections(doc, "#:1-3", separator=None)
- all2md.ast.generate_toc(doc: Document, max_level: int = 3, style: Literal['markdown', 'list', 'nested'] = 'markdown') str | List
Generate a table of contents from document headings.
- Parameters:
doc (Document) – Document to generate TOC from
max_level (int, default = 3) – Maximum heading level to include (1-6)
style ({"markdown", "list", "nested"}, default = "markdown") – Output style for the TOC
- Returns:
Table of contents as markdown string (for “markdown” style) or List node (for “list” and “nested” styles)
- Return type:
str or List
Notes
“markdown”: Returns markdown-formatted TOC as a string
“list”: Returns a flat List AST node with all headings
“nested”: Returns nested List AST nodes respecting heading hierarchy
Examples
>>> toc = generate_toc(doc, max_level=3) >>> print(toc) # Table of Contents
[Introduction](#introduction) - [Background](#background) - [Motivation](#motivation)
[Methods](#methods)
- all2md.ast.insert_toc(doc: Document, position: Literal['start', 'after_first_heading'] = 'start', max_level: int = 3, style: Literal['markdown', 'list', 'nested'] = 'markdown') Document
Insert a table of contents into the document.
- Parameters:
doc (Document) – Document to modify
position ({"start", "after_first_heading"}, default = "start") – Where to insert the TOC
max_level (int, default = 3) – Maximum heading level to include
style ({"markdown", "list", "nested"}, default = "markdown") – Style of the TOC
- Returns:
New document with TOC inserted
- Return type:
Examples
>>> doc_with_toc = insert_toc(doc, position="start", max_level=3)
- class all2md.ast.DocumentSplitter
Bases:
objectHandles various document splitting strategies.
This class provides methods to split documents at semantic boundaries based on different criteria: heading levels, word counts, number of parts, or automatic detection.
- static split_by_heading_level(doc: Document, level: int, include_preamble: bool = True) list[SplitResult]
Split document at every heading of specified level.
- Parameters:
doc (Document) – Document to split
level (int) – Heading level to split on (1-6)
include_preamble (bool) – Whether to include content before first heading as separate split
- Returns:
Split documents, one per section at specified level
- Return type:
list of SplitResult
- Raises:
ValueError – If level is not between 1 and 6
Examples
>>> DocumentSplitter.split_by_heading_level(doc, level=1)
- static split_by_word_count(doc: Document, target_words: int) list[SplitResult]
Split document by word count, maintaining section boundaries.
Accumulates sections until target word count is reached, then creates a split. Ensures splits occur at section boundaries for semantic coherence.
- Parameters:
doc (Document) – Document to split
target_words (int) – Target word count per split (approximate)
- Returns:
Split documents with roughly equal word counts
- Return type:
list of SplitResult
- Raises:
ValueError – If target_words is less than 1
Examples
>>> DocumentSplitter.split_by_word_count(doc, target_words=500)
- static split_by_parts(doc: Document, num_parts: int) list[SplitResult]
Split document into N roughly equal parts at section boundaries.
Calculates total word count and divides by num_parts to determine target words per part. Then uses word count splitting to create approximately equal splits.
- Parameters:
doc (Document) – Document to split
num_parts (int) – Number of parts to create
- Returns:
Split documents with roughly equal word counts
- Return type:
list of SplitResult
- Raises:
ValueError – If num_parts is less than 1
Examples
>>> DocumentSplitter.split_by_parts(doc, num_parts=5)
- static split_into_slices(doc: Document, num_slices: int) list[SplitResult]
Divide a document into exactly
num_slicesbalanced, contiguous slices.Unlike
split_by_parts()(which targets a word count and may yield a different number of parts), this guarantees exactlynum_slicesslices whenever the document has at least that many semantic “atoms” (sections, or word-count blocks for heading-light documents). When the document has fewer atoms than requested, it returns one slice per atom.This powers the
--slice X/Ypaging flag, where a deterministicYmatters so callers can page1/Y, 2/Y, ... Y/Y.- Parameters:
doc (Document) – Document to slice.
num_slices (int) – Desired number of slices (
Y). Must be >= 1.
- Returns:
min(num_slices, num_atoms)contiguous slices in document order.- Return type:
list of SplitResult
- Raises:
ValueError – If
num_slicesis less than 1.
- static split_by_break(doc: Document) list[SplitResult]
Split document at thematic breaks (horizontal rules).
Splits the document at any ThematicBreak nodes, which represent horizontal rules (
---,***,___) in Markdown and similar separators in other formats.- Parameters:
doc (Document) – Document to split
- Returns:
Split documents at thematic break boundaries
- Return type:
list of SplitResult
Examples
>>> DocumentSplitter.split_by_break(doc)
- static split_by_delimiter(doc: Document, delimiter: str) list[SplitResult]
Split document at custom text delimiters.
Searches for paragraphs or text nodes that contain only the delimiter text (allowing for whitespace) and splits the document at those points.
- Parameters:
doc (Document) – Document to split
delimiter (str) – Text delimiter to split on (e.g.,
"-----","***","<!-- split -->")
- Returns:
Split documents at delimiter boundaries
- Return type:
list of SplitResult
Examples
>>> DocumentSplitter.split_by_delimiter(doc, delimiter="-----")
- static split_auto(doc: Document, target_words: int = 1500) list[SplitResult]
Automatically determine best split strategy based on document structure.
Analyzes document to find natural split points: 1. Try h1 boundaries if sections are reasonable size 2. Otherwise try h2 boundaries 3. Fall back to word count splitting if sections too large
- Parameters:
doc (Document) – Document to split
target_words (int) – Target word count per split for fallback strategy
- Returns:
Split documents using the best detected strategy
- Return type:
list of SplitResult
Examples
>>> DocumentSplitter.split_auto(doc) # Target ~1500 words per split
- static split_by_sections(doc: Document, include_preamble: bool = True) list[SplitResult]
Split document into separate documents by sections.
This method was moved from document_utils.py and adapted to return list[SplitResult] for consistency with other DocumentSplitter methods.
- Parameters:
doc (Document) – Document to split
include_preamble (bool, default = True) – If True and there is content before the first heading, include it as a separate split at the beginning
- Returns:
List of split results, one per section (plus preamble if present)
- Return type:
list of SplitResult
Examples
>>> splits = DocumentSplitter.split_by_sections(doc) >>> for i, split_result in enumerate(splits): ... print(f"Section {i}: {len(split_result.document.children)} nodes")
- class all2md.ast.SplitResult
Bases:
objectRepresents a split portion of a document.
- Variables:
document (Document) – The split document AST
index (int) – 1-based index of this split (001, 002, etc.)
title (Optional[str]) – Title/heading text for this split (if available)
word_count (int) – Approximate word count for this split
metadata (dict) – Additional metadata for this split
- document: Document
- index: int
- title: str | None = None
- word_count: int = 0
- metadata: dict
- get_filename_slug() str
Generate filesystem-safe slug from title.
- Returns:
Sanitized slug suitable for filenames
- Return type:
str
Examples
>>> split = SplitResult(doc, 1, title="Chapter 1: Introduction") >>> split.get_filename_slug() 'chapter-1-introduction'
- __init__(document: Document, index: int, title: str | None = None, word_count: int = 0, metadata: dict = <factory>) None
- all2md.ast.parse_split_spec(spec: str) tuple[str, Any]
Parse –split-by CLI argument into strategy and parameters.
- Parameters:
spec (str) – Split specification string
- Returns:
(strategy_name, parameter) where: - (“heading”, 1) for “h1” - (“heading”, 2) for “h2” - (“length”, 400) for “length=400” - (“parts”, 4) for “parts=4” - (“delimiter”, “—–”) for “delimiter=—–” - (“break”, None) for “break” - (“page”, None) for “page” - (“chapter”, None) for “chapter” - (“auto”, None) for “auto”
- Return type:
tuple of (str, Any)
- Raises:
ValueError – If spec format is invalid
Examples
>>> parse_split_spec("h1") ('heading', 1) >>> parse_split_spec("length=500") ('length', 500) >>> parse_split_spec("parts=3") ('parts', 3) >>> parse_split_spec("delimiter=-----") ('delimiter', '-----') >>> parse_split_spec("auto") ('auto', None)
For AST documentation, see Advanced Modules and the user guide at Working with the AST.