52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
"""XML -> PDF (pretty text dump, monospace).
|
|
|
|
Validation is explicit: lxml must parse the document, otherwise the job
|
|
fails with "Invalid XML: <reason>" (never a render failure). A valid
|
|
document is rendered as its text content — every text node and tail in
|
|
document order, one per line, whitespace-only nodes dropped — inside
|
|
<pre> with PRE_CSS. Attribute values are intentionally not dumped: the
|
|
contract is that element text survives extraction.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from lxml import etree
|
|
|
|
from . import common
|
|
from .text_converter import decode_bytes
|
|
|
|
|
|
def convert(path: Path, job_id: str, progress_cb=None, output_dir=None,
|
|
source_name=None) -> str:
|
|
"""Render an XML file to a monospace text-dump PDF."""
|
|
raw = path.read_bytes()
|
|
if not raw.strip():
|
|
raise ValueError("empty file: nothing to convert")
|
|
text = decode_bytes(raw)
|
|
try:
|
|
root = etree.fromstring(text.encode("utf-8"))
|
|
except etree.XMLSyntaxError as exc:
|
|
raise ValueError(f"Invalid XML: {exc}") from exc
|
|
|
|
lines: list[str] = []
|
|
for el in root.iter():
|
|
for chunk in (el.text, el.tail):
|
|
if not chunk:
|
|
continue
|
|
piece = " ".join(chunk.split())
|
|
if piece:
|
|
lines.append(piece)
|
|
if not lines:
|
|
raise ValueError("empty file: XML has no text content")
|
|
body = "\n".join(lines)
|
|
|
|
def _build():
|
|
return f"<html><body><pre>{common.escape(body)}</pre></body></html>", path.name
|
|
|
|
return common.convert_single(
|
|
path, job_id, build_html=_build, css=common.PRE_CSS,
|
|
progress_cb=progress_cb, output_dir=output_dir,
|
|
name_src=source_name or path.name,
|
|
)
|