Merge pull request #133 from CSSAG/master

solved NPE on PDF without
This commit is contained in:
Jochen Staerk
2019-09-14 17:32:59 +02:00
committed by GitHub
2 changed files with 696 additions and 665 deletions

View File

@@ -1,421 +1,452 @@
/** ********************************************************************** /**
* * ********************************************************************** 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 package org.mustangproject.ZUGFeRD;
* of the License at http://www.apache.org/licenses/LICENSE-2.0. /**
* * Mustangproject's ZUGFeRD implementation ZUGFeRD importer Licensed under the APLv2
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * @date 2014-07-07
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * @version 1.1.0
* * @author jstaerk
* See the License for the specific language governing permissions and */
* limitations under the License.
* import java.io.ByteArrayInputStream;
*********************************************************************** */ import java.io.IOException;
package org.mustangproject.ZUGFeRD; import java.io.InputStream;
/** import java.nio.file.Files;
* Mustangproject's ZUGFeRD implementation import java.nio.file.Paths;
* ZUGFeRD importer import java.nio.file.StandardOpenOption;
* Licensed under the APLv2 import java.util.HashMap;
* @date 2014-07-07 import java.util.List;
* @version 1.1.0 import java.util.Map;
* @author jstaerk import java.util.Scanner;
* */ import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; import javax.xml.parsers.DocumentBuilder;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import javax.xml.parsers.ParserConfigurationException;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import javax.xml.xpath.XPath;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Document; import javax.xml.xpath.XPathFactory;
import org.xml.sax.SAXException;
import org.apache.pdfbox.pdmodel.PDDocument;
import javax.xml.parsers.DocumentBuilder; import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import javax.xml.parsers.DocumentBuilderFactory; import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import javax.xml.parsers.ParserConfigurationException; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import javax.xml.xpath.XPath; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import javax.xml.xpath.XPathExpressionException; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document;
import java.io.*; import org.xml.sax.SAXException;
import java.nio.file.Files;
import java.nio.file.Paths; public class ZUGFeRDImporter {
import java.nio.file.StandardOpenOption;
import java.util.*; /**
import java.util.logging.Level; * if metadata has been found
import java.util.logging.Logger; */
private boolean containsMeta = false;
public class ZUGFeRDImporter { /**
* map filenames of additional XML files to their contents
/** */
* if metadata has been found private HashMap<String, byte[]> additionalXMLs = new HashMap<>();
*/ /**
private boolean containsMeta = false; * Raw XML form of the extracted data - may be directly obtained.
/** */
* map filenames of additional XML files to their contents private byte[] rawXML = null;
*/ /**
private HashMap<String, byte[]> additionalXMLs = new HashMap<>(); * XMP metadata
/** */
* Raw XML form of the extracted data - may be directly obtained. private String xmpString = null; // XMP metadata
*/ /**
private byte[] rawXML = null; * parsed Document
/** */
* XMP metadata private Document document;
*/
private String xmpString = null; // XMP metadata
/** public ZUGFeRDImporter(String pdfFilename) {
* parsed Document try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) {
*/ extractLowLevel(bis);
private Document document; } catch (IOException e) {
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
public ZUGFeRDImporter(String pdfFilename) { throw new ZUGFeRDExportException(e);
try (InputStream bis = Files.newInputStream(Paths.get(pdfFilename), StandardOpenOption.READ)) { }
extractLowLevel(bis); }
} catch (IOException e) {
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
throw new ZUGFeRDExportException(e); public ZUGFeRDImporter(InputStream pdfStream) {
} try {
} extractLowLevel(pdfStream);
} catch (IOException e) {
public ZUGFeRDImporter(InputStream pdfStream) { Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
try { throw new ZUGFeRDExportException(e);
extractLowLevel(pdfStream); }
} catch (IOException e) { }
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
throw new ZUGFeRDExportException(e);
} /**
} * Extracts a ZUGFeRD invoice from a PDF document represented by an input stream. Errors are reported via exception handling.
*
/** * @param pdfStream a inputstream of a pdf file
* Extracts a ZUGFeRD invoice from a PDF document represented by an input */
* stream. Errors are reported via exception handling. private void extractLowLevel(InputStream pdfStream) throws IOException {
* try (PDDocument doc = PDDocument.load(pdfStream)) {
* @param pdfStream a inputstream of a pdf file // PDDocumentInformation info = doc.getDocumentInformation();
*/ PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
private void extractLowLevel(InputStream pdfStream) throws IOException { //start
try (PDDocument doc = PDDocument.load(pdfStream)) {
// PDDocumentInformation info = doc.getDocumentInformation(); if (doc.getDocumentCatalog() == null || doc.getDocumentCatalog().getMetadata() == null) {
PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog()); Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.INFO, "no-xmlpart");
//start return;
InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata(); }
xmpString = convertStreamToString(XMP); InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
xmpString = convertStreamToString(XMP);
PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
if (etn == null) { PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
return; if (etn == null) {
} return;
}
Map<String, PDComplexFileSpecification> efMap = etn.getNames();
// String filePath = "/tmp/"; Map<String, PDComplexFileSpecification> efMap = etn.getNames();
// String filePath = "/tmp/";
if (efMap != null) {
extractFiles(efMap); // see if (efMap != null) {
// https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/ extractFiles(efMap); // see
} else { // https://memorynotfound.com/apache-pdfbox-extract-embedded-file-pdf-document/
} else {
List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
for (PDNameTreeNode<PDComplexFileSpecification> node : kids) { List<PDNameTreeNode<PDComplexFileSpecification>> kids = etn.getKids();
Map<String, PDComplexFileSpecification> namesL = node.getNames(); for (PDNameTreeNode<PDComplexFileSpecification> node : kids) {
extractFiles(namesL); Map<String, PDComplexFileSpecification> namesL = node.getNames();
} extractFiles(namesL);
} }
} }
} }
}
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
for (String alias : names.keySet()) { private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
for (String alias : names.keySet()) {
PDComplexFileSpecification fileSpec = names.get(alias);
String filename=fileSpec.getFilename(); PDComplexFileSpecification fileSpec = names.get(alias);
/** String filename = fileSpec.getFilename();
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X) /**
*/ * filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml"))) { //$NON-NLS-1$ */
containsMeta = true; if ((filename.equals("ZUGFeRD-invoice.xml") || (filename.equals("zugferd-invoice.xml")) || filename.equals("factur-x.xml"))) { //$NON-NLS-1$
containsMeta = true;
PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
// String embeddedFilename = filePath + filename; PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
// File file = new File(filePath + filename); // String embeddedFilename = filePath + filename;
// System.out.println("Writing " + embeddedFilename); // File file = new File(filePath + filename);
// ByteArrayOutputStream fileBytes=new // System.out.println("Writing " + embeddedFilename);
// ByteArrayOutputStream(); // ByteArrayOutputStream fileBytes=new
// FileOutputStream fos = new FileOutputStream(file); // ByteArrayOutputStream();
// FileOutputStream fos = new FileOutputStream(file);
setRawXML(embeddedFile.toByteArray());
setRawXML(embeddedFile.toByteArray());
// fos.write(embeddedFile.getByteArray());
// fos.close(); // fos.write(embeddedFile.getByteArray());
} // fos.close();
if (filename.startsWith("additional_data")) { }
PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile(); if (filename.startsWith("additional_data")) {
additionalXMLs.put(filename, embeddedFile.toByteArray()); PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
} additionalXMLs.put(filename, embeddedFile.toByteArray());
} }
} }
}
private Document getDocument() { return document; }
private void setDocument() throws ParserConfigurationException, IOException, SAXException { private Document getDocument() {
DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance(); return document;
xmlFact.setNamespaceAware(false); }
DocumentBuilder builder = xmlFact.newDocumentBuilder();
ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
is.skip(guessBOMSize(is)); private void setDocument() throws ParserConfigurationException, IOException, SAXException {
document = builder.parse(is); DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
} xmlFact.setNamespaceAware(false);
DocumentBuilder builder = xmlFact.newDocumentBuilder();
public void setRawXML(byte[] rawXML) throws IOException { ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
this.rawXML = rawXML; is.skip(guessBOMSize(is));
try { document = builder.parse(is);
setDocument(); }
} catch (ParserConfigurationException | SAXException e) {
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
throw new ZUGFeRDExportException(e); public void setRawXML(byte[] rawXML) throws IOException {
} this.rawXML = rawXML;
} try {
setDocument();
/** } catch (ParserConfigurationException | SAXException e) {
* Skips over a BOM at the beginning of the given ByteArrayInputStream, if one exists. Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
* @param is the ByteArrayInputStream used throw new ZUGFeRDExportException(e);
* @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> }
*/
private int guessBOMSize(ByteArrayInputStream is) throws IOException {
byte[] pad = new byte[4]; /**
is.read(pad); * Skips over a BOM at the beginning of the given ByteArrayInputStream, if one exists.
is.reset(); *
int test2 = ((pad[0] & 0xFF) << 8) | (pad[1] & 0xFF); * @param is the ByteArrayInputStream used
int test3 = ((test2 & 0xFFFF) << 8) | (pad[2] & 0xFF); * @throws IOException if can not be read from is
int test4 = ((test3 & 0xFFFFFF) << 8) | (pad[3] & 0xFF); * @see <a href="https://www.w3.org/TR/xml/#sec-guessing">Autodetection of Character Encodings</a>
// */
if (test4 == 0x0000FEFF || test4 == 0xFFFE0000 || test4 == 0x0000FFFE || test4 == 0xFEFF0000) { private int guessBOMSize(ByteArrayInputStream is) throws IOException {
// UCS-4: BOM takes 4 bytes byte[] pad = new byte[4];
return 4; is.read(pad);
} else if (test3 == 0xEFBBFF) { is.reset();
// UTF-8: BOM takes 3 bytes int test2 = ((pad[0] & 0xFF) << 8) | (pad[1] & 0xFF);
return 3; int test3 = ((test2 & 0xFFFF) << 8) | (pad[2] & 0xFF);
} else if (test2 == 0xFEFF || test2 == 0xFFFE) { int test4 = ((test3 & 0xFFFFFF) << 8) | (pad[3] & 0xFF);
// UTF-16: BOM takes 2 bytes //
return 2; if (test4 == 0x0000FEFF || test4 == 0xFFFE0000 || test4 == 0x0000FFFE || test4 == 0xFEFF0000) {
} // UCS-4: BOM takes 4 bytes
return 0; return 4;
} } else if (test3 == 0xEFBBFF) {
// UTF-8: BOM takes 3 bytes
private String extractString(String xpathStr) { return 3;
if (!containsMeta) { } else if (test2 == 0xFEFF || test2 == 0xFFFE) {
throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found."); // UTF-16: BOM takes 2 bytes
} return 2;
String result; }
try { return 0;
Document document = getDocument(); }
XPathFactory xpathFact = XPathFactory.newInstance();
XPath xpath = xpathFact.newXPath();
result = xpath.evaluate(xpathStr, document); private String extractString(String xpathStr) {
} catch (XPathExpressionException e) { if (!containsMeta) {
Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e); throw new ZUGFeRDExportException("No suitable data/ZUGFeRD file could be found.");
throw new ZUGFeRDExportException(e); }
} String result;
return result; try {
} Document document = getDocument();
XPathFactory xpathFact = XPathFactory.newInstance();
/** XPath xpath = xpathFact.newXPath();
* @return the reference (purpose) the sender specified for this invoice result = xpath.evaluate(xpathStr, document);
*/ } catch (XPathExpressionException e) {
public String getForeignReference() { Logger.getLogger(ZUGFeRDImporter.class.getName()).log(Level.SEVERE, null, e);
String result = extractString("//ApplicableHeaderTradeSettlement/PaymentReference"); throw new ZUGFeRDExportException(e);
if(result == null || result.isEmpty()) }
result = extractString("//ApplicableSupplyChainTradeSettlement/PaymentReference"); return result;
return result; }
}
/** /**
* @return the document code * @return the reference (purpose) the sender specified for this invoice
*/ */
public String getDocumentCode() { public String getForeignReference() {
return extractString("//HeaderExchangedDocument/TypeCode"); String result = extractString("//ApplicableHeaderTradeSettlement/PaymentReference");
} if (result == null || result.isEmpty()) {
result = extractString("//ApplicableSupplyChainTradeSettlement/PaymentReference");
/** }
* @return the referred document return result;
*/ }
public String getReference() {
return extractString("//ApplicableHeaderTradeAgreement/BuyerReference");
} /**
* @return the document code
/** */
* @return the sender's bank's BLZ code public String getDocumentCode() {
* @deprecated use BIC and IBAN instead of BLZ and KTO return extractString("//HeaderExchangedDocument/TypeCode");
*/ }
@Deprecated
public String getBLZ() {
return extractString("//PayeeSpecifiedCreditorFinancialInstitution/GermanBankleitzahlID"); /**
} * @return the referred document
*/
/** public String getReference() {
* @return the sender's account number return extractString("//ApplicableHeaderTradeAgreement/BuyerReference");
* @deprecated use BIC and IBAN instead of BLZ and KTO }
*/
@Deprecated
public String getKTO() { /**
return extractString("//PayeePartyCreditorFinancialAccount/ProprietaryID"); * @return the sender's bank's BLZ code
} * @deprecated use BIC and IBAN instead of BLZ and KTO
*/
/** @Deprecated
* @return the sender's bank's BIC code public String getBLZ() {
*/ return extractString("//PayeeSpecifiedCreditorFinancialInstitution/GermanBankleitzahlID");
public String getBIC() { }
return extractString("//PayeeSpecifiedCreditorFinancialInstitution/BICID");
}
/**
* @return the sender's account number
/** * @deprecated use BIC and IBAN instead of BLZ and KTO
* @return the sender's bank name */
*/ @Deprecated
public String getBankName() { public String getKTO() {
return extractString("//PayeeSpecifiedCreditorFinancialInstitution/Name"); return extractString("//PayeePartyCreditorFinancialAccount/ProprietaryID");
} }
/**
* @return the sender's account IBAN code /**
*/ * @return the sender's bank's BIC code
public String getIBAN() { */
return extractString("//PayeePartyCreditorFinancialAccount/IBANID"); public String getBIC() {
} return extractString("//PayeeSpecifiedCreditorFinancialInstitution/BICID");
}
public String getHolder() {
return extractString("//SellerTradeParty/Name");
} /**
* @return the sender's bank name
/** */
* @return the total payable amount public String getBankName() {
*/ return extractString("//PayeeSpecifiedCreditorFinancialInstitution/Name");
public String getAmount() { }
String result = extractString("//SpecifiedTradeSettlementHeaderMonetarySummation/DuePayableAmount");
if(result == null || result.isEmpty())
result = extractString("//SpecifiedTradeSettlementMonetarySummation/GrandTotalAmount"); /**
return result; * @return the sender's account IBAN code
} */
public String getIBAN() {
/** return extractString("//PayeePartyCreditorFinancialAccount/IBANID");
* @return when the payment is due }
*/
public String getDueDate() {
return extractString("//SpecifiedTradePaymentTerms/DueDateDateTime/DateTimeString"); public String getHolder() {
} return extractString("//SellerTradeParty/Name");
}
public HashMap<String, byte[]> getAdditionalData() {
return additionalXMLs;
} /**
* @return the total payable amount
/** */
* get xmp metadata of the PDF, null if not available public String getAmount() {
* String result = extractString("//SpecifiedTradeSettlementHeaderMonetarySummation/DuePayableAmount");
* @return string if (result == null || result.isEmpty()) {
*/ result = extractString("//SpecifiedTradeSettlementMonetarySummation/GrandTotalAmount");
public String getXMP() { }
return xmpString; return result;
} }
/** /**
* @return if export found parseable ZUGFeRD data * @return when the payment is due
*/ */
public boolean containsMeta() { public String getDueDate() {
return containsMeta; return extractString("//SpecifiedTradePaymentTerms/DueDateDateTime/DateTimeString");
} }
/**
* @param meta raw XML to be set public HashMap<String, byte[]> getAdditionalData() {
* @throws IOException if raw can not be set return additionalXMLs;
*/ }
public void setMeta(String meta) throws IOException {
setRawXML(meta.getBytes());
} /**
* get xmp metadata of the PDF, null if not available
/** *
* @return raw XML of the invoice * @return string
*/ */
public String getMeta() { public String getXMP() {
if (rawXML == null) { return xmpString;
return null; }
}
return new String(rawXML); /**
} * @return if export found parseable ZUGFeRD data
*/
public boolean containsMeta() {
public int getVersion() throws Exception { return containsMeta;
if (!containsMeta) throw new Exception("Not yet parsed"); }
if (getUTF8().contains("<rsm:CrossIndustryDocument")) {
return 1;
} else if (getUTF8().contains("<rsm:CrossIndustryInvoice")) { /**
return 2; * @param meta raw XML to be set
} * @throws IOException if raw can not be set
throw new Exception("ZUGFeRD version could not be determined"); */
} public void setMeta(String meta) throws IOException {
setRawXML(meta.getBytes());
/** }
* @return return UTF8 XML (without BOM) of the invoice
*/
public String getUTF8() { /**
if (rawXML == null) { * @return raw XML of the invoice
return null; */
} public String getMeta() {
if (rawXML.length < 3) { if (rawXML == null) {
return new String(rawXML); return null;
} }
return new String(rawXML);
byte[] bomlessData; }
if ((rawXML[0] == (byte) 0xEF)
&& (rawXML[1] == (byte) 0xBB) public int getVersion() throws Exception {
&& (rawXML[2] == (byte) 0xBF)) { if (!containsMeta) {
// I don't like BOMs, lets remove it throw new Exception("Not yet parsed");
bomlessData = new byte[rawXML.length - 3]; }
System.arraycopy(rawXML, 3, bomlessData, 0, if (getUTF8().contains("<rsm:CrossIndustryDocument")) {
rawXML.length - 3); return 1;
} else { } else if (getUTF8().contains("<rsm:CrossIndustryInvoice")) {
bomlessData = rawXML; return 2;
} }
throw new Exception("ZUGFeRD version could not be determined");
return new String(bomlessData); }
}
/** /**
* Returns the raw XML data as extracted from the ZUGFeRD PDF file. * @return return UTF8 XML (without BOM) of the invoice
* @return the raw ZUGFeRD XML data */
*/ public String getUTF8() {
public byte[] getRawXML() { if (rawXML == null) {
return rawXML; return null;
} }
if (rawXML.length < 3) {
/** return new String(rawXML);
* will return true if the metadata (just extract-ed or set with setMeta) }
* contains ZUGFeRD XML
*
* @return true if the invoice contains ZUGFeRD XML byte[] bomlessData;
*/
public boolean canParse() { if ((rawXML[0] == (byte) 0xEF)
&& (rawXML[1] == (byte) 0xBB)
// SpecifiedExchangedDocumentContext is in the schema, so a relatively good && (rawXML[2] == (byte) 0xBF)) {
// indication if zugferd is present - better than just invoice // I don't like BOMs, lets remove it
String meta = getMeta(); bomlessData = new byte[rawXML.length - 3];
return (meta != null) && (meta.length() > 0) && ((meta.contains("SpecifiedExchangedDocumentContext") //$NON-NLS-1$ System.arraycopy(rawXML, 3, bomlessData, 0,
/* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */)); rawXML.length - 3);
} } else {
bomlessData = rawXML;
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
// https://community.oracle.com/blogs/pat/2004/10/23/stupid-scanner-tricks return new String(bomlessData);
Scanner s = new Scanner(is, "UTF-8").useDelimiter("\\A"); }
return s.hasNext() ? s.next() : "";
}
/**
} * Returns the raw XML data as extracted from the ZUGFeRD PDF file.
*
* @return the raw ZUGFeRD XML data
*/
public byte[] getRawXML() {
return rawXML;
}
/**
* will return true if the metadata (just extract-ed or set with setMeta) contains ZUGFeRD XML
*
* @return true if the invoice 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$
/* ZF1 */ || meta.contains("ExchangedDocumentContext") /* ZF2 */));
}
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
// https://community.oracle.com/blogs/pat/2004/10/23/stupid-scanner-tricks
Scanner s = new Scanner(is, "UTF-8").useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
}

View File

@@ -1,244 +1,244 @@
/** ********************************************************************** /** **********************************************************************
* *
* Copyright 2019 Jochen Staerk * Copyright 2019 Jochen Staerk
* *
* Use is subject to license terms. * Use is subject to license terms.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * 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 * 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. * of the License at http://www.apache.org/licenses/LICENSE-2.0.
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* *
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* *
*********************************************************************** */ *********************************************************************** */
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import junit.framework.Test; import junit.framework.Test;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import org.junit.FixMethodOrder; import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters; 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.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;
@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";
@Override @Override
public Date getDeliveryDate() { public Date getDeliveryDate() {
return new GregorianCalendar(2017, Calendar.MAY, 7).getTime(); return new GregorianCalendar(2017, Calendar.MAY, 7).getTime();
} }
@Override @Override
public Date getDueDate() { public Date getDueDate() {
return new GregorianCalendar(2017, Calendar.MAY, 30).getTime(); return new GregorianCalendar(2017, Calendar.MAY, 30).getTime();
} }
@Override @Override
public Date getIssueDate() { public Date getIssueDate() {
return new GregorianCalendar(2017, Calendar.MAY, 9).getTime(); return new GregorianCalendar(2017, Calendar.MAY, 9).getTime();
} }
@Override @Override
public String getNumber() { public String getNumber() {
return "RE-20170509/505"; return "RE-20170509/505";
} }
@Override @Override
public String getOwnCountry() { public String getOwnCountry() {
return "DE"; return "DE";
} }
@Override @Override
public String getOwnLocation() { public String getOwnLocation() {
return "Stadthausen"; return "Stadthausen";
} }
@Override @Override
public String getOwnOrganisationName() { public String getOwnOrganisationName() {
return "Bei Spiel GmbH"; return "Bei Spiel GmbH";
} }
@Override @Override
public String getOwnStreet() { public String getOwnStreet() {
return "Ecke 12"; return "Ecke 12";
} }
@Override @Override
public IZUGFeRDExportableContact getOwnContact() { public IZUGFeRDExportableContact getOwnContact() {
return new SenderContact(); return new SenderContact();
} }
@Override @Override
public String getOwnTaxID() { public String getOwnTaxID() {
return "22/815/0815/4"; return "22/815/0815/4";
} }
@Override @Override
public String getOwnVATID() { public String getOwnVATID() {
return "DE136695976"; return "DE136695976";
} }
@Override @Override
public String getOwnZIP() { public String getOwnZIP() {
return "12345"; return "12345";
} }
@Override @Override
public IZUGFeRDExportableContact getRecipient() { public IZUGFeRDExportableContact getRecipient() {
return new RecipientContact(); return new RecipientContact();
} }
@Override @Override
public String getOwnOrganisationFullPlaintextInfo() { public String getOwnOrganisationFullPlaintextInfo() {
return null; return null;
} }
@Override @Override
public String getCurrency() { public String getCurrency() {
return "EUR"; return "EUR";
} }
@Override @Override
public IZUGFeRDExportableItem[] getZFItems() { public IZUGFeRDExportableItem[] getZFItems() {
Item[] allItems = new Item[3]; Item[] allItems = new Item[3];
Product designProduct = new Product("", "Künstlerische Gestaltung (Stunde): Einer Beispielrechnung", "HUR", Product designProduct = new Product("", "Künstlerische Gestaltung (Stunde): Einer Beispielrechnung", "HUR",
new BigDecimal("7.000000")); new BigDecimal("7.000000"));
Product balloonProduct = new Product("", "Bestellerweiterung für E&F Umbau", "C62", Product balloonProduct = new Product("", "Bestellerweiterung für E&F Umbau", "C62",
new BigDecimal("19.000000"));// test for issue 103 new BigDecimal("19.000000"));// test for issue 103
Product airProduct = new Product("", "Heiße Luft pro Liter", "LTR", new BigDecimal("19.000000")); Product airProduct = new Product("", "Heiße Luft pro Liter", "LTR", new BigDecimal("19.000000"));
allItems[0] = new Item(new BigDecimal("160"), new BigDecimal("1"), designProduct); allItems[0] = new Item(new BigDecimal("160"), new BigDecimal("1"), designProduct);
allItems[1] = new Item(new BigDecimal("0.79"), new BigDecimal("400"), balloonProduct); allItems[1] = new Item(new BigDecimal("0.79"), new BigDecimal("400"), balloonProduct);
allItems[2] = new Item(new BigDecimal("0.10"), new BigDecimal("200"), airProduct); allItems[2] = new Item(new BigDecimal("0.10"), new BigDecimal("200"), airProduct);
return allItems; return allItems;
} }
@Override @Override
public String getPaymentTermDescription() { public String getPaymentTermDescription() {
SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy"); SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
return "Zahlbar ohne Abzug bis zum " + germanDateFormat.format(getDueDate()); return "Zahlbar ohne Abzug bis zum " + germanDateFormat.format(getDueDate());
} }
@Override @Override
public IZUGFeRDAllowanceCharge[] getZFAllowances() { public IZUGFeRDAllowanceCharge[] getZFAllowances() {
return null; return null;
} }
@Override @Override
public IZUGFeRDAllowanceCharge[] getZFCharges() { public IZUGFeRDAllowanceCharge[] getZFCharges() {
return null; return null;
} }
@Override @Override
public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() { public IZUGFeRDAllowanceCharge[] getZFLogisticsServiceCharges() {
return null; return null;
} }
@Override @Override
public String getReferenceNumber() { public String getReferenceNumber() {
return "AB321"; return "AB321";
} }
/** /**
* Create the test case * Create the test case
* *
* @param testName name of the test case * @param testName name of the test case
*/ */
public ZF2EdgeTest(String testName) { public ZF2EdgeTest(String testName) {
super(testName); super(testName);
} }
/** /**
* @return the suite of tests being tested * @return the suite of tests being tested
*/ */
public static Test suite() { public static Test suite() {
return new TestSuite(ZF2EdgeTest.class); return new TestSuite(ZF2EdgeTest.class);
} }
// //////// TESTS // //////// TESTS
// ////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////////
/** /**
* The exporter test bases on @{code * The exporter test bases on @{code
* ./src/test/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf}, adds * ./src/test/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf}, adds
* 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 testEdgeExport() { public void testEdgeExport() {
// the writing part // the writing part
try (InputStream SOURCE_PDF = this.getClass() try (InputStream SOURCE_PDF = this.getClass()
.getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf"); .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf");
ZUGFeRDExporter ze = new ZUGFeRDExporterFromA3Factory().setProducer("My Application") ZUGFeRDExporter ze = new ZUGFeRDExporterFromA3Factory().setProducer("My Application")
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).ignorePDFAErrors() .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).ignorePDFAErrors()
.load(SOURCE_PDF)) { .load(SOURCE_PDF)) {
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"));
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");
} }
// now check the contents (like MustangReaderTest) // now check the contents (like MustangReaderTest)
ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF); ZUGFeRDImporter zi = new ZUGFeRDImporter(TARGET_PDF);
// Reading ZUGFeRD // Reading ZUGFeRD
assertEquals(zi.getAmount(), "571.04"); assertEquals(zi.getAmount(), "571.04");
assertEquals(zi.getBIC(), getTradeSettlementPayment()[0].getOwnBIC()); assertEquals(zi.getBIC(), getTradeSettlementPayment()[0].getOwnBIC());
assertEquals(zi.getIBAN(), getTradeSettlementPayment()[0].getOwnIBAN()); assertEquals(zi.getIBAN(), getTradeSettlementPayment()[0].getOwnIBAN());
assertEquals(zi.getHolder(), getOwnOrganisationName()); assertEquals(zi.getHolder(), getOwnOrganisationName());
assertEquals(zi.getForeignReference(), getNumber()); assertEquals(zi.getForeignReference(), getNumber());
try { try {
assertEquals(zi.getVersion(), 2); assertEquals(zi.getVersion(), 2);
} catch (Exception e) { } catch (Exception e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }
/** /**
* The exporter test bases on @{code * The exporter test bases on @{code
* ./src/test/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf}, adds * ./src/test/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf}, adds
* 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 testOutpuStreamExport() {
// the writing part // the writing part
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (InputStream SOURCE_PDF = this.getClass() try (InputStream SOURCE_PDF = this.getClass()
.getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf"); .getResourceAsStream("/MustangGnuaccountingBeispielRE-20170509_505PDFA3.pdf");
ZUGFeRDExporter ze = new ZUGFeRDExporterFromA3Factory().setProducer("My Application") ZUGFeRDExporter ze = new ZUGFeRDExporterFromA3Factory().setProducer("My Application")
.setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).ignorePDFAErrors() .setCreator(System.getProperty("user.name")).setZUGFeRDVersion(2).ignorePDFAErrors()
.load(SOURCE_PDF)) { .load(SOURCE_PDF)) {
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"));
ze.export(bos); ze.export(bos);
} catch (IOException e) { } catch (IOException e) {
fail("IOException should not be raised in testEdgeExport"); fail("IOException should not be raised in testEdgeExport");
} }
} }
} }