From 35ecf57957acabdc16e93727283b5f5d4065f444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20Sta=CC=88rk?= Date: Thu, 21 Sep 2017 12:31:04 +0200 Subject: [PATCH] Applied the migrator on an old (non-jaxb) version of the exporter --- .../ZUGFeRD/ZUGFeRD2Exporter.java | 698 ++++++++++++++++++ .../ZUGFeRD/ZUGFeRDMigrator.java | 108 +++ 2 files changed, 806 insertions(+) create mode 100644 src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2Exporter.java create mode 100644 src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java diff --git a/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2Exporter.java b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2Exporter.java new file mode 100644 index 00000000..72c348f2 --- /dev/null +++ b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2Exporter.java @@ -0,0 +1,698 @@ +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.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.transform.TransformerException; + +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSDictionary; +import org.apache.pdfbox.cos.COSName; +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.COSObjectable; +import org.apache.pdfbox.pdmodel.common.PDMetadata; +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; +import org.apache.xmpbox.XMPMetadata; + + + +public class ZUGFeRD2Exporter { + /*** + * 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 + * + */ + + + + private class LineCalc { + private IZUGFeRDExportableItem currentItem=null; + private BigDecimal totalGross; + private BigDecimal itemTotalNetAmount; + private BigDecimal itemTotalVATAmount; + + public LineCalc(IZUGFeRDExportableItem currentItem) { + this.currentItem=currentItem; + BigDecimal multiplicator=currentItem.getProduct().getVATPercent().divide(new BigDecimal(100)).add(new BigDecimal(1)); +// priceGross=currentItem.getPrice().multiply(multiplicator); + totalGross=currentItem.getPrice().multiply(multiplicator).multiply(currentItem.getQuantity()); + itemTotalNetAmount=currentItem.getQuantity().multiply(currentItem.getPrice()).setScale(2,BigDecimal.ROUND_HALF_UP); + itemTotalVATAmount=totalGross.subtract(itemTotalNetAmount); + } + + public BigDecimal getItemTotalNetAmount() { + return itemTotalNetAmount; + } + + public BigDecimal getItemTotalVATAmount() { + return itemTotalVATAmount; + } + + } + + + + //// MAIN CLASS + + private String conformanceLevel = "U"; + private String versionStr = "1.2.0"; + + // BASIC, COMFORT etc - may be set from outside. + private String ZUGFeRDConformanceLevel = null; + + + /** + * 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 boolean isTest; + IZUGFeRDExportableTransaction trans=null; + + + private String nDigitFormat(BigDecimal value, int scale) { + /* + * 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( scale, BigDecimal.ROUND_HALF_UP ); // first, round so that e.g. 1.189999999999999946709294817992486059665679931640625 becomes 1.19 + char[] repeat = new char[scale]; + Arrays.fill(repeat, '0'); + + DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(); + otherSymbols.setDecimalSeparator('.'); + DecimalFormat dec = new DecimalFormat("0."+new String(repeat), otherSymbols); + return dec.format(value); + + } + + private String vatFormat(BigDecimal value) { + return nDigitFormat(value, 2); + } + + private String currencyFormat(BigDecimal value) { + return nDigitFormat(value, 2); + } + + private String priceFormat(BigDecimal value) { + return nDigitFormat(value, 4); + } + private String quantityFormat(BigDecimal value) { + return nDigitFormat(value, 4); + } + + /** + 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; + } + + /** + * enables the flag to indicate a test invoice in the XML structure + * */ + public void setTest() { + isTest=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) + * */ + 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) { + this.trans=trans; + + SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); //$NON-NLS-1$ + SimpleDateFormat zugferdDateFormat = new SimpleDateFormat("yyyyMMdd"); //$NON-NLS-1$ + String testBooleanStr="false"; + if (isTest) { + testBooleanStr="true"; + + } + String senderReg=""; + if (trans.getOwnOrganisationFullPlaintextInfo()!=null) { + senderReg="" + + "\n" + + " \n" + + trans.getOwnOrganisationFullPlaintextInfo() + + " \n" + + "REG\n" + + "\n"; + + } + String xml= "\n" //$NON-NLS-1$ + + + "\n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " urn:ferd:CrossIndustryDocument:invoice:1p0: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$ + + senderReg +// + " \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=getVATPercentAmountMap(); + for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { + VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); + if (amount != null) { + xml += " \n" //$NON-NLS-1$ + + " "+currencyFormat(amount.getCalculated())+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " VAT\n" //$NON-NLS-1$ + + " "+currencyFormat(amount.getBasis())+"\n" + + " S\n" //$NON-NLS-1$ + + " "+vatFormat(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 ohne Abzug bis "+germanDateFormat.format(trans.getDueDate())+"\n" + + " "+zugferdDateFormat.format(trans.getDueDate())+"\n"//20130704 //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+currencyFormat(getTotal())+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " 0.00\n" //$NON-NLS-1$ + + " 0.00\n" //$NON-NLS-1$ +// + " 5.80\n" +// + " 14.73\n" + + " "+currencyFormat(getTotal())+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+currencyFormat(getTotalGross().subtract(getTotal()))+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " "+currencyFormat(getTotalGross())+"\n" //$NON-NLS-1$ //$NON-NLS-2$ +// + " 0.00\n" + + " "+currencyFormat(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++; + + LineCalc lc=new LineCalc(currentItem); + 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$ + + " "+priceFormat(currentItem.getPrice())+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " 1.0000\n" //$NON-NLS-1$ //$NON-NLS-2$ +// + " \n" +// + " false\n" +// + " 0.6667\n" +// + " Rabatt\n" +// + " \n" + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+priceFormat(currentItem.getPrice())+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " 1.0000\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + + " \n" //$NON-NLS-1$ + + " "+quantityFormat(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$ + + " "+vatFormat(currentItem.getProduct().getVATPercent())+"\n" //$NON-NLS-1$ //$NON-NLS-2$ + + " \n" //$NON-NLS-1$ + + " \n" //$NON-NLS-1$ + + " "+currencyFormat(lc.getItemTotalNetAmount())+"\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; + } + + + private BigDecimal getTotalGross() { + + BigDecimal res=getTotal(); + HashMap VATPercentAmountMap=getVATPercentAmountMap(); + for (BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) { + VATAmount amount = VATPercentAmountMap.get(currentTaxPercent); + res=res.add(amount.getCalculated()); + } + + + return res; + } + + private BigDecimal getTotal() { + BigDecimal res=new BigDecimal(0); + for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { + LineCalc lc=new LineCalc(currentItem); + res=res.add(lc.getItemTotalNetAmount()); + } + return res; + } + + /** + * which taxes have been used with which amounts in this transaction, + * empty for no taxes, or e.g. 19=>190 and 7=>14 if 1000 Eur were applicable + * to 19% VAT (=>190 EUR VAT) and 200 EUR were applicable to 7% (=>14 EUR VAT) + * 190 Eur + * @return + * + */ + private HashMap getVATPercentAmountMap() { + HashMap hm=new HashMap (); + + for (IZUGFeRDExportableItem currentItem : trans.getZFItems()) { + BigDecimal percent=currentItem.getProduct().getVATPercent(); + LineCalc lc=new LineCalc(currentItem); + VATAmount itemVATAmount=new VATAmount(lc.getItemTotalNetAmount(), lc.getItemTotalVATAmount() ) ; + VATAmount current=hm.get(percent); + if (current==null) { + hm.put(percent, itemVATAmount); + } else { + hm.put(percent, current.add(itemVATAmount)); + + } + } + + return hm; + } + + /** + * 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 { + + 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; + } + } + + + PDFAttachGenericFile(doc, "ZUGFeRD-invoice.xml", "Alternative", "Invoice metadata conforming to ZUGFeRD standard (http://www.ferd-net.de/front_content.php?idcat=231&lang=4)", "text/xml", zugferdData); + } + + + /** + * Embeds an external file (generic - any type allowed) in the PDF. + * + * @param doc PDDocument to attach the file to. + * @param filename name of the file that will become attachment name in the PDF + * @param relationship how the file relates to the content, e.g. "Alternative" + * @param description Human-readable description of the file content + * @param subType type of the data e.g. could be "text/xml" - mime like + * @param data the binary data of the file/attachment + */ + public void PDFAttachGenericFile(PDDocument doc, String filename, String relationship, String description, String subType, byte[] data) + throws IOException + { + PDComplexFileSpecification fs = new PDComplexFileSpecification(); + fs.setFile(filename); + + COSDictionary dict = fs.getCOSDictionary(); + dict.setName("AFRelationship", relationship); + dict.setString("UF", filename); + dict.setString("Desc", description); + + ByteArrayInputStream fakeFile = new ByteArrayInputStream(data); + PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile); + ef.setSubtype(subType); + ef.setSize(data.length); + ef.setCreationDate(new GregorianCalendar()); + + ef.setModDate(GregorianCalendar.getInstance()); + + fs.setEmbeddedFile(ef); + + // In addition make sure the embedded file is set under /UF + dict = fs.getCOSDictionary(); + COSDictionary efDict = (COSDictionary)dict.getDictionaryObject(COSName.EF); + COSBase lowerLevelFile = efDict.getItem(COSName.F); + efDict.setItem(COSName.UF, lowerLevelFile); + + // now add the entry to the embedded file tree and set in the document. + PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog()); + PDEmbeddedFilesNameTreeNode efTree = names.getEmbeddedFiles(); + if (efTree == null) + { + efTree = new PDEmbeddedFilesNameTreeNode(); + } + + Map namesMap = new HashMap(); + Map oldNamesMap = efTree.getNames(); + if (oldNamesMap != null) + { + for (String key : oldNamesMap.keySet()) + { + namesMap.put(key, oldNamesMap.get(key)); + } + } + namesMap.put(filename, fs); + efTree.setNames(namesMap); + + names.setEmbeddedFiles(efTree); + doc.getDocumentCatalog().setNames(names); + + // AF entry (Array) in catalog with the FileSpec + COSArray cosArray = (COSArray)doc.getDocumentCatalog().getCOSDictionary().getItem("AF"); + if (cosArray == null) + { + cosArray = new COSArray(); + } + cosArray.add(fs); + doc.getDocumentCatalog().getCOSDictionary().setItem("AF", cosArray); + } + + + /** + * 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; + } + + + /** + * Sets the ZUGFeRD conformance level (override). + * + * @param ZUGFeRDConformanceLevel the new conformance level + */ + public void setZUGFeRDConformanceLevel(String ZUGFeRDConformanceLevel) + { + this.ZUGFeRDConformanceLevel = ZUGFeRDConformanceLevel; + } + +/*** + * 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, this.ZUGFeRDConformanceLevel); + zf.setAbout(""); //$NON-NLS-1$ + metadata.addSchema(zf); + + XMPSchemaPDFAExtensions pdfaex = new XMPSchemaPDFAExtensions(metadata); + pdfaex.setAbout(""); //$NON-NLS-1$ + metadata.addSchema(pdfaex); + + } + +} diff --git a/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java new file mode 100644 index 00000000..480867ee --- /dev/null +++ b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java @@ -0,0 +1,108 @@ +package org.mustangproject.ZUGFeRD; + +public class ZUGFeRDMigrator { + + public String migrateFromV1ToV2(String xml) { + + // 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"); + return xml; + } + +}