diff --git a/History.md b/History.md index 9d414c22..1ade536c 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,10 @@ +1.4.1 +===== +Now using factory. Export now also possible on OutputStream. +Further dev improvements: no more need to call PDFattachZugferdFile(null); +toecount now has options to combine documents and upgrade xml file from ZF1 to ZF2 +More unit tests, e.g. for custom xml export. + 1.4.0 ===== 2017-05-11 @@ -12,23 +19,30 @@ Thanks to AlexanderSchmidt and yankee42. 1.3.0 ===== 2016-03-15 +Additional checks in the new version facilitate correct usage within your own applications. For example, input data (PDF/A-1) and the sequence of the calls are checked for accuracy. +A community patch (thanks to Alexander Schmidt) improves the internal functioning and removes the last known bugs. 1.2.0 ===== 2015-10-15 +Compliance to GEFEG and ZUGFeRD Checker. 1.1.2 ===== 2015-06-15 +The ZUGFeRD-Metadata now conforms to more validators like the Konik Validation Service. As a new feature it is now also possible to create test invoices. 1.1.1 ===== 2015-03-31 +Maven and custom xml 1.1.0 ===== 2014-12-07 +ZUGFeRD 1.0 1.0.0 ===== -2014-05-22 \ No newline at end of file +2014-05-22 +ZUGFeRD 1.0RC Comfort \ No newline at end of file diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index d42a011f..71cda23c 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -46,6 +46,36 @@ + + maven-assembly-plugin + + + + org.mustangproject.toecount.Toecount + + + + jar-with-dependencies + + + + + maven-compiler-plugin + 2.3.2 + + + + true + org.mustangproject.toecount.Toecount + + + + jar-with-dependencies + + 1.7 + 1.7 + + maven-deploy-plugin 2.8.2 diff --git a/pom.xml b/pom.xml index 872746e8..13c21f30 100644 --- a/pom.xml +++ b/pom.xml @@ -58,9 +58,53 @@ 4.3.1 test + + + com.sanityinc + jargs + 2.0-SNAPSHOT + + + + + maven-assembly-plugin + + + + org.mustangproject.toecount.Toecount + + + + jar-with-dependencies + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + + + true + org.mustangproject.toecount.Toecount + + + + jar-with-dependencies + + + + 1.7 + 1.7 + + + + + maven-deploy-plugin 2.8.2 diff --git a/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java b/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java index 442d4144..b8d82a2d 100644 --- a/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java +++ b/src/main/java/org/mustangproject/ZUGFeRD/XMPSchemaPDFAExtensions.java @@ -1,4 +1,5 @@ package org.mustangproject.ZUGFeRD; + /** * Mustangproject's ZUGFeRD implementation * ZUGFeRD exporter helper class @@ -20,31 +21,35 @@ import org.apache.xmpbox.type.PDFASchemaType; import org.apache.xmpbox.type.StructuredType; import org.apache.xmpbox.type.TextType; - /** - * Additionally to adding a RDF namespace with a indication which file attachment if Zugferd, this namespace has to be described in a PDFA Extension Schema. I - * know there is a PDFAExtensionSchema in the context of PDFBox' XMPBOX but I have been using PDFBox' JempBOX so far because I could not find out how to write - * XMPBOX XMPMetadata to a PDF file. So this is my version of PDFAExtensionSchema for PDFBox' jempbox XMPMetadata + * Additionally to adding a RDF namespace with a indication which file + * attachment if Zugferd, this namespace has to be described in a PDFA Extension + * Schema. I know there is a PDFAExtensionSchema in the context of PDFBox' + * XMPBOX but I have been using PDFBox' JempBOX so far because I could not find + * out how to write XMPBOX XMPMetadata to a PDF file. So this is my version of + * PDFAExtensionSchema for PDFBox' jempbox XMPMetadata * * @author jstaerk */ - @StructuredType(preferedPrefix = "pdfaExtension", namespace = "http://www.aiim.org/pdfa/ns/extension/") - public class XMPSchemaPDFAExtensions extends PDFAExtensionSchema { +public class XMPSchemaPDFAExtensions extends PDFAExtensionSchema { public final String xmlns_pdfaSchema = "http://www.aiim.org/pdfa/ns/schema#"; public final String prefix_pdfaSchema = "pdfaSchema"; public final String xmlns_pdfaProperty = "http://www.aiim.org/pdfa/ns/property#"; public final String prefix_pdfaProperty = "pdfaProperty"; - private DefinedStructuredType addProperty(ArrayProperty parent, String name, String type, String category, String description) { + private DefinedStructuredType addProperty(ArrayProperty parent, String name, String type, String category, + String description) { XMPMetadata metadata = getMetadata(); - DefinedStructuredType li = new DefinedStructuredType(metadata, getNamespace(), getPrefix(), XmpConstants.LIST_NAME); + DefinedStructuredType li = new DefinedStructuredType(metadata, getNamespace(), getPrefix(), + XmpConstants.LIST_NAME); li.setAttribute(new Attribute(getNamespace(), XmpConstants.PARSE_TYPE, XmpConstants.RESOURCE_NAME)); - ChoiceType pdfa2 = new ChoiceType(metadata, xmlns_pdfaProperty, prefix_pdfaProperty, PDFAPropertyType.NAME, name); + ChoiceType pdfa2 = new ChoiceType(metadata, xmlns_pdfaProperty, prefix_pdfaProperty, PDFAPropertyType.NAME, + name); li.addProperty(pdfa2); pdfa2 = new ChoiceType(metadata, xmlns_pdfaProperty, prefix_pdfaProperty, PDFAPropertyType.VALUETYPE, type); @@ -53,7 +58,8 @@ import org.apache.xmpbox.type.TextType; pdfa2 = new ChoiceType(metadata, xmlns_pdfaProperty, prefix_pdfaProperty, PDFAPropertyType.CATEGORY, category); li.addProperty(pdfa2); - pdfa2 = new ChoiceType(metadata, xmlns_pdfaProperty, prefix_pdfaProperty, PDFAPropertyType.DESCRIPTION, description); + pdfa2 = new ChoiceType(metadata, xmlns_pdfaProperty, prefix_pdfaProperty, PDFAPropertyType.DESCRIPTION, + description); li.addProperty(pdfa2); parent.addProperty(li); @@ -64,31 +70,49 @@ import org.apache.xmpbox.type.TextType; public XMPSchemaPDFAExtensions(XMPMetadata metadata) { super(metadata); + attachExtensions(metadata, true); + + } + + public XMPSchemaPDFAExtensions(XMPMetadata metadata, boolean withZF) { + + super(metadata); + attachExtensions(metadata, withZF); + + } + + public void attachExtensions(XMPMetadata metadata, boolean withZF) { addNamespace(xmlns_pdfaSchema, prefix_pdfaSchema); addNamespace(xmlns_pdfaProperty, prefix_pdfaProperty); ArrayProperty newBag = createArrayProperty(SCHEMAS, Cardinality.Bag); - DefinedStructuredType li = new DefinedStructuredType(metadata, getNamespace(), getPrefix(), XmpConstants.LIST_NAME); + DefinedStructuredType li = new DefinedStructuredType(metadata, getNamespace(), getPrefix(), + XmpConstants.LIST_NAME); li.setAttribute(new Attribute(getNamespace(), XmpConstants.PARSE_TYPE, XmpConstants.RESOURCE_NAME)); newBag.addProperty(li); addProperty(newBag); + if (withZF) { + TextType pdfa1 = new TextType(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, PDFASchemaType.SCHEMA, + "ZUGFeRD PDFA Extension Schema"); + li.addProperty(pdfa1); - TextType pdfa1 = new TextType(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, PDFASchemaType.SCHEMA, "ZUGFeRD PDFA Extension Schema"); - li.addProperty(pdfa1); + pdfa1 = new TextType(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, PDFASchemaType.NAMESPACE_URI, + "urn:ferd:pdfa:CrossIndustryDocument:invoice:1p0#"); + li.addProperty(pdfa1); - pdfa1 = new TextType(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, PDFASchemaType.NAMESPACE_URI, "urn:ferd:pdfa:CrossIndustryDocument:invoice:1p0#"); - li.addProperty(pdfa1); + pdfa1 = new TextType(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, PDFASchemaType.PREFIX, "zf"); + li.addProperty(pdfa1); - pdfa1 = new TextType(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, PDFASchemaType.PREFIX, "zf"); - li.addProperty(pdfa1); + ArrayProperty newSeq = new ArrayProperty(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, + PDFASchemaType.PROPERTY, Cardinality.Seq); + li.addProperty(newSeq); - ArrayProperty newSeq = new ArrayProperty(metadata, xmlns_pdfaSchema, prefix_pdfaSchema, 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 conformance level of the embedded ZUGFeRD data"); + 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 conformance level of the embedded ZUGFeRD data"); + } } - } \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1Factory.java b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1Factory.java index e1d6f902..b853e3f2 100644 --- a/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1Factory.java +++ b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporterFromA1Factory.java @@ -26,245 +26,265 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.util.GregorianCalendar; public class ZUGFeRDExporterFromA1Factory { - private boolean ignoreA1Errors = false; - private ZUGFeRDConformanceLevel zugferdConformanceLevel = ZUGFeRDConformanceLevel.EXTENDED; - private PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; - private String producer = "mustangproject"; - private String creator = "mustangproject"; - private boolean attachZugferdHeaders = true; + private boolean ignoreA1Errors = false; + private ZUGFeRDConformanceLevel zugferdConformanceLevel = ZUGFeRDConformanceLevel.EXTENDED; + private PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE; + private String producer = "mustangproject"; + private String creator = "mustangproject"; + private boolean attachZugferdHeaders = true; + private static boolean includeXMPinOptionalXPacket=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 ZUGFeRDExporter loadFromPDFA1(String pdfFilename) throws IOException { + ensurePDFIsValidA1(new FileDataSource(pdfFilename)); + PDDocument doc = PDDocument.load(new File(pdfFilename)); + makePDFA3compliant(doc); + return new ZUGFeRDExporter(doc); + } - /** - * 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 ZUGFeRDExporter loadFromPDFA1(String pdfFilename) throws IOException { - ensurePDFIsValidA1(new FileDataSource(pdfFilename)); - PDDocument doc = PDDocument.load(new File(pdfFilename)); - makePDFA3compliant(doc); - return new ZUGFeRDExporter(doc); - } - - /** - * 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 ZUGFeRDExporter loadFromPDFA1(byte[] pdfBinary) throws IOException { - ensurePDFIsValidA1(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary))); - ZUGFeRDExporter zugFeRDExporter; - PDDocument doc = PDDocument.load(pdfBinary); - makePDFA3compliant(doc); - zugFeRDExporter = new ZUGFeRDExporter(doc); + /** + * 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 ZUGFeRDExporter loadFromPDFA1(byte[] pdfBinary) throws IOException { + ensurePDFIsValidA1(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary))); + ZUGFeRDExporter zugFeRDExporter; + PDDocument doc = PDDocument.load(pdfBinary); + makePDFA3compliant(doc); + zugFeRDExporter = new ZUGFeRDExporter(doc); return zugFeRDExporter; - } + } - /** - * 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 ZUGFeRDExporter loadFromPDFA1(InputStream pdfSource) throws IOException { - return loadFromPDFA1(readAllBytes(pdfSource)); - } + /** + * 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 ZUGFeRDExporter loadFromPDFA1(InputStream pdfSource) throws IOException { + return loadFromPDFA1(readAllBytes(pdfSource)); + } - private void ensurePDFIsValidA1(final DataSource dataSource) throws IOException { - if (!ignoreA1Errors && !isValidA1(dataSource)) { - throw new IOException("File is not a valid PDF/A-1 input file"); - } - } + private void ensurePDFIsValidA1(final DataSource dataSource) throws IOException { + if (!ignoreA1Errors && !isValidA1(dataSource)) { + throw new IOException("File is not a valid PDF/A-1 input file"); + } + } - private static byte[] readAllBytes(InputStream in) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - IOUtils.copy(in, buffer); - return buffer.toByteArray(); - } + private static byte[] readAllBytes(InputStream in) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + IOUtils.copy(in, buffer); + return buffer.toByteArray(); + } - private void makePDFA3compliant(PDDocument doc) throws IOException { - String fullProducer = producer + " (via mustangproject.org "+org.mustangproject.ZUGFeRD.Version.VERSION+")"; + private void makePDFA3compliant(PDDocument doc) throws IOException { + String fullProducer = producer + " (via mustangproject.org " + org.mustangproject.ZUGFeRD.Version.VERSION + ")"; - PDDocumentCatalog cat = doc.getDocumentCatalog(); - PDMetadata metadata = new PDMetadata(doc); - cat.setMetadata(metadata); - XMPMetadata xmp = XMPMetadata.createXMPMetadata(); + PDDocumentCatalog cat = doc.getDocumentCatalog(); + PDMetadata metadata = new PDMetadata(doc); + cat.setMetadata(metadata); + + XMPMetadata xmp = XMPMetadata.createXMPMetadata(); + PDFAIdentificationSchema pdfaid = new PDFAIdentificationSchema(xmp); - PDFAIdentificationSchema pdfaid = new PDFAIdentificationSchema(xmp); + xmp.addSchema(pdfaid); - xmp.addSchema(pdfaid); + DublinCoreSchema dc = xmp.createAndAddDublinCoreSchema(); - DublinCoreSchema dc = xmp.createAndAddDublinCoreSchema(); + dc.addCreator(creator); - dc.addCreator(creator); + XMPBasicSchema xsb = xmp.createAndAddXMPBasicSchema(); - XMPBasicSchema xsb = xmp.createAndAddXMPBasicSchema(); + xsb.setCreatorTool(creator); + xsb.setCreateDate(GregorianCalendar.getInstance()); + // PDDocumentInformation pdi=doc.getDocumentInformation(); + PDDocumentInformation pdi = new PDDocumentInformation(); + pdi.setProducer(fullProducer); + pdi.setAuthor(creator); + doc.setDocumentInformation(pdi); - xsb.setCreatorTool(creator); - xsb.setCreateDate(GregorianCalendar.getInstance()); - // PDDocumentInformation pdi=doc.getDocumentInformation(); - PDDocumentInformation pdi = new PDDocumentInformation(); - pdi.setProducer(fullProducer); - pdi.setAuthor(creator); - doc.setDocumentInformation(pdi); + AdobePDFSchema pdf = xmp.createAndAddAdobePDFSchema(); + pdf.setProducer(fullProducer); - AdobePDFSchema pdf = xmp.createAndAddAdobePDFSchema(); - pdf.setProducer(fullProducer); + /* + * // Mandatory: PDF/A3-a is tagged PDF which has to be expressed using a // + * MarkInfo dictionary (PDF A/3 Standard sec. 6.7.2.2) PDMarkInfo markinfo = new + * PDMarkInfo(); markinfo.setMarked(true); + * doc.getDocumentCatalog().setMarkInfo(markinfo); + */ + /* + * + * To be on the safe side, we use level B without Markinfo because we can not + * guarantee that the user correctly tagged the templates for the PDF. + */ + try { + pdfaid.setConformance(conformanceLevel.getLetter());// $NON-NLS-1$ //$NON-NLS-1$ + } 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); + } - /* - * // Mandatory: PDF/A3-a is tagged PDF which has to be expressed using - * a // MarkInfo dictionary (PDF A/3 Standard sec. 6.7.2.2) PDMarkInfo - * markinfo = new PDMarkInfo(); markinfo.setMarked(true); - * doc.getDocumentCatalog().setMarkInfo(markinfo); - */ - /* - * - * To be on the safe side, we use level B without Markinfo because we - * can not guarantee that the user correctly tagged the templates for - * the PDF. - */ - try { - pdfaid.setConformance(conformanceLevel.getLetter());//$NON-NLS-1$ //$NON-NLS-1$ - } 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); - pdfaid.setPart(3); + if (attachZugferdHeaders) { + addXMP(xmp); /* + * this is the only line where we do something Zugferd-specific, i.e. add PDF + * metadata specifically for Zugferd, not generically for a embedded file + */ - if (attachZugferdHeaders) { - addZugferdXMP(xmp); /* - * this is the only line where we do something - * Zugferd-specific, i.e. add PDF metadata - * specifically for Zugferd, not generically for - * a embedded file - */ + } - } + try { + metadata.importXMPMetadata(serializeXmpMetadata(xmp)); + + } catch (TransformerException e) { + throw new ZUGFeRDExportException("Could not export XmpMetadata", e); + } + } + private static byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException { + XmpSerializer serializer = new XmpSerializer(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - try { - metadata.importXMPMetadata(serializeXmpMetadata(xmp)); - } catch (TransformerException e) { - throw new ZUGFeRDExportException("Could not export XmpMetadata", e); - } - } + String prefix=""; + String suffix=""; + if (includeXMPinOptionalXPacket) { + prefix=""; + suffix=""; + + } + try { + buffer.write(prefix.getBytes("UTF-8")); + serializer.serialize(xmpMetadata, buffer, false); + buffer.write(suffix.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return buffer.toByteArray(); + } - private static byte[] serializeXmpMetadata(XMPMetadata xmpMetadata) throws TransformerException { - XmpSerializer serializer = new XmpSerializer(); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - serializer.serialize(xmpMetadata, buffer, false); - return buffer.toByteArray(); - } + private static boolean isValidA1(DataSource dataSource) throws IOException { + return getA1ParserValidationResult(new PreflightParser(dataSource)); + } - private static boolean isValidA1(DataSource dataSource) throws IOException { - return getA1ParserValidationResult(new PreflightParser(dataSource)); - } + /** + * 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 + */ + private void addXMP(XMPMetadata metadata) { - /** - * 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 - */ - private void addZugferdXMP(XMPMetadata metadata) { + if (attachZugferdHeaders) { + XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, zugferdConformanceLevel); - XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata, zugferdConformanceLevel); + metadata.addSchema(zf); + } - metadata.addSchema(zf); + XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(metadata, attachZugferdHeaders); - XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(metadata); + metadata.addSchema(pdfaex); - metadata.addSchema(pdfaex); + } - } + /** + * Sets the ZUGFeRD conformance level (override). + * + * @param zugferdConformanceLevel + * the new conformance level + */ + public ZUGFeRDExporterFromA1Factory setZugferdConformanceLevel(ZUGFeRDConformanceLevel zugferdConformanceLevel) { + this.zugferdConformanceLevel = zugferdConformanceLevel; + return this; + } - /** - * Sets the ZUGFeRD conformance level (override). - * - * @param zugferdConformanceLevel - * the new conformance level - */ - public ZUGFeRDExporterFromA1Factory setZugferdConformanceLevel(ZUGFeRDConformanceLevel zugferdConformanceLevel) { - this.zugferdConformanceLevel = zugferdConformanceLevel; - return this; - } - - private static boolean getA1ParserValidationResult(PreflightParser parser) throws IOException { + private static boolean getA1ParserValidationResult(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...) + * NonSequentialParser. Some additional controls are present to check a set of + * PDF/A requirements. (Stream length consistency, EOL after some Keyword...) */ - parser.parse(); + parser.parse(); - - try ( PreflightDocument document = parser.getPreflightDocument()) { + 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. + * PreflightDocument (that inherits from PDDocument) This document process the + * end of PDF/A validation. */ - document.validate(); + document.validate(); - // Get validation result - return document.getResult().isValid(); - } catch (ValidationException e) { + // 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 + * 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; - } - } + return false; + } + } - /** - * 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 ZUGFeRDExporterFromA1Factory setConformanceLevel(PDFAConformanceLevel newLevel) { - conformanceLevel = newLevel; - return this; - } + /** + * 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 ZUGFeRDExporterFromA1Factory setConformanceLevel(PDFAConformanceLevel newLevel) { + conformanceLevel = newLevel; + return this; + } - public ZUGFeRDExporterFromA1Factory ignoreA1Errors() { - this.ignoreA1Errors = true; - return this; - } + public ZUGFeRDExporterFromA1Factory ignoreA1Errors() { + this.ignoreA1Errors = true; + return this; + } - public ZUGFeRDExporterFromA1Factory setAttachZugferdHeaders(final boolean attachZugferdHeaders) { - this.attachZugferdHeaders = attachZugferdHeaders; - return this; - } + public ZUGFeRDExporterFromA1Factory setAttachZugferdHeaders(final boolean attachZugferdHeaders) { + this.attachZugferdHeaders = attachZugferdHeaders; + return this; + } - public ZUGFeRDExporterFromA1Factory setCreator(String creator) { - this.creator = creator; - return this; - } + public ZUGFeRDExporterFromA1Factory setCreator(String creator) { + this.creator = creator; + return this; + } - public ZUGFeRDExporterFromA1Factory setProducer(String producer) { - this.producer = producer; - return this; - } + public ZUGFeRDExporterFromA1Factory setProducer(String producer) { + this.producer = producer; + return this; + } } diff --git a/src/main/java/org/mustangproject/toecount/FileChecker.java b/src/main/java/org/mustangproject/toecount/FileChecker.java new file mode 100755 index 00000000..ab0e291a --- /dev/null +++ b/src/main/java/org/mustangproject/toecount/FileChecker.java @@ -0,0 +1,73 @@ +package org.mustangproject.toecount; + +import org.mustangproject.ZUGFeRD.ZUGFeRDImporter; + +public class FileChecker { + String filename; + StatRun thisRun; + boolean isPDF=false; + + public FileChecker(String filename, StatRun statistics) { + this.filename=filename; + thisRun=statistics; + thisRun.incFileCount(); + String extension = ""; + if (!thisRun.shallIgnoreFileExt()) { + int extIndex = filename.lastIndexOf("."); + if (extIndex >= 0) { + extension = filename.substring(extIndex).toLowerCase(); + isPDF=extension.equals(".pdf");// alternative check for PDF: File starts with %PDF- + thisRun.incPDFCount(); + } + + } else { + thisRun.incPDFCount(); + } + } + + public boolean checkForZUGFeRD() { + if ((!isPDF)&&(!thisRun.shallIgnoreFileExt())) { + return false; + } + ZUGFeRDImporter zi=new ZUGFeRDImporter(); + try { + zi.extract(filename); + if (zi.canParse()) { + thisRun.incZUGFeRDCount(); + return true; + } else { + return false; + } + } catch (NullPointerException e) { + // something really rare happened -- corrupted ZF? + /*** + * e.g. Exception in thread "main" java.lang.NullPointerException + at org.mustangproject.ZUGFeRD.ZUGFeRDImporter.extractLowLevel(ZUGFeRDImporter.java:90) + at org.mustangproject.ZUGFeRD.ZUGFeRDImporter.extract(ZUGFeRDImporter.java:64) + at toecount.FileChecker.checkForZUGFeRD(FileChecker.java:33) + at toecount.Toecount.main(Toecount.java:111) + * + */ + // Ignore nevertheless, most likely we're batch processing + return false; + } catch (Exception e2) { + /** + * probably thrown up from + AM org.apache.pdfbox.pdfparser.PDFParser parse, most likely + INFORMATION: Document is encrypted + but also other internal PDF errors possible like + ..Okt 23, 2015 11:17:53 AM org.apache.pdfbox.pdfparser.XrefTrailerResolver setStartxref + */ + return false; + } + } + + public boolean isPDF() { + return isPDF; + } + + public String getOutputLine() { + return thisRun.getOutputLine(); + } + +} diff --git a/src/main/java/org/mustangproject/toecount/FileTraverser.java b/src/main/java/org/mustangproject/toecount/FileTraverser.java new file mode 100755 index 00000000..5bae999a --- /dev/null +++ b/src/main/java/org/mustangproject/toecount/FileTraverser.java @@ -0,0 +1,56 @@ +package org.mustangproject.toecount; + +import static java.nio.file.FileVisitResult.CONTINUE; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +public class FileTraverser extends SimpleFileVisitor { + + + private StatRun thisRun; + public FileTraverser(StatRun statistics) { + this.thisRun=statistics; + } + + /*** + * check each file + */ + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { + if (attr.isSymbolicLink()) { + // Not yet handled + } else if (attr.isRegularFile()) { + String filename = file.toString(); + FileChecker fc = new FileChecker(filename, thisRun); + fc.checkForZUGFeRD(); + System.out.print(fc.getOutputLine()); + + } + return CONTINUE; + } + + /*** + * for each directory + */ + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + // System.out.format("Directory: %s%n", dir); + thisRun.incDirCount(); + return CONTINUE; + } + + + /*** + * show errors like file permission stacktraces + */ + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + System.err.println(exc); + return CONTINUE; + } + +} diff --git a/src/main/java/org/mustangproject/toecount/StatRun.java b/src/main/java/org/mustangproject/toecount/StatRun.java new file mode 100755 index 00000000..04769479 --- /dev/null +++ b/src/main/java/org/mustangproject/toecount/StatRun.java @@ -0,0 +1,69 @@ +package org.mustangproject.toecount; + +public class StatRun { + private int pdfCount = 0; + private int horseCount = 0; + private int fileCount = 0; + private int dirCount = 0; + private boolean checkFileExt=true; + + public void ignoreFileExtension() { + checkFileExt=false; + } + + public boolean shallIgnoreFileExt() { + return !checkFileExt; + } + public void incFileCount(){ + fileCount++; + } + + public void incPDFCount(){ + pdfCount++; + } + + public void incZUGFeRDCount(){ + horseCount++; + } + + public void incDirCount(){ + dirCount++; + } + + public int getFileCount(){ + return fileCount; + } + + public int getPDFCount(){ + return pdfCount; + } + + public int getZUGFeRDCount(){ + return horseCount; + } + + public int getDirCount(){ + return dirCount; + } + + /*** + * returns final statistics + * @return + */ + public String getSummaryLine() { + + return "\r\n===================================================================\r\n"+String.format( + "Files:\t%d\tDirs:\t%d\tPDF:\t%d\tZUGFeRD:\t%d\r\n", + getFileCount(), getDirCount(), getPDFCount(), getZUGFeRDCount()); + + } + /*** + * show that something is happening + * @return String + */ + public String getOutputLine() { + return "."; + } + + +} diff --git a/src/main/java/org/mustangproject/toecount/Toecount.java b/src/main/java/org/mustangproject/toecount/Toecount.java new file mode 100755 index 00000000..902e03b9 --- /dev/null +++ b/src/main/java/org/mustangproject/toecount/Toecount.java @@ -0,0 +1,306 @@ +package org.mustangproject.toecount; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Pattern; + +import javax.xml.bind.JAXBException; +import javax.xml.transform.TransformerException; + +import org.mustangproject.ZUGFeRD.ZUGFeRDExporter; +import org.mustangproject.ZUGFeRD.ZUGFeRDExporterFromA1Factory; +import org.mustangproject.ZUGFeRD.ZUGFeRDImporter; + +import com.sanityinc.jargs.CmdLineParser; +import com.sanityinc.jargs.CmdLineParser.Option; + +public class Toecount { + // build with: /opt/local/bin/mvn clean compile assembly:single + private static void printUsage() { + System.err.println(getUsage()); + } + + private static String getUsage() { + return "Usage: Toecount [-d,--directory] [-l,--listfromstdin] [-i,--ignorefileextension] | [-c,--combine] | [-e,--extract] | [-u,--upgrade] | [-a,--a3only] | [-h,--help]\r\n"; + } + + private static void printHelp() { + System.out.println("Mustangproject.org's Toecount 0.2.0 \r\n" + + "A Apache Public License command line tool for statistics on PDF invoices with\r\n" + + "ZUGFeRD Metadata (http://www.zugferd.org)\r\n" + "\r\n" + getUsage() + "Count operations" + + "\t--directory= count ZUGFeRD files in directory to be scanned\r\n" + + "\t\tIf it is a directory, it will recurse.\r\n" + + "\t--listfromstdin=count ZUGFeRD files from a list of linefeed separated files on runtime.\r\n" + + "\t\tIt will start once a blank line has been entered.\r\n" + + "\t--ignorefileextension=if PDF files are counted check *.* instead of *.pdf files" + + "Merge operations" + + "\t--combine= combine ZUGFeRD-invoice.xml and invoice.pdf to invoice.zugferd.pdf\r\n" + + "\t--extract= extract invoice.zugferd.pdf to ZUGFeRD-invoice.xml\r\n" + + "\t--upgrade= upgrade ZUGFeRD-invoice.pdf to ZUGFeRD-2-invoice.xml\r\n" + + "\t--a3only= upgrade from PDF/A1 to A3 only \r\n" + + + ); + } + + // /opt/local/bin/mvn clean compile assembly:single + public static void main(String[] args) { + CmdLineParser parser = new CmdLineParser(); + Option dirnameOption = parser.addStringOption('d', "directory"); + Option filesFromStdInOption = parser.addBooleanOption('l', "listfromstdin"); + Option ignoreFileExtOption = parser.addBooleanOption('i', "ignorefileextension"); + Option combineOption = parser.addBooleanOption('c', "combine"); + Option extractOption = parser.addBooleanOption('e', "extract"); + Option helpOption = parser.addBooleanOption('h', "help"); + Option upgradeOption = parser.addBooleanOption('u', "upgrade"); + Option a3onlyOption = parser.addBooleanOption('a', "a3only"); + + if (args.length == 0) { + printUsage(); + System.exit(2); + + } + try { + parser.parse(args); + } catch (CmdLineParser.OptionException e) { + System.err.println(e.getMessage()); + printUsage(); + System.exit(2); + } + + String directoryName = parser.getOptionValue(dirnameOption); + + Boolean filesFromStdIn = parser.getOptionValue(filesFromStdInOption, Boolean.FALSE); + + Boolean combineRequested = parser.getOptionValue(combineOption, Boolean.FALSE); + + Boolean extractRequested = parser.getOptionValue(extractOption, Boolean.FALSE); + + Boolean helpRequested = parser.getOptionValue(helpOption, Boolean.FALSE); + + Boolean upgradeRequested = parser.getOptionValue(upgradeOption, Boolean.FALSE); + + Boolean ignoreFileExt = parser.getOptionValue(ignoreFileExtOption, Boolean.FALSE); + + Boolean a3only = parser.getOptionValue(a3onlyOption, Boolean.FALSE); + + if (helpRequested) { + printHelp(); + } + + if (((directoryName != null) && (directoryName.length() > 0)) || filesFromStdIn.booleanValue()) { + + StatRun sr = new StatRun(); + if (ignoreFileExt) { + sr.ignoreFileExtension(); + } + if (directoryName != null) { + Path startingDir = Paths.get(directoryName); + + if (Files.isRegularFile(startingDir)) { + String filename = startingDir.toString(); + FileChecker fc = new FileChecker(filename, sr); + + fc.checkForZUGFeRD(); + System.out.print(fc.getOutputLine()); + + } else if (Files.isDirectory(startingDir)) { + FileTraverser pf = new FileTraverser(sr); + try { + Files.walkFileTree(startingDir, pf); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + } + + if (filesFromStdIn) { + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + String s; + try { + while ((s = in.readLine()) != null && s.length() != 0) { + FileChecker fc = new FileChecker(s, sr); + + fc.checkForZUGFeRD(); + System.out.print(fc.getOutputLine()); + + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + System.out.println(sr.getSummaryLine()); + } else if (combineRequested) { + /* + * ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory() + * .setProducer("toecount") .setCreator(System.getProperty("user.name")) + * .loadFromPDFA1("invoice.pdf"); + */ + try { + ZUGFeRDExporter ze = new ZUGFeRDExporter(); + ze.PDFmakeA3compliant("./invoice.pdf", "Toecount", System.getProperty("user.name"), true); + ze.setZUGFeRDXMLData(Files.readAllBytes(Paths.get("./ZUGFeRD-invoice.xml"))); + ze.PDFattachZugferdFile(null); + ze.export("invoice.ZUGFeRD.pdf"); + } catch (IOException e) { + e.printStackTrace(); + // } catch (JAXBException e) { + // e.printStackTrace(); + } catch (TransformerException e) { + e.printStackTrace(); + } + + System.out.println("Written to invoice.ZUGFeRD.pdf"); + } else if (extractRequested) { + ZUGFeRDImporter zi = new ZUGFeRDImporter(); + zi.extract("invoice.zugferd.pdf"); + try { + Files.write(Paths.get("./ZUGFeRD-invoice.xml"), zi.getRawXML()); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println("Written to ZUGFeRD-invoice.xml"); + + } else if (a3only) { + /* + * ZUGFeRDExporter ze= new ZUGFeRDExporterFromA1Factory() + * .setProducer("toecount") .setCreator(System.getProperty("user.name")) + * .loadFromPDFA1("invoice.pdf"); + */ + try { + ZUGFeRDExporter ze = new ZUGFeRDExporterFromA1Factory().setAttachZugferdHeaders(false).loadFromPDFA1("./invoice.pdf"); + + ze.export("invoice.a3.pdf"); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println("Written to invoice.a3.pdf"); + } else if (upgradeRequested) { + try { + String xml = new String(Files.readAllBytes(Paths.get("./ZUGFeRD-invoice.xml")), StandardCharsets.UTF_8); + // todo: attributes may also be in single quotes, this one hardcodedly expects + // double ones + xml = xml.replace("\"urn:ferd:CrossIndustryDocument:invoice:1p0", + "\"urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:13"); + xml = xml.replace("urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12", + "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:20"); + xml = xml.replace("urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15", + "urn:un:unece:uncefact:data:standard:UnqualifiedDataType:20"); + xml = xml.replace("rsm:CrossIndustryDocument", "rsm:CrossIndustryInvoice"); + xml = xml.replace("rsm:SpecifiedExchangedDocumentContext", "rsm:CIExchangedDocumentContext"); + xml = xml.replace("rsm:HeaderExchangedDocument", "rsm:CIIHExchangedDocument"); + xml = xml.replace("SpecifiedSupplyChainTradeTransaction", "CIIHSupplyChainTradeTransaction"); + xml = xml.replace("ram:GuidelineSpecifiedDocumentContextParameter", + "ram:GuidelineSpecifiedCIDocumentContextParameter"); + xml = xml.replace("ram:IncludedNote", "ram:IncludedCINote"); + xml = xml.replace("ram:ApplicableSupplyChainTradeAgreement", + "ram:ApplicableCIIHSupplyChainTradeAgreement"); + xml = xml.replace("ram:SellerTradeParty", "ram:SellerCITradeParty"); + xml = xml.replace("ram:BuyerTradeParty", "ram:BuyerCITradeParty"); + xml = xml.replace("ram:ApplicableSupplyChainTradeDelivery", + "ram:ApplicableCIIHSupplyChainTradeDelivery"); + xml = xml.replace("ram:ApplicableSupplyChainTradeSettlement", + "ram:ApplicableCIIHSupplyChainTradeSettlement"); + xml = xml.replace("ram:IncludedSupplyChainTradeLineItem", "ram:IncludedCIILSupplyChainTradeLineItem"); + xml = xml.replace("ram:AssociatedDocumentLineDocument", "ram:AssociatedCIILDocumentLineDocument"); + xml = xml.replace("ram:SpecifiedSupplyChainTradeDelivery", "ram:SpecifiedCIILSupplyChainTradeDelivery"); + xml = xml.replace("ram:SpecifiedSupplyChainTradeSettlement", + "ram:SpecifiedCIILSupplyChainTradeSettlement"); + xml = xml.replace("ram:SpecifiedTradeProduct", "ram:SpecifiedCITradeProduct"); + xml = xml.replace("ram:ActualDeliverySupplyChainEvent", "ram:ActualDeliveryCISupplyChainEvent"); + xml = xml.replace("ram:SpecifiedTradeSettlementPaymentMeans", + "ram:SpecifiedCITradeSettlementPaymentMeans"); + xml = xml.replace("ram:PayeePartyCreditorFinancialAccount", "ram:PayeePartyCICreditorFinancialAccount"); + xml = xml.replace("ram:PayeeSpecifiedCreditorFinancialInstitution", + "ram:PayeeSpecifiedCICreditorFinancialInstitution"); + xml = xml.replace("ram:ApplicableTradeTax", "ram:ApplicableCITradeTax"); + xml = xml.replace("ram:ApplicablePercent", "ram:RateApplicablePercent"); + xml = xml.replace("ram:PostalTradeAddress", "ram:PostalCITradeAddress"); + + xml = xml.replace("ram:ApplicableTradePaymentDiscountTerms", + "ram:ApplicableCITradePaymentDiscountTerms"); + xml = xml.replace("ram:ApplicableProductCharacteristic", "ram:ApplicableCIProductCharacteristic"); + xml = xml.replace("ram:ShipToTradeParty", "ram:ShipToCITradeParty"); + xml = xml.replace("ram:ShipFromTradeParty", "ram:ShipFromCITradeParty"); + xml = xml.replace("ram:ReceivableSpecifiedTradeAccountingAccount", + "ram:ReceivableSpecifiedCITradeAccountingAccount"); + xml = xml.replace("ram:ContractReferencedDocument", "ram:ContractReferencedCIReferencedDocument"); + // "ram:SpecifiedTradeAccountingAccount ram:SalesSpecifiedTradeAccountingAccount + // oder ReceivablesSpecifiedTradeAccountingAccount oder + // PurchaseSpecifiedTradeAccountingAccount + xml = xml.replace("ram:AdditionalReferencedDocument", "ram:AdditionalReferencedCIReferencedDocument"); + xml = xml.replace("ram:TelephoneUniversalCommunication", "ram:TelephoneCIUniversalCommunication"); + xml = xml.replace("ram:EmailURIUniversalCommunication", "ram:EmailURICIUniversalCommunication"); + xml = xml.replace("ram:AdditionalReferencedDocument", "ram:AdditionalReferencedCIReferencedDocument"); + xml = xml.replace("ram:IncludedReferencedProduct", "ram:IncludedReferencedProduct"); + + xml = xml.replace("ram:DefinedTradeContact", "ram:DefinedCITradeContact"); + xml = xml.replace("ram:BillingSpecifiedPeriod", "ram:BillingCISpecifiedPeriod"); + xml = xml.replace("ram:BuyerOrderReferencedDocument", "ram:BuyerOrderReferencedCIReferencedDocument"); + xml = xml.replace("ram:DeliveryNoteReferencedDocument", + "ram:DeliveryNoteReferencedCIReferencedDocument"); + xml = xml.replace("ram:SpecifiedTradeAllowanceCharge", "ram:SpecifiedCITradeAllowanceCharge"); + xml = xml.replace("ram:SpecifiedLogisticsServiceCharge", "ram:SpecifiedCILogisticsServiceCharge"); + xml = xml.replace("ram:AppliedTradeAllowanceCharge", "ram:AppliedCITradeAllowanceCharge"); + xml = xml.replace("ram:InvoiceeTradeParty", "ram:InvoiceeCITradeParty"); + xml = xml.replace("ram:CategoryTradeTax", "ram:CategoryCITradeTax"); + xml = xml.replace("ram:SpecifiedTaxRegistration", "ram:SpecifiedCITaxRegistration"); + xml = xml.replace("ram:PostalTradeAddress", "ram:PostalCITradeAddress"); + xml = xml.replace("ram:SpecifiedTradePaymentTerms", "ram:SpecifiedCITradePaymentTerms"); + xml = xml.replace("ram:SpecifiedSupplyChainTradeAgreement", + "ram:SpecifiedCIILSupplyChainTradeAgreement"); + xml = xml.replace("ram:GrossPriceProductTradePrice", "ram:GrossPriceProductCITradePrice"); + xml = xml.replace("ram:NetPriceProductTradePrice", "ram:NetPriceProductCITradePrice"); + xml = xml.replaceAll("(?s)\\", ""); + // remove manually for the time being: + // xml=xml.replaceAll("ram:TestIndicator>(.*?)/ram:TestIndicator>", ""); + // one ram:SpecifiedCIILTradeSettlementMonetarySummation will have to be + // ram:SpecifiedCIIHTradeSettlementMonetarySummation afterwards + + String summationClose = ""; + int posFirstSummation = xml.indexOf(summationClose) + summationClose.length(); + // if ram:SpecifiedTradeSettlementMonetarySummation were not found indexOf would + // return -1, therefore, + // to check if it + if (posFirstSummation > summationClose.length()) { + String xmlAfterFirstSummation = xml.substring(posFirstSummation); + String xmlBeforeIncludingFirstSummation = xml.substring(0, posFirstSummation); + // replace only once the header + + xmlBeforeIncludingFirstSummation = xmlBeforeIncludingFirstSummation.replace( + "ram:SpecifiedTradeSettlementMonetarySummation", + "ram:SpecifiedCIIHTradeSettlementMonetarySummation"); + // reconstruct the document now with a replaced first + // ram:SpecifiedTradeSettlementMonetarySummation to SpecifiedCIIH... + xml = xmlBeforeIncludingFirstSummation + xmlAfterFirstSummation; + + } + // replace the rest of the ram:SpecifiedTradeSettlementMonetarySummation with + // the line value SpecifiedCIIL... + xml = xml.replace("ram:SpecifiedTradeSettlementMonetarySummation", + "ram:SpecifiedCIILTradeSettlementMonetarySummation"); + + // the rest of the ram:SpecifiedTradeSettlementMonetarySummation should be in + // ram:ApplicableSupplyChainTradeSettlement + // xml=xml.replaceAll(Pattern.quote("ram:SpecifiedTradeSettlementMonetarySummation"), + // "ram:SpecifiedCIILTradeSettlementMonetarySummation"); + + Files.write(Paths.get("./ZUGFeRD-2-invoice.xml"), xml.getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println("Written to ZUGFeRD-2-invoice.xml"); + + } + } +} diff --git a/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java b/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java index bf7b7d44..72e661c7 100644 --- a/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java +++ b/src/test/java/org/mustangproject/ZUGFeRD/MustangReaderWriterTest.java @@ -1,5 +1,6 @@ package org.mustangproject.ZUGFeRD; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; @@ -409,12 +410,16 @@ public class MustangReaderWriterTest extends TestCase implements IZUGFeRDExporta this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505blanko.pdf"); ZUGFeRDExporter ze = new ZUGFeRDExporterFromA1Factory() - .setProducer("My Application") - .setCreator(System.getProperty("user.name")) .loadFromPDFA1(SOURCE_PDF)) { ze.PDFattachZugferdFile(this); ze.export(TARGET_PDF); + + ByteArrayOutputStream baos=new ByteArrayOutputStream(); + ze.export(baos); + String pdfContent=baos.toString("UTF-8"); + assertFalse(pdfContent.indexOf("(via mustangproject.org")==-1); + } // now check the contents (like MustangReaderTest) @@ -445,5 +450,36 @@ public class MustangReaderWriterTest extends TestCase implements IZUGFeRDExporta assertEquals(ref, getNumber()); } + + /** + * @Test(expected = IndexOutOfBoundsException.class) + * @throws Exception + */ + public void testExceptionOnPDF14() throws Exception + { + + final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505new.pdf"; + + boolean exceptionThrown=false; + // the writing part + try(InputStream SOURCE_PDF = + this.getClass().getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDF14.pdf"); + + ZUGFeRDExporter ze = new ZUGFeRDExporterFromA1Factory() + .loadFromPDFA1(SOURCE_PDF)) { + + ze.PDFattachZugferdFile(this); + ze.export(TARGET_PDF); + + ByteArrayOutputStream baos=new ByteArrayOutputStream(); + ze.export(baos); + } catch (IOException ex) { + // should throw a java.io.IOException: File is not a valid PDF/A-1 input file + exceptionThrown=true; + } + assertTrue(exceptionThrown); + + } + } diff --git a/src/test/resources/MustangGnuaccountingBeispielRE-20170509_505PDF14.pdf b/src/test/resources/MustangGnuaccountingBeispielRE-20170509_505PDF14.pdf new file mode 100644 index 00000000..afd897a1 Binary files /dev/null and b/src/test/resources/MustangGnuaccountingBeispielRE-20170509_505PDF14.pdf differ diff --git a/src/test/resources/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf b/src/test/resources/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf new file mode 100644 index 00000000..71e23ea7 Binary files /dev/null and b/src/test/resources/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf differ