From b882f783fc809d835b15da3939bc08870eda6055 Mon Sep 17 00:00:00 2001 From: Philipp Wolak Date: Mon, 4 May 2026 12:42:26 +0200 Subject: [PATCH] Use xrechnung.xml for XRECHNUNG attachments --- drafthorse/pdf.py | 10 ++++++++-- tests/test_roundtrip.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/drafthorse/pdf.py b/drafthorse/pdf.py index a5bc124..a157589 100644 --- a/drafthorse/pdf.py +++ b/drafthorse/pdf.py @@ -149,6 +149,10 @@ def _prepare_pdf_metadata_txt(pdf_metadata): } +def _get_xml_attachment_filename(profile): + return "xrechnung.xml" if profile == "XRECHNUNG" else "factur-x.xml" + + def _prepare_xmp_metadata(profile, pdf_metadata): """ Prepare pdf metadata using the FACTUR-X XMP extension schema @@ -161,6 +165,7 @@ def _prepare_xmp_metadata(profile, pdf_metadata): escaped_metadata = { key: xml_escape(str(value)) for key, value in pdf_metadata.items() } + xml_filename = _get_xml_attachment_filename(profile) xml_str = XMP_SCHEMA.format( title=escaped_metadata.get("title", ""), @@ -171,7 +176,7 @@ def _prepare_xmp_metadata(profile, pdf_metadata): timestamp=datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00"), urn="urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#", documenttype="INVOICE", - xml_filename="factur-x.xml", + xml_filename=xml_filename, version="1.0", xmp_level=profile, ) @@ -222,7 +227,8 @@ def _update_metadata_add_attachment( {NameObject("/F"): file_entry_obj, NameObject("/UF"): file_entry_obj} ) - fname_obj = create_string_object("factur-x.xml") + xml_filename = _get_xml_attachment_filename(facturx_level) + fname_obj = create_string_object(xml_filename) filespec_dict = DictionaryObject( { NameObject("/AFRelationship"): NameObject( diff --git a/tests/test_roundtrip.py b/tests/test_roundtrip.py index d745cca..bb3a1d7 100644 --- a/tests/test_roundtrip.py +++ b/tests/test_roundtrip.py @@ -58,6 +58,16 @@ def test_sample_roundtrip(filename): # Read back the PDF. We don't support extensive parsing, but this way we can assert that metadata is at least present # and syntactically valid. pdf_reader = PdfReader(BytesIO(created_pdf_bytes)) + expected_xml_filename = ( + "xrechnung.xml" if filename.split("_")[2] == "XRECHNUNG" else "factur-x.xml" + ) + embedded_names = ( + pdf_reader.trailer["/Root"]["/Names"]["/EmbeddedFiles"]["/Names"] + ).get_object() + metadata_xml = pdf_reader.trailer["/Root"]["/Metadata"].get_object().get_data() + + assert str(embedded_names[0]) == expected_xml_filename + assert expected_xml_filename.encode() in metadata_xml assert pdf_reader.xmp_metadata # Parse the sample file into our internal python structure