all2md.transforms.builtin
Built-in transforms for common document processing tasks.
This module provides a collection of ready-to-use transforms for common document manipulation operations. All transforms are registered via entry points for discovery and CLI usage.
Available Transforms
RemoveImagesTransform: Remove all images
RemoveNodesTransform: Remove nodes of specified types
HeadingOffsetTransform: Shift heading levels
TitlePromotionTransform: Promote leading H1 to title and shift subsequent headings
LinkRewriterTransform: Rewrite URLs with regex patterns
TextReplacerTransform: Find and replace text
AddHeadingIdsTransform: Generate unique IDs for headings
RemoveBoilerplateTextTransform: Remove common boilerplate patterns
AddConversionTimestampTransform: Add timestamp to metadata
CalculateWordCountTransform: Calculate word and character counts
AddAttachmentFootnotesTransform: Add footnote definitions for attachment references
GenerateTocTransform: Generate table of contents from document headings
Examples
Remove all images:
>>> transform = RemoveImagesTransform()
>>> new_doc = transform.transform(doc)
Offset headings by 2 levels:
>>> transform = HeadingOffsetTransform(offset=2)
>>> new_doc = transform.transform(doc)
Add unique IDs to headings:
>>> transform = AddHeadingIdsTransform(id_prefix="doc-")
>>> new_doc = transform.transform(doc)
Add footnote definitions for attachment references:
>>> transform = AddAttachmentFootnotesTransform(section_title="Image Sources")
>>> new_doc = transform.transform(doc)
Generate table of contents:
>>> transform = GenerateTocTransform(max_depth=3, position="top")
>>> new_doc = transform.transform(doc)
- class all2md.transforms.builtin.RemoveImagesTransform
Bases:
NodeTransformerRemove all Image nodes from the AST.
This transform removes every Image node it encounters, useful for creating text-only versions of documents or reducing document size.
Examples
>>> transform = RemoveImagesTransform() >>> doc_without_images = transform.transform(document)
- class all2md.transforms.builtin.RemoveNodesTransform
Bases:
NodeTransformerRemove nodes of specified types from the AST.
This is a generic transform that can remove any combination of node types. Useful for stripping specific elements like tables, code blocks, or any other node type.
- Parameters:
node_types (list[str]) – List of node type names to remove (e.g., [‘image’, ‘table’, ‘code_block’])
Examples
Remove images and tables:
>>> transform = RemoveNodesTransform(node_types=['image', 'table']) >>> cleaned_doc = transform.transform(document)
Initialize with list of node types to remove.
- Parameters:
node_types (list[str]) – Node type names to remove
- Raises:
ValueError – If ‘document’ is in node_types (cannot remove root node), or if any node_type is unknown (typo detection)
- __init__(node_types: list[str])
Initialize with list of node types to remove.
- Parameters:
node_types (list[str]) – Node type names to remove
- Raises:
ValueError – If ‘document’ is in node_types (cannot remove root node), or if any node_type is unknown (typo detection)
- class all2md.transforms.builtin.HeadingOffsetTransform
Bases:
NodeTransformerShift heading levels by a specified offset.
This transform adjusts all heading levels in the document by adding an offset value. Levels are clamped to the valid range of 1-6.
- Parameters:
offset (int, default = 1) – Number of levels to shift (positive to increase, negative to decrease)
Examples
Increase all heading levels by 1 (H1 becomes H2):
>>> transform = HeadingOffsetTransform(offset=1) >>> new_doc = transform.transform(document)
Decrease all heading levels by 1 (H2 becomes H1):
>>> transform = HeadingOffsetTransform(offset=-1) >>> new_doc = transform.transform(document)
Initialize with heading level offset.
- Parameters:
offset (int) – Heading level adjustment
- __init__(offset: int = 1)
Initialize with heading level offset.
- Parameters:
offset (int) – Heading level adjustment
- class all2md.transforms.builtin.TitlePromotionTransform
Bases:
NodeTransformerPromote a leading H1 to a document title and shift subsequent headings.
When converting Markdown to Word, a leading
# Headingis typically the document title rather than a “Heading 1”. This transform detects a leading H1 (skipping empty / whitespace-only paragraphs before it), marks it withmetadata["is_title"] = True, and promotes all subsequent headings by one level (H2 → H1, H3 → H2, etc.) so they style properly under the title.If the first real content node is not an H1, the document passes through unchanged.
Examples
>>> transform = TitlePromotionTransform() >>> new_doc = transform.transform(document)
- class all2md.transforms.builtin.LinkRewriterTransform
Bases:
NodeTransformerRewrite link URLs using regex pattern matching.
This transform allows flexible URL rewriting using regular expressions. Useful for converting relative links to absolute, updating base URLs, or modifying link schemes.
- Parameters:
pattern (str) – Regex pattern to match in URLs
replacement (str) – Replacement string (can include regex groups like \1, \2)
- Raises:
SecurityError – If the pattern contains dangerous constructs that could lead to ReDoS (Regular Expression Denial of Service) attacks
Examples
Convert relative links to absolute:
>>> transform = LinkRewriterTransform( ... pattern=r'^/docs/', ... replacement='https://example.com/docs/' ... ) >>> new_doc = transform.transform(document)
Notes
For security reasons, this transform validates user-supplied regex patterns to prevent ReDoS attacks. Patterns with nested quantifiers or excessive backtracking potential are rejected. See validate_user_regex_pattern() for details on what patterns are considered safe.
Initialize with pattern and replacement.
- Parameters:
pattern (str) – Regex pattern
replacement (str) – Replacement string
- Raises:
SecurityError – If pattern contains dangerous constructs
- __init__(pattern: str, replacement: str)
Initialize with pattern and replacement.
- Parameters:
pattern (str) – Regex pattern
replacement (str) – Replacement string
- Raises:
SecurityError – If pattern contains dangerous constructs
- class all2md.transforms.builtin.TextReplacerTransform
Bases:
NodeTransformerFind and replace text in Text nodes.
This transform performs simple text replacement across all Text nodes in the document. For regex-based replacement, use a custom transform.
- Parameters:
find (str) – Text to find
replace (str) – Replacement text
Examples
Replace all instances of “TODO”:
>>> transform = TextReplacerTransform(find="TODO", replace="DONE") >>> new_doc = transform.transform(document)
Initialize with find and replace strings.
- Parameters:
find (str) – Text to find
replace (str) – Replacement text
- __init__(find: str, replace: str)
Initialize with find and replace strings.
- Parameters:
find (str) – Text to find
replace (str) – Replacement text
- class all2md.transforms.builtin.AddHeadingIdsTransform
Bases:
NodeTransformerGenerate and add unique IDs to heading nodes.
This transform creates slugified IDs from heading text and adds them to the heading metadata. These IDs can be used by renderers to create HTML anchors for linkable sections.
- Parameters:
id_prefix (str, default = "") – Prefix to add to all generated IDs
separator (str, default = "-") – Separator for multi-word slugs and duplicate handling
Examples
Basic usage:
>>> transform = AddHeadingIdsTransform() >>> new_doc = transform.transform(document) >>> # "My Heading" -> metadata['id'] = "my-heading"
With prefix:
>>> transform = AddHeadingIdsTransform(id_prefix="doc-") >>> new_doc = transform.transform(document) >>> # "My Heading" -> metadata['id'] = "doc-my-heading"
Initialize with prefix and separator.
- Parameters:
id_prefix (str) – Prefix for IDs
separator (str) – Word separator
- __init__(id_prefix: str = '', separator: str = '-')
Initialize with prefix and separator.
- Parameters:
id_prefix (str) – Prefix for IDs
separator (str) – Word separator
- class all2md.transforms.builtin.RemoveBoilerplateTextTransform
Bases:
NodeTransformerRemove paragraphs matching common boilerplate patterns.
This transform removes paragraphs whose text matches predefined patterns like “CONFIDENTIAL”, “Page X of Y”, etc. Useful for cleaning up corporate documents and reports.
- Parameters:
patterns (list[str], optional) – List of regex patterns to match (default: common boilerplate)
skip_if_truncated (bool, default = True) – If True, skip pattern matching when text exceeds MAX_TEXT_LENGTH_FOR_REGEX to avoid false positives with end-anchored patterns ($). If False, match against truncated text (may produce incorrect results with anchors).
- Raises:
SecurityError – If any user-supplied pattern contains dangerous constructs that could lead to ReDoS (Regular Expression Denial of Service) attacks
Examples
Use default patterns:
>>> transform = RemoveBoilerplateTextTransform() >>> cleaned_doc = transform.transform(document)
Custom patterns with anchoring:
>>> transform = RemoveBoilerplateTextTransform( ... patterns=[r"^DRAFT$", r"^INTERNAL ONLY$", r"^Page \d+ of \d+$"] ... ) >>> cleaned_doc = transform.transform(document)
Allow matching truncated text (not recommended):
>>> transform = RemoveBoilerplateTextTransform(skip_if_truncated=False) >>> cleaned_doc = transform.transform(document)
Notes
Pattern Matching Semantics: This transform uses Python’s re.match(), which implicitly anchors at the start of the string (equivalent to adding ^ at the beginning). For exact matching of entire paragraphs, patterns should include an end anchor ($). For example:
r”CONFIDENTIAL” - Matches paragraphs starting with “CONFIDENTIAL”
r”CONFIDENTIAL$” - Matches paragraphs that are exactly “CONFIDENTIAL” or start with “CONFIDENTIAL” followed by only whitespace
r”^CONFIDENTIAL$” - Explicitly anchored (redundant ^, but clearer)
If you need to match patterns anywhere in the text (not just at the start), use re.search() semantics by implementing a custom transform.
Security: For security reasons, this transform validates user-supplied regex patterns to prevent ReDoS attacks. Default patterns are pre-validated and trusted. Patterns with nested quantifiers or excessive backtracking potential are rejected. See validate_user_regex_pattern() for details.
Truncation Behavior: Text longer than MAX_TEXT_LENGTH_FOR_REGEX (10000 characters) is truncated before matching for ReDoS protection. With
skip_if_truncated=True(default), such paragraphs are preserved to avoid false positives from patterns using end anchors ($). This is safer but may miss some boilerplate. Withskip_if_truncated=False, matching proceeds on truncated text, which may incorrectly match or not match patterns with anchors.Initialize with patterns.
- Parameters:
patterns (list[str] or None) – Regex patterns to match (None uses defaults)
skip_if_truncated (bool) – Skip matching when text is truncated (safer default)
- Raises:
SecurityError – If any user-supplied pattern contains dangerous constructs
- __init__(patterns: list[str] | None = None, skip_if_truncated: bool = True)
Initialize with patterns.
- Parameters:
patterns (list[str] or None) – Regex patterns to match (None uses defaults)
skip_if_truncated (bool) – Skip matching when text is truncated (safer default)
- Raises:
SecurityError – If any user-supplied pattern contains dangerous constructs
- class all2md.transforms.builtin.AddConversionTimestampTransform
Bases:
NodeTransformerAdd conversion timestamp to document metadata.
This transform adds a timezone-aware UTC timestamp to the document metadata indicating when the conversion occurred. Useful for tracking document versions and conversion history. All timestamps are generated in UTC to ensure consistency across different time zones.
- Parameters:
field_name (str, default = "conversion_timestamp") – Metadata field name for the timestamp
timestamp_format (str, default = "iso") – Timestamp format: “iso” for ISO 8601 with timezone, “unix” for Unix timestamp, or any strftime format string
timespec (str, default = "seconds") – Time precision for ISO format timestamps. Valid values are: - “auto”: Automatic precision - “hours”: Hours precision - “minutes”: Minutes precision - “seconds”: Seconds precision (default, reduces noisy diffs) - “milliseconds”: Milliseconds precision - “microseconds”: Microseconds precision Only applies when timestamp_format=”iso”. Ignored for other formats.
Examples
Add ISO 8601 timestamp with second precision (default):
>>> transform = AddConversionTimestampTransform() >>> new_doc = transform.transform(document) >>> # metadata['conversion_timestamp'] = "2025-01-01T12:00:00+00:00"
Add ISO 8601 timestamp with microsecond precision:
>>> transform = AddConversionTimestampTransform(timespec="microseconds") >>> new_doc = transform.transform(document) >>> # metadata['conversion_timestamp'] = "2025-01-01T12:00:00.123456+00:00"
Add Unix timestamp:
>>> transform = AddConversionTimestampTransform(timestamp_format="unix") >>> new_doc = transform.transform(document) >>> # metadata['conversion_timestamp'] = "1735732800"
Custom strftime format:
>>> transform = AddConversionTimestampTransform( ... field_name="converted_at", ... timestamp_format="%Y-%m-%d %H:%M:%S UTC" ... ) >>> new_doc = transform.transform(document) >>> # metadata['converted_at'] = "2025-01-01 12:00:00 UTC"
Notes
All timestamps are generated in UTC (Coordinated Universal Time) using datetime.now(timezone.utc). This ensures consistent timestamps regardless of the server’s local timezone.
The default timespec=”seconds” is recommended to reduce noisy git diffs when regenerating documents, as subsecond precision is rarely needed for document conversion timestamps.
Initialize with field name, format, and time precision.
- Parameters:
field_name (str) – Metadata field name
timestamp_format (str) – Timestamp format
timespec (str) – Time precision for ISO format (default: “seconds”)
- __init__(field_name: str = 'conversion_timestamp', timestamp_format: str = 'iso', timespec: str = 'seconds')
Initialize with field name, format, and time precision.
- Parameters:
field_name (str) – Metadata field name
timestamp_format (str) – Timestamp format
timespec (str) – Time precision for ISO format (default: “seconds”)
- class all2md.transforms.builtin.CalculateWordCountTransform
Bases:
NodeTransformerCalculate word and character counts and add to metadata.
This transform traverses the entire document, extracts all text, and calculates word and character counts. The counts are added to the document metadata.
- Parameters:
word_field (str, default = "word_count") – Metadata field name for word count
char_field (str, default = "char_count") – Metadata field name for character count
Examples
Basic usage:
>>> transform = CalculateWordCountTransform() >>> new_doc = transform.transform(document) >>> # metadata['word_count'] = 150 >>> # metadata['char_count'] = 890
Custom field names:
>>> transform = CalculateWordCountTransform( ... word_field="words", ... char_field="characters" ... ) >>> new_doc = transform.transform(document)
Notes
Character Count Behavior: The char_count metric represents the length of normalized text extracted from the AST, not the original document’s character count. During text extraction, text fragments from separate AST nodes are joined with spaces, which may introduce synthetic spacing not present in the original document. For example, if the AST contains two adjacent Text nodes
Text("hello")andText("world"), the extracted text will be"hello world"(11 characters including the inserted space), even though the original text nodes only contain 10 characters total.This normalized approach provides consistent metrics across different AST structures, though it may not exactly match the original document’s byte count. Word count is calculated by splitting the normalized text on whitespace, which is generally more robust to these variations.
Initialize with field names.
- Parameters:
word_field (str) – Field name for word count
char_field (str) – Field name for character count
- __init__(word_field: str = 'word_count', char_field: str = 'char_count')
Initialize with field names.
- Parameters:
word_field (str) – Field name for word count
char_field (str) – Field name for character count
- class all2md.transforms.builtin.AddAttachmentFootnotesTransform
Bases:
NodeTransformerAdd footnote definitions for attachment references.
When attachments are processed with alt_text_mode=”footnote”, they generate footnote-style references like ![image][^label] but no corresponding definitions. This transform scans the AST for such references and adds FootnoteDefinition nodes with source information.
- Parameters:
section_title (str or None, default "Attachments") – Title for the footnote section heading. If None, no heading is added.
add_definitions_for_images (bool, default True) – Add definitions for image footnote references
add_definitions_for_links (bool, default True) – Add definitions for link footnote references
Examples
Add footnote definitions after conversion:
>>> transform = AddAttachmentFootnotesTransform() >>> doc_with_footnotes = transform.transform(document)
Custom section title:
>>> transform = AddAttachmentFootnotesTransform(section_title="Image Sources") >>> doc_with_footnotes = transform.transform(document)
Notes
This transform works by: 1. Collecting all Image and Link nodes with empty URLs (indicates footnote mode) 2. Extracting footnote labels from alt text or title 3. Handling duplicate labels by appending numeric suffixes (-2, -3, etc.) 4. Creating FootnoteDefinition nodes with source information 5. Appending definitions to the end of the document
Duplicate labels are resolved using a counter mechanism similar to heading ID generation. When a label appears multiple times, subsequent occurrences get a numeric suffix to ensure unique footnote identifiers
Initialize transform with options.
- Parameters:
section_title (str or None) – Heading for footnotes section
add_definitions_for_images (bool) – Whether to process image footnotes
add_definitions_for_links (bool) – Whether to process link footnotes
- __init__(section_title: str | None = 'Attachments', add_definitions_for_images: bool = True, add_definitions_for_links: bool = True)
Initialize transform with options.
- Parameters:
section_title (str or None) – Heading for footnotes section
add_definitions_for_images (bool) – Whether to process image footnotes
add_definitions_for_links (bool) – Whether to process link footnotes
- class all2md.transforms.builtin.GenerateTocTransform
Bases:
NodeTransformerGenerate a table of contents from document headings.
This transform extracts headings from the document and generates a nested list representing the table of contents. The TOC can be placed at the top or bottom of the document.
- Parameters:
title (str, default = "Table of Contents") – Title for the TOC section
max_depth (int, default = 3) – Maximum heading level to include (1-6)
position ({"top", "bottom"}, default = "top") – Position to insert the TOC
add_links (bool, default = True) – Whether to create links to headings (requires heading IDs)
separator (str, default = "-") – Separator for generating heading IDs when not present
set_ids_if_missing (bool, default = False) – If True, inject generated IDs into heading metadata when missing. This ensures renderers create anchors matching the TOC links. If False (default), IDs are only used for TOC links.
Examples
Basic usage:
>>> transform = GenerateTocTransform() >>> doc_with_toc = transform.transform(document)
Custom depth and position:
>>> transform = GenerateTocTransform( ... title="Contents", ... max_depth=2, ... position="bottom" ... ) >>> doc_with_toc = transform.transform(document)
Inject IDs into headings:
>>> transform = GenerateTocTransform(set_ids_if_missing=True) >>> doc_with_toc = transform.transform(document) >>> # Headings now have 'id' in metadata for renderer anchors
Notes
This transform works best when combined with AddHeadingIdsTransform, which generates unique IDs for headings that can be used for navigation. If headings don’t have IDs, the transform will generate slugified IDs on-the-fly for link targets.
ID Injection: With
set_ids_if_missing=True, generated IDs are injected into heading metadata so renderers can create matching anchors. This is recommended when not using AddHeadingIdsTransform. Alternatively, run AddHeadingIdsTransform before GenerateTocTransform to ensure all headings have IDs upfront.Initialize with TOC generation options.
- Parameters:
title (str) – TOC section title
max_depth (int) – Maximum heading level (1-6)
position (str) – Position for TOC (“top” or “bottom”)
add_links (bool) – Whether to generate links
separator (str) – Separator for ID generation
set_ids_if_missing (bool) – Inject generated IDs into heading metadata
- Raises:
ValueError – If max_depth is not between 1 and 6, or position is invalid
- __init__(title: str = 'Table of Contents', max_depth: int = 3, position: str = 'top', add_links: bool = True, separator: str = '-', set_ids_if_missing: bool = False)
Initialize with TOC generation options.
- Parameters:
title (str) – TOC section title
max_depth (int) – Maximum heading level (1-6)
position (str) – Position for TOC (“top” or “bottom”)
add_links (bool) – Whether to generate links
separator (str) – Separator for ID generation
set_ids_if_missing (bool) – Inject generated IDs into heading metadata
- Raises:
ValueError – If max_depth is not between 1 and 6, or position is invalid