Merge branch 'master' of github.com:ZUGFeRD/mustangproject
This commit is contained in:
@@ -3,11 +3,14 @@ package org.mustangproject.Exceptions;
|
||||
import java.text.ParseException;
|
||||
|
||||
/***
|
||||
* will be thrown if a invoice cant be reproduced numerically
|
||||
* will be thrown if an invoice cant be reproduced numerically
|
||||
*/
|
||||
public class ArithmetricException extends ParseException {
|
||||
public ArithmetricException() {
|
||||
super(
|
||||
"Could not reproduce the invoice, this could mean that it could not be read properly", 0);
|
||||
this("");
|
||||
}
|
||||
|
||||
public ArithmetricException(String details) {
|
||||
super("Could not reproduce the invoice. " + details, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Invoice implements IExportableTransaction {
|
||||
|
||||
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, invoiceReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
|
||||
protected String documentName = null, documentCode = null, number = null, ownOrganisationFullPlaintextInfo = null, referenceNumber = null, shipToOrganisationID = null, shipToOrganisationName = null, shipToStreet = null, shipToZIP = null, shipToLocation = null, shipToCountry = null, buyerOrderReferencedDocumentID = null, buyerOrderReferencedDocumentIssueDateTime = null, ownForeignOrganisationID = null, ownOrganisationName = null, currency = null, paymentTermDescription = null;
|
||||
protected Date issueDate = null, dueDate = null, deliveryDate = null;
|
||||
protected TradeParty sender = null, recipient = null, deliveryAddress = null, payee = null;
|
||||
protected ArrayList<CashDiscount> cashDiscounts = null;
|
||||
@@ -57,7 +57,12 @@ public class Invoice implements IExportableTransaction {
|
||||
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>(),
|
||||
Charges = new ArrayList<>(), LogisticsServiceCharges = new ArrayList<>();
|
||||
protected IZUGFeRDPaymentTerms paymentTerms = null;
|
||||
|
||||
protected String invoiceReferencedDocumentID = null;
|
||||
protected Date invoiceReferencedIssueDate;
|
||||
// New field for storing Invoiced Object Identifier (BG-3)
|
||||
protected ArrayList<ReferencedDocument> invoiceReferencedDocuments = null;
|
||||
|
||||
protected String specifiedProcuringProjectID = null;
|
||||
protected String specifiedProcuringProjectName = null;
|
||||
protected String despatchAdviceReferencedDocumentID = null;
|
||||
@@ -148,6 +153,7 @@ public class Invoice implements IExportableTransaction {
|
||||
*/
|
||||
public Invoice setCorrection(String number) {
|
||||
setInvoiceReferencedDocumentID(number);
|
||||
addInvoiceReferencedDocument(new ReferencedDocument(number));
|
||||
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
|
||||
return this;
|
||||
}
|
||||
@@ -546,6 +552,26 @@ public class Invoice implements IExportableTransaction {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Getter for BG-3
|
||||
@Override
|
||||
public ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
|
||||
return invoiceReferencedDocuments;
|
||||
}
|
||||
|
||||
// Setter for BG-3
|
||||
public void setInvoiceReferencedDocuments(ArrayList<ReferencedDocument> invoiceReferencedDocuments) {
|
||||
this.invoiceReferencedDocuments = invoiceReferencedDocuments;
|
||||
}
|
||||
|
||||
// Method to add a single ReferencedDocument for BG-3
|
||||
public Invoice addInvoiceReferencedDocument(ReferencedDocument doc) {
|
||||
if (invoiceReferencedDocuments == null) {
|
||||
invoiceReferencedDocuments = new ArrayList<>();
|
||||
}
|
||||
invoiceReferencedDocuments.add(doc);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IZUGFeRDAllowanceCharge[] getZFAllowances() {
|
||||
if (Allowances.isEmpty()) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.mustangproject;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import java.util.Date;
|
||||
|
||||
import org.mustangproject.ZUGFeRD.IReferencedDocument;
|
||||
import org.mustangproject.util.NodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -13,20 +15,33 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
String issuerAssignedID;
|
||||
String typeCode;
|
||||
String referenceTypeCode;
|
||||
Date formattedIssueDateTime;
|
||||
|
||||
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode) {
|
||||
this.issuerAssignedID = issuerAssignedID;
|
||||
this(issuerAssignedID);
|
||||
this.typeCode = typeCode;
|
||||
this.referenceTypeCode = referenceTypeCode;
|
||||
}
|
||||
|
||||
public ReferencedDocument(String issuerAssignedID, String referenceTypeCode) {
|
||||
this.issuerAssignedID = issuerAssignedID;
|
||||
this.typeCode = "916"; // additional invoice related document
|
||||
this.referenceTypeCode = referenceTypeCode;
|
||||
public ReferencedDocument(String issuerAssignedID, String typeCode, String referenceTypeCode, Date formattedIssueDateTime) {
|
||||
this(issuerAssignedID, typeCode, referenceTypeCode);
|
||||
this.formattedIssueDateTime = formattedIssueDateTime;
|
||||
}
|
||||
|
||||
/***
|
||||
public ReferencedDocument(String issuerAssignedID, String referenceTypeCode) {
|
||||
this(issuerAssignedID, "916", referenceTypeCode); // additional invoice related document
|
||||
}
|
||||
|
||||
public ReferencedDocument(String issuerAssingedID, Date formattedIssueDateTime) {
|
||||
this(issuerAssingedID);
|
||||
this.formattedIssueDateTime = formattedIssueDateTime;
|
||||
}
|
||||
|
||||
public ReferencedDocument(String issuerAssignedID) {
|
||||
this.issuerAssignedID = issuerAssignedID;
|
||||
}
|
||||
|
||||
/***
|
||||
* sets an ID assigned by the sender
|
||||
* @param issuerAssignedID the ID as a string :-)
|
||||
*/
|
||||
@@ -50,6 +65,15 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
this.referenceTypeCode = referenceTypeCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* issue date of this line
|
||||
*
|
||||
* @param formattedIssueDateTime as Date
|
||||
*/
|
||||
public void setFormattedIssueDateTime(Date formattedIssueDateTime) {
|
||||
this.formattedIssueDateTime = formattedIssueDateTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIssuerAssignedID() {
|
||||
return issuerAssignedID;
|
||||
@@ -65,6 +89,12 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
return referenceTypeCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getFormattedIssueDateTime()
|
||||
{
|
||||
return formattedIssueDateTime;
|
||||
}
|
||||
|
||||
public static ReferencedDocument fromNode(Node node) {
|
||||
if (!node.hasChildNodes()) {
|
||||
return null;
|
||||
@@ -72,6 +102,7 @@ public class ReferencedDocument implements IReferencedDocument {
|
||||
NodeMap nodes = new NodeMap(node);
|
||||
return new ReferencedDocument(nodes.getAsStringOrNull("IssuerAssignedID", "ID"),
|
||||
nodes.getAsStringOrNull("TypeCode", "DocumentTypeCode"),
|
||||
nodes.getAsStringOrNull("ReferenceTypeCode"));
|
||||
nodes.getAsStringOrNull("ReferenceTypeCode"),
|
||||
XMLTools.tryDate(nodes.getAsStringOrNull("FormattedIssueDateTime")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,13 @@ package org.mustangproject.ZUGFeRD;
|
||||
* */
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.mustangproject.FileAttachment;
|
||||
import org.mustangproject.IncludedNote;
|
||||
import org.mustangproject.ReferencedDocument;
|
||||
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
|
||||
|
||||
/***
|
||||
@@ -424,13 +426,23 @@ public interface IExportableTransaction {
|
||||
*
|
||||
* @return the ID of the document
|
||||
*/
|
||||
@Deprecated
|
||||
default String getInvoiceReferencedDocumentID() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default Date getInvoiceReferencedIssueDate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for BG-3
|
||||
* @return list of documents
|
||||
*/
|
||||
default ArrayList<ReferencedDocument> getInvoiceReferencedDocuments() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the issue timestamp of the BuyerOrderReferencedDocument, which sits in
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface IReferencedDocument {
|
||||
|
||||
/***
|
||||
@@ -20,4 +22,12 @@ public interface IReferencedDocument {
|
||||
*/
|
||||
String getReferenceTypeCode();
|
||||
|
||||
/***
|
||||
*
|
||||
* issue date of this line
|
||||
*
|
||||
* @return date of the issue
|
||||
*/
|
||||
Date getFormattedIssueDateTime();
|
||||
|
||||
}
|
||||
|
||||
@@ -459,6 +459,19 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
if (trans.getInvoiceReferencedDocuments() != null) {
|
||||
for (var doc : trans.getInvoiceReferencedDocuments()) {
|
||||
xml += "<ram:InvoiceReferencedDocument>"
|
||||
+ "<ram:IssuerAssignedID>"
|
||||
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
|
||||
if (doc.getFormattedIssueDateTime() != null) {
|
||||
xml += "<ram:FormattedIssueDateTime>"
|
||||
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
|
||||
+ "</ram:FormattedIssueDateTime>";
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
}
|
||||
|
||||
xml += "</ram:ApplicableHeaderTradeSettlement>";
|
||||
// + " <IncludedSupplyChainTradeLineItem>"
|
||||
|
||||
@@ -4,7 +4,10 @@ import static java.math.BigDecimal.ZERO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/***
|
||||
@@ -91,17 +94,14 @@ public class TransactionCalculator implements IAbsoluteValueProvider {
|
||||
}
|
||||
|
||||
private String getAllowanceChargeReasonForPercent(BigDecimal percent, IZUGFeRDAllowanceCharge[] charges) {
|
||||
String res = " ";
|
||||
if ((charges != null) && (charges.length > 0)) {
|
||||
for (IZUGFeRDAllowanceCharge currentCharge : charges) {
|
||||
if ((percent == null) || (currentCharge.getTaxPercent().compareTo(percent) == 0)
|
||||
&& currentCharge.getReason() != null) {
|
||||
res += currentCharge.getReason() + " ";
|
||||
}
|
||||
}
|
||||
if (charges == null) {
|
||||
return "";
|
||||
}
|
||||
res = res.substring(0, res.length() - 1);
|
||||
return res;
|
||||
return Arrays.stream(charges)
|
||||
.filter(currentCharge -> (percent == null || currentCharge.getTaxPercent().compareTo(percent) == 0))
|
||||
.map(IZUGFeRDAllowanceCharge::getReason)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
/***
|
||||
|
||||
@@ -828,6 +828,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
if (paymentTermsDescription != null) {
|
||||
xml += "<ram:Description>" + paymentTermsDescription + "</ram:Description>";
|
||||
}
|
||||
|
||||
if (trans.getDueDate() != null) {
|
||||
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
|
||||
+ DATE.udtFormat(trans.getDueDate())
|
||||
+ "</ram:DueDateDateTime>";// 20130704
|
||||
}
|
||||
|
||||
if (trans.getTradeSettlement() != null) {
|
||||
for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
|
||||
@@ -837,12 +843,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if (trans.getDueDate() != null) {
|
||||
xml += "<ram:DueDateDateTime>" // $NON-NLS-2$
|
||||
+ DATE.udtFormat(trans.getDueDate())
|
||||
+ "</ram:DueDateDateTime>";// 20130704
|
||||
|
||||
}
|
||||
xml += "</ram:SpecifiedTradePaymentTerms>";
|
||||
} else {
|
||||
xml += buildPaymentTermsXml();
|
||||
@@ -893,6 +893,19 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
if (trans.getInvoiceReferencedDocuments() != null) {
|
||||
for (var doc : trans.getInvoiceReferencedDocuments()) {
|
||||
xml += "<ram:InvoiceReferencedDocument>"
|
||||
+ "<ram:IssuerAssignedID>"
|
||||
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";
|
||||
if (doc.getFormattedIssueDateTime() != null) {
|
||||
xml += "<ram:FormattedIssueDateTime>"
|
||||
+ DATE.qdtFormat(doc.getFormattedIssueDateTime())
|
||||
+ "</ram:FormattedIssueDateTime>";
|
||||
}
|
||||
xml += "</ram:InvoiceReferencedDocument>";
|
||||
}
|
||||
}
|
||||
|
||||
xml += "</ram:ApplicableHeaderTradeSettlement>";
|
||||
// + "<IncludedSupplyChainTradeLineItem>\n"
|
||||
|
||||
@@ -32,6 +32,9 @@ import java.nio.file.StandardOpenOption;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
public class ZUGFeRDInvoiceImporter {
|
||||
|
||||
@@ -472,9 +475,11 @@ public class ZUGFeRDInvoiceImporter {
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"DuePayableAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"PayableAmount\"]");
|
||||
NodeList lineDueNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
BigDecimal duePayableAmount = null;
|
||||
if (lineDueNodes.getLength() > 0) {
|
||||
duePayableAmount = new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0)));
|
||||
if (zpp instanceof CalculatedInvoice) {
|
||||
((CalculatedInvoice) zpp).setDuePayable(new BigDecimal(XMLTools.trimOrNull(lineDueNodes.item(0))));
|
||||
((CalculatedInvoice) zpp).setDuePayable(duePayableAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +821,23 @@ public class ZUGFeRDInvoiceImporter {
|
||||
}
|
||||
zpp.setInvoiceReferencedDocumentID(extractString("//*[local-name()=\"InvoiceReferencedDocument\"]/*[local-name()=\"IssuerAssignedID\"]|//*[local-name()=\"BillingReference\"]/*[local-name()=\"InvoiceDocumentReference\"]/*[local-name()=\"ID\"]"));
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"InvoiceReferencedDocument\"]");
|
||||
NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
if (nodes.getLength() != 0) {
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
|
||||
Node currentItemNode = nodes.item(i);
|
||||
ReferencedDocument doc = ReferencedDocument.fromNode(currentItemNode);
|
||||
if (doc != null
|
||||
&& (!Objects.equals(zpp.getInvoiceReferencedDocumentID(), doc.getIssuerAssignedID())
|
||||
|| !Objects.equals(zpp.getInvoiceReferencedIssueDate(), doc.getFormattedIssueDateTime())))
|
||||
{
|
||||
zpp.addInvoiceReferencedDocument(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zpp.setOwnOrganisationName(extractString("//*[local-name()=\"SellerTradeParty\"]/*[local-name()=\"Name\"]|//*[local-name()=\"AccountingSupplierParty\"]/*[local-name()=\"Party\"]/*[local-name()=\"PartyName\"]").trim());
|
||||
|
||||
String rounding = extractString("//*[local-name()=\"SpecifiedTradeSettlementHeaderMonetarySummation\"]/*[local-name()=\"RoundingAmount\"]|//*[local-name()=\"LegalMonetaryTotal\"]/*[local-name()=\"Party\"]/*[local-name()=\"PayableRoundingAmount\"]");
|
||||
@@ -834,7 +856,7 @@ public class ZUGFeRDInvoiceImporter {
|
||||
}
|
||||
|
||||
xpr = xpath.compile("//*[local-name()=\"IncludedSupplyChainTradeLineItem\"]|//*[local-name()=\"InvoiceLine\"]");
|
||||
NodeList nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
nodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
|
||||
|
||||
if (nodes.getLength() != 0) {
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
@@ -938,8 +960,7 @@ public class ZUGFeRDInvoiceImporter {
|
||||
|
||||
|
||||
TransactionCalculator tc = new TransactionCalculator(zpp);
|
||||
String expectedStringTotalGross = tc.getGrandTotal()
|
||||
.subtract(Objects.requireNonNullElse(zpp.getTotalPrepaidAmount(), BigDecimal.ZERO)).toPlainString();
|
||||
String calculatedPayableTotal = tc.getDuePayable().toPlainString();
|
||||
EStandard whichType;
|
||||
try {
|
||||
whichType = getStandard();
|
||||
@@ -947,10 +968,21 @@ public class ZUGFeRDInvoiceImporter {
|
||||
throw new StructureException("Could not find out if it's an invoice, order, or delivery advice", 0);
|
||||
}
|
||||
|
||||
if ((whichType != EStandard.despatchadvice)
|
||||
&& ((!expectedStringTotalGross.equals(XMLTools.nDigitFormat(expectedGrandTotal, 2)))
|
||||
&& (!ignoreCalculationErrors))) {
|
||||
throw new ArithmetricException();
|
||||
if (whichType != EStandard.despatchadvice && !ignoreCalculationErrors) {
|
||||
// Check calculation if document type allows it and calculation errors should not be ignored
|
||||
|
||||
String payableTotalFromXml = XMLTools.nDigitFormat(Objects.requireNonNullElse(duePayableAmount, expectedGrandTotal), 2);
|
||||
if (!calculatedPayableTotal.equals(payableTotalFromXml)) {
|
||||
String moreDetails = "";
|
||||
try {
|
||||
moreDetails = " with tax basis " + tc.getTaxBasis() + " and with positions " + tc.getTotal() + " = "
|
||||
+ Stream.of(tc.trans.getZFItems())
|
||||
.map(item -> new LineCalculator(item).getItemTotalNetAmount().toPlainString())
|
||||
.collect(Collectors.joining(" + "));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
throw new ArithmetricException("Payable total in XML is " + payableTotalFromXml + ", but calculated total is " + calculatedPayableTotal + moreDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
return zpp;
|
||||
@@ -1068,5 +1100,4 @@ public class ZUGFeRDInvoiceImporter {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,42 +20,9 @@
|
||||
*/
|
||||
package org.mustangproject.ZUGFeRD;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Templates;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.URIResolver;
|
||||
import javax.xml.transform.sax.SAXResult;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.apache.fop.apps.FOPException;
|
||||
import org.apache.fop.apps.FOUserAgent;
|
||||
import org.apache.fop.apps.Fop;
|
||||
import org.apache.fop.apps.FopFactory;
|
||||
import org.apache.fop.apps.FopFactoryBuilder;
|
||||
import com.helger.commons.io.stream.StreamHelper;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.fop.apps.*;
|
||||
import org.apache.fop.apps.io.ResourceResolverFactory;
|
||||
import org.apache.fop.configuration.Configuration;
|
||||
import org.apache.fop.configuration.ConfigurationException;
|
||||
@@ -65,12 +32,19 @@ import org.mustangproject.ClasspathResolverURIAdapter;
|
||||
import org.mustangproject.EStandard;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.helger.commons.io.stream.StreamHelper;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.*;
|
||||
import javax.xml.transform.sax.SAXResult;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ZUGFeRDVisualizer {
|
||||
|
||||
@@ -113,7 +87,7 @@ public class ZUGFeRDVisualizer {
|
||||
* @param fis inputstream (will be consumed)
|
||||
* @return (facturx = cii)
|
||||
*/
|
||||
public EStandard findOutStandardFromRootNode(InputStream fis) {
|
||||
private EStandard findOutStandardFromRootNode(InputStream fis) {
|
||||
|
||||
String zf1Signature = "CrossIndustryDocument";
|
||||
String zf2Signature = "CrossIndustryInvoice";
|
||||
@@ -144,106 +118,97 @@ public class ZUGFeRDVisualizer {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String visualize(String xmlFilename, Language lang)
|
||||
throws FileNotFoundException, TransformerException, IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
try {
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xr-pdf.xsl")));
|
||||
}
|
||||
if (mXsltHTMLTemplate == null) {
|
||||
mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
|
||||
}
|
||||
if (mXsltZF1HTMLTemplate == null) {
|
||||
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
|
||||
}
|
||||
} catch (TransformerConfigurationException ex) {
|
||||
LOGGER.error("Failed to init XSLT templates", ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* *
|
||||
* http://www.unece.org/fileadmin/DAM/cefact/xml/XML-Naming-And-Design-Rules-V2_1.pdf
|
||||
* http://www.ferd-net.de/upload/Dokumente/FACTUR-X_ZUGFeRD_2p0_Teil1_Profil_EN16931_1p03.pdf
|
||||
* http://countwordsfree.com/xmlviewer
|
||||
*/
|
||||
public String visualize(String xmlFilename, Language lang) throws IOException, TransformerException {
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
String fileContent = "";
|
||||
try {
|
||||
fileContent = new String(Files.readAllBytes(Paths.get(xmlFilename)), StandardCharsets.UTF_8);
|
||||
} catch (IOException e2) {
|
||||
LOGGER.error("Failed to read file content", e2);
|
||||
}
|
||||
return visualize(fis, lang);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
public String visualize(InputStream inputXml, Language lang) throws IOException, TransformerException {
|
||||
initTemplates(lang);
|
||||
|
||||
boolean doPostProcessing = false;
|
||||
String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8);
|
||||
EStandard thestandard = findOutStandardFromRootNode(new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream htmlOutput = new ByteArrayOutputStream();
|
||||
|
||||
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
EStandard thestandard = findOutStandardFromRootNode(fis);
|
||||
fis = new FileInputStream(xmlFilename); // fis wont reset() so re-read from beginning
|
||||
ByteArrayInputStream xmlContentStream = new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
if (thestandard == EStandard.zugferd) {
|
||||
applyZF1XSLT(fis, baos);
|
||||
applyZF1XSLT(xmlContentStream, htmlOutput);
|
||||
return htmlOutput.toString(StandardCharsets.UTF_8);
|
||||
} else if (thestandard == EStandard.facturx) {
|
||||
//zf2 or fx
|
||||
applyZF2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
applyZF2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.ubl) {
|
||||
//zf2 or fx
|
||||
applyUBL2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
applyUBL2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.ubl_creditnote) {
|
||||
//zf2 or fx
|
||||
applyUBLCreditNote2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
applyUBLCreditNote2XSLT(xmlContentStream, htmlOutput);
|
||||
} else if (thestandard == EStandard.orderx) {
|
||||
//zf2 or fx
|
||||
applyCIO2XSLT(fis, iaos);
|
||||
doPostProcessing = true;
|
||||
applyCIO2XSLT(xmlContentStream, htmlOutput);
|
||||
} else {
|
||||
throw new IllegalArgumentException("File does not look like CII or UBL");
|
||||
}
|
||||
if (doPostProcessing) {
|
||||
// take the copy of the stream and re-write it to an InputStream
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
PipedOutputStream out;
|
||||
try {
|
||||
out = new PipedOutputStream(in);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
// write the original OutputStream to the PipedOutputStream
|
||||
// note that in order for the below method to work, you need
|
||||
// to ensure that the data has finished writing to the
|
||||
// ByteArrayOutputStream
|
||||
iaos.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
applyXSLTToHTML(in, baos);
|
||||
} catch (IOException e1) {
|
||||
LOGGER.error("Failed to create HTML", e1);
|
||||
}
|
||||
|
||||
Optional<InputStream> in = copyStream(htmlOutput);
|
||||
ByteArrayOutputStream htmlOutStream = new ByteArrayOutputStream();
|
||||
if (in.isPresent()) {
|
||||
applyXSLTToHTML(in.get(), htmlOutStream);
|
||||
}
|
||||
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
return htmlOutStream.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: jstaerk: why not copy with that simple call: new ByteArrayInputStream(byteArrayOutputStream.toByteArray()) ?
|
||||
*/
|
||||
private Optional<InputStream> copyStream(ByteArrayOutputStream byteArrayOutputStream) {
|
||||
// take the copy of the stream and re-write it to an InputStream
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
try {
|
||||
PipedOutputStream out = new PipedOutputStream(in);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// write the original OutputStream to the PipedOutputStream
|
||||
// note that in order for the below method to work, you need
|
||||
// to ensure that the data has finished writing to the
|
||||
// ByteArrayOutputStream
|
||||
byteArrayOutputStream.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}).start();
|
||||
} catch (IOException e1) {
|
||||
LOGGER.error("Failed to create HTML", e1);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(in);
|
||||
}
|
||||
|
||||
|
||||
private void initTemplates(Language lang) throws TransformerConfigurationException {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/cii-xr.xsl")));
|
||||
}
|
||||
|
||||
if (mXsltHTMLTemplate == null) {
|
||||
mXsltHTMLTemplate = mFactory.newTemplates(new StreamSource(
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/xrechnung-html." + lang.name().toLowerCase() + ".xsl")));
|
||||
}
|
||||
if (mXsltZF1HTMLTemplate == null) {
|
||||
mXsltZF1HTMLTemplate = mFactory.newTemplates(new StreamSource(
|
||||
CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/ZUGFeRD_1p0_c1p0_s1p0.xslt")));
|
||||
}
|
||||
}
|
||||
|
||||
protected String toFOP(String xmlFilename)
|
||||
throws FileNotFoundException, TransformerException {
|
||||
throws IOException, TransformerException {
|
||||
|
||||
FileInputStream fis = new FileInputStream(xmlFilename);
|
||||
EStandard theStandard = findOutStandardFromRootNode(fis);
|
||||
@@ -253,8 +218,8 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
|
||||
protected String toFOP(InputStream is, EStandard theStandard)
|
||||
throws FileNotFoundException, TransformerException {
|
||||
|
||||
throws TransformerException, IOException {
|
||||
|
||||
try {
|
||||
if (mXsltPDFTemplate == null) {
|
||||
mXsltPDFTemplate = mFactory.newTemplates(
|
||||
@@ -265,7 +230,6 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
|
||||
ByteArrayOutputStream iaos = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
//zf2 or fx
|
||||
if (theStandard == EStandard.facturx) {
|
||||
@@ -277,33 +241,11 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
|
||||
|
||||
PipedInputStream in = new PipedInputStream();
|
||||
PipedOutputStream out;
|
||||
try {
|
||||
out = new PipedOutputStream(in);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
// write the original OutputStream to the PipedOutputStream
|
||||
// note that in order for the below method to work, you need
|
||||
// to ensure that the data has finished writing to the
|
||||
// ByteArrayOutputStream
|
||||
iaos.writeTo(out);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write to stream", e);
|
||||
} finally {
|
||||
// close the PipedOutputStream here because we're done writing data
|
||||
// once this thread has completed its run
|
||||
StreamHelper.close(out);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
applyXSLTToPDF(in, baos);
|
||||
} catch (IOException e1) {
|
||||
LOGGER.error("Failed to create PDF", e1);
|
||||
Optional<InputStream> in = copyStream(iaos);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (in.isPresent()) {
|
||||
applyXSLTToPDF(in.get(), baos);
|
||||
}
|
||||
|
||||
|
||||
return baos.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@@ -319,7 +261,7 @@ public class ZUGFeRDVisualizer {
|
||||
*/
|
||||
try {
|
||||
result = this.toFOP(XMLinputFile.getAbsolutePath());
|
||||
} catch (FileNotFoundException | TransformerException e) {
|
||||
} catch (TransformerException | IOException e) {
|
||||
LOGGER.error("Failed to apply FOP", e);
|
||||
}
|
||||
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
|
||||
@@ -373,7 +315,7 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
if (mXsltXRTemplate == null) {
|
||||
mXsltXRTemplate = mFactory.newTemplates(
|
||||
@@ -382,10 +324,10 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
Transformer transformer = mXsltXRTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyCIO2XSLT(final InputStream xmlFile, final OutputStream htmlOutstream)
|
||||
throws TransformerException {
|
||||
if (mXsltCIOTemplate == null) {
|
||||
mXsltCIOTemplate = mFactory.newTemplates(
|
||||
@@ -393,10 +335,10 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
Transformer transformer = mXsltCIOTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutstream));
|
||||
}
|
||||
|
||||
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyUBL2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
if (mXsltUBLTemplate == null) {
|
||||
mXsltUBLTemplate = mFactory.newTemplates(
|
||||
@@ -404,10 +346,10 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
Transformer transformer = mXsltUBLTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyUBLCreditNote2XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
if (mXsltUBLTemplate == null) {
|
||||
mXsltUBLTemplate = mFactory.newTemplates(
|
||||
@@ -415,28 +357,30 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
Transformer transformer = mXsltUBLTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
protected void applyZF1XSLT(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException {
|
||||
Transformer transformer = mXsltZF1HTMLTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
}
|
||||
|
||||
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream HTMLOutstream)
|
||||
throws TransformerException {
|
||||
protected void applyXSLTToHTML(final InputStream xmlFile, final OutputStream htmlOutStream)
|
||||
throws TransformerException, IOException {
|
||||
Transformer transformer = mXsltHTMLTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(HTMLOutstream));
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(htmlOutStream));
|
||||
xmlFile.close();
|
||||
}
|
||||
|
||||
protected void applyXSLTToPDF(final InputStream xmlFile, final OutputStream PDFOutstream)
|
||||
throws TransformerException {
|
||||
throws TransformerException, IOException {
|
||||
Transformer transformer = mXsltPDFTemplate.newTransformer();
|
||||
|
||||
transformer.transform(new StreamSource(xmlFile), new StreamResult(PDFOutstream));
|
||||
xmlFile.close();
|
||||
}
|
||||
|
||||
private static class ClasspathResourceURIResolver implements URIResolver {
|
||||
@@ -445,7 +389,7 @@ public class ZUGFeRDVisualizer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Source resolve(String href, String base) throws TransformerException {
|
||||
public Source resolve(String href, String base) {
|
||||
return new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/" + href));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user