diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA1.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA1.java
new file mode 100644
index 00000000..fc25c64f
--- /dev/null
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA1.java
@@ -0,0 +1,124 @@
+/** **********************************************************************
+ *
+ * Copyright 2018 Jochen Staerk
+ *
+ * Use is subject to license terms.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *********************************************************************** */
+package org.mustangproject.ZUGFeRD;
+
+
+import org.apache.pdfbox.preflight.PreflightDocument;
+import org.apache.pdfbox.preflight.exception.ValidationException;
+import org.apache.pdfbox.preflight.parser.PreflightParser;
+
+import javax.activation.DataSource;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class OXExporterFromA1 extends OXExporterFromA3 implements IZUGFeRDExporter {
+ protected boolean ignorePDFAErrors = false;
+
+ public OXExporterFromA1 ignorePDFAErrors() {
+ this.ignorePDFAErrors = true;
+ return this;
+ }
+
+ private static boolean isValidA1(DataSource dataSource) throws IOException {
+ return getPDFAParserValidationResult(new PreflightParser(dataSource));
+ }
+
+ private static boolean getPDFAParserValidationResult(PreflightParser parser) throws IOException {
+ /*
+ * Parse the PDF file with PreflightParser that inherits from the
+ * NonSequentialParser. Some additional controls are present to check a set of
+ * PDF/A requirements. (Stream length consistency, EOL after some Keyword...)
+ */
+ parser.parse();// might add a Format.PDF_A1A as parameter and iterate through A1 and A3
+
+ try (PreflightDocument document = parser.getPreflightDocument()) {
+ /*
+ * Once the syntax validation is done, the parser can provide a
+ * PreflightDocument (that inherits from PDDocument) This document process the
+ * end of PDF/A validation.
+ */
+
+ document.validate();
+
+ // Get validation result
+ return document.getResult().isValid();
+ } catch (ValidationException e) {
+ /*
+ * the parse method can throw a SyntaxValidationException if the PDF file can't
+ * be parsed. In this case, the exception contains an instance of
+ * ValidationResult
+ */
+ return false;
+ }
+ }
+
+
+ public OXExporterFromA1 setProfile(Profile p) {
+ return (OXExporterFromA1)super.setProfile(p);
+ }
+ public OXExporterFromA1 setProfile(String profileName) {
+ return (OXExporterFromA1)super.setProfile(profileName);
+ }
+
+ public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
+ if (!ignorePDFAErrors && !isValidA1(dataSource)) {
+ throw new IOException("File is not a valid PDF/A input file");
+ }
+ return true;
+ }
+
+ public OXExporterFromA1() {
+ setZUGFeRDVersion(ZUGFeRDExporterFromA3.DefaultZUGFeRDVersion);
+
+ }
+
+ public OXExporterFromA1 load(String pdfFilename) throws IOException {
+ return (OXExporterFromA1) super.load(pdfFilename);
+ }
+ public OXExporterFromA1 load(byte[] pdfBinary) throws IOException {
+ return (OXExporterFromA1) super.load(pdfBinary);
+ }
+ public OXExporterFromA1 load(InputStream pdfSource) throws IOException{
+ return (OXExporterFromA1) super.load(pdfSource);
+ }
+ public OXExporterFromA1 setCreator(String creator) {
+ return (OXExporterFromA1) super.setCreator(creator);
+ }
+ public OXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
+ return (OXExporterFromA1) super.setConformanceLevel(newLevel);
+ }
+ public OXExporterFromA1 setProducer(String producer){
+ return (OXExporterFromA1) super.setProducer(producer);
+ }
+ public OXExporterFromA1 setZUGFeRDVersion(int version){
+ return (OXExporterFromA1) super.setZUGFeRDVersion(version);
+ }
+ public OXExporterFromA1 setXML(byte[] zugferdData) throws IOException{
+ return (OXExporterFromA1) super.setXML(zugferdData);
+ }
+
+ public OXExporterFromA1 disableAutoClose(boolean disableAutoClose){
+ return (OXExporterFromA1) super.disableAutoClose(disableAutoClose);
+ }
+ public OXExporterFromA1 convertOnly() {
+ setAttachZUGFeRDHeaders(false);
+ return this;
+ }
+
+}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java
new file mode 100644
index 00000000..9d7073e4
--- /dev/null
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXExporterFromA3.java
@@ -0,0 +1,659 @@
+/**
+ * *********************************************************************
+ *
+ * Copyright 2018 Jochen Staerk
+ *
+ * Use is subject to license terms.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * **********************************************************************
+ */
+package org.mustangproject.ZUGFeRD;
+
+import org.apache.pdfbox.cos.*;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.*;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
+import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
+import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
+import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;
+import org.apache.xmpbox.XMPMetadata;
+import org.apache.xmpbox.schema.AdobePDFSchema;
+import org.apache.xmpbox.schema.DublinCoreSchema;
+import org.apache.xmpbox.schema.PDFAIdentificationSchema;
+import org.apache.xmpbox.schema.XMPBasicSchema;
+import org.apache.xmpbox.type.ArrayProperty;
+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.FileAttachment;
+
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.xml.transform.TransformerException;
+import java.io.*;
+import java.util.*;
+
+public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
+
+ protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
+ protected ArrayList fileAttachments = new ArrayList();
+
+ /**
+ * This flag controls whether or not the metadata is overwritten, or kind of merged.
+ * The merging probably needs to be overhauled, but for my purpose it was good enough.
+ */
+ protected boolean overwrite = true;
+
+ private boolean disableAutoClose;
+ private boolean fileAttached = false;
+ private Profile profile = null;
+ private boolean documentPrepared = false;
+
+ /**
+ * Data (XML invoice) to be added to the ZUGFeRD PDF. It may be externally set,
+ * in which case passing a IZUGFeRDExportableTransaction is not necessary. By
+ * default it is null meaning the caller needs to pass a
+ * IZUGFeRDExportableTransaction for the XML to be populated.
+ */
+ protected PDMetadata metadata = null;
+ /**
+ * Producer attribute for PDF
+ */
+ protected String producer = "mustangproject";
+ /**
+ * Author/Creator attribute for PDF
+ */
+ protected String creator = "mustangproject";
+ /**
+ * CreatorTool
+ */
+ protected String creatorTool = "mustangproject";
+
+ /**
+ * @deprecated author is never set yet
+ */
+ @Deprecated
+ protected String author;
+ /**
+ * @deprecated title is never set yet
+ */
+ @Deprecated
+ protected String title;
+ /**
+ * @deprecated subject is never set yet
+ */
+ @Deprecated
+ protected String subject;
+
+
+ private HashMap additionalXMLs = new HashMap();
+
+
+ private boolean attachZUGFeRDHeaders = true;
+
+ /**
+ * 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)
+ *
+ * @param pdfFilename filename of an PDF/A1 compliant document
+ */
+ public OXExporterFromA3 load(String pdfFilename) throws IOException {
+
+ ensurePDFIsValid(new FileDataSource(pdfFilename));
+ try (FileInputStream pdf = new FileInputStream(pdfFilename)) {
+ return load(readAllBytes(pdf));
+ }
+ }
+
+ public IXMLProvider getProvider() {
+ return xmlProvider;
+ }
+
+ public OXExporterFromA3 setProfile(Profile p) {
+ this.profile = p;
+ if (xmlProvider != null) {
+ xmlProvider.setProfile(p);
+ }
+ return this;
+ }
+
+ public OXExporterFromA3 setProfile(String profilename) {
+ this.profile = Profiles.getByName(profilename);
+
+ if (xmlProvider != null) {
+ xmlProvider.setProfile(this.profile);
+ }
+ return this;
+ }
+
+ public OXExporterFromA3 addAdditionalFile(String name, byte[] content) {
+ fileAttachments.add(new FileAttachment(name, "text/xml", "Supplement", content).setDescription("ZUGFeRD extension/additional data"));
+ return this;
+ }
+
+
+
+ /**
+ * 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)
+ *
+ * @param pdfBinary binary of a PDF/A1 compliant document
+ */
+ public OXExporterFromA3 load(byte[] pdfBinary) throws IOException {
+ ensurePDFIsValid(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
+ doc = PDDocument.load(pdfBinary);
+ return this;
+ }
+
+ public OXExporterFromA3() {
+ super();
+ }
+
+ public void attachFile(FileAttachment file) {
+ fileAttachments.add(file);
+ }
+
+ public void attachFile(String filename, byte[] data, String mimetype, String relation) {
+ FileAttachment fa = new FileAttachment(filename, mimetype, relation, data);
+ fileAttachments.add(fa);
+ }
+
+ /***
+ * Perform the final export to a now ZUGFeRD-enriched PDF file
+ * @param ZUGFeRDfilename the pdf file name
+ * @throws IOException if anything is wrong in the target location
+ */
+ public void export(String ZUGFeRDfilename) throws IOException {
+ if (!documentPrepared) {
+ prepareDocument();
+ }
+ if ((!fileAttached) && (attachZUGFeRDHeaders)) {
+ throw new IOException(
+ "File must be attached (usually with setTransaction) before perfoming this operation");
+ }
+ doc.save(ZUGFeRDfilename);
+ if (!disableAutoClose) {
+ close();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (doc != null) {
+ doc.close();
+ }
+ }
+
+
+ /***
+ * Perform the final export to a now ZUGFeRD-enriched PDF file as OutputStream
+ * @param output the OutputStream
+ * @throws IOException if anything is wrong in the OutputStream
+ */
+ public void export(OutputStream output) throws IOException {
+ if (!documentPrepared) {
+ prepareDocument();
+ }
+ if ((!fileAttached) && (attachZUGFeRDHeaders)) {
+ throw new IOException(
+ "File must be attached (usually with setTransaction) before perfoming this operation");
+ }
+ doc.save(output);
+ if (!disableAutoClose) {
+ close();
+ }
+ }
+
+ /**
+ * Embeds an external file (generic - any type allowed) in the PDF.
+ *
+ * @param doc PDDocument to attach the file to.
+ * @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 IOException if anything is wrong with filename
+ */
+ public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description,
+ String subType, byte[] data) throws IOException {
+ fileAttached = true;
+
+ PDComplexFileSpecification fs = new PDComplexFileSpecification();
+ fs.setFile(filename);
+
+ COSDictionary dict = fs.getCOSObject();
+ dict.setName("AFRelationship", relationship);
+ dict.setString("UF", filename);
+ dict.setString("Desc", description);
+
+ ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
+ PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
+// ef.addCompression();
+ ef.setSubtype(subType);
+ ef.setSize(data.length);
+ ef.setCreationDate(new GregorianCalendar());
+
+ ef.setModDate(GregorianCalendar.getInstance());
+
+ fs.setEmbeddedFile(ef);
+
+ // In addition make sure the embedded file is set under /UF
+ dict = fs.getCOSObject();
+ COSDictionary efDict = (COSDictionary) dict.getDictionaryObject(COSName.EF);
+ COSBase lowerLevelFile = efDict.getItem(COSName.F);
+ efDict.setItem(COSName.UF, lowerLevelFile);
+
+ // now add the entry to the embedded file tree and set in the document.
+ PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
+ PDEmbeddedFilesNameTreeNode efTree = names.getEmbeddedFiles();
+ if (efTree == null) {
+ efTree = new PDEmbeddedFilesNameTreeNode();
+ }
+
+ Map namesMap = new HashMap<>();
+
+ Map oldNamesMap = efTree.getNames();
+ if (oldNamesMap != null) {
+ for (String key : oldNamesMap.keySet()) {
+ namesMap.put(key, oldNamesMap.get(key));
+ }
+ }
+ namesMap.put(filename, fs);
+ efTree.setNames(namesMap);
+
+ names.setEmbeddedFiles(efTree);
+ doc.getDocumentCatalog().setNames(names);
+
+ // AF entry (Array) in catalog with the FileSpec
+ COSBase AFEntry = (COSBase) doc.getDocumentCatalog().getCOSObject().getItem("AF");
+ if ((AFEntry == null)) {
+ COSArray cosArray = new COSArray();
+ cosArray.add(fs);
+ doc.getDocumentCatalog().getCOSObject().setItem("AF", cosArray);
+ } else if (AFEntry instanceof COSArray) {
+ COSArray cosArray = (COSArray) AFEntry;
+ cosArray.add(fs);
+ doc.getDocumentCatalog().getCOSObject().setItem("AF", cosArray);
+ } else if ((AFEntry instanceof COSObject) &&
+ ((COSObject) AFEntry).getObject() instanceof COSArray) {
+ COSArray cosArray = (COSArray) ((COSObject) AFEntry).getObject();
+ cosArray.add(fs);
+ } else {
+ throw new IOException("Unexpected object type for PDFDocument/Catalog/COSDictionary/Item(AF)");
+ }
+ }
+
+ /**
+ * Sets the ZUGFeRD XML data to be attached as a single byte array. This is
+ * useful for use-cases where the XML has already been produced by some external
+ * API or component.
+ *
+ * @param zugferdData XML data to be set as a byte array (XML file in raw form).
+ * @throws IOException (should not happen)
+ */
+ public OXExporterFromA3 setXML(byte[] zugferdData) throws IOException {
+ CustomXMLProvider cus = new CustomXMLProvider();
+ cus.setXML(zugferdData);
+ this.setXMLProvider(cus);
+ prepare();
+ return this;
+ }
+
+
+ /**
+ * 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)
+ *
+ * @param pdfSource source to read a PDF/A1 compliant document from
+ */
+ public OXExporterFromA3 load(InputStream pdfSource) throws IOException {
+ return load(readAllBytes(pdfSource));
+ }
+
+ public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException {
+ return true;
+ }
+
+ private static byte[] readAllBytes(InputStream in) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ IOUtils.copy(in, buffer);
+ return buffer.toByteArray();
+ }
+
+
+ /**
+ * All files are PDF/A-3, setConformance refers to the level conformance.
+ *
+ * PDF/A-3 has three coformance levels, called "A", "U" and "B".
+ *
+ * PDF/A-3-B where B means only visually preservable, U -standard for Mustang-
+ * means visually and unicode preservable and A means full compliance, i.e.
+ * visually, unicode and structurally preservable and tagged PDF, i.e. useful
+ * metainformation for blind people.
+ *
+ * Feel free to pass "A" as new level if you know what you are doing :-)
+ */
+ public OXExporterFromA3 setConformanceLevel(PDFAConformanceLevel newLevel) {
+ conformanceLevel = newLevel;
+ return this;
+ }
+
+
+ public OXExporterFromA3 setCreator(String creator) {
+ this.creator = creator;
+ return this;
+ }
+
+ public OXExporterFromA3 setCreatorTool(String creatorTool) {
+ this.creatorTool = creatorTool;
+ return this;
+ }
+
+ public OXExporterFromA3 setProducer(String producer) {
+ this.producer = producer;
+ return this;
+ }
+
+ protected OXExporterFromA3 setAttachZUGFeRDHeaders(boolean attachHeaders) {
+ this.attachZUGFeRDHeaders = attachHeaders;
+ return this;
+ }
+
+
+ /**
+ * This will add both the RDF-indication which embedded file is Zugferd and the
+ * neccessary PDF/A schema extension description to be able to add this
+ * information to RDF
+ *
+ * @param metadata the PDFbox XMPMetadata object
+ */
+ protected void addXMP(XMPMetadata metadata) {
+
+ if (attachZUGFeRDHeaders) {
+ XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, 1, true, xmlProvider.getProfile(),
+ "urn:factur-x:pdfa:CrossIndustryDocument:1p0#", "fx",
+ "order-x.xml");
+
+ metadata.addSchema(zf);
+ }
+
+ XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(this, metadata, 1, attachZUGFeRDHeaders);
+ pdfaex.setZUGFeRDVersion(1);
+ metadata.addSchema(pdfaex);
+ }
+
+
+ /**
+ * Embeds the Zugferd XML structure in a file named ZUGFeRD-invoice.xml.
+ *
+ * @param trans a IZUGFeRDExportableTransaction that provides the data-model to
+ * populate the XML. This parameter may be null, if so the XML data
+ * should hav ebeen set via
+ * setZUGFeRDXMLData(byte[] zugferdData)
+ * @throws IOException if anything is wrong with already loaded PDF
+ */
+ public IExporter setTransaction(IExportableTransaction trans) throws IOException {
+ this.trans = trans;
+ return prepare();
+ }
+
+ public IExporter prepare() throws IOException {
+ prepareDocument();
+ 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)",
+ "text/xml", xmlProvider.getXML());
+
+ for (FileAttachment attachment : fileAttachments) {
+ PDFAttachGenericFile(doc, attachment.getFilename(), attachment.getRelation(), attachment.getDescription(), attachment.getMimetype(), attachment.getData());
+ }
+
+ return this;
+ }
+
+ /**
+ * Reads the XMPMetadata from the PDDocument, if it exists.
+ * Otherwise creates XMPMetadata.
+ */
+ protected XMPMetadata getXmpMetadata() {
+ PDMetadata meta = doc.getDocumentCatalog().getMetadata();
+ if (meta != null) {
+ try {
+ DomXmpParser xmpParser = new DomXmpParser();
+ return xmpParser.parse(meta.toByteArray());
+ } catch (XmpParsingException | IOException e) {
+ // TODO use logging or handle the error somehow
+ }
+ }
+ return XMPMetadata.createXMPMetadata();
+ }
+
+ protected byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ new XmpSerializer().serialize(xmpMetadata, buffer, true); // see https://github.com/ZUGFeRD/mustangproject/issues/44
+ return buffer.toByteArray();
+ }
+
+ /**
+ * Sets the producer if the overwrite flag is set or the producer is not already set.
+ * Sets the PDFVersion to 1.4 if the field is empty.
+ */
+ protected void writeAdobePDFSchema(XMPMetadata xmp) {
+ AdobePDFSchema pdf = getAdobePDFSchema(xmp);
+ if (overwrite || isEmpty(pdf.getProducer()))
+ pdf.setProducer(producer);
+ }
+
+ /**
+ * Returns the AdobePDFSchema from the XMPMetadata if it exists.
+ * If the overwrite flag is set or no AdobePDFSchema exists in the XMPMetadata, it is created, added and returned.
+ */
+ protected AdobePDFSchema getAdobePDFSchema(XMPMetadata xmp) {
+ AdobePDFSchema pdf = xmp.getAdobePDFSchema();
+ if (pdf != null)
+ if (overwrite)
+ xmp.removeSchema(pdf);
+ else
+ return pdf;
+ return xmp.createAndAddAdobePDFSchema();
+ }
+
+ protected void writePDFAIdentificationSchema(XMPMetadata xmp) {
+ PDFAIdentificationSchema pdfaid = getPDFAIdentificationSchema(xmp);
+ if (overwrite || isEmpty(pdfaid.getConformance())) {
+ try {
+ pdfaid.setConformance(conformanceLevel.getLetter());
+ } catch (BadFieldValueException ex) {
+ // 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);
+ }
+ }
+ pdfaid.setPart(3);
+ }
+
+ protected PDFAIdentificationSchema getPDFAIdentificationSchema(XMPMetadata xmp) {
+ PDFAIdentificationSchema pdfaid = xmp.getPDFIdentificationSchema();
+ if (pdfaid != null)
+ if (overwrite)
+ xmp.removeSchema(pdfaid);
+ else
+ return pdfaid;
+ return xmp.createAndAddPFAIdentificationSchema();
+ }
+
+ protected void writeDublinCoreSchema(XMPMetadata xmp) {
+ DublinCoreSchema dc = getDublinCoreSchema(xmp);
+ if (dc.getFormat() == null)
+ dc.setFormat("application/pdf");
+ if ((overwrite || dc.getCreators() == null || dc.getCreators().isEmpty()) && creator != null)
+ dc.addCreator(creator);
+ if ((overwrite || dc.getDates() == null || dc.getDates().isEmpty()) && creator != null)
+ dc.addDate(Calendar.getInstance());
+
+ ArrayProperty titleProperty = dc.getTitleProperty();
+ if (titleProperty != null) {
+ if (overwrite && !isEmpty(title)) {
+ dc.removeProperty(titleProperty);
+ dc.setTitle(title);
+ } else if (titleProperty.getElementsAsString().stream().anyMatch("Untitled"::equalsIgnoreCase)) {
+ // remove unfitting ghostscript default
+ dc.removeProperty(titleProperty);
+ }
+ } else if (!isEmpty(title)) {
+ dc.setTitle(title);
+ }
+ }
+
+ protected DublinCoreSchema getDublinCoreSchema(XMPMetadata xmp) {
+ DublinCoreSchema dc = xmp.getDublinCoreSchema();
+ if (dc != null)
+ if (overwrite)
+ xmp.removeSchema(dc);
+ else
+ return dc;
+ return xmp.createAndAddDublinCoreSchema();
+ }
+
+ protected void writeXMLBasicSchema(XMPMetadata xmp) {
+ XMPBasicSchema xsb = getXmpBasicSchema(xmp);
+ if (overwrite || isEmpty(xsb.getCreatorTool()) || "UnknownApplication".equals(xsb.getCreatorTool()))
+ xsb.setCreatorTool(creatorTool);
+ if (overwrite || xsb.getCreateDate() == null)
+ xsb.setCreateDate(GregorianCalendar.getInstance());
+ }
+
+ protected XMPBasicSchema getXmpBasicSchema(XMPMetadata xmp) {
+ XMPBasicSchema xsb = xmp.getXMPBasicSchema();
+ if (xsb != null)
+ if (overwrite)
+ xmp.removeSchema(xsb);
+ else
+ return xsb;
+ return xmp.createAndAddXMPBasicSchema();
+ }
+
+ protected void writeDocumentInformation() {
+ String fullProducer = producer + " (via mustangproject.org " + Version.VERSION + ")";
+ PDDocumentInformation info = doc.getDocumentInformation();
+ if (overwrite || info.getCreationDate() == null)
+ info.setCreationDate(Calendar.getInstance());
+ if (overwrite || info.getModificationDate() == null)
+ info.setModificationDate(Calendar.getInstance());
+ if (overwrite || (isEmpty(info.getAuthor()) && !isEmpty(author)))
+ info.setAuthor(author);
+ if (overwrite || (isEmpty(info.getProducer()) && !isEmpty(fullProducer)))
+ info.setProducer(fullProducer);
+ if (overwrite || (isEmpty(info.getCreator()) && !isEmpty(creator)))
+ info.setCreator(creator);
+ if (overwrite || (isEmpty(info.getTitle()) && !isEmpty(title)))
+ info.setTitle(title);
+ if (overwrite || (isEmpty(info.getSubject()) && !isEmpty(subject)))
+ info.setSubject(subject);
+ }
+
+ /**
+ * Adds an OutputIntent and the sRGB color profile if no OutputIntent exist
+ */
+ protected void addSRGBOutputIntend() {
+ if (!doc.getDocumentCatalog().getOutputIntents().isEmpty()) {
+ return;
+ }
+
+ try {
+ InputStream colorProfile = Thread.currentThread().getContextClassLoader().getResourceAsStream("sRGB.icc");
+ if (colorProfile != null) {
+ PDOutputIntent intent = new PDOutputIntent(doc, colorProfile);
+ intent.setInfo("sRGB IEC61966-2.1");
+ intent.setOutputCondition("sRGB IEC61966-2.1");
+ intent.setOutputConditionIdentifier("sRGB IEC61966-2.1");
+ intent.setRegistryName("http://www.color.org");
+ doc.getDocumentCatalog().addOutputIntent(intent);
+ }
+ } catch (IOException e) {
+ // TODO use logging or handle the error somehow
+ }
+ }
+
+ /**
+ * Adds a MarkInfo element to the PDF if it doesn't already exist and sets it as marked.
+ */
+ protected void setMarked() {
+ PDDocumentCatalog catalog = doc.getDocumentCatalog();
+ if (catalog.getMarkInfo() == null) {
+ catalog.setMarkInfo(new PDMarkInfo(doc.getPages().getCOSObject()));
+ }
+ catalog.getMarkInfo().setMarked(true);
+ }
+
+ /**
+ * Adds a StructureTreeRoot element to the PDF if it doesn't already exist.
+ */
+ protected void addStructureTreeRoot() {
+ if (doc.getDocumentCatalog().getStructureTreeRoot() == null) {
+ doc.getDocumentCatalog().setStructureTreeRoot(new PDStructureTreeRoot());
+ }
+ }
+
+
+ /**
+ * @return if pdf file will be automatically closed after adding ZF
+ */
+ public boolean isAutoCloseDisabled() {
+ return disableAutoClose;
+ }
+
+ /**
+ * @param disableAutoClose prevent PDF file from being closed after adding ZF
+ */
+ public OXExporterFromA3 disableAutoClose(boolean disableAutoClose) {
+ this.disableAutoClose = disableAutoClose;
+ return this;
+ }
+
+ protected void setXMLProvider(IXMLProvider p) {
+ this.xmlProvider = p;
+ if (profile != null) {
+ xmlProvider.setProfile(profile);
+ }
+ }
+
+ @Override
+ public OXExporterFromA3 setZUGFeRDVersion(int version) {
+ OXPullProvider z2p = new OXPullProvider();
+ setXMLProvider(z2p);
+ return this;
+ }
+
+ /**
+ * Utility method inspired by apache commons-lang3 StringUtils.
+ *
+ * @param string the string to test
+ * @return true if the string is null or empty
+ */
+ private boolean isEmpty(String string) {
+ return string == null || string.isEmpty();
+ }
+}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
new file mode 100644
index 00000000..efb6f26b
--- /dev/null
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/OXPullProvider.java
@@ -0,0 +1,585 @@
+/**
+ * *********************************************************************
+ *
+ * Copyright 2018 Jochen Staerk
+ *
+ * Use is subject to license terms.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * **********************************************************************
+ */
+package org.mustangproject.ZUGFeRD;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.XMLWriter;
+import org.mustangproject.FileAttachment;
+import org.mustangproject.XMLTools;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.Base64;
+import java.util.Date;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE;
+import static org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants.CORRECTEDINVOICE;
+import static org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants.CATEGORY_CODES_WITH_EXEMPTION_REASON;
+
+public class OXPullProvider extends ZUGFeRD2PullProvider implements IXMLProvider {
+
+ protected IExportableTransaction trans;
+ protected TransactionCalculator calc;
+ private String paymentTermsDescription;
+ protected Profile profile = Profiles.getByName("EN16931");
+
+
+
+
+
+ @Override
+ public void generateXML(IExportableTransaction trans) {
+ this.trans = trans;
+ this.calc = new TransactionCalculator(trans);
+
+ boolean hasDueDate = false;
+ final SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
+
+ String exemptionReason = "";
+
+ if (trans.getPaymentTermDescription() != null) {
+ paymentTermsDescription = trans.getPaymentTermDescription();
+ }
+
+ if ((paymentTermsDescription == null) && (trans.getDocumentCode() != CORRECTEDINVOICE)/* && (trans.getDocumentCode() != DocumentCodeTypeConstants.CREDITNOTE)*/) {
+ paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());
+
+ }
+
+ String senderReg = "";
+ if (trans.getOwnOrganisationFullPlaintextInfo() != null) {
+ senderReg = "" + "\n" + " \n"
+ + XMLTools.encodeXML(trans.getOwnOrganisationFullPlaintextInfo()) + " \n"
+ + "REG\n" + "\n";
+
+ }
+
+ String rebateAgreement = "";
+ if (trans.rebateAgreementExists()) {
+ rebateAgreement = "\n" + " "
+ + "Es bestehen Rabatt- und Bonusvereinbarungen.\n"
+ + "AAK\n" + "\n";
+ }
+
+ String subjectNote = "";
+ if (trans.getSubjectNote() != null) {
+ subjectNote = "\n" + " "
+ + XMLTools.encodeXML(trans.getSubjectNote()) + "\n"
+ + "\n";
+ }
+
+ String typecode = "380";
+ if (trans.getDocumentCode() != null) {
+ typecode = trans.getDocumentCode();
+ }
+ String notes = "";
+ if (trans.getNotes() != null) {
+ for (final String currentNote : trans.getNotes()) {
+ notes = notes + "" + XMLTools.encodeXML(currentNote) + "";
+
+ }
+ }
+ String xml = "\n"
+
+ +"\n"
+ // + "
+ // xsi:schemaLocation=\"urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100
+ // ../Schema/ZUGFeRD1p0.xsd\""
+ + " xmlns:ram=\"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100\""
+ + " xmlns:udt=\"urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100\""
+ + " xmlns:qdt=\"urn:un:unece:uncefact:data:standard:QualifiedDataType:100\">\n"
+ + " \n"
+ // + "
+ // "+testBooleanStr+"\n"
+ //
+ + " \n"
+ + " " + getProfile().getID() + "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " " + XMLTools.encodeXML(trans.getNumber()) + "\n"
+ // + " RECHNUNG\n"
+ // + " 380\n"
+ + " " + typecode + "\n"
+ + " "
+ + DATE.udtFormat(trans.getIssueDate()) + "\n" // date
+ + notes
+ + subjectNote
+ + rebateAgreement
+ + senderReg
+
+ + " \n"
+ + " \n";
+ int lineID = 0;
+ for (final IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
+ lineID++;
+ if (currentItem.getProduct().getTaxExemptionReason() != null) {
+ exemptionReason = "" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "";
+ }
+ notes = "";
+ if (currentItem.getNotes() != null) {
+ for (final String currentNote : currentItem.getNotes()) {
+ notes = notes + "" + XMLTools.encodeXML(currentNote) + "";
+
+ }
+ }
+ final LineCalculator lc = new LineCalculator(currentItem);
+ xml = xml + " \n" +
+ " \n"
+ + " " + lineID + "\n"
+ + notes
+ + " \n"
+
+ + " \n";
+ // + " 4012345001235\n"
+ if (currentItem.getProduct().getSellerAssignedID() != null) {
+ xml = xml + " "
+ + XMLTools.encodeXML(currentItem.getProduct().getSellerAssignedID()) + "\n";
+ }
+ if (currentItem.getProduct().getBuyerAssignedID() != null) {
+ xml = xml + " "
+ + XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + "\n";
+ }
+ String allowanceChargeStr = "";
+ if (currentItem.getItemAllowances() != null && currentItem.getItemAllowances().length > 0) {
+ for (final IZUGFeRDAllowanceCharge allowance : currentItem.getItemAllowances()) {
+ allowanceChargeStr += getAllowanceChargeStr(allowance, currentItem);
+ }
+ }
+ if (currentItem.getItemCharges() != null && currentItem.getItemCharges().length > 0) {
+ for (final IZUGFeRDAllowanceCharge charge : currentItem.getItemCharges()) {
+ allowanceChargeStr += getAllowanceChargeStr(charge, currentItem);
+
+ }
+ }
+
+
+ xml = xml + " " + XMLTools.encodeXML(currentItem.getProduct().getName()) + "\n"
+ + " " + XMLTools.encodeXML(currentItem.getProduct().getDescription())
+ + "\n"
+ + " \n"
+
+ + " \n";
+ if (currentItem.getReferencedDocuments() != null) {
+ for (IReferencedDocument currentReferencedDocument : currentItem.getReferencedDocuments()) {
+ xml = xml + "\n" +
+ "" + XMLTools.encodeXML(currentReferencedDocument.getIssuerAssignedID()) + "\n" +
+ "" + XMLTools.encodeXML(currentReferencedDocument.getTypeCode()) + "\n" +
+ "" + XMLTools.encodeXML(currentReferencedDocument.getReferenceTypeCode()) + "\n" +
+ "\n";
+
+
+ }
+
+ }
+ if (currentItem.getBuyerOrderReferencedDocumentLineID() != null) {
+ xml = xml + " \n"
+ + " " + XMLTools.encodeXML(currentItem.getBuyerOrderReferencedDocumentLineID()) + "\n"
+ + " \n";
+
+ }
+ xml = xml + " \n"
+ + " " + priceFormat(lc.getPriceGross())
+ + "\n" //currencyID=\"EUR\"
+ + "" + quantityFormat(currentItem.getBasisQuantity()) + "\n"
+ + allowanceChargeStr
+ // + " \n"
+ // + " false\n"
+ // + " 0.6667\n"
+ // + " Rabatt\n"
+ // + " \n"
+ + " \n"
+ + " \n"
+ + " " + priceFormat(lc.getPrice())
+ + "\n" // currencyID=\"EUR\"
+ + " " + quantityFormat(currentItem.getBasisQuantity()) + "\n"
+ + " \n"
+ + " \n"
+
+ + " \n"
+ + " "
+ + quantityFormat(currentItem.getQuantity()) + "\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " VAT\n"
+ + exemptionReason
+ + " " + currentItem.getProduct().getTaxCategoryCode() + "\n"
+
+ + " "
+ + vatFormat(currentItem.getProduct().getVATPercent()) + "\n"
+ + " \n";
+ if ((currentItem.getDetailedDeliveryPeriodFrom() != null) || (currentItem.getDetailedDeliveryPeriodTo() != null)) {
+ xml = xml + "";
+ if (currentItem.getDetailedDeliveryPeriodFrom() != null) {
+ xml = xml + "" + DATE.udtFormat(currentItem.getDetailedDeliveryPeriodFrom()) + "";
+ }
+ if (currentItem.getDetailedDeliveryPeriodTo() != null) {
+ xml = xml + "" + DATE.udtFormat(currentItem.getDetailedDeliveryPeriodTo()) + "";
+ }
+ xml = xml + "";
+
+ }
+
+ xml = xml + " \n"
+ + " " + currencyFormat(lc.getItemTotalNetAmount())
+ + "\n" // currencyID=\"EUR\"
+ + " \n";
+ if (currentItem.getAdditionalReferencedDocumentID() != null) {
+ xml = xml + " " + currentItem.getAdditionalReferencedDocumentID() + "130\n";
+
+ }
+ xml = xml + " \n"
+ + " \n";
+
+ }
+
+ xml = xml + " \n";
+ if (trans.getReferenceNumber() != null) {
+ xml = xml + " " + XMLTools.encodeXML(trans.getReferenceNumber()) + "\n";
+
+ }
+ xml = xml + " \n"
+ + getTradePartyAsXML(trans.getSender(), true, false)
+ + " \n"
+ + " \n";
+ // + " GE2020211\n"
+ // + " 4000001987658\n"
+
+ xml += getTradePartyAsXML(trans.getRecipient(), false, false);
+ xml += " \n";
+
+ if (trans.getSellerOrderReferencedDocumentID() != null) {
+ xml = xml + " \n"
+ + " "
+ + XMLTools.encodeXML(trans.getSellerOrderReferencedDocumentID()) + "\n"
+ + " \n";
+ }
+ if (trans.getBuyerOrderReferencedDocumentID() != null) {
+ xml = xml + " \n"
+ + " "
+ + XMLTools.encodeXML(trans.getBuyerOrderReferencedDocumentID()) + "\n"
+ + " \n";
+ }
+ if (trans.getContractReferencedDocument() != null) {
+ xml = xml + " \n"
+ + " "
+ + XMLTools.encodeXML(trans.getContractReferencedDocument()) + "\n"
+ + " \n";
+ }
+
+ // Additional Documents of XRechnung (Rechnungsbegruendende Unterlagen - BG-24 XRechnung)
+ if (trans.getAdditionalReferencedDocuments() != null) {
+ for (final FileAttachment f : trans.getAdditionalReferencedDocuments()) {
+ final String documentContent = new String(Base64.getEncoder().encodeToString(f.getData()));
+ xml = xml + " \n"
+ + " " + f.getFilename() + "\n"
+ + " 916\n"
+ + " " + f.getDescription() + "\n"
+ + " " + documentContent + "\n"
+ + " \n";
+ }
+ }
+
+ if (trans.getSpecifiedProcuringProjectID() != null) {
+ xml = xml + " \n"
+ + " "
+ + XMLTools.encodeXML(trans.getSpecifiedProcuringProjectID()) + "\n";
+ if(trans.getSpecifiedProcuringProjectName()!= null) {
+ xml += " " + XMLTools.encodeXML(trans.getSpecifiedProcuringProjectName()) + "\n";
+ }
+ xml += " \n";
+ }
+ xml = xml + " \n"
+ + " \n";
+ if (this.trans.getDeliveryAddress() != null) {
+ xml += "" +
+ getTradePartyAsXML(this.trans.getDeliveryAddress(), false, true) +
+ "";
+ }
+
+ xml += " \n"
+ + " ";
+
+ if (trans.getDeliveryDate() != null) {
+ xml += DATE.udtFormat(trans.getDeliveryDate());
+ } else {
+ throw new IllegalStateException("No delivery date provided");
+ }
+ xml += "\n";
+ xml += " \n"
+ /*
+ * + " \n" +
+ * " 20130603\n" +
+ * " 2013-51112\n" +
+ * " \n"
+ */
+ + " \n" + " \n"
+ + " " + XMLTools.encodeXML(trans.getNumber()) + "\n"
+ + " " + trans.getCurrency() + "\n";
+
+ if (trans.getTradeSettlementPayment() != null) {
+ for (final IZUGFeRDTradeSettlementPayment payment : trans.getTradeSettlementPayment()) {
+ if (payment != null) {
+ hasDueDate = true;
+ xml += payment.getSettlementXML();
+ }
+ }
+ }
+ if (trans.getTradeSettlement() != null) {
+ for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
+ if (payment != null) {
+ if (payment instanceof IZUGFeRDTradeSettlementPayment) {
+ hasDueDate = true;
+ }
+ xml += payment.getSettlementXML();
+ }
+ }
+ }
+ if ((trans.getDocumentCode() == CORRECTEDINVOICE)/*||(trans.getDocumentCode() == DocumentCodeTypeConstants.CREDITNOTE)*/) {
+ hasDueDate = false;
+ }
+
+ final Map VATPercentAmountMap = calc.getVATPercentAmountMap();
+ for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
+ final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
+ if (amount != null) {
+ final String amountCategoryCode = amount.getCategoryCode();
+ final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
+ xml += " \n"
+ + " " + currencyFormat(amount.getCalculated())
+ + "\n" //currencyID=\"EUR\"
+ + " VAT\n"
+ + (displayExemptionReason ? exemptionReason : "")
+ + " " + currencyFormat(amount.getBasis()) + "\n" // currencyID=\"EUR\"
+ + " " + amountCategoryCode + "\n"
+ + " "
+ + vatFormat(currentTaxPercent) + "\n" + " \n";
+ }
+ }
+ if ((trans.getDetailedDeliveryPeriodFrom() != null) || (trans.getDetailedDeliveryPeriodTo() != null)) {
+ xml = xml + "";
+ if (trans.getDetailedDeliveryPeriodFrom() != null) {
+ xml = xml + "" + DATE.udtFormat(trans.getDetailedDeliveryPeriodFrom()) + "";
+ }
+ if (trans.getDetailedDeliveryPeriodTo() != null) {
+ xml = xml + "" + DATE.udtFormat(trans.getDetailedDeliveryPeriodTo()) + "";
+ }
+ xml = xml + "";
+
+
+ }
+
+ if ((trans.getZFCharges() != null) && (trans.getZFCharges().length > 0)) {
+
+ for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
+ if (calc.getChargesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
+
+
+ xml = xml + " \n" +
+ " \n" +
+ " true\n" +
+ " \n" +
+ " " + currencyFormat(calc.getChargesForPercent(currentTaxPercent)) + "\n" +
+ " " + XMLTools.encodeXML(calc.getChargeReasonForPercent(currentTaxPercent)) + "\n" +
+ " \n" +
+ " VAT\n" +
+ " " + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "\n" +
+ " " + vatFormat(currentTaxPercent) + "\n" +
+ " \n" +
+ " \n";
+
+ }
+ }
+
+ }
+
+ if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
+ for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
+ if (calc.getAllowancesForPercent(currentTaxPercent).compareTo(BigDecimal.ZERO) != 0) {
+ xml = xml + " \n" +
+ " \n" +
+ " false\n" +
+ " \n" +
+ " " + currencyFormat(calc.getAllowancesForPercent(currentTaxPercent)) + "\n" +
+ " " + XMLTools.encodeXML(calc.getAllowanceReasonForPercent(currentTaxPercent)) + "\n" +
+ " \n" +
+ " VAT\n" +
+ " " + VATPercentAmountMap.get(currentTaxPercent).getCategoryCode() + "\n" +
+ " " + vatFormat(currentTaxPercent) + "\n" +
+ " \n" +
+ " \n";
+ }
+ }
+ }
+
+
+ if ((trans.getPaymentTerms() == null) && ((paymentTermsDescription != null) || (trans.getTradeSettlement() != null) || (hasDueDate))) {
+ xml = xml + "\n";
+
+ if (paymentTermsDescription != null) {
+ xml = xml + "" + paymentTermsDescription + "\n";
+ }
+
+ if (trans.getTradeSettlement() != null) {
+ for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
+ if ((payment != null) && (payment instanceof IZUGFeRDTradeSettlementDebit)) {
+ xml += payment.getPaymentXML();
+ }
+ }
+ }
+
+ if (hasDueDate && (trans.getDueDate() != null)) {
+ xml = xml + " " // $NON-NLS-2$
+ + DATE.udtFormat(trans.getDueDate())
+ + "\n";// 20130704
+
+ }
+ xml = xml + " \n";
+ } else {
+ xml = xml + buildPaymentTermsXml();
+ }
+
+
+ final String allowanceTotalLine = "" + currencyFormat(calc.getAllowancesForPercent(null)) + "";
+
+ final String chargesTotalLine = "" + currencyFormat(calc.getChargesForPercent(null)) + "";
+
+ xml = xml + " \n"
+ + " " + currencyFormat(calc.getTotal()) + "\n"
+ + chargesTotalLine
+ + allowanceTotalLine
+ + " " + currencyFormat(calc.getTaxBasis()) + "\n"
+ // //
+ // currencyID=\"EUR\"
+ + " "
+ + currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "\n"
+ + " " + currencyFormat(calc.getGrandTotal()) + "\n"
+ // //
+ // currencyID=\"EUR\"
+ + " " + currencyFormat(calc.getTotalPrepaid()) + "\n"
+ + " " + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "\n"
+ + " \n";
+ if (trans.getInvoiceReferencedDocumentID() != null) {
+ xml = xml + " \n"
+ + " "
+ + XMLTools.encodeXML(trans.getInvoiceReferencedDocumentID()) + "\n";
+ if(trans.getInvoiceReferencedIssueDate()!= null){
+ xml += ""
+ + DATE.qdtFormat(trans.getInvoiceReferencedIssueDate())
+ + "\n";
+ }
+ xml += " \n";
+ }
+
+ xml = xml + " \n";
+ // + " \n"
+ // + " \n"
+ // + " \n"
+ // + " Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr.
+ // 2013-51112 in Rechnung zu stellen:\n"
+ // + " \n"
+ // + " \n"
+ // + " \n";
+
+ xml = xml + " \n"
+ + "";
+
+ final byte[] zugferdRaw;
+ try {
+ zugferdRaw = xml.getBytes("UTF-8");
+
+ zugferdData = XMLTools.removeBOM(zugferdRaw);
+ } catch (final UnsupportedEncodingException e) {
+ Logger.getLogger(OXPullProvider.class.getName()).log(Level.SEVERE, null, e);
+ }
+ }
+
+
+ @Override
+ public void setProfile(Profile p) {
+ profile = p;
+ }
+
+ private String buildPaymentTermsXml() {
+
+ final IZUGFeRDPaymentTerms paymentTerms = trans.getPaymentTerms();
+ if (paymentTerms == null) {
+ return "";
+ }
+ String paymentTermsXml = "";
+
+ final IZUGFeRDPaymentDiscountTerms discountTerms = paymentTerms.getDiscountTerms();
+ final Date dueDate = paymentTerms.getDueDate();
+ if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null) {
+ throw new IllegalStateException(
+ "if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
+ }
+ paymentTermsXml += "" + paymentTerms.getDescription() + "";
+ if (dueDate != null) {
+ paymentTermsXml += "";
+ paymentTermsXml += DATE.udtFormat(dueDate) ;
+ paymentTermsXml += "";
+ }
+
+ if (discountTerms != null) {
+ paymentTermsXml += "";
+ final String currency = trans.getCurrency();
+ final String basisAmount = currencyFormat(calc.getGrandTotal());
+ paymentTermsXml += "" + basisAmount + "";
+ paymentTermsXml += "" + discountTerms.getCalculationPercentage().toString()
+ + "";
+
+ if (discountTerms.getBaseDate() != null) {
+ final Date baseDate = discountTerms.getBaseDate();
+ paymentTermsXml += "";
+ paymentTermsXml += DATE.udtFormat(baseDate);
+ paymentTermsXml += "";
+
+ paymentTermsXml += ""
+ + discountTerms.getBasePeriodMeasure() + "";
+ }
+
+ paymentTermsXml += "";
+ }
+
+ paymentTermsXml += "";
+ return paymentTermsXml;
+ }
+
+}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java
index a5bd0457..55b221a4 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD1PullProvider.java
@@ -57,19 +57,19 @@ public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider implements IXMLPr
public void setTest() {
}
- private String vatFormat(BigDecimal value) {
+ protected String vatFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 2);
}
- private String currencyFormat(BigDecimal value) {
+ protected String currencyFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 2);
}
- private String priceFormat(BigDecimal value) {
+ protected String priceFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 4);
}
- private String quantityFormat(BigDecimal value) {
+ protected String quantityFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 4);
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
index f4996ff0..6502b65c 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java
@@ -59,19 +59,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
public void setTest() {
}
- private String vatFormat(BigDecimal value) {
+ protected String vatFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 2);
}
- private String currencyFormat(BigDecimal value) {
+ protected String currencyFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 2);
}
- private String priceFormat(BigDecimal value) {
+ protected String priceFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 4);
}
- private String quantityFormat(BigDecimal value) {
+ protected String quantityFormat(BigDecimal value) {
return XMLTools.nDigitFormat(value, 4);
}
diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
index 4113811d..5730d337 100644
--- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
+++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA3.java
@@ -69,7 +69,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
private boolean disableAutoClose;
private boolean fileAttached = false;
private Profile profile = null;
- private boolean documentPrepared = false;
+ protected boolean documentPrepared = false;
/**
* Data (XML invoice) to be added to the ZUGFeRD PDF. It may be externally set,
@@ -108,7 +108,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
@Deprecated
protected String subject;
- private PDDocument doc;
+ protected PDDocument doc;
private HashMap additionalXMLs = new HashMap();
@@ -481,7 +481,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
metadata.addSchema(pdfaex);
}
- private void removeCidSet(PDDocumentCatalog catalog, PDDocument doc) {
+ protected void removeCidSet(PDDocumentCatalog catalog, PDDocument doc) {
// https://github.com/ZUGFeRD/mustangproject/issues/249
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java
index b68e7dfe..4ea4fdaf 100644
--- a/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterEdgeTest.java
@@ -135,8 +135,7 @@ public class MustangReaderWriterEdgeTest extends MustangReaderTestCase {
@Override
public String getPaymentTermDescription() {
- SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
- return "Zahlbar ohne Abzug bis zum " + germanDateFormat.format(getDueDate());
+ return "#SKONTO#TAGE=7#PROZENT=3.00#BASISBETRAG=571.04#\n";
}
@Override
diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/OXTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/OXTest.java
new file mode 100644
index 00000000..f0cd421e
--- /dev/null
+++ b/library/src/test/java/org/mustangproject/ZUGFeRD/OXTest.java
@@ -0,0 +1,299 @@
+
+/** **********************************************************************
+ *
+ * Copyright 2019 Jochen Staerk
+ *
+ * Use is subject to license terms.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ *********************************************************************** */
+package org.mustangproject.ZUGFeRD;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.junit.FixMethodOrder;
+import org.junit.runners.MethodSorters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class OXTest extends MustangReaderTestCase implements IExportableTransaction {
+ final String TARGET_PDF = "./target/testout-OX.pdf";
+
+ protected class EdgeProduct implements IZUGFeRDExportableProduct {
+ private String description, name, unit;
+
+ public EdgeProduct(String description, String name, String unit) {
+ super();
+ this.description = description;
+ this.name = name;
+ this.unit = unit;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getUnit() {
+ return unit;
+ }
+
+ public void setUnit(String unit) {
+ this.unit = unit;
+ }
+
+ @Override
+ public BigDecimal getVATPercent() {
+ return BigDecimal.ZERO;
+ }
+
+ @Override
+ public boolean isIntraCommunitySupply() {
+ return true; // simulate intra community supply
+ }
+ }
+
+ protected class DebitPayment implements IZUGFeRDTradeSettlementDebit {
+
+ @Override
+ public String getIBAN() {
+ return "DE540815";
+ }
+
+ @Override
+ public String getMandate() {
+ return "DE99XX12345";
+ }
+
+ }
+
+ @Override
+ public IZUGFeRDTradeSettlement[] getTradeSettlement() {
+ IZUGFeRDTradeSettlement[] payments = new DebitPayment[1];
+ payments[0] = new DebitPayment();
+ return payments;
+ }
+
+ @Override
+ public Date getDeliveryDate() {
+ return new GregorianCalendar(2017, Calendar.MAY, 7).getTime();
+ }
+
+ @Override
+ public Date getDueDate() {
+ return new GregorianCalendar(2017, Calendar.MAY, 30).getTime();
+ }
+
+ @Override
+ public Date getIssueDate() {
+ return new GregorianCalendar(2017, Calendar.MAY, 9).getTime();
+ }
+
+ @Override
+ public String getNumber() {
+ return "RE-20170509/505";
+ }
+
+ @Override
+ public String getOwnCountry() {
+ return "DE";
+ }
+
+ @Override
+ public String getOwnLocation() {
+ return "Stadthausen";
+ }
+
+ @Override
+ public String getOwnOrganisationName() {
+ return "Bei Spiel GmbH";
+ }
+
+ @Override
+ public String getOwnStreet() {
+ return "Ecke 12";
+ }
+
+ @Override
+ public IZUGFeRDExportableTradeParty getSender() {
+ return new SenderTradeParty();
+ }
+
+ @Override
+ public String getOwnTaxID() {
+ return "22/815/0815/4";
+ }
+
+ @Override
+ public String getOwnVATID() {
+ return "DE136695976";
+ }
+
+ @Override
+ public String getOwnZIP() {
+ return "12345";
+ }
+
+ @Override
+ public IZUGFeRDExportableTradeParty getRecipient() {
+ return new RecipientTradeParty();
+ }
+
+ @Override
+ public IZUGFeRDExportableTradeParty getDeliveryAddress() {
+ return new RecipientTradeParty();
+ }
+
+ @Override
+ public String getOwnOrganisationFullPlaintextInfo() {
+ return null;
+ }
+
+ @Override
+ public String getCurrency() {
+ return "USD";
+ }
+
+ @Override
+ public IZUGFeRDExportableItem[] getZFItems() {
+ Item[] allItems = new Item[3];
+ EdgeProduct designProduct = new EdgeProduct("", "Künstlerische Gestaltung (Stunde): Einer Beispielrechnung",
+ "HUR");
+ EdgeProduct balloonProduct = new EdgeProduct("", "Bestellerweiterung für E&F Umbau", "C62");// test for issue
+ // 103
+ EdgeProduct airProduct = new EdgeProduct("", "Heiße Luft pro Liter", "LTR");
+
+ Item design=new Item(new BigDecimal("160"), new BigDecimal("1"), designProduct);
+ design.setAddReference("1825");
+ allItems[0] = design;
+ allItems[1] = new Item(new BigDecimal("0.79"), new BigDecimal("400"), balloonProduct);
+ allItems[2] = new Item(new BigDecimal("0.10"), new BigDecimal("200"), airProduct);
+ return allItems;
+ }
+
+ @Override
+ public String getPaymentTermDescription() {
+ SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
+ return "Zahlbar ohne Abzug bis zum " + germanDateFormat.format(getDueDate());
+ }
+
+ @Override
+ public IZUGFeRDAllowanceCharge[] getZFAllowances() {
+ return null;
+ }
+
+ @Override
+ public IZUGFeRDAllowanceCharge[] getZFCharges() {
+ return null;
+ }
+
+ @Override
+ public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {
+ return null;
+ }
+
+ @Override
+ public String getReferenceNumber() {
+ return "AB321";
+ }
+
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public OXTest(String testName) {
+ super(testName);
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite() {
+ return new TestSuite(OXTest.class);
+ }
+
+ // //////// TESTS
+ // //////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The exporter test bases on @{code
+ * ./src/test/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf}, adds
+ * metadata, writes to @{code ./target/testout-*} and then imports to check the
+ * values.
+ */
+ public void testOXExport() {
+
+ // the writing part
+
+ try (InputStream SOURCE_PDF = this.getClass()
+ .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf");
+
+ OXExporterFromA1 oe = new OXExporterFromA1().setProducer("My Application")
+ .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(1).ignorePDFAErrors()
+ .load(SOURCE_PDF)) {
+ oe.setTransaction(this);
+ String theXML = new String(oe.getProvider().getXML(), StandardCharsets.UTF_8);
+ assertTrue(theXML.contains("59"));
+ assertTrue(zi.getUTF8().contains(""));
+ assertTrue(zi.getUTF8().contains("DE540815"));
+ assertTrue(zi.getUTF8().contains("DE99XX12345"));
+ assertFalse(zi.getUTF8().contains(""));
+ assertFalse(zi.getUTF8().contains("EUR"));
+ assertTrue(zi.getUTF8().contains("USD"));//currency should be USD, test for #150
+
+ // Reading ZUGFeRD
+ assertEquals("496.00", zi.getAmount());
+ assertEquals(zi.getHolder(), getOwnOrganisationName());
+ assertEquals(zi.getForeignReference(), getNumber());
+ try {
+ assertEquals(zi.getVersion(), 2);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+}