diff --git a/mustang/pom.xml b/mustang/pom.xml
index f88270c9..c44e810c 100644
--- a/mustang/pom.xml
+++ b/mustang/pom.xml
@@ -4,7 +4,7 @@
org.mustangproject.ZUGFeRD
mustang
- 1.1.0
+ 1.1.1alpha
diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.java
index 588797d6..c8fa3d20 100644
--- a/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.java
+++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.java
@@ -1,501 +1,522 @@
-package org.mustangproject.ZUGFeRD;
-/**
- * Mustangproject's ZUGFeRD implementation
- * ZUGFeRD exporter
- * Licensed under the APLv2
- * @date 2014-07-12
- * @version 1.1
- * @author jstaerk
- * */
-
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.text.SimpleDateFormat;
-import java.util.Collections;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-
-import javax.xml.transform.TransformerException;
-
-import org.apache.jempbox.xmp.XMPMetadata;
-import org.apache.jempbox.xmp.XMPSchemaBasic;
-import org.apache.jempbox.xmp.XMPSchemaDublinCore;
-import org.apache.jempbox.xmp.XMPSchemaPDF;
-import org.apache.jempbox.xmp.pdfa.XMPSchemaPDFAId;
-import org.apache.pdfbox.cos.COSArray;
-import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
-import org.apache.pdfbox.pdmodel.PDDocumentInformation;
-import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
-import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
-import org.apache.pdfbox.pdmodel.common.PDMetadata;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
-
-
-public class ZUGFeRDExporter {
- /***
- * You will need Apache PDFBox. To use the ZUGFeRD exporter,
- implement IZUGFeRDExportableTransaction in yourTransaction
- (which will require you to implement Product, Item and Contact)
- then call
- doc = PDDocument.load(PDFfilename);
- // automatically add Zugferd to all outgoing invoices
- ZUGFeRDExporter ze = new ZUGFeRDExporter();
- ze.PDFmakeA3compliant(doc, "Your application name",
- System.getProperty("user.name"), true);
- ze.PDFattachZugferdFile(doc, yourTransaction);
-
- doc.save(PDFfilename);
-
- * @author jstaerk
- *
- */
-
-
-
-
-
- //// MAIN CLASS
-
- private String conformanceLevel="U";
- private String versionStr="1.0";
-
- private String currencyFormat(BigDecimal value, char decimalDelimiter) {
- /*
- * I needed 123,45, locale independent.I tried
- * NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is
- * locale specific.I also tried DecimalFormat df = new DecimalFormat(
- * "0,00" ); df.setDecimalSeparatorAlwaysShown(true);
- * df.setGroupingUsed(false); DecimalFormatSymbols symbols = new
- * DecimalFormatSymbols(); symbols.setDecimalSeparator(',');
- * symbols.setGroupingSeparator(' ');
- * df.setDecimalFormatSymbols(symbols);
- *
- * but that would not switch off grouping. Although I liked very much
- * the (incomplete) "BNF diagram" in
- * http://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html
- * in the end I decided to calculate myself and take eur+sparator+cents
- *
- * This function will cut off, i.e. floor() subcent values Tests:
- * System.err.println(utils.currencyFormat(new BigDecimal(0),
- * ".")+"\n"+utils.currencyFormat(new BigDecimal("-1.10"),
- * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.1"),
- * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.01"),
- * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3489"),
- * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3419"),
- * ",")+"\n"+utils.currencyFormat(new BigDecimal("12"), ","));
- *
- * results 0.00 -1,10 -1,10 -1,01 20000123,34 20000123,34 12,00
- */
- value=value.setScale( 2, BigDecimal.ROUND_HALF_UP ); // first, round so that e.g. 1.189999999999999946709294817992486059665679931640625 becomes 1.19
- long totalCent = value.multiply(new BigDecimal(100)).intValue(); //now get the cents
- long eurOnly = value.longValue();
- long centOnly = Math.abs(totalCent % 100);
- StringBuffer res = new StringBuffer();
- res.append(eurOnly);
- res.append(decimalDelimiter);
- if (centOnly < 10) {
- res.append('0');
- }
- res.append(centOnly);
- return res.toString();
- }
-
- /**
- 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 void setConformanceLevel(String newLevel) {
- conformanceLevel=newLevel;
- }
-
-
- /**
- * 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)
- * */
- public PDDocumentCatalog PDFmakeA3compliant(PDDocument doc, String producer, String creator,
- boolean attachZugferdHeaders) throws IOException,
- TransformerException {
- String fullProducer=producer+"(via mustangproject.org "+versionStr+")";
- PDDocumentCatalog cat = doc.getDocumentCatalog();
- PDMetadata metadata = new PDMetadata(doc);
- cat.setMetadata(metadata);
- // we're using the jempbox org.apache.jempbox.xmp.XMPMetadata version,
- // not the xmpbox one
- XMPMetadata xmp = new XMPMetadata();
-
- XMPSchemaPDFAId pdfaid = new XMPSchemaPDFAId(xmp);
- pdfaid.setAbout(""); //$NON-NLS-1$
- xmp.addSchema(pdfaid);
-
- XMPSchemaDublinCore dc = xmp.addDublinCoreSchema();
- dc.addCreator(creator);
- dc.setAbout(""); //$NON-NLS-1$
-
- XMPSchemaBasic xsb = xmp.addBasicSchema();
- xsb.setAbout(""); //$NON-NLS-1$
-
- xsb.setCreatorTool(creator);
- xsb.setCreateDate(GregorianCalendar.getInstance());
- // PDDocumentInformation pdi=doc.getDocumentInformation();
- PDDocumentInformation pdi = new PDDocumentInformation();
- pdi.setProducer(fullProducer);
- pdi.setAuthor(creator);
- doc.setDocumentInformation(pdi);
-
- XMPSchemaPDF pdf = xmp.addPDFSchema();
- pdf.setProducer(fullProducer);
- pdf.setAbout(""); //$NON-NLS-1$
-
- /*
- // 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.
-
- * */
- pdfaid.setConformance(conformanceLevel);//$NON-NLS-1$ //$NON-NLS-1$
-
- pdfaid.setPart(3);
-
- 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
- */
- }
-
- metadata.importXMPMetadata(xmp);
- return cat;
- }
-
- private String getZugferdXMLForTransaction(IZUGFeRDExportableTransaction trans) {
- SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$
- String xml= "\n" //$NON-NLS-1$
-
- + "\n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " false\n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " urn:ferd:invoice:rc:comfort\n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+trans.getNumber()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " RECHNUNG\n" //$NON-NLS-1$
- + " 380\n" //$NON-NLS-1$
- + " "+zugferdDateFormat.format(trans.getIssueDate())+"\n" //date format was 20130605 //$NON-NLS-1$ //$NON-NLS-2$
-// + " \n"
-// + " \n"
-// + "Rechnung gemäß Bestellung Nr. 2013-471331 vom 01.03.2013.\n"
-// + "\n"
-// + " \n"
-// + " \n"
-// + " \n"
-// + " \n"
-// + "Es bestehen Rabatt- und Bonusvereinbarungen.\n"
-// + " \n"
-// + " AAK\n"
-// + " \n"
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
-// + " AB-312\n"
- + " \n" //$NON-NLS-1$
-// + " 4000001123452\n"
- + " "+trans.getOwnOrganisationName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n"
- + " "+trans.getOwnZIP()+"\n"
- + " "+trans.getOwnStreet()+"\n"
- + " "+trans.getOwnLocation()+"\n"
- + " "+trans.getOwnCountry()+"\n"
- + " \n"
- + " \n" //$NON-NLS-1$
- + " "+trans.getOwnTaxID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+trans.getOwnVATID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
-// + " GE2020211\n"
-// + " 4000001987658\n"
- + " "+trans.getRecipient().getName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
-// + " \n"
-// + " xxx\n"
-// + " \n"
- + " \n" //$NON-NLS-1$
- + " "+trans.getRecipient().getZIP()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " "+trans.getRecipient().getStreet()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " "+trans.getRecipient().getLocation()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " "+trans.getRecipient().getCountry()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+trans.getRecipient().getVATID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
-// + " \n"
-// + " 20130301\n"
-// + " 2013-471331\n"
-// + " \n"
- + " \n" //$NON-NLS-1$
- + " \n"
- + " \n"
- + " "+zugferdDateFormat.format(trans.getDeliveryDate())+"\n"
- + " \n"
- /*
- + " \n"
- + " 20130603\n"
- + " 2013-51112\n"
- + " \n" */
- + " \n"
- + " \n" //$NON-NLS-1$
- + " "+trans.getNumber()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " EUR\n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " 42\n" //$NON-NLS-1$
- + " Überweisung\n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+trans.getOwnIBAN()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+trans.getOwnBIC()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " "+trans.getOwnBankName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n"; //$NON-NLS-1$
-
-
- /*
- HashMap VATPercentAmountMap=trans.getVATPercentAmountMap();
- for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
- BigDecimal amount = VATPercentAmountMap.get(currentTaxPercent);
- if (amount != null) {
- xml += " \n" //$NON-NLS-1$
- + " "+currencyFormat(amount, '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " VAT\n" //$NON-NLS-1$
-// + " 129.37\n"
- + " S\n" //$NON-NLS-1$
- + " "+currentTaxPercent+"\n" //$NON-NLS-1$
- + " \n"; //$NON-NLS-1$
-
-
-
- }
- }*/
-/* xml+= "
- + " \n"
- + " false\n"
- + " 10\n"
- + " 1.00\n"
- + " Sondernachlass\n"
- + " \n"
- + " VAT\n"
- + " S\n"
- + " 19\n"
- + " \n"
- + " \n"
- + " \n"
- + " false\n"
- + " 137.30\n"
- + " 13.73\n"
- + " Sondernachlass\n"
- + " \n"
- + " VAT\n"
- + " S\n"
- + " 7\n"
- + " \n"
- + " \n"
- + " \n"
- + " Versandkosten\n"
- + " 5.80\n"
- + " \n"
- + " VAT\n"
- + " S\n"
- + " 7\n"
- + " \n"
- + " \n"*/
-
- xml=xml+ " \n" //$NON-NLS-1$
-// + " Zahlbar innerhalb 30 Tagen netto bis 04.07.2013, 3% Skonto innerhalb 10 Tagen bis 15.06.2013\n"
- + " "+zugferdDateFormat.format(trans.getDueDate())+"\n"//20130704 //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+currencyFormat(trans.getTotal(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
-// + " 5.80\n"
-// + " 14.73\n"
- + " "+currencyFormat(trans.getTotal(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " "+currencyFormat(trans.getTotalGross().subtract(trans.getTotal()), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " "+currencyFormat(trans.getTotalGross(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
-// + " 0.00\n"
- + " "+currencyFormat(trans.getTotalGross(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n"; //$NON-NLS-1$
-// + " \n"
-// + " \n"
-// + " \n"
-// + " Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. 2013-51112 in Rechnung zu stellen:\n"
-// + " \n"
-// + " \n"
-// + " \n";
-
-
- int lineID=0;
- for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
- lineID++;
- xml=xml+ " \n"+ //$NON-NLS-1$
- " \n" //$NON-NLS-1$
- + " "+lineID+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
-
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+currencyFormat(currentItem.getPriceGross(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " 1\n" //$NON-NLS-1$ //$NON-NLS-2$
-// + " \n"
-// + " false\n"
-// + " 0.6667\n"
-// + " Rabatt\n"
-// + " \n"
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+currencyFormat(currentItem.getPrice(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " 1\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
-
- + " \n" //$NON-NLS-1$
- + " "+currentItem.getQuantity()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " VAT\n" //$NON-NLS-1$
- + " S\n" //$NON-NLS-1$
- + " "+currentItem.getProduct().getVATPercent()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " "+currencyFormat(currentItem.getTotalGross(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
- + " \n" //$NON-NLS-1$
-// + " 4012345001235\n"
-// + " KR3M\n"
-// + " 55T01\n"
- + " "+currentItem.getProduct().getName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " "+currentItem.getProduct().getDescription()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
- + " \n" //$NON-NLS-1$
- + " \n"; //$NON-NLS-1$
-
-
-
- }
-
-
- xml=xml + " \n" //$NON-NLS-1$
- + ""; //$NON-NLS-1$
- return xml;
- }
-
- /**
- * embed the Zugferd XML structure in a file named ZUGFeRD-invoice.xml
- * */
- public void PDFattachZugferdFile(PDDocument doc, IZUGFeRDExportableTransaction trans) throws IOException {
-
- // embedded files are stored in a named tree
- PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode();
-
- String filename="ZUGFeRD-invoice.xml"; //$NON-NLS-1$
- // first create the file specification, which holds the embedded file
- PDComplexFileSpecification fs = new PDComplexFileSpecification();
- fs.setFile(filename);
-
- COSDictionary dict = fs.getCOSDictionary();
- // Relation "Source" for linking with eg. catalog
- dict.setName("AFRelationship", "Alternative"); // as defined in Zugferd standard //$NON-NLS-1$ //$NON-NLS-2$
-
- dict.setString("UF", filename); //$NON-NLS-1$
-
- // create a dummy file stream, this would probably normally be a
- // FileInputStream
-
- byte[] zugferdRaw = getZugferdXMLForTransaction(trans).getBytes("UTF-8"); //$NON-NLS-1$
-
- byte[] zugferdData;
-
-
-
- if ((zugferdRaw[0]==(byte)0xEF)&&(zugferdRaw[1]==(byte)0xBB)&&(zugferdRaw[2]==(byte)0xBF)) {
- // I don't like BOMs, lets remove it
- zugferdData=new byte[zugferdRaw.length-3];
- System.arraycopy(zugferdRaw,3,zugferdData,0,zugferdRaw.length-3);
- } else {
- zugferdData=zugferdRaw;
- }
-
- ByteArrayInputStream fakeFile = new ByteArrayInputStream(zugferdData);
- PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
- // now lets some of the optional parameters
- ef.setSubtype("text/xml");// as defined in Zugferd standard //$NON-NLS-1$
- ef.setSize(zugferdData.length);
- ef.setCreationDate(new GregorianCalendar());
-
- ef.setModDate(GregorianCalendar.getInstance());
-
- fs.setEmbeddedFile(ef);
-
- // now add the entry to the embedded file tree and set in the document.
- efTree.setNames(Collections.singletonMap(filename, fs));
- PDDocumentNameDictionary names = new PDDocumentNameDictionary(
- doc.getDocumentCatalog());
- names.setEmbeddedFiles(efTree);
- doc.getDocumentCatalog().setNames(names);
- // AF entry (Array) in catalog with the FileSpec
- COSArray cosArray = new COSArray();
- cosArray.add(fs);
- doc.getDocumentCatalog().getCOSDictionary().setItem("AF", cosArray); //$NON-NLS-1$
-
- }
-
-/***
- * 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) {
-
- XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata);
- zf.setAbout(""); //$NON-NLS-1$
- metadata.addSchema(zf);
-
- XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(metadata);
- pdfaex.setAbout(""); //$NON-NLS-1$
- metadata.addSchema(pdfaex);
-
- }
-
-}
+package org.mustangproject.ZUGFeRD;
+/**
+ * Mustangproject's ZUGFeRD implementation
+ * ZUGFeRD exporter
+ * Licensed under the APLv2
+ * @date 2014-07-12
+ * @version 1.1
+ * @author jstaerk
+ * */
+
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+
+import javax.xml.transform.TransformerException;
+
+import org.apache.jempbox.xmp.XMPMetadata;
+import org.apache.jempbox.xmp.XMPSchemaBasic;
+import org.apache.jempbox.xmp.XMPSchemaDublinCore;
+import org.apache.jempbox.xmp.XMPSchemaPDF;
+import org.apache.jempbox.xmp.pdfa.XMPSchemaPDFAId;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
+import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
+import org.apache.pdfbox.pdmodel.common.PDMetadata;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
+
+
+public class ZUGFeRDExporter {
+ /***
+ * You will need Apache PDFBox. To use the ZUGFeRD exporter,
+ implement IZUGFeRDExportableTransaction in yourTransaction
+ (which will require you to implement Product, Item and Contact)
+ then call
+ doc = PDDocument.load(PDFfilename);
+ // automatically add Zugferd to all outgoing invoices
+ ZUGFeRDExporter ze = new ZUGFeRDExporter();
+ ze.PDFmakeA3compliant(doc, "Your application name",
+ System.getProperty("user.name"), true);
+ ze.PDFattachZugferdFile(doc, yourTransaction);
+
+ doc.save(PDFfilename);
+
+ * @author jstaerk
+ *
+ */
+
+
+
+
+
+ //// MAIN CLASS
+
+ private String conformanceLevel="U";
+ private String versionStr="1.0";
+
+ /**
+ * 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.
+ */
+ byte[] zugferdData = null;
+
+ private String currencyFormat(BigDecimal value, char decimalDelimiter) {
+ /*
+ * I needed 123,45, locale independent.I tried
+ * NumberFormat.getCurrencyInstance().format( 12345.6789 ); but that is
+ * locale specific.I also tried DecimalFormat df = new DecimalFormat(
+ * "0,00" ); df.setDecimalSeparatorAlwaysShown(true);
+ * df.setGroupingUsed(false); DecimalFormatSymbols symbols = new
+ * DecimalFormatSymbols(); symbols.setDecimalSeparator(',');
+ * symbols.setGroupingSeparator(' ');
+ * df.setDecimalFormatSymbols(symbols);
+ *
+ * but that would not switch off grouping. Although I liked very much
+ * the (incomplete) "BNF diagram" in
+ * http://docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html
+ * in the end I decided to calculate myself and take eur+sparator+cents
+ *
+ * This function will cut off, i.e. floor() subcent values Tests:
+ * System.err.println(utils.currencyFormat(new BigDecimal(0),
+ * ".")+"\n"+utils.currencyFormat(new BigDecimal("-1.10"),
+ * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.1"),
+ * ",")+"\n"+utils.currencyFormat(new BigDecimal("-1.01"),
+ * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3489"),
+ * ",")+"\n"+utils.currencyFormat(new BigDecimal("20000123.3419"),
+ * ",")+"\n"+utils.currencyFormat(new BigDecimal("12"), ","));
+ *
+ * results 0.00 -1,10 -1,10 -1,01 20000123,34 20000123,34 12,00
+ */
+ value=value.setScale( 2, BigDecimal.ROUND_HALF_UP ); // first, round so that e.g. 1.189999999999999946709294817992486059665679931640625 becomes 1.19
+ long totalCent = value.multiply(new BigDecimal(100)).intValue(); //now get the cents
+ long eurOnly = value.longValue();
+ long centOnly = Math.abs(totalCent % 100);
+ StringBuffer res = new StringBuffer();
+ res.append(eurOnly);
+ res.append(decimalDelimiter);
+ if (centOnly < 10) {
+ res.append('0');
+ }
+ res.append(centOnly);
+ return res.toString();
+ }
+
+ /**
+ 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 void setConformanceLevel(String newLevel) {
+ conformanceLevel=newLevel;
+ }
+
+
+ /**
+ * 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)
+ * */
+ public PDDocumentCatalog PDFmakeA3compliant(PDDocument doc, String producer, String creator,
+ boolean attachZugferdHeaders) throws IOException,
+ TransformerException {
+ String fullProducer=producer + " (via mustangproject.org " + versionStr + ")";
+ PDDocumentCatalog cat = doc.getDocumentCatalog();
+ PDMetadata metadata = new PDMetadata(doc);
+ cat.setMetadata(metadata);
+ // we're using the jempbox org.apache.jempbox.xmp.XMPMetadata version,
+ // not the xmpbox one
+ XMPMetadata xmp = new XMPMetadata();
+
+ XMPSchemaPDFAId pdfaid = new XMPSchemaPDFAId(xmp);
+ pdfaid.setAbout(""); //$NON-NLS-1$
+ xmp.addSchema(pdfaid);
+
+ XMPSchemaDublinCore dc = xmp.addDublinCoreSchema();
+ dc.addCreator(creator);
+ dc.setAbout(""); //$NON-NLS-1$
+
+ XMPSchemaBasic xsb = xmp.addBasicSchema();
+ xsb.setAbout(""); //$NON-NLS-1$
+
+ xsb.setCreatorTool(creator);
+ xsb.setCreateDate(GregorianCalendar.getInstance());
+ // PDDocumentInformation pdi=doc.getDocumentInformation();
+ PDDocumentInformation pdi = new PDDocumentInformation();
+ pdi.setProducer(fullProducer);
+ pdi.setAuthor(creator);
+ doc.setDocumentInformation(pdi);
+
+ XMPSchemaPDF pdf = xmp.addPDFSchema();
+ pdf.setProducer(fullProducer);
+ pdf.setAbout(""); //$NON-NLS-1$
+
+ /*
+ // 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.
+
+ * */
+ pdfaid.setConformance(conformanceLevel);//$NON-NLS-1$ //$NON-NLS-1$
+
+ pdfaid.setPart(3);
+
+ 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
+ */
+ }
+
+ metadata.importXMPMetadata(xmp);
+ return cat;
+ }
+
+ private String getZugferdXMLForTransaction(IZUGFeRDExportableTransaction trans) {
+ SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$
+ String xml= "\n" //$NON-NLS-1$
+
+ + "\n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " false\n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " urn:ferd:invoice:rc:comfort\n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getNumber()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " RECHNUNG\n" //$NON-NLS-1$
+ + " 380\n" //$NON-NLS-1$
+ + " "+zugferdDateFormat.format(trans.getIssueDate())+"\n" //date format was 20130605 //$NON-NLS-1$ //$NON-NLS-2$
+// + " \n"
+// + " \n"
+// + "Rechnung gemäß Bestellung Nr. 2013-471331 vom 01.03.2013.\n"
+// + "\n"
+// + " \n"
+// + " \n"
+// + " \n"
+// + " \n"
+// + "Es bestehen Rabatt- und Bonusvereinbarungen.\n"
+// + " \n"
+// + " AAK\n"
+// + " \n"
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+// + " AB-312\n"
+ + " \n" //$NON-NLS-1$
+// + " 4000001123452\n"
+ + " "+trans.getOwnOrganisationName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n"
+ + " "+trans.getOwnZIP()+"\n"
+ + " "+trans.getOwnStreet()+"\n"
+ + " "+trans.getOwnLocation()+"\n"
+ + " "+trans.getOwnCountry()+"\n"
+ + " \n"
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getOwnTaxID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getOwnVATID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+// + " GE2020211\n"
+// + " 4000001987658\n"
+ + " "+trans.getRecipient().getName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+// + " \n"
+// + " xxx\n"
+// + " \n"
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getRecipient().getZIP()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " "+trans.getRecipient().getStreet()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " "+trans.getRecipient().getLocation()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " "+trans.getRecipient().getCountry()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getRecipient().getVATID()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+// + " \n"
+// + " 20130301\n"
+// + " 2013-471331\n"
+// + " \n"
+ + " \n" //$NON-NLS-1$
+ + " \n"
+ + " \n"
+ + " "+zugferdDateFormat.format(trans.getDeliveryDate())+"\n"
+ + " \n"
+ /*
+ + " \n"
+ + " 20130603\n"
+ + " 2013-51112\n"
+ + " \n" */
+ + " \n"
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getNumber()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " EUR\n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " 42\n" //$NON-NLS-1$
+ + " Überweisung\n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getOwnIBAN()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+trans.getOwnBIC()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " "+trans.getOwnBankName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n"; //$NON-NLS-1$
+
+
+ /*
+ HashMap VATPercentAmountMap=trans.getVATPercentAmountMap();
+ for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
+ BigDecimal amount = VATPercentAmountMap.get(currentTaxPercent);
+ if (amount != null) {
+ xml += " \n" //$NON-NLS-1$
+ + " "+currencyFormat(amount, '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " VAT\n" //$NON-NLS-1$
+// + " 129.37\n"
+ + " S\n" //$NON-NLS-1$
+ + " "+currentTaxPercent+"\n" //$NON-NLS-1$
+ + " \n"; //$NON-NLS-1$
+
+
+
+ }
+ }*/
+/* xml+= "
+ + " \n"
+ + " false\n"
+ + " 10\n"
+ + " 1.00\n"
+ + " Sondernachlass\n"
+ + " \n"
+ + " VAT\n"
+ + " S\n"
+ + " 19\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " false\n"
+ + " 137.30\n"
+ + " 13.73\n"
+ + " Sondernachlass\n"
+ + " \n"
+ + " VAT\n"
+ + " S\n"
+ + " 7\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " Versandkosten\n"
+ + " 5.80\n"
+ + " \n"
+ + " VAT\n"
+ + " S\n"
+ + " 7\n"
+ + " \n"
+ + " \n"*/
+
+ xml=xml+ " \n" //$NON-NLS-1$
+// + " Zahlbar innerhalb 30 Tagen netto bis 04.07.2013, 3% Skonto innerhalb 10 Tagen bis 15.06.2013\n"
+ + " "+zugferdDateFormat.format(trans.getDueDate())+"\n"//20130704 //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+currencyFormat(trans.getTotal(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+// + " 5.80\n"
+// + " 14.73\n"
+ + " "+currencyFormat(trans.getTotal(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " "+currencyFormat(trans.getTotalGross().subtract(trans.getTotal()), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " "+currencyFormat(trans.getTotalGross(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+// + " 0.00\n"
+ + " "+currencyFormat(trans.getTotalGross(), '.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n"; //$NON-NLS-1$
+// + " \n"
+// + " \n"
+// + " \n"
+// + " Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr. 2013-51112 in Rechnung zu stellen:\n"
+// + " \n"
+// + " \n"
+// + " \n";
+
+
+ int lineID=0;
+ for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
+ lineID++;
+ xml=xml+ " \n"+ //$NON-NLS-1$
+ " \n" //$NON-NLS-1$
+ + " "+lineID+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+currencyFormat(currentItem.getPriceGross(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " 1\n" //$NON-NLS-1$ //$NON-NLS-2$
+// + " \n"
+// + " false\n"
+// + " 0.6667\n"
+// + " Rabatt\n"
+// + " \n"
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+currencyFormat(currentItem.getPrice(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " 1\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+
+ + " \n" //$NON-NLS-1$
+ + " "+currentItem.getQuantity()+"\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " VAT\n" //$NON-NLS-1$
+ + " S\n" //$NON-NLS-1$
+ + " "+currentItem.getProduct().getVATPercent()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " "+currencyFormat(currentItem.getTotalGross(),'.')+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+ + " \n" //$NON-NLS-1$
+// + " 4012345001235\n"
+// + " KR3M\n"
+// + " 55T01\n"
+ + " "+currentItem.getProduct().getName()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " "+currentItem.getProduct().getDescription()+"\n" //$NON-NLS-1$ //$NON-NLS-2$
+ + " \n" //$NON-NLS-1$
+ + " \n"; //$NON-NLS-1$
+
+
+
+ }
+
+
+ xml=xml + " \n" //$NON-NLS-1$
+ + ""; //$NON-NLS-1$
+ return xml;
+ }
+
+ /**
+ * Embeds the Zugferd XML structure in a file named ZUGFeRD-invoice.xml.
+ *
+ * @param doc PDDocument to attach an XML invoice to
+ * @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)
+ */
+ public void PDFattachZugferdFile(PDDocument doc, IZUGFeRDExportableTransaction trans) throws IOException {
+
+ // embedded files are stored in a named tree
+ PDEmbeddedFilesNameTreeNode efTree = new PDEmbeddedFilesNameTreeNode();
+
+ String filename="ZUGFeRD-invoice.xml"; //$NON-NLS-1$
+ // first create the file specification, which holds the embedded file
+ PDComplexFileSpecification fs = new PDComplexFileSpecification();
+ fs.setFile(filename);
+
+ COSDictionary dict = fs.getCOSDictionary();
+ // Relation "Source" for linking with eg. catalog
+ dict.setName("AFRelationship", "Alternative"); // as defined in Zugferd standard //$NON-NLS-1$ //$NON-NLS-2$
+
+ dict.setString("UF", filename); //$NON-NLS-1$
+
+ if (zugferdData == null) // XML ZUGFeRD data not set externally, needs to be built
+ {
+ // create a dummy file stream, this would probably normally be a
+ // FileInputStream
+
+ byte[] zugferdRaw = getZugferdXMLForTransaction(trans).getBytes("UTF-8"); //$NON-NLS-1$
+
+ if ((zugferdRaw[0]==(byte)0xEF)&&(zugferdRaw[1]==(byte)0xBB)&&(zugferdRaw[2]==(byte)0xBF)) {
+ // I don't like BOMs, lets remove it
+ zugferdData=new byte[zugferdRaw.length-3];
+ System.arraycopy(zugferdRaw,3,zugferdData,0,zugferdRaw.length-3);
+ } else {
+ zugferdData=zugferdRaw;
+ }
+ }
+
+ ByteArrayInputStream fakeFile = new ByteArrayInputStream(zugferdData);
+ PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
+ // now lets some of the optional parameters
+ ef.setSubtype("text/xml");// as defined in Zugferd standard //$NON-NLS-1$
+ ef.setSize(zugferdData.length);
+ ef.setCreationDate(new GregorianCalendar());
+
+ ef.setModDate(GregorianCalendar.getInstance());
+
+ fs.setEmbeddedFile(ef);
+
+ // now add the entry to the embedded file tree and set in the document.
+ efTree.setNames(Collections.singletonMap(filename, fs));
+ PDDocumentNameDictionary names = new PDDocumentNameDictionary(
+ doc.getDocumentCatalog());
+ names.setEmbeddedFiles(efTree);
+ doc.getDocumentCatalog().setNames(names);
+ // AF entry (Array) in catalog with the FileSpec
+ COSArray cosArray = new COSArray();
+ cosArray.add(fs);
+ doc.getDocumentCatalog().getCOSDictionary().setItem("AF", cosArray); //$NON-NLS-1$
+
+ }
+
+ /**
+ * 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).
+ */
+ public void setZUGFeRDXMLData(byte[] zugferdData)
+ {
+ this.zugferdData = zugferdData;
+ }
+
+/***
+ * 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) {
+
+ XMPSchemaZugferd zf = new XMPSchemaZugferd(metadata);
+ zf.setAbout(""); //$NON-NLS-1$
+ metadata.addSchema(zf);
+
+ XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(metadata);
+ pdfaex.setAbout(""); //$NON-NLS-1$
+ metadata.addSchema(pdfaex);
+
+ }
+
+}
diff --git a/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java
index 1c8f7dc6..10c32cc1 100644
--- a/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java
+++ b/mustang/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.java
@@ -1,358 +1,390 @@
-package org.mustangproject.ZUGFeRD;
-/**
- * Mustangproject's ZUGFeRD implementation
- * ZUGFeRD importer
- * Licensed under the APLv2
- * @date 2014-07-07
- * @version 1.1.0
- * @author jstaerk
- * */
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Map;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
-import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
-import org.apache.pdfbox.pdmodel.common.COSObjectable;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
-import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-//root.setNamespace(Namespace.getNamespace("http://www.energystar.gov/manageBldgs/req"));
-
-public class ZUGFeRDImporter {
- /*
- call extract(importFilename).
- containsMeta() will return if ZUGFeRD data has been found,
- afterwards you can call getBIC(), getIBAN() etc.
-
-*/
-
- /**@var if metadata has been found */
- private boolean containsMeta=false;
- /**@var the reference (i.e. invoice number) of the sender */
- private String foreignReference;
- private String BIC;
- private String IBAN;
- private String holder;
- private String amount;
- private String meta;
- private String bankName;
- private boolean amountFound;
-
-
-
-
- public void extract(String pdfFilename) {
- PDDocument doc = null;
- try {
- doc = PDDocument.load(pdfFilename);
-// PDDocumentInformation info = doc.getDocumentInformation();
- PDDocumentNameDictionary names = new PDDocumentNameDictionary(
- doc.getDocumentCatalog());
- PDEmbeddedFilesNameTreeNode etn;
- etn = names.getEmbeddedFiles();
- if (etn==null) {
- doc.close();
- return;
- }
- Map efMap = etn.getNames();
- // String filePath = "/tmp/";
- for (String filename : efMap.keySet()) {
- /**
- * currently (in the release candidate of version 1) only one
- * attached file with the name ZUGFeRD-invoice.xml is allowed
- * */
- if (filename.equals("ZUGFeRD-invoice.xml")) { //$NON-NLS-1$
- containsMeta = true;
-
- PDComplexFileSpecification fileSpec = (PDComplexFileSpecification) efMap
- .get(filename);
- PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
- // String embeddedFilename = filePath + filename;
- // File file = new File(filePath + filename);
- // System.out.println("Writing " + embeddedFilename);
- // ByteArrayOutputStream fileBytes=new
- // ByteArrayOutputStream();
- // FileOutputStream fos = new FileOutputStream(file);
-
- setMeta(new String(embeddedFile.getByteArray()));
- // fos.write(embeddedFile.getByteArray());
- // fos.close();
- }
- }
-
- } catch (IOException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- finally {
- try {
- if(doc!=null) {
- doc.close();
- }
- } catch (IOException e) {}
- }
-
- }
-
- public void parse() {
- DocumentBuilderFactory factory = null;
- DocumentBuilder builder = null;
- Document document = null;
-
- factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true); //otherwise we can not act namespace independend, i.e. use document.getElementsByTagNameNS("*",...
- try {
- builder = factory.newDocumentBuilder();
- } catch (ParserConfigurationException ex3) {
- // TODO Auto-generated catch block
- ex3.printStackTrace();
- }
-
- try {
- InputStream bais = new ByteArrayInputStream(meta.getBytes());
- document = builder.parse(bais);
- } catch (SAXException ex1) {
- ex1.printStackTrace();
- } catch (IOException ex2) {
- ex2.printStackTrace();
- }
- NodeList ndList ;
-
- // rootNode = document.getDocumentElement();
- // ApplicableSupplyChainTradeSettlement
- ndList = document.getDocumentElement()
- .getElementsByTagNameNS("*","PaymentReference"); //$NON-NLS-1$
-
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
- Node booking = ndList.item(bookingIndex);
- // if there is a attribute in the tag number:value
-
- setForeignReference(booking.getTextContent());
-
- }
-/*
- ndList = document
- .getElementsByTagName("GermanBankleitzahlID"); //$NON-NLS-1$
-
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
- Node booking = ndList.item(bookingIndex);
- // if there is a attribute in the tag number:value
- setBIC(booking.getTextContent());
-
- }
-
- ndList = document.getElementsByTagName("ProprietaryID"); //$NON-NLS-1$
-
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
- Node booking = ndList.item(bookingIndex);
- // if there is a attribute in the tag number:value
- setIBAN(booking.getTextContent());
-
- }
-
- DE1234
-
-
- DE5656565
- Commerzbank
-
-
-*/
- ndList = document.getElementsByTagNameNS("*","PayeePartyCreditorFinancialAccount"); //$NON-NLS-1$
-
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
-
- Node booking = ndList.item(bookingIndex);
- // there are many "name" elements, so get the one below
- // SellerTradeParty
- NodeList bookingDetails = booking.getChildNodes();
-
-
- for (int detailIndex = 0; detailIndex < bookingDetails
- .getLength(); detailIndex++) {
- Node detail = bookingDetails.item(detailIndex);
- if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("IBANID"))) { //$NON-NLS-1$
- setIBAN(detail.getTextContent());
-
- }
- }
-
- }
-
- ndList = document.getElementsByTagNameNS("*","PayeeSpecifiedCreditorFinancialInstitution"); //$NON-NLS-1$
-
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
- Node booking = ndList.item(bookingIndex);
- // there are many "name" elements, so get the one below
- // SellerTradeParty
- NodeList bookingDetails = booking.getChildNodes();
- for (int detailIndex = 0; detailIndex < bookingDetails
- .getLength(); detailIndex++) {
- Node detail = bookingDetails.item(detailIndex);
- if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("BICID"))) { //$NON-NLS-1$
- setBIC(detail.getTextContent());
- }
- if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("Name"))) { //$NON-NLS-1$
- setBankName(detail.getTextContent());
- }
- }
-
- }
-
- //
- ndList = document.getElementsByTagNameNS("*","SellerTradeParty"); //$NON-NLS-1$
-
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
- Node booking = ndList.item(bookingIndex);
- // there are many "name" elements, so get the one below
- // SellerTradeParty
- NodeList bookingDetails = booking.getChildNodes();
- for (int detailIndex = 0; detailIndex < bookingDetails
- .getLength(); detailIndex++) {
- Node detail = bookingDetails.item(detailIndex);
- if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("Name"))) { //$NON-NLS-1$
- setHolder(detail.getTextContent());
- }
- }
-
- }
-
- ndList = document.getElementsByTagNameNS("*","DuePayableAmount"); //$NON-NLS-1$
-
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
- Node booking = ndList.item(bookingIndex);
- // if there is a attribute in the tag number:value
- amountFound=true;
- setAmount(booking.getTextContent());
-
- }
-
-
- if (!amountFound) {
- /* there is apparently no requirement to mention DuePayableAmount,,
- * if it's not there, check for GrandTotalAmount
- */
- ndList = document.getElementsByTagNameNS("*","GrandTotalAmount"); //$NON-NLS-1$
- for (int bookingIndex = 0; bookingIndex < ndList
- .getLength(); bookingIndex++) {
- Node booking = ndList.item(bookingIndex);
- // if there is a attribute in the tag number:value
- amountFound=true;
- setAmount(booking.getTextContent());
-
- }
-
- }
-
-
- }
-
-
- public boolean containsMeta() {
- return containsMeta;
- }
-
- public String getForeignReference() {
- return foreignReference;
- }
-
-
-
- public void setForeignReference(String foreignReference) {
- this.foreignReference = foreignReference;
- }
-
-
-
- public String getBIC() {
- return BIC;
- }
-
-
-
- private void setBIC(String bic) {
- this.BIC = bic;
- }
-
-
- private void setBankName(String bankname) {
- this.bankName = bankname;
- }
-
-
-
- public String getIBAN() {
- return IBAN;
- }
-
-
- public String getBankName() {
- return bankName;
- }
-
-
-
- public void setIBAN(String IBAN) {
- this.IBAN = IBAN;
- }
-
-
-
- public String getHolder() {
- return holder;
- }
-
-
-
- private void setHolder(String holder) {
- this.holder = holder;
- }
-
-
-
- public String getAmount() {
- return amount;
- }
-
-
- private void setAmount(String amount) {
- this.amount = amount;
- }
-
- public void setMeta(String meta) {
- this.meta=meta;
- }
-
- public String getMeta() {
- return meta;
- }
-
- /**
- * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML
- * */
- public boolean canParse() {
-
-
- //SpecifiedExchangedDocumentContext is in the schema, so a relatively good indication if zugferd is present - better than just invoice
- return (meta!=null)&&( meta.length()>0)&&( meta.contains("SpecifiedExchangedDocumentContext")); //$NON-NLS-1$
- }
-}
+package org.mustangproject.ZUGFeRD;
+/**
+ * Mustangproject's ZUGFeRD implementation
+ * ZUGFeRD importer
+ * Licensed under the APLv2
+ * @date 2014-07-07
+ * @version 1.1.0
+ * @author jstaerk
+ * */
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
+import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
+import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+//root.setNamespace(Namespace.getNamespace("http://www.energystar.gov/manageBldgs/req"));
+
+public class ZUGFeRDImporter {
+ /*
+ call extract(importFilename).
+ containsMeta() will return if ZUGFeRD data has been found,
+ afterwards you can call getBIC(), getIBAN() etc.
+
+*/
+
+ /**@var if metadata has been found */
+ private boolean containsMeta=false;
+ /**@var the reference (i.e. invoice number) of the sender */
+ private String foreignReference;
+ private String BIC;
+ private String IBAN;
+ private String holder;
+ private String amount;
+ /** Raw XML form of the extracted data - may be directly obtained. */
+ private byte[] rawXML=null;
+ private String bankName;
+ private boolean amountFound;
+
+ /**
+ * Extracts a ZUGFeRD invoice from a PDF document represented by a file name.
+ * Errors are just logged to STDOUT.
+ */
+ public void extract(String pdfFilename)
+ {
+ try
+ {
+ extractLowLevel(new BufferedInputStream(new FileInputStream(pdfFilename)));
+ } catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+
+ /**
+ * Extracts a ZUGFeRD invoice from a PDF document represented by an input stream.
+ * Errors are reported via exception handling.
+ */
+ public void extractLowLevel(InputStream pdfStream) throws IOException {
+ PDDocument doc = null;
+ try {
+ doc = PDDocument.load(pdfStream);
+// PDDocumentInformation info = doc.getDocumentInformation();
+ PDDocumentNameDictionary names = new PDDocumentNameDictionary(
+ doc.getDocumentCatalog());
+ PDEmbeddedFilesNameTreeNode etn;
+ etn = names.getEmbeddedFiles();
+ if (etn==null) {
+ doc.close();
+ return;
+ }
+ Map efMap = etn.getNames();
+ // String filePath = "/tmp/";
+ for (String filename : efMap.keySet()) {
+ /**
+ * currently (in the release candidate of version 1) only one
+ * attached file with the name ZUGFeRD-invoice.xml is allowed
+ * */
+ if (filename.equals("ZUGFeRD-invoice.xml")) { //$NON-NLS-1$
+ containsMeta = true;
+
+ PDComplexFileSpecification fileSpec = (PDComplexFileSpecification) efMap
+ .get(filename);
+ PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
+ // String embeddedFilename = filePath + filename;
+ // File file = new File(filePath + filename);
+ // System.out.println("Writing " + embeddedFilename);
+ // ByteArrayOutputStream fileBytes=new
+ // ByteArrayOutputStream();
+ // FileOutputStream fos = new FileOutputStream(file);
+ rawXML = embeddedFile.getByteArray();
+ setMeta(new String(rawXML));
+ // fos.write(embeddedFile.getByteArray());
+ // fos.close();
+ }
+ }
+
+ } catch (IOException e1) {
+ throw e1;
+ }
+ finally {
+ try {
+ if(doc!=null) {
+ doc.close();
+ }
+ } catch (IOException e) {}
+ }
+
+ }
+
+ public void parse() {
+ DocumentBuilderFactory factory = null;
+ DocumentBuilder builder = null;
+ Document document = null;
+
+ factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true); //otherwise we can not act namespace independend, i.e. use document.getElementsByTagNameNS("*",...
+ try {
+ builder = factory.newDocumentBuilder();
+ } catch (ParserConfigurationException ex3) {
+ // TODO Auto-generated catch block
+ ex3.printStackTrace();
+ }
+
+ try {
+ InputStream bais = new ByteArrayInputStream(rawXML);
+ document = builder.parse(bais);
+ } catch (SAXException ex1) {
+ ex1.printStackTrace();
+ } catch (IOException ex2) {
+ ex2.printStackTrace();
+ }
+ NodeList ndList ;
+
+ // rootNode = document.getDocumentElement();
+ // ApplicableSupplyChainTradeSettlement
+ ndList = document.getDocumentElement()
+ .getElementsByTagNameNS("*","PaymentReference"); //$NON-NLS-1$
+
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+ Node booking = ndList.item(bookingIndex);
+ // if there is a attribute in the tag number:value
+
+ setForeignReference(booking.getTextContent());
+
+ }
+/*
+ ndList = document
+ .getElementsByTagName("GermanBankleitzahlID"); //$NON-NLS-1$
+
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+ Node booking = ndList.item(bookingIndex);
+ // if there is a attribute in the tag number:value
+ setBIC(booking.getTextContent());
+
+ }
+
+ ndList = document.getElementsByTagName("ProprietaryID"); //$NON-NLS-1$
+
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+ Node booking = ndList.item(bookingIndex);
+ // if there is a attribute in the tag number:value
+ setIBAN(booking.getTextContent());
+
+ }
+
+ DE1234
+
+
+ DE5656565
+ Commerzbank
+
+
+*/
+ ndList = document.getElementsByTagNameNS("*","PayeePartyCreditorFinancialAccount"); //$NON-NLS-1$
+
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+
+ Node booking = ndList.item(bookingIndex);
+ // there are many "name" elements, so get the one below
+ // SellerTradeParty
+ NodeList bookingDetails = booking.getChildNodes();
+
+
+ for (int detailIndex = 0; detailIndex < bookingDetails
+ .getLength(); detailIndex++) {
+ Node detail = bookingDetails.item(detailIndex);
+ if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("IBANID"))) { //$NON-NLS-1$
+ setIBAN(detail.getTextContent());
+
+ }
+ }
+
+ }
+
+ ndList = document.getElementsByTagNameNS("*","PayeeSpecifiedCreditorFinancialInstitution"); //$NON-NLS-1$
+
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+ Node booking = ndList.item(bookingIndex);
+ // there are many "name" elements, so get the one below
+ // SellerTradeParty
+ NodeList bookingDetails = booking.getChildNodes();
+ for (int detailIndex = 0; detailIndex < bookingDetails
+ .getLength(); detailIndex++) {
+ Node detail = bookingDetails.item(detailIndex);
+ if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("BICID"))) { //$NON-NLS-1$
+ setBIC(detail.getTextContent());
+ }
+ if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("Name"))) { //$NON-NLS-1$
+ setBankName(detail.getTextContent());
+ }
+ }
+
+ }
+
+ //
+ ndList = document.getElementsByTagNameNS("*","SellerTradeParty"); //$NON-NLS-1$
+
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+ Node booking = ndList.item(bookingIndex);
+ // there are many "name" elements, so get the one below
+ // SellerTradeParty
+ NodeList bookingDetails = booking.getChildNodes();
+ for (int detailIndex = 0; detailIndex < bookingDetails
+ .getLength(); detailIndex++) {
+ Node detail = bookingDetails.item(detailIndex);
+ if ((detail.getLocalName()!=null)&&(detail.getLocalName().equals("Name"))) { //$NON-NLS-1$
+ setHolder(detail.getTextContent());
+ }
+ }
+
+ }
+
+ ndList = document.getElementsByTagNameNS("*","DuePayableAmount"); //$NON-NLS-1$
+
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+ Node booking = ndList.item(bookingIndex);
+ // if there is a attribute in the tag number:value
+ amountFound=true;
+ setAmount(booking.getTextContent());
+
+ }
+
+
+ if (!amountFound) {
+ /* there is apparently no requirement to mention DuePayableAmount,,
+ * if it's not there, check for GrandTotalAmount
+ */
+ ndList = document.getElementsByTagNameNS("*","GrandTotalAmount"); //$NON-NLS-1$
+ for (int bookingIndex = 0; bookingIndex < ndList
+ .getLength(); bookingIndex++) {
+ Node booking = ndList.item(bookingIndex);
+ // if there is a attribute in the tag number:value
+ amountFound=true;
+ setAmount(booking.getTextContent());
+
+ }
+
+ }
+
+
+ }
+
+
+ public boolean containsMeta() {
+ return containsMeta;
+ }
+
+ public String getForeignReference() {
+ return foreignReference;
+ }
+
+
+
+ public void setForeignReference(String foreignReference) {
+ this.foreignReference = foreignReference;
+ }
+
+
+
+ public String getBIC() {
+ return BIC;
+ }
+
+
+
+ private void setBIC(String bic) {
+ this.BIC = bic;
+ }
+
+
+ private void setBankName(String bankname) {
+ this.bankName = bankname;
+ }
+
+
+
+ public String getIBAN() {
+ return IBAN;
+ }
+
+
+ public String getBankName() {
+ return bankName;
+ }
+
+
+
+ public void setIBAN(String IBAN) {
+ this.IBAN = IBAN;
+ }
+
+
+
+ public String getHolder() {
+ return holder;
+ }
+
+
+
+ private void setHolder(String holder) {
+ this.holder = holder;
+ }
+
+
+
+ public String getAmount() {
+ return amount;
+ }
+
+
+ private void setAmount(String amount) {
+ this.amount = amount;
+ }
+
+ public void setMeta(String meta) {
+ this.rawXML=meta.getBytes();
+ }
+
+ public String getMeta() {
+ if (rawXML==null){
+ return null;
+ } else {
+ return new String(rawXML);
+ }
+ }
+
+
+ /**
+ * Returns the raw XML data as extracted from the ZUGFeRD PDF file.
+ */
+ public byte[] getRawXML()
+ {
+ return rawXML;
+ }
+
+ /**
+ * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML
+ * */
+ public boolean canParse() {
+
+
+ //SpecifiedExchangedDocumentContext is in the schema, so a relatively good indication if zugferd is present - better than just invoice
+ String meta=getMeta();
+ return (meta!=null)&&( meta.length()>0)&&( meta.contains("SpecifiedExchangedDocumentContext")); //$NON-NLS-1$
+ }
+}
diff --git a/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.class b/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.class
index b5c652b2..54f41f99 100644
Binary files a/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.class and b/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDExporter.class differ
diff --git a/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.class b/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.class
index f2e67642..e4f918d3 100644
Binary files a/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.class and b/mustang/target/classes/org/mustangproject/ZUGFeRD/ZUGFeRDImporter.class differ
diff --git a/mustang/target/maven-archiver/pom.properties b/mustang/target/maven-archiver/pom.properties
index f76d5b7c..d353e8da 100644
--- a/mustang/target/maven-archiver/pom.properties
+++ b/mustang/target/maven-archiver/pom.properties
@@ -1,5 +1,5 @@
#Generated by Maven
-#Sat Jun 28 19:58:49 CEST 2014
-version=1.1.0.alpha
+#Fri Aug 08 19:33:01 CEST 2014
+version=1.1.1alpha
groupId=org.mustangproject.ZUGFeRD
artifactId=mustang
diff --git a/mustang/target/mustang-1.0.1.jar b/mustang/target/mustang-1.0.1.jar
deleted file mode 100644
index a699b18d..00000000
Binary files a/mustang/target/mustang-1.0.1.jar and /dev/null differ
diff --git a/mustang/target/mustang-1.0.2.jar b/mustang/target/mustang-1.0.2.jar
deleted file mode 100644
index 85a027b7..00000000
Binary files a/mustang/target/mustang-1.0.2.jar and /dev/null differ
diff --git a/mustang/target/mustang-1.0.jar b/mustang/target/mustang-1.0.jar
deleted file mode 100644
index 4c1c0e68..00000000
Binary files a/mustang/target/mustang-1.0.jar and /dev/null differ
diff --git a/mustang/target/mustang-1.1.0.alpha.jar b/mustang/target/mustang-1.1.0.alpha.jar
deleted file mode 100644
index 410917b3..00000000
Binary files a/mustang/target/mustang-1.1.0.alpha.jar and /dev/null differ
diff --git a/mustang/target/mustang-1.1.0.jar b/mustang/target/mustang-1.1.0.jar
deleted file mode 100644
index 88bf3ae3..00000000
Binary files a/mustang/target/mustang-1.1.0.jar and /dev/null differ
diff --git a/mustang/target/surefire-reports/TEST-org.mustangproject.ZUGFeRD.AppTest.xml b/mustang/target/surefire-reports/TEST-org.mustangproject.ZUGFeRD.AppTest.xml
deleted file mode 100644
index 2aaddb88..00000000
--- a/mustang/target/surefire-reports/TEST-org.mustangproject.ZUGFeRD.AppTest.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mustang/target/surefire-reports/org.mustangproject.ZUGFeRD.AppTest.txt b/mustang/target/surefire-reports/org.mustangproject.ZUGFeRD.AppTest.txt
deleted file mode 100644
index e5dc4367..00000000
--- a/mustang/target/surefire-reports/org.mustangproject.ZUGFeRD.AppTest.txt
+++ /dev/null
@@ -1,4 +0,0 @@
--------------------------------------------------------------------------------
-Test set: org.mustangproject.ZUGFeRD.AppTest
--------------------------------------------------------------------------------
-Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.223 sec