Markdown Text Escaping#
MinerU applies a two-layer escaping strategy when converting extracted text spans into Markdown output. Both the standard pipeline backend and the VLM backend share the same utility functions, defined in markdown_utils.py.
Escaping Functions#
All escaping logic lives in mineru/backend/utils/markdown_utils.py. Two functions are relevant:
escape_conservative_markdown_text(content)#
This function escapes inline Markdown special characters within a span's text content. The set of characters it escapes is defined by the constant :
CONSERVATIVE_MARKDOWN_SPECIAL_CHARS = ("*", "_", "`", "~", "$")
Critically, angle brackets (< and >) are NOT in this set. This is the "conservative" design choice — angle brackets are intentionally left unescaped, meaning text containing <tag> patterns will be interpreted as HTML by Markdown renderers. The function walks the string character-by-character and prepends a backslash to any unescaped special character . Already-backslashed characters (odd number of preceding backslashes) are passed through without double-escaping.
escape_text_block_markdown_prefix(content)#
This function operates on the fully assembled paragraph string, not on individual spans. It escapes leading block-level Markdown markers (#, +, -) if they appear at the start of a paragraph (optionally after up to 3 spaces/tabs of indentation), followed by a space or tab. This prevents document text that happens to start with # or - from being misinterpreted as a Markdown heading or list item.
Where Escaping Is Applied#
Pipeline Backend#
In pipeline_middle_json_mkcontent.py, both functions are imported and used in the text rendering path :
- Inline escaping:
_render_span()callsescape_special_markdown_char()— a thin alias forescape_conservative_markdown_text()— on every TEXT span whenescape_markdown=True(the default) . - Block prefix escaping:
merge_para_with_text()callsescape_text_block_markdown_prefix()on assembled TEXT-type paragraphs . - Code blocks bypass escaping: When a block is identified as a fenced code block (
CODE_BODY+sub_type=CODE),_merge_para_text()is called withescape_markdown=False.
VLM Backend#
In vlm_middle_json_mkcontent.py, the same two functions are imported and applied with identical logic:
- Inline escaping:
merge_para_with_text()callsescape_conservative_markdown_text()on TEXT spans whenescape_markdown_textisTrue(i.e., for all non-code blocks) . - Block prefix escaping: Applied to assembled TEXT blocks via
escape_text_block_markdown_prefix().
The Angle Bracket Gap and HTML Tag Side Effects#
Because < and > are not escaped, any text span containing angle-bracket sequences — whether sourced from OCR, VLM output, or document content — will be passed through as raw HTML. Markdown renderers that handle raw HTML inline (which is standard CommonMark behavior) will interpret these as HTML tags rather than literal text.
VLM image/chart analysis blocks: The VLM backend also intentionally wraps image and chart analysis results in <details>/<summary> HTML blocks via _build_visual_details_block(). This is by design (introduced in PR #4784) — it creates collapsible sections in the rendered output. Users who encounter unexpected <details> tags in their VLM output are seeing this feature, not a bug . The --image-analysis false CLI flag disables VLM image analysis entirely, suppressing these blocks.
The pipeline backend applies the same _build_visual_details_block() pattern for image bodies that carry a content field , meaning any image with extracted content will also produce a <details> block.
Summary of Escaped vs. Unescaped Characters#
| Character | Escaped? | Notes |
|---|---|---|
* | ✅ Yes | Inline: bold/italic |
_ | ✅ Yes | Inline: bold/italic |
| ``` | ✅ Yes | Inline: code |
~ | ✅ Yes | Inline: strikethrough |
$ | ✅ Yes | Inline: math |
<, > | ❌ No | Angle brackets left raw — HTML tag interpretation risk |
#, +, - | ✅ Yes (block-level) | Escaped only at paragraph start via escape_text_block_markdown_prefix |
Key Files#
| File | Role |
|---|---|
mineru/backend/utils/markdown_utils.py | Defines escape_conservative_markdown_text and escape_text_block_markdown_prefix |
mineru/backend/pipeline/pipeline_middle_json_mkcontent.py | Pipeline backend: applies escaping in _render_span() and merge_para_with_text() |
mineru/backend/vlm/vlm_middle_json_mkcontent.py | VLM backend: same escaping pattern; also emits <details> blocks for visual content |