Merge branch 'master' of github.com:ZUGFeRD/mustangproject

# Conflicts:
#	src/test/java/org/mustangproject/ZUGFeRD/ZF2EdgeTest.java
This commit is contained in:
Jochen Stärk
2019-09-14 18:52:12 +02:00
2 changed files with 695 additions and 660 deletions

View File

@@ -1,30 +1,38 @@
/** ********************************************************************** /**
* * ********************************************************************** Copyright 2018 Jochen Staerk Use is subject to license terms. Licensed under the
* Copyright 2018 Jochen Staerk * Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
* * http://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* Use is subject to license terms. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* * and limitations under the License.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not */
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
*********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
/** /**
* Mustangproject's ZUGFeRD implementation * Mustangproject's ZUGFeRD implementation ZUGFeRD importer Licensed under the APLv2
* ZUGFeRD importer *
* Licensed under the APLv2
* @date 2014-07-07 * @date 2014-07-07
* @version 1.1.0 * @version 1.1.0
* @author jstaerk * @author jstaerk
* */ */
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
@@ -35,20 +43,6 @@ import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ZUGFeRDImporter { public class ZUGFeRDImporter {
/** /**
@@ -72,6 +66,7 @@ public class ZUGFeRDImporter {
*/ */
private Document document; private Document document;
public ZUGFeRDImporter(String pdfFilename) { public ZUGFeRDImporter(String pdfFilename) {
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) { try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
extractLowLevel(bis); extractLowLevel(bis);
@@ -81,6 +76,7 @@ public class ZUGFeRDImporter {
} }
} }
public ZUGFeRDImporter(InputStream pdfStream) { public ZUGFeRDImporter(InputStream pdfStream) {
try { try {
extractLowLevel(pdfStream); extractLowLevel(pdfStream);
@@ -90,9 +86,9 @@ public class ZUGFeRDImporter {
} }
} }
/** /**
* Extracts a ZUGFeRD invoice from a PDF document represented by an input * Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
* stream. Errors are reported via exception handling.
* *
* @param pdfStream a inputstream of a pdf file * @param pdfStream a inputstream of a pdf file
*/ */
@@ -101,8 +97,13 @@ public class ZUGFeRDImporter {
// PDDocumentInformation info = doc.getDocumentInformation(); // PDDocumentInformation info = doc.getDocumentInformation();
PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog()); PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
//start //start
InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.INFO, "no-xmlpart");
return;
}
InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
xmpString = convertStreamToString(XMP); xmpString = convertStreamToString(XMP);
PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles(); PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
@@ -132,7 +133,7 @@ public class ZUGFeRDImporter {
for (String alias : names.keySet()) { for (String alias : names.keySet()) {
PDComplexFileSpecification fileSpec = names.get(alias); PDComplexFileSpecification fileSpec = names.get(alias);
String filename=fileSpec.getFilename(); String filename = fileSpec.getFilename();
/** /**
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X) * filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
*/ */
@@ -159,7 +160,11 @@ public class ZUGFeRDImporter {
} }
} }
private Document getDocument() { return document; }
private Document getDocument() {
return document;
}
private void setDocument() throws ParserConfigurationException, IOException, SAXException { private void setDocument() throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
@@ -170,6 +175,7 @@ public class ZUGFeRDImporter {
document = builder.parse(is); document = builder.parse(is);
} }
public void setRawXML(byte[] rawXML) throws IOException { public void setRawXML(byte[] rawXML) throws IOException {
this.rawXML = rawXML; this.rawXML = rawXML;
try { try {
@@ -180,8 +186,10 @@ public class ZUGFeRDImporter {
} }
} }
/** /**
* Skips over a BOM at the beginning of the given ByteArrayInputStream, if one exists. * Skips over a BOM at the beginning of the given ByteArrayInputStream, if one exists.
*
* @param is the ByteArrayInputStream used * @param is the ByteArrayInputStream used
* @throws IOException if can not be read from is * @throws IOException if can not be read from is
* @see <a href="https://www.w3.org/TR/xml/#sec-guessing">Autodetection of Character Encodings</a> * @see <a href="https://www.w3.org/TR/xml/#sec-guessing">Autodetection of Character Encodings</a>
@@ -207,6 +215,7 @@ public class ZUGFeRDImporter {
return 0; return 0;
} }
private String extractString(String xpathStr) { private String extractString(String xpathStr) {
if (!containsMeta) { if (!containsMeta) {
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found."); throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
@@ -224,16 +233,19 @@ public class ZUGFeRDImporter {
return result; return result;
} }
/** /**
* @return the reference (purpose) the sender specified for this invoice * @return the reference (purpose) the sender specified for this invoice
*/ */
public String getForeignReference() { public String getForeignReference() {
String result = extractString("//ApplicableHeaderTradeSettlement/PaymentReference"); String result = extractString("//ApplicableHeaderTradeSettlement/PaymentReference");
if(result == null || result.isEmpty()) if (result == null || result.isEmpty()) {
result = extractString("//ApplicableSupplyChainTradeSettlement/PaymentReference"); result = extractString("//ApplicableSupplyChainTradeSettlement/PaymentReference");
}
return result; return result;
} }
/** /**
* @return the document code * @return the document code
*/ */
@@ -241,6 +253,7 @@ public class ZUGFeRDImporter {
return extractString("//HeaderExchangedDocument/TypeCode"); return extractString("//HeaderExchangedDocument/TypeCode");
} }
/** /**
* @return the referred document * @return the referred document
*/ */
@@ -248,6 +261,7 @@ public class ZUGFeRDImporter {
return extractString("//ApplicableHeaderTradeAgreement/BuyerReference"); return extractString("//ApplicableHeaderTradeAgreement/BuyerReference");
} }
/** /**
* @return the sender's bank's BLZ code * @return the sender's bank's BLZ code
* @deprecated use BIC and IBAN instead of BLZ and KTO * @deprecated use BIC and IBAN instead of BLZ and KTO
@@ -257,6 +271,7 @@ public class ZUGFeRDImporter {
return extractString("//PayeeSpecifiedCreditorFinancialInstitution/GermanBankleitzahlID"); return extractString("//PayeeSpecifiedCreditorFinancialInstitution/GermanBankleitzahlID");
} }
/** /**
* @return the sender's account number * @return the sender's account number
* @deprecated use BIC and IBAN instead of BLZ and KTO * @deprecated use BIC and IBAN instead of BLZ and KTO
@@ -266,6 +281,7 @@ public class ZUGFeRDImporter {
return extractString("//PayeePartyCreditorFinancialAccount/ProprietaryID"); return extractString("//PayeePartyCreditorFinancialAccount/ProprietaryID");
} }
/** /**
* @return the sender's bank's BIC code * @return the sender's bank's BIC code
*/ */
@@ -281,6 +297,7 @@ public class ZUGFeRDImporter {
return extractString("//PayeeSpecifiedCreditorFinancialInstitution/Name"); return extractString("//PayeeSpecifiedCreditorFinancialInstitution/Name");
} }
/** /**
* @return the sender's account IBAN code * @return the sender's account IBAN code
*/ */
@@ -288,20 +305,24 @@ public class ZUGFeRDImporter {
return extractString("//PayeePartyCreditorFinancialAccount/IBANID"); return extractString("//PayeePartyCreditorFinancialAccount/IBANID");
} }
public String getHolder() { public String getHolder() {
return extractString("//SellerTradeParty/Name"); return extractString("//SellerTradeParty/Name");
} }
/** /**
* @return the total payable amount * @return the total payable amount
*/ */
public String getAmount() { public String getAmount() {
String result = extractString("//SpecifiedTradeSettlementHeaderMonetarySummation/DuePayableAmount"); String result = extractString("//SpecifiedTradeSettlementHeaderMonetarySummation/DuePayableAmount");
if(result == null || result.isEmpty()) if (result == null || result.isEmpty()) {
result = extractString("//SpecifiedTradeSettlementMonetarySummation/GrandTotalAmount"); result = extractString("//SpecifiedTradeSettlementMonetarySummation/GrandTotalAmount");
}
return result; return result;
} }
/** /**
* @return when the payment is due * @return when the payment is due
*/ */
@@ -309,10 +330,12 @@ public class ZUGFeRDImporter {
return extractString("//SpecifiedTradePaymentTerms/DueDateDateTime/DateTimeString"); return extractString("//SpecifiedTradePaymentTerms/DueDateDateTime/DateTimeString");
} }
public HashMap<String, byte[]> getAdditionalData() { public HashMap<String, byte[]> getAdditionalData() {
return additionalXMLs; return additionalXMLs;
} }
/** /**
* get xmp metadata of the PDF, null if not available * get xmp metadata of the PDF, null if not available
* *
@@ -330,6 +353,7 @@ public class ZUGFeRDImporter {
return containsMeta; return containsMeta;
} }
/** /**
* @param meta raw XML to be set * @param meta raw XML to be set
* @throws IOException if raw can not be set * @throws IOException if raw can not be set
@@ -338,6 +362,7 @@ public class ZUGFeRDImporter {
setRawXML(meta.getBytes()); setRawXML(meta.getBytes());
} }
/** /**
* @return raw XML of the invoice * @return raw XML of the invoice
*/ */
@@ -351,7 +376,9 @@ public class ZUGFeRDImporter {
public int getVersion() throws Exception { public int getVersion() throws Exception {
if (!containsMeta) throw new Exception("Not yet parsed"); if (!containsMeta) {
throw new Exception("Not yet parsed");
}
if (getUTF8().contains("<rsm:CrossIndustryDocument")) { if (getUTF8().contains("<rsm:CrossIndustryDocument")) {
return 1; return 1;
} else if (getUTF8().contains("<rsm:CrossIndustryInvoice")) { } else if (getUTF8().contains("<rsm:CrossIndustryInvoice")) {
@@ -360,6 +387,7 @@ public class ZUGFeRDImporter {
throw new Exception("ZUGFeRD version could not be determined"); throw new Exception("ZUGFeRD version could not be determined");
} }
/** /**
* @return return UTF8 XML (without BOM) of the invoice * @return return UTF8 XML (without BOM) of the invoice
*/ */
@@ -388,17 +416,19 @@ public class ZUGFeRDImporter {
return new String(bomlessData); return new String(bomlessData);
} }
/** /**
* Returns the raw XML data as extracted from the ZUGFeRD PDF file. * Returns the raw XML data as extracted from the ZUGFeRD PDF file.
*
* @return the raw ZUGFeRD XML data * @return the raw ZUGFeRD XML data
*/ */
public byte[] getRawXML() { public byte[] getRawXML() {
return rawXML; return rawXML;
} }
/** /**
* will return true if the metadata (just extract-ed or set with setMeta) * will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML
* contains ZUGFeRD XML
* *
* @return true if the invoice contains ZUGFeRD XML * @return true if the invoice contains ZUGFeRD XML
*/ */
@@ -411,6 +441,7 @@ public class ZUGFeRDImporter {
/* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */)); /* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */));
} }
static String convertStreamToString(java.io.InputStream is) { static String convertStreamToString(java.io.InputStream is) {
// source https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java referring to // source https://stackoverflow.com/questions/309424/how-do-i-read-convert-an-inputstream-into-a-string-in-java referring to
// https://community.oracle.com/blogs/pat/2004/10/23/stupid-scanner-tricks // https://community.oracle.com/blogs/pat/2004/10/23/stupid-scanner-tricks

View File

@@ -1,3 +1,4 @@
/** ********************************************************************** /** **********************************************************************
* *
* Copyright 2019 Jochen Staerk * Copyright 2019 Jochen Staerk
@@ -18,24 +19,25 @@
*********************************************************************** */ *********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import junit.framework.Test;
import junit.framework.TestSuite;
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ZF2EdgeTest extends MustangReaderTestCase { public class ZF2EdgeTest extends MustangReaderTestCase {
final String TARGET_PDF = "./target/testout-ZF2newEdge.pdf"; final String TARGET_PDF = "./target/testout-ZF2newEdge.pdf";
protected class DebitPayment implements IZUGFeRDTradeSettlementDebit { protected class DebitPayment implements IZUGFeRDTradeSettlementDebit {
@@ -65,6 +67,7 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
return payments; return payments;
} }
@Override @Override
public Date getDeliveryDate() { public Date getDeliveryDate() {
return new GregorianCalendar(2017, Calendar.MAY, 7).getTime(); return new GregorianCalendar(2017, Calendar.MAY, 7).getTime();
@@ -158,7 +161,8 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
@Override @Override
public String getPaymentTermDescription() { public String getPaymentTermDescription() {
return "Zahlbar sofort"; SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
return "Zahlbar ohne Abzug bis zum " + germanDateFormat.format(getDueDate());
} }
@Override @Override
@@ -219,7 +223,6 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
ze.PDFattachZugferdFile(this); ze.PDFattachZugferdFile(this);
String theXML = new String(ze.getProvider().getXML()); String theXML = new String(ze.getProvider().getXML());
assertTrue(theXML.contains("<rsm:CrossIndustryInvoice")); assertTrue(theXML.contains("<rsm:CrossIndustryInvoice"));
assertTrue(theXML.contains("Zahlbar sofort"));
ze.export(TARGET_PDF); ze.export(TARGET_PDF);
} catch (IOException e) { } catch (IOException e) {
fail("IOException should not be raised in testEdgeExport"); fail("IOException should not be raised in testEdgeExport");
@@ -231,6 +234,7 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
assertTrue(zi.getUTF8().contains("<ram:TypeCode>59</ram:TypeCode>")); assertTrue(zi.getUTF8().contains("<ram:TypeCode>59</ram:TypeCode>"));
assertTrue(zi.getUTF8().contains("<ram:IBANID>DE540815</ram:IBANID>")); assertTrue(zi.getUTF8().contains("<ram:IBANID>DE540815</ram:IBANID>"));
assertTrue(zi.getUTF8().contains("<ram:DirectDebitMandateID>DE99XX12345</ram:DirectDebitMandateID>")); assertTrue(zi.getUTF8().contains("<ram:DirectDebitMandateID>DE99XX12345</ram:DirectDebitMandateID>"));
assertFalse(zi.getUTF8().contains("<ram:DueDateTime>"));
// Reading ZUGFeRD // Reading ZUGFeRD
assertEquals(zi.getAmount(), "571.04"); assertEquals(zi.getAmount(), "571.04");
@@ -251,7 +255,7 @@ public class ZF2EdgeTest extends MustangReaderTestCase {
* metadata, writes to @{code ./target/testout-*} and then imports to check the * metadata, writes to @{code ./target/testout-*} and then imports to check the
* values. * values.
*/ */
public void testOutpuStreamExport() { public void testOutputStreamExport() {
// the writing part // the writing part