diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java index 999c7f7f..92433b46 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java @@ -44,6 +44,7 @@ import org.apache.xmpbox.type.BadFieldValueException; import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.XmpParsingException; import org.apache.xmpbox.xml.XmpSerializer; +import org.mustangproject.EStandard; import org.mustangproject.FileAttachment; import javax.activation.DataSource; @@ -104,6 +105,12 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { @Deprecated protected String subject; + /** + * OrderX document type. As of version 1.0 it may be + * ORDER, ORDER_RESPONSE, or ORDER_CHANGE + */ + protected String orderXDocumentType = "ORDER"; + private HashMap additionalXMLs = new HashMap(); @@ -223,6 +230,24 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { } } + + /** + * Embeds an external file (generic - any type allowed) in the PDF. + * The embedding is done in the default PDF document. + * + * @param filename name of the file that will become attachment name in the PDF + * @param relationship how the file relates to the content, e.g. "Alternative" + * @param description Human-readable description of the file content + * @param subType type of the data e.g. could be "text/xml" - mime like + * @param data the binary data of the file/attachment + * @throws java.io.IOException if anything is wrong with filename + */ + public void PDFAttachGenericFile(String filename, String relationship, String description, + String subType, byte[] data) throws IOException { + PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data); + } + + /** * Embeds an external file (generic - any type allowed) in the PDF. * @@ -374,6 +399,20 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { return this; } + /** + * Sets the property orderXDocumentType. + * + * @param orderXDocumentType new value. Expected: ORDER, ORDER_RESPONSE, or ORDER_CHANGE + * + * @return this exporter + */ + public OXExporterFromA3 setOrderXDocumentType(String orderXDocumentType) + { + this.orderXDocumentType = orderXDocumentType; + + return this; + } + protected OXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) { this.attachZUGFeRDHeaders = attachHeaders; return this; @@ -393,11 +432,11 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, 1, true, xmlProvider.getProfile(), "urn:factur-x:pdfa:CrossIndustryDocument:1p0#", "fx", "order-x.xml"); - zf.setType("ORDER"); + zf.setType(this.orderXDocumentType); metadata.addSchema(zf); // also add the schema extensions... - XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(this, metadata, 1, attachZUGFeRDHeaders); + XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(this, metadata, 1, attachZUGFeRDHeaders, EStandard.orderx); pdfaex.setZUGFeRDVersion(1); metadata.addSchema(pdfaex); } @@ -424,7 +463,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { xmlProvider.generateXML(trans); String filename = "order-x.xml"; PDFAttachGenericFile(doc, filename, "Alternative", - "Invoice metadata conforming to ZUGFeRD standard (http://www.ferd-net.de/front_content.php?idcat=231&lang=4)", + "Order metadata conforming to the Order-X standard (https://www.ferd-net.de/standards/order-x/index.html)", "text/xml", xmlProvider.getXML()); for (FileAttachment attachment : fileAttachments) { @@ -438,14 +477,14 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { * Reads the XMPMetadata from the PDDocument, if it exists. * Otherwise creates XMPMetadata. */ - protected XMPMetadata getXmpMetadata() { + protected XMPMetadata getXmpMetadata() throws IOException { PDMetadata meta = doc.getDocumentCatalog().getMetadata(); - if (meta != null) { + if ((meta != null) && (meta.getLength() > 0)) { try { DomXmpParser xmpParser = new DomXmpParser(); return xmpParser.parse(meta.toByteArray()); } catch (XmpParsingException | IOException e) { - // TODO use logging or handle the error somehow + throw new IOException(e); } } return XMPMetadata.createXMPMetadata(); @@ -490,7 +529,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { // This should be impossible, because it would occur only if an illegal // conformance level is supplied, // however the enum enforces that the conformance level is valid. - throw new Error(ex); + throw new RuntimeException(ex); } } pdfaid.setPart(3); @@ -579,7 +618,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { /** * Adds an OutputIntent and the sRGB color profile if no OutputIntent exist */ - protected void addSRGBOutputIntend() { + protected void addSRGBOutputIntend() throws IOException { if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) { return; } @@ -595,7 +634,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 { doc.getDocumentCatalog().addOutputIntent(intent); } } catch (IOException e) { - // TODO use logging or handle the error somehow + throw e; } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java b/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java index 117a1ee3..c4e81202 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java @@ -31,6 +31,7 @@ import org.apache.xmpbox.XMPMetadata; import org.apache.xmpbox.XmpConstants; import org.apache.xmpbox.schema.PDFAExtensionSchema; import org.apache.xmpbox.type.*; +import org.mustangproject.EStandard; /** * Additionally to adding a RDF namespace with a indication which file @@ -52,7 +53,7 @@ public class XMPSchemaPDFAExtensions extends PDFAExtensionSchema { public final String prefix_pdfaProperty = "pdfaProperty"; public String namespace = null; public String prefix = null; - + protected IZUGFeRDExporter exporter; @@ -90,20 +91,21 @@ public class XMPSchemaPDFAExtensions extends PDFAExtensionSchema { } public XMPSchemaPDFAExtensions(IZUGFeRDExporter ze, XMPMetadata metadata, int ZFVersion) { - super(metadata); - exporter=ze; - setZUGFeRDVersion(ZFVersion); - attachExtensions(metadata, true); + this(ze, metadata, ZFVersion, true); } public XMPSchemaPDFAExtensions(IZUGFeRDExporter ze, XMPMetadata metadata, int ZFVersion, boolean withZF) { + this(ze, metadata, ZFVersion, withZF, null); + } + + public XMPSchemaPDFAExtensions(IZUGFeRDExporter ze, XMPMetadata metadata, int ZFVersion, boolean withZF, EStandard eStandard) { super(metadata); exporter=ze; setZUGFeRDVersion(ZFVersion); - attachExtensions(metadata, withZF); + attachExtensions(metadata, withZF, eStandard); } - public void attachExtensions(XMPMetadata metadata, boolean withZF) { + public void attachExtensions(XMPMetadata metadata, boolean withZF, EStandard eStandard) { addNamespace(xmlns_pdfaSchema, prefix_pdfaSchema); addNamespace(xmlns_pdfaProperty, prefix_pdfaProperty); @@ -130,11 +132,21 @@ public class XMPSchemaPDFAExtensions extends PDFAExtensionSchema { PDFASchemaType.PROPERTY, Cardinality.Seq); li.addProperty(newSeq); - addProperty(newSeq, "DocumentFileName", "Text", "external", "name of the embedded XML invoice file"); - addProperty(newSeq, "DocumentType", "Text", "external", "INVOICE"); - addProperty(newSeq, "Version", "Text", "external", "The actual version of the ZUGFeRD XML schema"); - addProperty(newSeq, "ConformanceLevel", "Text", "external", - "The selected ZUGFeRD profile completeness"); + if ((eStandard != null) && (eStandard == EStandard.orderx)) + { + addProperty(newSeq, "DocumentFileName", "Text", "external", "Name of the embedded XML Order (or related) file"); + addProperty(newSeq, "DocumentType", "Text", "external", "ORDER, ORDER_RESPONSE, or ORDER_CHANGE"); + addProperty(newSeq, "Version", "Text", "external", "The actual version of the Order-X XML schema"); + addProperty(newSeq, "ConformanceLevel", "Text", "external", + "The selected Order-X profile completeness"); + } else + { + addProperty(newSeq, "DocumentFileName", "Text", "external", "name of the embedded XML invoice file"); + addProperty(newSeq, "DocumentType", "Text", "external", "INVOICE"); + addProperty(newSeq, "Version", "Text", "external", "The actual version of the ZUGFeRD XML schema"); + addProperty(newSeq, "ConformanceLevel", "Text", "external", + "The selected ZUGFeRD profile completeness"); + } } } } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaZugferd.java b/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaZugferd.java index c2b12c3b..a708b82a 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaZugferd.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaZugferd.java @@ -33,6 +33,7 @@ public class XMPSchemaZugferd extends XMPSchema { /*** * This is what needs to be added to the RDF metadata - basically the name of the embedded Zugferd file + * * @param metadata the xmp to be added to * @param zfVersion which ZF version to use (2 for FX) * @param isFacturX whether to export as Factur-X @@ -41,7 +42,26 @@ public class XMPSchemaZugferd extends XMPSchema { * @param prefix the xml namespace prefix for the XMP, zf for ZUGFeRD, fx for Factur-X * @param filename the filename of the invoice */ - public XMPSchemaZugferd(XMPMetadata metadata, int zfVersion, boolean isFacturX, Profile conformanceLevel, String URN, String prefix, String filename) { + public XMPSchemaZugferd(XMPMetadata metadata, int zfVersion, boolean isFacturX, Profile conformanceLevel, + String URN, String prefix, String filename) { + this(metadata, zfVersion, isFacturX, conformanceLevel, URN, prefix, filename, null); + } + + + /*** + * This is what needs to be added to the RDF metadata - basically the name of the embedded Zugferd file + * + * @param metadata the xmp to be added to + * @param zfVersion which ZF version to use (2 for FX) + * @param isFacturX whether to export as Factur-X + * @param conformanceLevel e.g. conformanceLevel.EN16931 + * @param URN the xml URI for the XMP + * @param prefix the xml namespace prefix for the XMP, zf for ZUGFeRD, fx for Factur-X + * @param filename the filename of the invoice + * @param version meta-data version to set. May be null, and then it is set automatically + */ + public XMPSchemaZugferd(XMPMetadata metadata, int zfVersion, boolean isFacturX, Profile conformanceLevel, + String URN, String prefix, String filename, String version) { super(metadata, URN, prefix, "ZUGFeRD Schema"); setAboutAsSimple(""); @@ -50,14 +70,17 @@ public class XMPSchemaZugferd extends XMPSchema { setTextPropertyValue("ConformanceLevel", conformanceLevelValue); setTextPropertyValue("DocumentType", "INVOICE"); setTextPropertyValue("DocumentFileName", filename); - String version="1.0"; - if ((zfVersion==2)&&(!isFacturX)) { - version="2p0"; + + if (version == null) { + version = "1.0"; + if ((zfVersion==2)&&(!isFacturX)) { + version="2p0"; + } } setTextPropertyValue("Version", version); } - /*** + /*** * Specify a custom XMP metadata document type * @param type INVOICE or ORDER */ diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java index 6d4d720b..82bb5f3b 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java @@ -116,6 +116,11 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte protected int ZFVersion = DefaultZUGFeRDVersion; private boolean attachZUGFeRDHeaders = true; + + // Specific metaData version in case of XRechnung. We need it to be settable + // by the caller if necessary. + protected String XRechnungVersion = null; // Default XRechnung as of late 2021 is 2p0 + /** * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the * metadata level, this will not e.g. convert graphics to JPG-2000) @@ -221,6 +226,20 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte return this; } + + /** + * Sets a specific XRechnung version from outside. This version needs to be present in the + * meta-data as well. The caller may wish to generate a specific version of XRechnung + * depending on the time period etc. + * + * @param XRechnungVersion the XRechnung version + */ + public void setXRechnungSpecificVersion(String XRechnungVersion) + { + this.XRechnungVersion = XRechnungVersion; + } + + /*** * Generate ZF2.0/2.1 files with filename zugferd-invoice.xml instead of factur-x.xml */ @@ -301,6 +320,24 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte } } + + /** + * Embeds an external file (generic - any type allowed) in the PDF. + * The embedding is done in the default PDF document. + * + * @param filename name of the file that will become attachment name in the PDF + * @param relationship how the file relates to the content, e.g. "Alternative" + * @param description Human-readable description of the file content + * @param subType type of the data e.g. could be "text/xml" - mime like + * @param data the binary data of the file/attachment + * @throws java.io.IOException if anything is wrong with filename + */ + public void PDFAttachGenericFile(String filename, String relationship, String description, + String subType, byte[] data) throws IOException { + PDFAttachGenericFile(this.doc, filename, relationship, description, subType, data); + } + + /** * Embeds an external file (generic - any type allowed) in the PDF. * @@ -467,10 +504,19 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte */ protected void addXMP(XMPMetadata metadata) { + String metaDataVersion = null; // default will be used + + // The XRechnung version may be settable from outside. + if ((this.XRechnungVersion != null) && (this.profile != null) && + this.profile.getName().equalsIgnoreCase(Profiles.getByName("XRECHNUNG").getName())) + { + metaDataVersion = this.XRechnungVersion; + } + if (attachZUGFeRDHeaders) { XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, ZFVersion, isFacturX, xmlProvider.getProfile(), getNamespaceForVersion(ZFVersion), getPrefixForVersion(ZFVersion), - getFilenameForVersion(ZFVersion, xmlProvider.getProfile())); + getFilenameForVersion(ZFVersion, xmlProvider.getProfile()), metaDataVersion); metadata.addSchema(zf); } @@ -480,8 +526,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte metadata.addSchema(pdfaex); } - protected void removeCidSet(PDDocumentCatalog catalog, PDDocument doc) { - + private void removeCidSet(PDDocumentCatalog catalog, PDDocument doc) + throws IOException + { // https://github.com/ZUGFeRD/mustangproject/issues/249 COSName cidSet = COSName.getPDFName("CIDSet"); @@ -507,7 +554,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte } } } catch (IOException e) { - e.printStackTrace(); + throw e; } // do stuff with the font } @@ -567,7 +614,21 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte prepareDocument(); xmlProvider.generateXML(trans); String filename = getFilenameForVersion(ZFVersion, xmlProvider.getProfile()); - PDFAttachGenericFile(doc, filename, "Alternative", + + String relationship = "Alternative"; + // ZUGFeRD 2.1.1 Technical Supplement | Part A | 2.2.2. Data Relationship + // See documentation ZUGFeRD211_EN/Documentation/ZUGFeRD-2.1.1 - Specification_TA_Part-A.pdf + // https://www.ferd-net.de/standards/zugferd-2.1.1/index.html + if ((this.profile != null) && (ZFVersion >= 2)) + { + if (this.profile.getName().equalsIgnoreCase(Profiles.getByName("MINIMUM").getName()) || + this.profile.getName().equalsIgnoreCase(Profiles.getByName("BASICWL").getName())) + { + relationship = "Data"; + } + } + + PDFAttachGenericFile(doc, filename, relationship, "Invoice metadata conforming to ZUGFeRD standard (http://www.ferd-net.de/front_content.php?idcat=231&lang=4)", "text/xml", xmlProvider.getXML()); @@ -582,14 +643,16 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte * Reads the XMPMetadata from the PDDocument, if it exists. * Otherwise creates XMPMetadata. */ - protected XMPMetadata getXmpMetadata() { + protected XMPMetadata getXmpMetadata() + throws IOException + { PDMetadata meta = doc.getDocumentCatalog().getMetadata(); - if (meta != null) { + if ((meta != null) && (meta.getLength() > 0)) { try { DomXmpParser xmpParser = new DomXmpParser(); return xmpParser.parse(meta.toByteArray()); - } catch (XmpParsingException | IOException e) { - // TODO use logging or handle the error somehow + } catch (XmpParsingException e) { + throw new IOException(e); } } return XMPMetadata.createXMPMetadata(); @@ -723,7 +786,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte /** * Adds an OutputIntent and the sRGB color profile if no OutputIntent exist */ - protected void addSRGBOutputIntend() { + protected void addSRGBOutputIntend() + throws IOException + { if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) { return; } @@ -739,7 +804,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte doc.getDocumentCatalog().addOutputIntent(intent); } } catch (IOException e) { - // TODO use logging or handle the error somehow + throw e; } }