all2md.utils.attachments
Unified attachment handling utilities for all2md conversion modules.
This module provides common functions for handling attachments (images and files) across all conversion modules in the all2md library. It implements the unified AttachmentMode system with consistent behavior across different parsers.
The attachment handling modes are: - “skip”: Remove attachments completely - “alt_text”: Use alt-text for images, filename for files - “save”: Save to folder and reference with markdown links - “base64”: Embed as base64 data URIs (images only)
Functions
process_attachment: Main function for processing attachments based on mode
extract_pptx_image_data: Extract image data from PowerPoint shapes
extract_docx_image_data: Extract image data from Word document relationships
- all2md.utils.attachments.sanitize_footnote_label(attachment_name: str) str
Sanitize attachment name for use as a Markdown footnote label.
Footnote labels in Markdown cannot contain spaces or many special characters without breaking rendering. This function creates a safe label by normalizing the attachment name.
- Parameters:
attachment_name (str) – Original attachment filename
- Returns:
Sanitized footnote label safe for Markdown
- Return type:
str
Examples
>>> sanitize_footnote_label("my image.png") 'my_image' >>> sanitize_footnote_label("file (1).jpg") 'file_1' >>> sanitize_footnote_label("document%20name.pdf") 'document_20name'
- all2md.utils.attachments.sanitize_attachment_filename(filename: str, max_length: int = 255, preserve_case: bool = False, allow_unicode: bool = False) str
Sanitize an attachment filename for secure file system storage.
This function normalizes Unicode characters, removes dangerous patterns, and ensures cross-platform compatibility while preventing security issues.
By default, this function is conservative and may be lossy: - Converts to lowercase (unless preserve_case=True) - Removes non-ASCII characters (unless allow_unicode=True) - Removes special characters except alphanumeric, underscore, dot, and hyphen
- Parameters:
filename (str) – Original filename to sanitize
max_length (int, default 255) – Maximum length for the sanitized filename
preserve_case (bool, default False) – If True, preserve original case. If False, convert to lowercase for cross-platform compatibility (case-insensitive filesystems).
allow_unicode (bool, default False) – If True, allow Unicode letters and numbers (e.g., Chinese, Arabic). If False, only allow ASCII alphanumeric characters. Note: Unicode filenames may cause issues on some systems.
- Returns:
Sanitized filename safe for file system use
- Return type:
str
- Raises:
ValueError – If the filename contains potentially malicious patterns that cannot be sanitized
Examples
>>> sanitize_attachment_filename("test́.png") # test with combining accent 'test.png' >>> sanitize_attachment_filename("../../../etc/passwd") 'passwd' >>> sanitize_attachment_filename("file<>|name?.txt") 'filename.txt' >>> sanitize_attachment_filename("Test.PNG", preserve_case=True) 'Test.PNG' >>> sanitize_attachment_filename("文件.txt", allow_unicode=True) '文件.txt'
- all2md.utils.attachments.ensure_unique_attachment_path(base_path: Path, max_attempts: int = 1000, atomic: bool = True) Path
Ensure a unique file path by adding numeric suffixes for collisions.
Thread Safety and Race Conditions
When atomic=True (default), this function uses atomic file operations (os.open with O_CREAT | O_EXCL) to prevent TOCTOU race conditions. This makes it safe for: - Concurrent file creation from multiple processes - Concurrent file creation from multiple threads
When atomic=True, a 0-byte placeholder file is created at the returned path. The caller should overwrite this file with actual content.
When atomic=False, the function uses non-atomic existence checks, which is suitable for single-threaded usage only.
- param base_path:
The desired base path for the attachment
- type base_path:
Path
- param max_attempts:
Maximum number of collision resolution attempts
- type max_attempts:
int, default 1000
- param atomic:
If True, use atomic file creation to prevent race conditions. If False, use simple existence checks (not thread-safe).
- type atomic:
bool, default True
- returns:
A unique file path. When atomic=True, a placeholder file exists at this path that should be overwritten.
- rtype:
Path
- raises RuntimeError:
If unable to find a unique path after max_attempts
- raises OSError:
If atomic creation fails for reasons other than file collision (e.g., permission denied, parent directory doesn’t exist)
Examples
>>> # If image.png exists, returns image-1.png (and creates placeholder if atomic=True) >>> ensure_unique_attachment_path(Path("./attachments/image.png")) Path('./attachments/image-1.png')
Notes
When atomic=True, the caller MUST write to the returned path, as a 0-byte placeholder file has been created. If the caller fails to write, the placeholder should be cleaned up.
For typical single-process document conversion, atomic=False is adequate. Use atomic=True when concurrent access is expected.
- all2md.utils.attachments.process_attachment(attachment_data: bytes | None, attachment_name: str, alt_text: str = '', attachment_mode: Literal['skip', 'alt_text', 'save', 'base64'] = 'alt_text', attachment_output_dir: str | None = None, attachment_base_url: str | None = None, is_image: bool = True, alt_text_mode: Literal['default', 'plain_filename', 'strict_markdown', 'footnote'] = 'default', allowed_output_base_dirs: list[str | Path] | None = None, block_sensitive_paths: bool = True) dict[str, Any]
Process an attachment according to the specified mode.
- Parameters:
attachment_data (bytes | None) – Raw attachment data, or None if not available
attachment_name (str) – Name/filename of the attachment
alt_text (str, default "") – Alt text for images or description for files
attachment_mode (AttachmentMode, default "alt_text") – How to handle the attachment
attachment_output_dir (str | None, default None) – Directory to save attachments in save mode. For security, the directory is validated to ensure it stays within the current working directory and does not contain path traversal patterns. If validation fails, falls back to alt_text mode. Defaults to “attachments” if None.
attachment_base_url (str | None, default None) – Base URL for resolving relative URLs in save mode. Note: Only the filename (not the directory structure) is appended to this URL. For example, if attachment_output_dir=”attachments/images” and attachment_base_url=”https://example.com/assets/”, the resulting URL will be “https://example.com/assets/filename.png” (not “…/assets/attachments/images/filename.png”)
is_image (bool, default True) – Whether this is an image attachment
alt_text_mode (AltTextMode, default "default") – How to render alt-text content: - “default”: Current behavior - ![alt] for images, [filename] for files - “plain_filename”: Render non-images as plain filename text - “strict_markdown”: Use  format for proper Markdown structure - “footnote”: Use footnote references for accessibility
allowed_output_base_dirs (list[str | Path] | None, default None) – Optional allowlist of base directories for output validation. If provided, attachment_output_dir must be within one of these directories. When None, uses default security checks (blocks path traversal and sensitive paths). Useful for server environments where output should be restricted to specific directories.
block_sensitive_paths (bool, default True) – Block output to sensitive system directories (/etc, /sys, C:\Windows, etc.). Set to False to allow writing to any directory (use with caution). Only applies when allowed_output_base_dirs is None.
- Returns:
Dictionary with keys: - “markdown”: str - Markdown representation of the attachment - “footnote_label”: str | None - Footnote label if alt_text_mode is “footnote” - “footnote_content”: str | None - Content for footnote definition - “url”: str - URL/path for the attachment (empty for alt_text mode) - “source_data”: str | None - Source of the attachment data (e.g., “base64”, “downloaded”)
- Return type:
dict[str, Any]
- all2md.utils.attachments.extract_pptx_image_data(shape: Any) bytes | None
Extract raw image data from a PowerPoint shape.
- Parameters:
shape (Any) – PowerPoint shape object with image property
- Returns:
Raw image bytes, or None if extraction fails
- Return type:
bytes | None
- all2md.utils.attachments.extract_docx_image_data(parent: Any, blip_rId: str) tuple[bytes | None, str | None]
Extract image data and format information from Word document relationships.
- Parameters:
parent (Any) – Word document parent element
blip_rId (str) – Relationship ID for the image
- Returns:
Tuple of (raw image bytes, file extension), or (None, None) if extraction fails
- Return type:
tuple[bytes | None, str | None]
- all2md.utils.attachments.generate_attachment_filename(base_stem: str, attachment_type: str = 'img', format_type: str = 'general', page_num: int | None = None, slide_num: int | None = None, sequence_num: int = 1, extension: str = 'png') str
Generate standardized attachment filenames across all parsers.
- Parameters:
base_stem (str) – Base filename stem (without extension) from the source document
attachment_type (str, default "img") – Type of attachment (e.g., “img”, “file”)
format_type (str, default "general") – Format context - one of: - “pdf”: For PDF pages - generates {stem}_p{page}_img{n}.{ext} - “pptx”: For PowerPoint slides - generates {stem}_slide{n}_img{m}.{ext} - “general”: For other formats - generates {stem}_img{n}.{ext}
page_num (int | None, default None) – Page number (1-based) for PDF format
slide_num (int | None, default None) – Slide number (1-based) for PPTX format
sequence_num (int, default 1) – Sequence number for multiple attachments
extension (str, default "png") – File extension without dot
- Returns:
Standardized filename
- Return type:
str
Examples
>>> generate_attachment_filename("document", format_type="pdf", page_num=1, sequence_num=2) 'document_p1_img2.png' >>> generate_attachment_filename("presentation", format_type="pptx", slide_num=3, sequence_num=1) 'presentation_slide3_img1.png' >>> generate_attachment_filename("article", format_type="general", sequence_num=5) 'article_img5.png'
- class all2md.utils.attachments.AttachmentSequencer
Bases:
ProtocolProtocol for attachment filename sequencer callables.
This protocol defines the signature for functions returned by create_attachment_sequencer(). The sequencer generates unique, sequential filenames for attachments based on format-specific rules.
Thread Safety
Sequencers created by create_attachment_sequencer() are thread-safe. They use internal locking to protect shared mutable state, allowing safe concurrent use from multiple threads.
- param base_stem:
Base filename stem (without extension) from the source document
- type base_stem:
str
- param format_type:
Format context - one of “pdf”, “pptx”, or “general”
- type format_type:
str, default “general”
- param **kwargs:
Additional keyword arguments: - page_num : int | None - Page number for PDF format (required if format_type=”pdf”) - slide_num : int | None - Slide number for PPTX format (required if format_type=”pptx”) - extension : str - File extension without dot (default: “png”) - attachment_type : str - Type of attachment (default: “img”)
- type **kwargs:
Any
- returns:
Tuple of (generated filename, sequence number)
- rtype:
tuple[str, int]
- __init__(*args, **kwargs)
- all2md.utils.attachments.create_attachment_sequencer() AttachmentSequencer
Create a closure that tracks attachment sequence numbers to prevent duplicates.
Thread Safety
The returned sequencer is thread-safe. It uses a lock to protect the internal mutable state (used_filenames set and sequence_counters dict).
This allows safe concurrent use from multiple threads, though each document conversion typically uses its own sequencer instance.
- returns:
Function that generates sequential attachment filenames and tracks usage
- rtype:
callable
Examples
>>> sequencer = create_attachment_sequencer() >>> sequencer("doc", "pdf", page_num=1) # Returns: ('doc_p1_img1.png', 1) >>> sequencer("doc", "pdf", page_num=1) # Returns: ('doc_p1_img2.png', 2) >>> sequencer("doc", "pdf", page_num=2) # Returns: ('doc_p2_img1.png', 1)