PrERT-CNM-Demo / src /prert /extract /docx_reader.py
JayNightmare's picture
Upload folder using huggingface_hub
64c8e6c verified
Raw
History Blame Contribute Delete
2.26 kB
"""Helpers for extracting normalized text content from DOCX files."""
from __future__ import annotations
from pathlib import Path
import re
from typing import List
from docx import Document # type: ignore[import-not-found]
_VISIBLE_CLAUSE_RE = re.compile(r"^(?:[1-9]\d*(?:\.[0-9]+){0,4}|A\.[0-9]+(?:\.[0-9]+)?)\b")
def read_docx_text(path: Path) -> str:
"""Read text from a DOCX file as newline-delimited logical paragraphs."""
doc = Document(str(path))
lines: List[str] = []
numbering_state: dict[int, List[int]] = {}
for paragraph in doc.paragraphs:
text = _clean_text(paragraph.text)
if text:
heading_prefix = _heading_number_prefix(paragraph, numbering_state)
if heading_prefix and not _VISIBLE_CLAUSE_RE.match(text):
text = f"{heading_prefix} {text}"
lines.append(text)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
text = _clean_text(paragraph.text)
if text:
lines.append(text)
return "\n".join(lines)
def _clean_text(value: str) -> str:
cleaned = value.replace("\u200b", "").replace("\ufeff", "").replace("\u2060", "")
cleaned = cleaned.replace("\xa0", " ")
return cleaned.strip()
def _heading_number_prefix(paragraph, numbering_state: dict[int, List[int]]) -> str | None:
style_name = ""
if paragraph.style is not None and paragraph.style.name:
style_name = str(paragraph.style.name)
if not style_name.lower().startswith("heading"):
return None
ppr = paragraph._p.pPr
if ppr is None or ppr.numPr is None:
return None
if ppr.numPr.numId is None or ppr.numPr.ilvl is None:
return None
num_id = int(ppr.numPr.numId.val)
level = int(ppr.numPr.ilvl.val)
if level < 0:
return None
counters = numbering_state.setdefault(num_id, [0] * 9)
counters[level] += 1
for idx in range(level + 1, len(counters)):
counters[idx] = 0
segments = [str(counters[idx]) for idx in range(level + 1) if counters[idx] > 0]
if not segments:
return None
return ".".join(segments)