test_document_extractors.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. from __future__ import annotations
  2. import io
  3. from docx import Document
  4. from app.core.data_research.extractors.base import ExtractionContext
  5. def docx_bytes(*, empty=False):
  6. document = Document()
  7. if not empty:
  8. document.add_paragraph("客户主数据定义")
  9. table = document.add_table(rows=3, cols=2)
  10. table.rows[0].cells[0].text = "字段"
  11. table.rows[0].cells[1].text = "定义"
  12. table.rows[1].cells[0].text = "customer_id"
  13. table.rows[1].cells[1].text = "客户唯一编号"
  14. table.rows[2].cells[0].text = "字段"
  15. table.rows[2].cells[1].text = "定义"
  16. stream = io.BytesIO()
  17. document.save(stream)
  18. return stream.getvalue()
  19. def test_docx_extracts_paragraphs_and_table_cells_with_exact_locations():
  20. from app.core.data_research.extractors.docx import DocxExtractor
  21. batch = DocxExtractor().extract(
  22. docx_bytes(),
  23. ExtractionContext(
  24. filename="governance.docx",
  25. media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  26. ),
  27. )
  28. assert batch.parser_version == "docx-v1"
  29. assert batch.items[0].data["text"] == "客户主数据定义"
  30. assert batch.items[0].evidence[0].locator == {"kind": "docx.paragraph", "paragraph": 1}
  31. table_cells = [item for item in batch.items if item.data["kind"] == "table_cell"]
  32. assert table_cells[-1].data["text"] == "客户唯一编号"
  33. assert table_cells[-1].evidence[0].locator == {
  34. "kind": "docx.table",
  35. "table": 1,
  36. "row": 2,
  37. "cell": 2,
  38. }
  39. assert len(table_cells) == 4 # repeated header row is removed
  40. def test_empty_docx_returns_a_deterministic_warning():
  41. from app.core.data_research.extractors.docx import DocxExtractor
  42. batch = DocxExtractor().extract(
  43. docx_bytes(empty=True),
  44. ExtractionContext(filename="empty.docx", media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
  45. )
  46. assert batch.items == ()
  47. assert batch.warnings == ("document contains no usable text",)
  48. class FakePage:
  49. def __init__(self, text, tables=(), bbox=(0, 0, 600, 800)):
  50. self._text = text
  51. self._tables = tables
  52. self.bbox = bbox
  53. def extract_text(self):
  54. return self._text
  55. def extract_tables(self):
  56. return list(self._tables)
  57. class FakePdf:
  58. def __init__(self, pages):
  59. self.pages = pages
  60. def __enter__(self):
  61. return self
  62. def __exit__(self, *_args):
  63. return False
  64. def test_pdf_preserves_page_order_tables_and_marks_scanned_pages(monkeypatch):
  65. import pdfplumber
  66. from app.core.data_research.extractors.pdf import PdfExtractor
  67. pages = [
  68. FakePage("第一页定义", tables=[[['字段', '定义'], ['id', '唯一编号']]]),
  69. FakePage(None),
  70. FakePage("第三页定义"),
  71. ]
  72. monkeypatch.setattr(pdfplumber, "open", lambda _stream: FakePdf(pages))
  73. batch = PdfExtractor().extract(
  74. b"%PDF-fixture",
  75. ExtractionContext(filename="standard.pdf", media_type="application/pdf"),
  76. )
  77. assert [item.evidence[0].locator["page"] for item in batch.items] == [1, 1, 1, 1, 1, 3]
  78. assert batch.items[0].evidence[0].locator == {
  79. "kind": "pdf.page",
  80. "page": 1,
  81. "bbox": [0.0, 0.0, 1.0, 1.0],
  82. }
  83. assert batch.metadata["ocr_required_pages"] == [2]
  84. assert batch.warnings == ("OCR required for pages: 2",)
  85. def test_registry_resolves_docx_and_pdf():
  86. from app.core.data_research.extractors.docx import DocxExtractor
  87. from app.core.data_research.extractors.pdf import PdfExtractor
  88. from app.core.data_research.extractors.registry import default_registry
  89. registry = default_registry()
  90. assert isinstance(registry.resolve("application/pdf", "a.pdf"), PdfExtractor)
  91. assert isinstance(
  92. registry.resolve(
  93. "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  94. "a.docx",
  95. ),
  96. DocxExtractor,
  97. )