"""Shared markdown rendering helpers for parser engines. Used by the native docx parser and the external (mineru / docling) IR builders so heading-line rendering stays identical across engines. This is a leaf module with no heavy imports — ``lightrag/parser/__init__.py`` only carries a docstring — so all three engines can import it without risking a circular dependency. """ from __future__ import annotations import re # Markdown caps heading levels at 6 (``######``); deeper outline levels are # clamped to 6 rather than emitting an illegal 7+ run of ``#``. MAX_HEADING_LEVEL = 6 # A heading line that is ALREADY markdown: 1-6 ``#`` followed by one or more # spaces. Used to avoid double-prefixing text that an upstream engine emitted # with its own markdown heading marker (e.g. mineru/docling extracting # ``# Foo``). # # Ambiguity note: this is a heuristic, not a parse. A heading whose text # genuinely starts with ``#`` plus a space (an author literally writing # ``"# Note"`` as heading content) is indistinguishable from an # already-rendered markdown heading and will be treated as the latter. This # is accepted as a rare edge case in exchange for engine-agnostic dedup. _MD_HEADING_RE = re.compile(r"^#{1,6} +") def strip_heading_markdown_prefix(text: str) -> str: """Return heading metadata without an existing markdown heading prefix. The content renderer may keep a source line such as ``"# Foo"`` verbatim to avoid double-prefixing, but structured metadata (``heading``, ``parent_headings``, doc title) must stay clean. The whole ``#`` run and all following spaces are removed, so ``"# Extra"`` yields ``"Extra"`` (no leading space leaks into the metadata). See the module-level ambiguity note: text that genuinely begins with a ``#`` + space run is stripped here too. """ return _MD_HEADING_RE.sub("", text, count=1) def render_heading_line(level: int, text: str) -> str: """Render a heading as a markdown-prefixed content line. Args: level: 1-based heading level (1 = H1). Values < 1 are treated as 1; values > :data:`MAX_HEADING_LEVEL` are clamped so a level >= 7 heading still gets ``######``. text: The heading text. Returns: ``text`` unchanged when it already starts with a markdown heading prefix (``^#{1,6} +`` — 1-6 ``#`` then one or more spaces); otherwise ``"#" * clamped_level + " " + text``. See the module-level ambiguity note: a heading whose content genuinely begins with such a run is kept verbatim rather than re-prefixed. """ if _MD_HEADING_RE.match(text): return text hashes = "#" * min(max(level, 1), MAX_HEADING_LEVEL) return f"{hashes} {text}" __all__ = [ "MAX_HEADING_LEVEL", "render_heading_line", "strip_heading_markdown_prefix", ]