Files
ax_hub_mcp_tool/docs/tool-guide/verify_tool_guides_docx.py
jade e998127fa5
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1m3s
feat: implement MCI session fetching using employee number
2026-08-19 16:41:13 +09:00

133 lines
4.6 KiB
Python
Raw Permalink Blame History

from __future__ import annotations
import re
import zipfile
from pathlib import Path
from docx import Document
from docx.oxml.ns import qn
ROOT = Path(__file__).resolve().parent
DOCX_DIR = ROOT / "docx-professional"
def dxa(value):
return round(value.inches * 1440)
def all_text(doc):
values = [p.text for p in doc.paragraphs]
for table in doc.tables:
for row in table.rows:
values.extend(cell.text for cell in row.cells)
return "\n".join(values)
def table_geometry_errors(table, index):
errors = []
tbl_pr = table._tbl.tblPr
tbl_w = tbl_pr.find(qn("w:tblW"))
if tbl_w is None or tbl_w.get(qn("w:w")) != "9360":
errors.append(f"table {index}: tblW != 9360")
tbl_ind = tbl_pr.find(qn("w:tblInd"))
if tbl_ind is None or tbl_ind.get(qn("w:w")) != "120":
errors.append(f"table {index}: tblInd != 120")
grid_widths = [int(c.get(qn("w:w"))) for c in table._tbl.tblGrid]
if sum(grid_widths) != 9360:
errors.append(f"table {index}: grid sum={sum(grid_widths)}")
for row_index, row in enumerate(table.rows):
widths = []
for cell in row.cells:
tc_w = cell._tc.get_or_add_tcPr().find(qn("w:tcW"))
if tc_w is None:
errors.append(f"table {index} row {row_index}: missing tcW")
continue
widths.append(int(tc_w.get(qn("w:w"))))
if widths and sum(widths) != 9360:
errors.append(f"table {index} row {row_index}: cell sum={sum(widths)}")
return errors
def verify(source, output):
errors = []
with zipfile.ZipFile(output) as package:
bad = package.testzip()
if bad:
errors.append(f"broken ZIP member: {bad}")
doc = Document(output)
text = all_text(doc)
source_text = source.read_text(encoding="utf-8")
expected_title = source_text.splitlines()[0].removeprefix("# ").strip()
if expected_title not in text:
errors.append("title missing")
for heading in re.findall(r"^##\s+(.+)$", source_text, re.MULTILINE):
if heading not in text:
errors.append(f"heading missing: {heading}")
if "<EFBFBD>" in text or "\ufeff" in text:
errors.append("invalid/replacement Unicode character")
if re.search(r"\b(gateway|portal|router)\b", text, re.IGNORECASE):
errors.append("out-of-scope routing term found")
if len(doc.paragraphs) < 35:
errors.append(f"too few paragraphs: {len(doc.paragraphs)}")
if len(doc.tables) < 2:
errors.append(f"too few tables: {len(doc.tables)}")
for section_index, section in enumerate(doc.sections):
values = {
"page_width": dxa(section.page_width),
"page_height": dxa(section.page_height),
"top_margin": dxa(section.top_margin),
"bottom_margin": dxa(section.bottom_margin),
"left_margin": dxa(section.left_margin),
"right_margin": dxa(section.right_margin),
}
expected = {
"page_width": 12240,
"page_height": 15840,
"top_margin": 1440,
"bottom_margin": 1440,
"left_margin": 1440,
"right_margin": 1440,
}
for key, expected_value in expected.items():
if abs(values[key] - expected_value) > 2:
errors.append(f"section {section_index}: {key}={values[key]}")
footer_xml = section.footer._element.xml
if section_index > 0 and " PAGE " not in footer_xml:
errors.append(f"section {section_index}: page field missing")
for index, table in enumerate(doc.tables):
errors.extend(table_geometry_errors(table, index))
return {
"file": output.name,
"paragraphs": len(doc.paragraphs),
"tables": len(doc.tables),
"sections": len(doc.sections),
"errors": errors,
}
def main():
pairs = [
(ROOT / "01-Tool-개발환경-가이드.md", DOCX_DIR / "01-Tool-개발환경-가이드.docx"),
(ROOT / "02-Tool-설계-가이드.md", DOCX_DIR / "02-Tool-설계-가이드.docx"),
(ROOT / "03-Tool-개발가이드-1차.md", DOCX_DIR / "03-Tool-개발가이드-1차.docx"),
]
failed = False
for source, output in pairs:
result = verify(source, output)
print(f"{result['file']}: paragraphs={result['paragraphs']}, tables={result['tables']}, sections={result['sections']}")
for error in result["errors"]:
failed = True
print(f" ERROR: {error}")
if failed:
raise SystemExit(1)
print("DOCX structural QA passed")
if __name__ == "__main__":
main()