Parsing exceptions of date strings does not break the whole XML parsing and sets the unparsable dates to null

This commit is contained in:
Daniel Luckas
2025-08-08 10:17:26 +02:00
parent 7a17055f8c
commit cd712aae67

View File

@@ -1,6 +1,5 @@
package org.mustangproject.ZUGFeRD; package org.mustangproject.ZUGFeRD;
import javax.xml.XMLConstants;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
@@ -9,8 +8,21 @@ import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.common.PDNameTreeNode; import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.mustangproject.*; import org.mustangproject.Allowance;
import org.mustangproject.BankDetails;
import org.mustangproject.CalculatedInvoice;
import org.mustangproject.Charge;
import org.mustangproject.DirectDebit;
import org.mustangproject.EStandard;
import org.mustangproject.Exceptions.StructureException; import org.mustangproject.Exceptions.StructureException;
import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.Invoice;
import org.mustangproject.Item;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.SchemedID;
import org.mustangproject.TradeParty;
import org.mustangproject.XMLTools;
import org.mustangproject.util.NodeMap; import org.mustangproject.util.NodeMap;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -19,11 +31,19 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*; import javax.xml.xpath.XPath;
import java.io.*; import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
@@ -31,7 +51,16 @@ import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -127,7 +156,7 @@ public class ZUGFeRDInvoiceImporter {
if (Arrays.equals(pad, pdfSignature)) { // we have a pdf if (Arrays.equals(pad, pdfSignature)) { // we have a pdf
try(PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) { try (PDDocument doc = Loader.loadPDF(IOUtils.toByteArray(pdfStream))) {
// PDDocumentInformation info = doc.getDocumentInformation(); // PDDocumentInformation info = doc.getDocumentInformation();
final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog()); final PDDocumentNameDictionary names = new PDDocumentNameDictionary(doc.getDocumentCatalog());
//start //start
@@ -175,7 +204,7 @@ public class ZUGFeRDInvoiceImporter {
containsMeta = true; containsMeta = true;
try { try {
setRawXML(XMLTools.getBytesFromStream(pdfStream)); setRawXML(XMLTools.getBytesFromStream(pdfStream));
} catch(ParseException e) { } catch (ParseException e) {
LOGGER.error("Failed to parse PDF", e); LOGGER.error("Failed to parse PDF", e);
} }
@@ -209,19 +238,20 @@ public class ZUGFeRDInvoiceImporter {
public boolean hasXMLFileAttachment() { public boolean hasXMLFileAttachment() {
return containsAXMLFileAttachment; return containsAXMLFileAttachment;
} }
/*** /***
* sets th pdf attachments, and if a file is recognized (e.g. a factur-x.xml) triggers processing * sets th pdf attachments, and if a file is recognized (e.g. a factur-x.xml) triggers processing
* @param names the Hashmap of String, PDComplexFileSpecification * @param names the Hashmap of String, PDComplexFileSpecification
* @throws IOException * @throws IOException
*/ */
private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException { private void extractFiles(Map<String, PDComplexFileSpecification> names) throws IOException {
containsAXMLFileAttachment=false; containsAXMLFileAttachment = false;
for (final String alias : names.keySet()) { for (final String alias : names.keySet()) {
final PDComplexFileSpecification fileSpec = names.get(alias); final PDComplexFileSpecification fileSpec = names.get(alias);
final String filename = fileSpec.getFilename(); final String filename = fileSpec.getFilename();
if (filename.toUpperCase().endsWith(".XML")) { if (filename.toUpperCase().endsWith(".XML")) {
containsAXMLFileAttachment=true; containsAXMLFileAttachment = true;
} }
/** /**
* filenames for invoice data (ZUGFeRD v1 and v2, Factur-X) * filenames for invoice data (ZUGFeRD v1 and v2, Factur-X)
@@ -523,7 +553,8 @@ public class ZUGFeRDInvoiceImporter {
for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) { for (int issueDateChildIndex = 0; issueDateChildIndex < issueDateTimeChilds.getLength(); issueDateChildIndex++) {
if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null) if ((issueDateTimeChilds.item(issueDateChildIndex).getLocalName() != null)
&& (issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) { && (issueDateTimeChilds.item(issueDateChildIndex).getLocalName().equals("DateTimeString"))) {
issueDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(issueDateTimeChilds.item(issueDateChildIndex))); String issueDateString = XMLTools.trimOrNull(issueDateTimeChilds.item(issueDateChildIndex));
issueDate = parseDate(issueDateString, "yyyyMMdd");
} }
} }
} }
@@ -582,15 +613,15 @@ public class ZUGFeRDInvoiceImporter {
typeCode = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim(); typeCode = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"InvoiceTypeCode\"]").trim();
String issueDateStr = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"IssueDate\"]").trim(); String issueDateStr = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"IssueDate\"]").trim();
if (!issueDateStr.isEmpty()) { if (!issueDateStr.isEmpty()) {
issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(issueDateStr); issueDate = parseDate(issueDateStr, "yyyy-MM-dd");
} }
String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"]").trim(); String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"]").trim();
if (!dueDt.isEmpty()) { if (!dueDt.isEmpty()) {
dueDate = new SimpleDateFormat("yyyy-MM-dd").parse(dueDt); dueDate = parseDate(dueDt, "yyyy-MM-dd");
} }
String deliveryDt = extractString("//*[local-name()=\"Delivery\"]/*[local-name()=\"ActualDeliveryDate\"]").trim(); String deliveryDt = extractString("//*[local-name()=\"Delivery\"]/*[local-name()=\"ActualDeliveryDate\"]").trim();
if (!deliveryDt.isEmpty()) { if (!deliveryDt.isEmpty()) {
deliveryDate = new SimpleDateFormat("yyyy-MM-dd").parse(deliveryDt); deliveryDate = parseDate(deliveryDt, "yyyy-MM-dd");
} }
} }
@@ -620,7 +651,8 @@ public class ZUGFeRDInvoiceImporter {
for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds.getLength(); occurenceChildIndex++) { for (int occurenceChildIndex = 0; occurenceChildIndex < occurenceChilds.getLength(); occurenceChildIndex++) {
if ((occurenceChilds.item(occurenceChildIndex).getLocalName() != null) if ((occurenceChilds.item(occurenceChildIndex).getLocalName() != null)
&& (occurenceChilds.item(occurenceChildIndex).getLocalName().equals("DateTimeString"))) { && (occurenceChilds.item(occurenceChildIndex).getLocalName().equals("DateTimeString"))) {
deliveryDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(occurenceChilds.item(occurenceChildIndex))); String deliveryDateString = XMLTools.trimOrNull(occurenceChilds.item(occurenceChildIndex));
deliveryDate = parseDate(deliveryDateString, "yyyyMMdd");
} }
} }
} }
@@ -714,7 +746,8 @@ public class ZUGFeRDInvoiceImporter {
NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes(); NodeList dueDateChilds = paymentTermChilds.item(paymentTermChildIndex).getChildNodes();
for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) { for (int dueDateChildIndex = 0; dueDateChildIndex < dueDateChilds.getLength(); dueDateChildIndex++) {
if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) { if ((dueDateChilds.item(dueDateChildIndex).getLocalName() != null) && (dueDateChilds.item(dueDateChildIndex).getLocalName().equals("DateTimeString"))) {
dueDate = new SimpleDateFormat("yyyyMMdd").parse(XMLTools.trimOrNull(dueDateChilds.item(dueDateChildIndex))); String dueDateString = XMLTools.trimOrNull(dueDateChilds.item(dueDateChildIndex));
dueDate = parseDate(dueDateString, "yyyyMMdd");
} }
} }
} }
@@ -767,7 +800,7 @@ public class ZUGFeRDInvoiceImporter {
if (BIC != null) { if (BIC != null) {
bd.setBIC(BIC); bd.setBIC(BIC);
} }
if (accountName!=null) { if (accountName != null) {
bd.setAccountName(accountName); bd.setAccountName(accountName);
} }
bankDetails.add(bd); bankDetails.add(bd);
@@ -803,7 +836,7 @@ public class ZUGFeRDInvoiceImporter {
NodeList periodNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET); NodeList periodNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int periodChildIndex = 0; periodChildIndex < periodNodes.getLength(); periodChildIndex++) { for (int periodChildIndex = 0; periodChildIndex < periodNodes.getLength(); periodChildIndex++) {
String localName=periodNodes.item(periodChildIndex).getLocalName(); String localName = periodNodes.item(periodChildIndex).getLocalName();
if ((localName != null) && (periodNodes.item(periodChildIndex).getLocalName().equals("StartDate"))) { if ((localName != null) && (periodNodes.item(periodChildIndex).getLocalName().equals("StartDate"))) {
deliveryPeriodStart = XMLTools.trimOrNull(periodNodes.item(periodChildIndex)); deliveryPeriodStart = XMLTools.trimOrNull(periodNodes.item(periodChildIndex));
} }
@@ -854,7 +887,7 @@ public class ZUGFeRDInvoiceImporter {
} }
if (IBAN != null) { if (IBAN != null) {
BankDetails bd = new BankDetails(IBAN); BankDetails bd = new BankDetails(IBAN);
if (accountName!=null) { if (accountName != null) {
bd.setAccountName(accountName); bd.setAccountName(accountName);
} }
bankDetails.add(bd); bankDetails.add(bd);
@@ -1004,7 +1037,7 @@ public class ZUGFeRDInvoiceImporter {
} else if (chargeChildName.equals("ActualAmount") || chargeChildName.equals("Amount")) { } else if (chargeChildName.equals("ActualAmount") || chargeChildName.equals("Amount")) {
chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("BasisAmount")) { } else if (chargeChildName.equals("BasisAmount")) {
basisAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); basisAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("Reason") || chargeChildName.equals("AllowanceChargeReason")) { } else if (chargeChildName.equals("Reason") || chargeChildName.equals("AllowanceChargeReason")) {
reason = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex)); reason = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
@@ -1119,6 +1152,18 @@ public class ZUGFeRDInvoiceImporter {
} }
private Date parseDate(String issueDateString, String datePattern) throws ParseException {
Date parsedDate = null;
if (issueDateString != null) {
try {
parsedDate = new SimpleDateFormat(datePattern).parse(issueDateString);
} catch (ParseException e) {
LOGGER.warn("Failed to parse date {} with pattern {}", issueDateString, datePattern, e);
}
}
return parsedDate;
}
protected Document getDocument() { protected Document getDocument() {
return document; return document;
} }
@@ -1201,7 +1246,7 @@ public class ZUGFeRDInvoiceImporter {
*/ */
@Deprecated @Deprecated
public List<FileAttachment> getFileAttachmentsXML() { public List<FileAttachment> getFileAttachmentsXML() {
if (importedInvoice.getAdditionalReferencedDocuments()!=null) { if (importedInvoice.getAdditionalReferencedDocuments() != null) {
return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments())); return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments()));
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
@@ -1228,7 +1273,7 @@ public class ZUGFeRDInvoiceImporter {
* sets the XML for the importer to parse * sets the XML for the importer to parse
* @param XML the UBL or CII * @param XML the UBL or CII
*/ */
public void fromXML(String XML) throws ParseException{ public void fromXML(String XML) throws ParseException {
try { try {
containsMeta = true; containsMeta = true;
setRawXML(XML.getBytes(StandardCharsets.UTF_8)); setRawXML(XML.getBytes(StandardCharsets.UTF_8));