cmboulanger's picture
disaster-recovery deploy of pdf-tei-editor
6a49f21 verified
Raw
History Blame Contribute Delete
6.31 kB
"""
Mock extraction engine for testing without external dependencies.
"""
import os
import datetime
from typing import Dict, Any, Optional
from lxml import etree
from fastapi_app.lib.extraction import BaseExtractor
from fastapi_app.lib.utils.tei_utils import (
create_tei_document,
create_tei_header,
create_revision_desc_with_status,
serialize_tei_with_formatted_header,
get_file_id_from_options,
create_encoding_desc_with_extractor,
)
from fastapi_app.lib.utils.doi_utils import encode_for_xml_id
class MockExtractor(BaseExtractor):
"""Mock extractor for testing without external dependencies."""
SUPPORTED_VARIANTS = [
"mock-default",
"mock-variant-a",
"mock-variant-b",
]
@classmethod
def get_info(cls) -> Dict[str, Any]:
"""Return information about the mock extractor."""
return {
"id": "mock-extractor",
"name": "Mock Extractor",
"description": "Mock extractor for testing without external dependencies (available in testing mode only)",
"input": ["pdf", "xml"],
"output": ["tei-document"],
"variants": list(cls.SUPPORTED_VARIANTS),
"options": {
"doi": {
"type": "string",
"label": "DOI",
"description": "DOI of the document for metadata enrichment",
"required": False
},
"variant_id": {
"type": "string",
"label": "Variant identifier",
"description": "Variant identifier for the mock extraction",
"required": False,
"options": list(cls.SUPPORTED_VARIANTS)
}
},
"navigation_xpath": {
"mock-default": [
{
"value": "//tei:biblStruct",
"label": "<biblStruct>"
}
]
}
}
@classmethod
def is_available(cls) -> bool:
"""Mock extractor available only in testing mode."""
app_mode = os.environ.get("FASTAPI_APPLICATION_MODE", "development")
return app_mode == "testing"
async def extract(self, pdf_path: Optional[str] = None, xml_content: Optional[str] = None,
options: Optional[Dict[str, Any]] = None) -> str:
"""
Mock extraction that returns a simple TEI document.
Args:
pdf_path: Path to the PDF file (for PDF input)
xml_content: XML content string (for XML input)
options: Extraction options
Returns:
Complete TEI document as XML string
"""
if not pdf_path and not xml_content:
raise ValueError("Either PDF path or XML content is required for mock extraction")
if options is None:
options = {}
# Create TEI document
tei_doc = create_tei_document()
# Create basic TEI header
doi = options.get("doi", "")
tei_header = create_tei_header(doi, {})
assert tei_header is not None
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
file_id = get_file_id_from_options(options, pdf_path)
if not file_id:
file_id = f"mock-extracted-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}"
fileDesc = tei_header.find("fileDesc")
assert fileDesc is not None
# Set xml:id on fileDesc for document identity (NCName-safe encoding)
fileDesc.set("{http://www.w3.org/XML/1998/namespace}id", encode_for_xml_id(file_id))
# Create encodingDesc with applications
existing_encodingDesc = tei_header.find("encodingDesc")
if existing_encodingDesc is not None:
tei_header.remove(existing_encodingDesc)
# Get variant_id from options
info = self.get_info()
default_variant_id = info["options"]["variant_id"]["options"][0] # "mock-default"
variant_id = options.get("variant_id", default_variant_id)
encodingDesc = create_encoding_desc_with_extractor(
timestamp=timestamp,
extractor_name="Mock Extractor",
extractor_ident="mock-extractor",
extractor_version="1.0",
variant_id=variant_id,
)
tei_header.append(encodingDesc)
# Replace revisionDesc with mock-specific version
existing_revisionDesc = tei_header.find("revisionDesc")
if existing_revisionDesc is not None:
tei_header.remove(existing_revisionDesc)
revision_desc = create_revision_desc_with_status(timestamp, "extraction", "Generated with mock extractor for testing")
tei_header.append(revision_desc)
tei_doc.append(tei_header)
# Create mock content with sample references
standOff = etree.SubElement(tei_doc, "standOff")
listBibl = etree.SubElement(standOff, "listBibl")
# Add a few mock bibliography entries
for i in range(3):
biblStruct = etree.SubElement(listBibl, "biblStruct", status="verified")
analytic = etree.SubElement(biblStruct, "analytic")
title = etree.SubElement(analytic, "title", level="a", type="main")
title.text = f"Mock Reference Title {i+1}"
author = etree.SubElement(analytic, "author")
persName = etree.SubElement(author, "persName")
forename = etree.SubElement(persName, "forename", type="first")
forename.text = f"John{i+1}"
surname = etree.SubElement(persName, "surname")
surname.text = f"Doe{i+1}"
monogr = etree.SubElement(biblStruct, "monogr")
title_monogr = etree.SubElement(monogr, "title", level="j")
title_monogr.text = f"Mock Journal {i+1}"
imprint = etree.SubElement(monogr, "imprint")
date_imprint = etree.SubElement(imprint, "date", type="published", when=f"202{i}")
date_imprint.text = f"202{i}"
# Serialize to XML with formatted header (no schema validation for mock extractor)
return serialize_tei_with_formatted_header(tei_doc, [])