XRechnung and OrderX improvements for upstream.

This commit is contained in:
Ivan Vaklinov
2022-01-31 12:37:49 +02:00
parent 3f80c1daaf
commit 2f912f5597
4 changed files with 176 additions and 37 deletions

View File

@@ -44,6 +44,7 @@ import org.apache.xmpbox.type.BadFieldValueException;
import org.apache.xmpbox.xml.DomXmpParser; import org.apache.xmpbox.xml.DomXmpParser;
import org.apache.xmpbox.xml.XmpParsingException; import org.apache.xmpbox.xml.XmpParsingException;
import org.apache.xmpbox.xml.XmpSerializer; import org.apache.xmpbox.xml.XmpSerializer;
import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment; import org.mustangproject.FileAttachment;
import javax.activation.DataSource; import javax.activation.DataSource;
@@ -104,6 +105,12 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
@Deprecated @Deprecated
protected String subject; 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<String, byte[]> additionalXMLs = new HashMap<String, byte[]>(); private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();
@@ -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. * Embeds an external file (generic - any type allowed) in the PDF.
* *
@@ -374,6 +399,20 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this; 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) { protected OXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) {
this.attachZUGFeRDHeaders = attachHeaders; this.attachZUGFeRDHeaders = attachHeaders;
return this; return this;
@@ -393,11 +432,11 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, 1, true, xmlProvider.getProfile(), XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, 1, true, xmlProvider.getProfile(),
"urn:factur-x:pdfa:CrossIndustryDocument:1p0#", "fx", "urn:factur-x:pdfa:CrossIndustryDocument:1p0#", "fx",
"order-x.xml"); "order-x.xml");
zf.setType("ORDER"); zf.setType(this.orderXDocumentType);
metadata.addSchema(zf); metadata.addSchema(zf);
// also add the schema extensions... // 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); pdfaex.setZUGFeRDVersion(1);
metadata.addSchema(pdfaex); metadata.addSchema(pdfaex);
} }
@@ -424,7 +463,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
xmlProvider.generateXML(trans); xmlProvider.generateXML(trans);
String filename = "order-x.xml"; String filename = "order-x.xml";
PDFAttachGenericFile(doc, filename, "Alternative", 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()); "text/xml", xmlProvider.getXML());
for (FileAttachment attachment : fileAttachments) { for (FileAttachment attachment : fileAttachments) {
@@ -438,14 +477,14 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
* Reads the XMPMetadata from the PDDocument, if it exists. * Reads the XMPMetadata from the PDDocument, if it exists.
* Otherwise creates XMPMetadata. * Otherwise creates XMPMetadata.
*/ */
protected XMPMetadata getXmpMetadata() { protected XMPMetadata getXmpMetadata() throws IOException {
PDMetadata meta = doc.getDocumentCatalog().getMetadata(); PDMetadata meta = doc.getDocumentCatalog().getMetadata();
if (meta != null) { if ((meta != null) && (meta.getLength() > 0)) {
try { try {
DomXmpParser xmpParser = new DomXmpParser(); DomXmpParser xmpParser = new DomXmpParser();
return xmpParser.parse(meta.toByteArray()); return xmpParser.parse(meta.toByteArray());
} catch (XmpParsingException | IOException e) { } catch (XmpParsingException | IOException e) {
// TODO use logging or handle the error somehow throw new IOException(e);
} }
} }
return XMPMetadata.createXMPMetadata(); return XMPMetadata.createXMPMetadata();
@@ -490,7 +529,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
// This should be impossible, because it would occur only if an illegal // This should be impossible, because it would occur only if an illegal
// conformance level is supplied, // conformance level is supplied,
// however the enum enforces that the conformance level is valid. // however the enum enforces that the conformance level is valid.
throw new Error(ex); throw new RuntimeException(ex);
} }
} }
pdfaid.setPart(3); pdfaid.setPart(3);
@@ -579,7 +618,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
/** /**
* Adds an OutputIntent and the sRGB color profile if no OutputIntent exist * 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()) { if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
return; return;
} }
@@ -595,7 +634,7 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
doc.getDocumentCatalog().addOutputIntent(intent); doc.getDocumentCatalog().addOutputIntent(intent);
} }
} catch (IOException e) { } catch (IOException e) {
// TODO use logging or handle the error somehow throw e;
} }
} }

View File

@@ -31,6 +31,7 @@ import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.XmpConstants; import org.apache.xmpbox.XmpConstants;
import org.apache.xmpbox.schema.PDFAExtensionSchema; import org.apache.xmpbox.schema.PDFAExtensionSchema;
import org.apache.xmpbox.type.*; import org.apache.xmpbox.type.*;
import org.mustangproject.EStandard;
/** /**
* Additionally to adding a RDF namespace with a indication which file * 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 final String prefix_pdfaProperty = "pdfaProperty";
public String namespace = null; public String namespace = null;
public String prefix = null; public String prefix = null;
protected IZUGFeRDExporter exporter; protected IZUGFeRDExporter exporter;
@@ -90,20 +91,21 @@ public class XMPSchemaPDFAExtensions extends PDFAExtensionSchema {
} }
public XMPSchemaPDFAExtensions(IZUGFeRDExporter ze, XMPMetadata metadata, int ZFVersion) { public XMPSchemaPDFAExtensions(IZUGFeRDExporter ze, XMPMetadata metadata, int ZFVersion) {
super(metadata); this(ze, metadata, ZFVersion, true);
exporter=ze;
setZUGFeRDVersion(ZFVersion);
attachExtensions(metadata, true);
} }
public XMPSchemaPDFAExtensions(IZUGFeRDExporter ze, XMPMetadata metadata, int ZFVersion, boolean withZF) { 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); super(metadata);
exporter=ze; exporter=ze;
setZUGFeRDVersion(ZFVersion); 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_pdfaSchema, prefix_pdfaSchema);
addNamespace(xmlns_pdfaProperty, prefix_pdfaProperty); addNamespace(xmlns_pdfaProperty, prefix_pdfaProperty);
@@ -130,11 +132,21 @@ public class XMPSchemaPDFAExtensions extends PDFAExtensionSchema {
PDFASchemaType.PROPERTY, Cardinality.Seq); PDFASchemaType.PROPERTY, Cardinality.Seq);
li.addProperty(newSeq); li.addProperty(newSeq);
addProperty(newSeq, "DocumentFileName", "Text", "external", "name of the embedded XML invoice file"); if ((eStandard != null) && (eStandard == EStandard.orderx))
addProperty(newSeq, "DocumentType", "Text", "external", "INVOICE"); {
addProperty(newSeq, "Version", "Text", "external", "The actual version of the ZUGFeRD XML schema"); addProperty(newSeq, "DocumentFileName", "Text", "external", "Name of the embedded XML Order (or related) file");
addProperty(newSeq, "ConformanceLevel", "Text", "external", addProperty(newSeq, "DocumentType", "Text", "external", "ORDER, ORDER_RESPONSE, or ORDER_CHANGE");
"The selected ZUGFeRD profile completeness"); 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");
}
} }
} }
} }

View File

@@ -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 * 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 metadata the xmp to be added to
* @param zfVersion which ZF version to use (2 for FX) * @param zfVersion which ZF version to use (2 for FX)
* @param isFacturX whether to export as Factur-X * @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 prefix the xml namespace prefix for the XMP, zf for ZUGFeRD, fx for Factur-X
* @param filename the filename of the invoice * @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"); super(metadata, URN, prefix, "ZUGFeRD Schema");
setAboutAsSimple(""); setAboutAsSimple("");
@@ -50,14 +70,17 @@ public class XMPSchemaZugferd extends XMPSchema {
setTextPropertyValue("ConformanceLevel", conformanceLevelValue); setTextPropertyValue("ConformanceLevel", conformanceLevelValue);
setTextPropertyValue("DocumentType", "INVOICE"); setTextPropertyValue("DocumentType", "INVOICE");
setTextPropertyValue("DocumentFileName", filename); setTextPropertyValue("DocumentFileName", filename);
String version="1.0";
if ((zfVersion==2)&&(!isFacturX)) { if (version == null) {
version="2p0"; version = "1.0";
if ((zfVersion==2)&&(!isFacturX)) {
version="2p0";
}
} }
setTextPropertyValue("Version", version); setTextPropertyValue("Version", version);
} }
/*** /***
* Specify a custom XMP metadata document type * Specify a custom XMP metadata document type
* @param type INVOICE or ORDER * @param type INVOICE or ORDER
*/ */

View File

@@ -115,6 +115,11 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
protected int ZFVersion = DefaultZUGFeRDVersion; protected int ZFVersion = DefaultZUGFeRDVersion;
private boolean attachZUGFeRDHeaders = true; 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 * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the
@@ -220,6 +225,20 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
isFacturX = true; isFacturX = true;
return this; 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 * 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. * 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) { 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) { if (attachZUGFeRDHeaders) {
XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, ZFVersion, isFacturX, xmlProvider.getProfile(), XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, ZFVersion, isFacturX, xmlProvider.getProfile(),
getNamespaceForVersion(ZFVersion), getPrefixForVersion(ZFVersion), getNamespaceForVersion(ZFVersion), getPrefixForVersion(ZFVersion),
getFilenameForVersion(ZFVersion, xmlProvider.getProfile())); getFilenameForVersion(ZFVersion, xmlProvider.getProfile()), metaDataVersion);
metadata.addSchema(zf); metadata.addSchema(zf);
} }
@@ -480,8 +526,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
metadata.addSchema(pdfaex); 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 // https://github.com/ZUGFeRD/mustangproject/issues/249
COSName cidSet = COSName.getPDFName("CIDSet"); COSName cidSet = COSName.getPDFName("CIDSet");
@@ -507,7 +554,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
} }
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); throw e;
} }
// do stuff with the font // do stuff with the font
} }
@@ -567,7 +614,21 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
prepareDocument(); prepareDocument();
xmlProvider.generateXML(trans); xmlProvider.generateXML(trans);
String filename = getFilenameForVersion(ZFVersion, xmlProvider.getProfile()); 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 (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)", "Invoice metadata conforming to ZUGFeRD standard (http://www.ferd-net.de/front_content.php?idcat=231&lang=4)",
"text/xml", xmlProvider.getXML()); "text/xml", xmlProvider.getXML());
@@ -582,14 +643,16 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
* Reads the XMPMetadata from the PDDocument, if it exists. * Reads the XMPMetadata from the PDDocument, if it exists.
* Otherwise creates XMPMetadata. * Otherwise creates XMPMetadata.
*/ */
protected XMPMetadata getXmpMetadata() { protected XMPMetadata getXmpMetadata()
throws IOException
{
PDMetadata meta = doc.getDocumentCatalog().getMetadata(); PDMetadata meta = doc.getDocumentCatalog().getMetadata();
if (meta != null) { if ((meta != null) && (meta.getLength() > 0)) {
try { try {
DomXmpParser xmpParser = new DomXmpParser(); DomXmpParser xmpParser = new DomXmpParser();
return xmpParser.parse(meta.toByteArray()); return xmpParser.parse(meta.toByteArray());
} catch (XmpParsingException | IOException e) { } catch (XmpParsingException e) {
// TODO use logging or handle the error somehow throw new IOException(e);
} }
} }
return XMPMetadata.createXMPMetadata(); 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 * 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()) { if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
return; return;
} }
@@ -739,7 +804,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
doc.getDocumentCatalog().addOutputIntent(intent); doc.getDocumentCatalog().addOutputIntent(intent);
} }
} catch (IOException e) { } catch (IOException e) {
// TODO use logging or handle the error somehow throw e;
} }
} }