"""Extract visible text from OOXML runs/paragraphs (incl. w:noBreakHyphen).""" from __future__ import annotations from docx.oxml.ns import qn def _local(tag: str) -> str: if "}" in tag: return tag.rsplit("}", 1)[-1] return tag def run_element_text(run_el) -> str: """ Text of a w:r including non-breaking hyphens. md2gost writes ASCII '-' as w:noBreakHyphen (python-docx run.text skips it). Soft hyphens are optional break hints — omit them from Markdown. """ if run_el is None: return "" parts: list[str] = [] for child in run_el: tag = _local(child.tag) if tag == "t": parts.append(child.text or "") elif tag == "noBreakHyphen": parts.append("-") elif tag == "softHyphen": continue elif tag == "tab": parts.append("\t") elif tag == "br": # page/line break inside run — treat as space for inline text if child.get(qn("w:type")) == "page": continue parts.append("\n") elif tag == "cr": parts.append("\n") return "".join(parts) def hyperlink_element_text(hyperlink_el) -> str: if hyperlink_el is None: return "" parts: list[str] = [] for run in hyperlink_el.findall(qn("w:r")): parts.append(run_element_text(run)) return "".join(parts) def paragraph_element_text(p_el) -> str: """Visible body text of w:p (runs + hyperlinks), without OMML.""" if p_el is None: return "" parts: list[str] = [] for child in p_el: tag = child.tag if tag == qn("w:r"): parts.append(run_element_text(child)) elif tag == qn("w:hyperlink"): parts.append(hyperlink_element_text(child)) return "".join(parts) def paragraph_plain(paragraph) -> str: """Strip CR/bell from a python-docx Paragraph, keeping noBreakHyphen as '-'.""" return paragraph_raw(paragraph).strip() def paragraph_raw(paragraph) -> str: """Full paragraph text with noBreakHyphen → '-', without strip (for Code lines).""" try: text = paragraph_element_text(paragraph._element) except Exception: text = paragraph.text or "" return text.replace("\r", "").replace("\x07", "")