Merge branch 'master' into issues/458-multiple-payment-details

This commit is contained in:
Jochen Staerk
2025-04-22 11:10:10 +02:00
committed by GitHub
47 changed files with 2487 additions and 256 deletions

View File

@@ -134,7 +134,10 @@ public class Charge implements IZUGFeRDAllowanceCharge {
if (totalAmount!=null) {
return totalAmount;
} else {
throw new RuntimeException("totalAmount must be set");
if (percent==null) {
throw new RuntimeException("totalAmount must be set");
}
return null;
}
}

View File

@@ -9,7 +9,7 @@ public class FileAttachment {
protected String filename;
protected String mimetype;
protected String relation;
protected String relation = "Unspecified";
protected String description;
protected byte[] data;
@@ -29,6 +29,13 @@ public class FileAttachment {
this.description = "Additional file attachment";
}
public FileAttachment(String filename, String mimetype, byte[] data) {
this.filename = filename;
this.mimetype = mimetype;
this.data = data;
this.description = "Additional file attachment";
}
public String getDescription() {
return description;
}
@@ -60,6 +67,28 @@ public class FileAttachment {
return relation;
}
/***
* only needed when embedded in PDF described
*
* values
* - Source shall be used if this file specification is the original
* source material for the associated content.
* - Data shall be used if this file specification represents information
* used to derive a visual presentation, such as for a table or a
* graph.
* - Alternative shall be used if this file specification is an alternative
* representation of content, for example audio.
* - Supplement shall be used if this file specification represents a
* supplemental representation of the original source or data that
* may be more easily consumable (e.g. A MathML version of an
* equation).
* - Unspecified shall be used when the relationship is not known
* or cannot be described using one of the other values.
* @param relation String: either : Source, Data or Alternative. Usually Data, except source if the file attachment
* is the basis for the pdf (xrechnung2fx) or Alternative if it contains the same content (e.g. the
* factur-x.xml file in a factur-x PDF)
* @return fluent setter
*/
public FileAttachment setRelation(String relation) {
this.relation = relation;
return this;

View File

@@ -69,6 +69,7 @@ public class Invoice implements IExportableTransaction {
protected String vatDueDateTypeCode = null;
protected String creditorReferenceID; // required when direct debit is used.
private BigDecimal roundingAmount=null;
private String paymentReference; // Remittance information / Verwendungszweck, BT-83
public Invoice() {
ZFItems = new ArrayList<>();
@@ -124,7 +125,11 @@ public class Invoice implements IExportableTransaction {
* @return fluent setter
*/
public Invoice setAdditionalReferencedDocuments(FileAttachment[] fileArr) {
xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr));
if (fileArr!=null) {
xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr));
} else {
xmlEmbeddedFiles = new ArrayList<>();
}
return this;
}
@@ -153,7 +158,6 @@ public class Invoice implements IExportableTransaction {
*/
public Invoice setCorrection(String number) {
setInvoiceReferencedDocumentID(number);
addInvoiceReferencedDocument(new ReferencedDocument(number));
documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
return this;
}
@@ -679,6 +683,15 @@ public class Invoice implements IExportableTransaction {
}
public String getPaymentReference() {
return paymentReference;
}
public Invoice setPaymentReference(String paymentReference) {
this.paymentReference = paymentReference;
return this;
}
@Override
public TradeParty getDeliveryAddress() {
return deliveryAddress;

View File

@@ -42,6 +42,7 @@ public class Item implements IZUGFeRDExportableItem {
protected ArrayList<IZUGFeRDAllowanceCharge> Allowances = new ArrayList<>();
protected ArrayList<IZUGFeRDAllowanceCharge> Charges = new ArrayList<>();
protected List<IncludedNote> includedNotes = null;
protected String accountingReference;
//protected HashMap<String, String> attributes = new HashMap<>();
/***
@@ -172,7 +173,10 @@ public class Item implements IZUGFeRDExportableItem {
icnm.getAsNodeMap("ApplicableTradeTax")
.flatMap(cnm -> cnm.getAsBigDecimal("RateApplicablePercent", "ApplicablePercent"))
.ifPresent(product::setVATPercent);
icnm.getAsNodeMap("SpecifiedTradeAllowanceCharge").ifPresent(stac -> {
icnm.getAsNodeMap("ApplicableTradeTax")
.flatMap(cnm -> cnm.getAsString("ExemptionReason"))
.ifPresent(product::setTaxExemptionReason);
icnm.getAllNodes("SpecifiedTradeAllowanceCharge").map(NodeMap::new).forEach(stac -> {
stac.getAsNodeMap("ChargeIndicator").ifPresent(ci -> {
String isChargeString=ci.getAsString("Indicator").get();
String percentString=stac.getAsStringOrNull("CalculationPercent");
@@ -187,8 +191,12 @@ public class Item implements IZUGFeRDExportableItem {
if (amountString!=null) {
izac.setTotalAmount(new BigDecimal(amountString));
}
if(percentString!=null) {
izac.setPercent(new BigDecimal(percentString));
}
if(reason!=null) {
izac.setReason(reason);
}
if (isChargeString.equalsIgnoreCase("false")) {
addAllowance(izac);
@@ -206,6 +214,14 @@ public class Item implements IZUGFeRDExportableItem {
}
icnm.getAllNodes("AdditionalReferencedDocument").map(ReferencedDocument::fromNode).forEach(this::addAdditionalReference);
icnm.getAsString("ReceivableSpecifiedTradeAccountingAccount").ifPresent(s -> this.accountingReference = s == null ? null : s.trim());
icnm.getAsNodeMap("BillingSpecifiedPeriod").ifPresent(periodNode -> {
Date start = periodNode.getAsNodeMap("StartDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
Date end = periodNode.getAsNodeMap("EndDateTime").flatMap(dateTimeNode -> dateTimeNode.getNode("DateTimeString")).map(dts -> XMLTools.tryDate(dts)).orElse(null);
setDetailedDeliveryPeriod(start, end);
});
});
itemMap.getAsNodeMap("AssociatedDocumentLineDocument").ifPresent(adld -> {
@@ -353,6 +369,29 @@ public class Item implements IZUGFeRDExportableItem {
return Allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
}
/***
* jackson convenience method
*/
public void setItemAllowances(ArrayList<Allowance> theAllowances) {
if (theAllowances!=null) {
Allowances.clear();
for (Allowance theAllowance : theAllowances) {
Allowances.add(theAllowance);
}
}
}
/***
* jackson convenience method
*/
public void setItemCharges(ArrayList<Charge> theCharges) {
if (theCharges!=null) {
Charges.clear();
for (Charge theCharge : theCharges) {
Charges.add(theCharge);
}
}
}
@Override
public IZUGFeRDAllowanceCharge[] getItemCharges() {
if (Charges.isEmpty()) {
@@ -525,4 +564,9 @@ public class Item implements IZUGFeRDExportableItem {
public List<IncludedNote> getNotesWithSubjectCode() {
return includedNotes;
}
@Override
public String getAccountingReference() {
return accountingReference;
}
}

View File

@@ -23,6 +23,10 @@ public class LegalOrganisation implements IZUGFeRDLegalOrganisation {
this.schemedID = new SchemedID(scheme, ID);
}
public LegalOrganisation(String ID) {
this.schemedID = new SchemedID(null, ID);
}
public LegalOrganisation(SchemedID schemedID, String tradingBusinessName) {
this.schemedID = schemedID;
this.tradingBusinessName=tradingBusinessName;

View File

@@ -69,7 +69,7 @@ public class Product implements IZUGFeRDExportableProduct {
nodeMap.getAsString("Description").ifPresent(this::setDescription);
nodeMap.getAsNodeMap("ApplicableProductCharacteristic").ifPresent(apcNodes -> {
nodeMap.getAllNodes("ApplicableProductCharacteristic").map(NodeMap::new).forEach(apcNodes -> { // ApplicableProductCharacteristic is 0 .. unbounded
String key = apcNodes.getAsStringOrNull("Description");
String value = apcNodes.getAsStringOrNull("Value");
if (key != null && value != null) {
@@ -80,7 +80,6 @@ public class Product implements IZUGFeRDExportableProduct {
}
});
//UBL
nodeMap.getAsNodeMap("AdditionalItemProperty").ifPresent(aipNodes -> {
String name = aipNodes.getAsStringOrNull("Name");

View File

@@ -129,7 +129,16 @@ public class XMLTools extends XMLWriter {
* @return a util.Date, or null, if not parseable
*/
public static Date tryDate(String toParse) {
final SimpleDateFormat formatter = ZUGFeRDDateFormat.DATE.getFormatter();
SimpleDateFormat formatter = null;
if (toParse==null) {
return null;
}
if (toParse.contains("-")) {
// from ubl
formatter = new SimpleDateFormat("yyyy-MM-dd");
} else {
formatter = ZUGFeRDDateFormat.DATE.getFormatter();
}
try {
return formatter.parse(toParse);
} catch (final Exception e) {
@@ -211,7 +220,8 @@ public class XMLTools extends XMLWriter {
}
public static byte[] getBytesFromStream(InputStream fileinput) throws IOException {
return IOUtils.toByteArray (fileinput);
// Stream closing responsibility is with the caller
return IOUtils.toByteArray(fileinput);
}

View File

@@ -313,6 +313,10 @@ public interface IExportableTransaction {
return null;
}
default String getPaymentReference() {
return null;
}
/**
* Get payment terms for the EXTENDED profile (multiple terms are allowed)
* @return

View File

@@ -172,4 +172,8 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{
default List<IncludedNote> getNotesWithSubjectCode() {
return null;
}
default String getAccountingReference() {
return null;
}
}

View File

@@ -32,6 +32,7 @@ import java.util.Map;
import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.XMLTools;
public class OXPullProvider extends ZUGFeRD2PullProvider {
@@ -460,7 +461,7 @@ public class OXPullProvider extends ZUGFeRD2PullProvider {
xml += "</ram:InvoiceReferencedDocument>";
}
if (trans.getInvoiceReferencedDocuments() != null) {
for (var doc : trans.getInvoiceReferencedDocuments()) {
for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) {
xml += "<ram:InvoiceReferencedDocument>"
+ "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";

View File

@@ -13,14 +13,14 @@ import org.apache.pdfbox.preflight.parser.PreflightParser;
import jakarta.activation.DataSource;
// Copied from PDFBox preflight 2.0.x
// Copied from PDFBox preflight 2.0.x
final class ByteArrayDataSource implements DataSource
{
private ByteArrayOutputStream data;
private String type = null;
private String name = null;
public ByteArrayDataSource (InputStream is) throws IOException
public ByteArrayDataSource (final InputStream is) throws IOException
{
data = new ByteArrayOutputStream ();
IOUtils.copy (is, data);
@@ -36,7 +36,7 @@ final class ByteArrayDataSource implements DataSource
* @param type
* the type to set
*/
public void setType (String type)
public void setType (final String type)
{
this.type = type;
}
@@ -45,7 +45,7 @@ final class ByteArrayDataSource implements DataSource
* @param name
* the name to set
*/
public void setName (String name)
public void setName (final String name)
{
this.name = name;
}
@@ -70,12 +70,13 @@ final class ByteArrayDataSource implements DataSource
// Try to create an API similar to the 2.x one
final class PreflightParserHelper
{
private static File createTmpFile (InputStream input) throws IOException
private static File createTmpFile (final InputStream input) throws IOException
{
FileOutputStream fos = null;
try
{
File tmpFile = File.createTempFile ("mustang-pdf", ".pdf");
final File tmpFile = File.createTempFile ("mustang-pdf", ".pdf");
tmpFile.deleteOnExit ();
fos = new FileOutputStream (tmpFile);
IOUtils.copy (input, fos);
return tmpFile;
@@ -87,7 +88,7 @@ final class PreflightParserHelper
}
}
public static PreflightParser createPreflightParser (DataSource dataSource) throws IOException
public static PreflightParser createPreflightParser (final DataSource dataSource) throws IOException
{
return new PreflightParser (createTmpFile (dataSource.getInputStream ()));
}

View File

@@ -1,5 +1,13 @@
package org.mustangproject.ZUGFeRD;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import org.apache.fop.apps.*;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.fop.configuration.Configuration;
@@ -10,11 +18,12 @@ import org.mustangproject.ClasspathResolverURIAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.XMLConstants;
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;
public class ValidationLogVisualizer {
@@ -70,7 +79,7 @@ public class ValidationLogVisualizer {
return baos.toString(StandardCharsets.UTF_8);
}
public void toPDF(String xmlLogfileContent, String pdfFilename) {
public byte[] createPDFBytes(String xmlLogfileContent) {
// the writing part
@@ -111,13 +120,19 @@ public class ValidationLogVisualizer {
// Step 2: Set up output stream.
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (OutputStream out = new BufferedOutputStream(baos)) {
// Step 3: Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
// Step 4: Setup JAXP using identity transformer
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
Transformer transformer = factory.newTransformer(); // identity transformer
// Step 5: Setup input and output for XSLT transformation
@@ -133,6 +148,20 @@ public class ValidationLogVisualizer {
} catch (FOPException | IOException | TransformerException e) {
LOGGER.error("Failed to create PDF", e);
}
return baos.toByteArray();
}
public byte[] toPDF(String xmlLogfileContent) {
return createPDFBytes(xmlLogfileContent);
}
public void toPDF(String xmlLogfileContent, String pdfFilename) {
byte[] pdfData = createPDFBytes(xmlLogfileContent);
try (FileOutputStream fos = new FileOutputStream(pdfFilename)) {
fos.write(pdfData);
} catch (IOException e) {
LOGGER.error("Failed to write PDF to file", e);
}
}
private static class ClasspathResourceURIResolver implements URIResolver {

View File

@@ -45,6 +45,7 @@ import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.mustangproject.FileAttachment;
import org.mustangproject.IncludedNote;
import org.mustangproject.ReferencedDocument;
import org.mustangproject.XMLTools;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;
import org.slf4j.Logger;
@@ -139,7 +140,9 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "<ram:GlobalID schemeID=\"" + XMLTools.encodeXML(party.getGlobalIDScheme()) + "\">"
+ XMLTools.encodeXML(party.getGlobalID()) + "</ram:GlobalID>";
}
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
if (party.getName() != null && !party.getName().isEmpty()) {
xml += "<ram:Name>" + XMLTools.encodeXML(party.getName()) + "</ram:Name>";
}
if (party.getDescription() != null) {
xml += "<ram:Description>" + XMLTools.encodeXML(party.getDescription()) + "</ram:Description>";
}
@@ -149,7 +152,12 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if (profile == Profiles.getByName("Minimum")) {
xml += "<ram:ID>" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
} else {
xml += "<ram:ID schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme()) + "\">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
String schemeAttribute="";
if ((party.getLegalOrganisation().getSchemedID().getScheme()!=null)&&(party.getLegalOrganisation().getSchemedID().getScheme().length()>0)) {
schemeAttribute="schemeID=\"" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getScheme())+"\"";
}
xml += "<ram:ID "+schemeAttribute+">" + XMLTools.encodeXML(party.getLegalOrganisation().getSchemedID().getID()) + "</ram:ID>";
}
}
if (party.getLegalOrganisation().getTradingBusinessName() != null) {
@@ -277,7 +285,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
String reason = "";
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung")) || profile == Profiles.getByName("EN16931")) {
if ((allowance.getReason() != null) && (profile == Profiles.getByName("Extended") || profile == Profiles.getByName("XRechnung") || profile == Profiles.getByName("EN16931"))) {
reason = "<ram:Reason>" + XMLTools.encodeXML(allowance.getReason()) + "</ram:Reason>";
}
String reasonCode = "";
@@ -337,8 +345,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
boolean hasDueDate = trans.getDueDate() != null;
final SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
String exemptionReason = "";
if (trans.getPaymentTermDescription() != null) {
paymentTermsDescription = XMLTools.encodeXML(trans.getPaymentTermDescription());
}
@@ -388,14 +394,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "</ram:GuidelineSpecifiedDocumentContextParameter>"
+ "</rsm:ExchangedDocumentContext>"
+ "<rsm:ExchangedDocument>"
+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>"
// + "<ram:Name>RECHNUNG</ram:Name>"
// + "<ram:TypeCode>380</ram:TypeCode>"
+ "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
+ "<ram:IssueDateTime>"
+ DATE.udtFormat(trans.getIssueDate()) + "</ram:IssueDateTime>" // date
+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>";
if (profile == Profiles.getByName("Extended") && trans.getDocumentName() != null) {
xml += "<ram:Name>" + XMLTools.encodeXML(trans.getDocumentName()) + "</ram:Name>";
}
xml += "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
+ "<ram:IssueDateTime>" + DATE.udtFormat(trans.getIssueDate()) + "</ram:IssueDateTime>" // date
+ buildNotes(trans)
+ "</rsm:ExchangedDocument>"
+ "<rsm:SupplyChainTradeTransaction>";
int lineID = 0;
@@ -405,9 +410,6 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if (currentItem.getId()!=null) {
lineIDStr=currentItem.getId();
}
if (currentItem.getProduct().getTaxExemptionReason() != null) {
exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
}
final LineCalculator lc = new LineCalculator(currentItem);
if ((getProfile() != Profiles.getByName("Minimum")) && (getProfile() != Profiles.getByName("BasicWL"))) {
xml += "<ram:IncludedSupplyChainTradeLineItem>" +
@@ -450,7 +452,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
xml += "<ram:Name>" + XMLTools.encodeXML(currentItem.getProduct().getName()) + "</ram:Name>";
if (currentItem.getProduct().getDescription().length() > 0) {
if (currentItem.getProduct().getDescription() != null && currentItem.getProduct().getDescription().length() > 0) {
xml += "<ram:Description>" +
XMLTools.encodeXML(currentItem.getProduct().getDescription()) +
"</ram:Description>";
@@ -458,7 +460,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if (currentItem.getProduct().getClassifications() != null && currentItem.getProduct().getClassifications().length > 0) {
for (IDesignatedProductClassification classification : currentItem.getProduct().getClassifications()) {
xml += "<ram:DesignatedProductClassification>"
+ "<ram:ClassCode listId=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
+ "<ram:ClassCode listID=\"" + XMLTools.encodeXML(classification.getClassCode().getListID()) + "\"";
if (classification.getClassCode().getListVersionID() != null) {
xml += " listVersionID=\"" + XMLTools.encodeXML(classification.getClassCode().getListVersionID()) + "\"";
}
@@ -528,9 +530,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
+ "</ram:SpecifiedLineTradeDelivery>"
+ "<ram:SpecifiedLineTradeSettlement>"
+ "<ram:ApplicableTradeTax>"
+ "<ram:TypeCode>VAT</ram:TypeCode>"
+ exemptionReason
+ "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>"
+ "<ram:TypeCode>VAT</ram:TypeCode>";
if (currentItem.getProduct().getTaxExemptionReason() != null) {
xml += "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
}
xml += "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>"
+ "<ram:RateApplicablePercent>"
+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:RateApplicablePercent>"
+ "</ram:ApplicableTradeTax>";
@@ -662,8 +668,8 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
if ((trans.getCreditorReferenceID() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
xml += "<ram:CreditorReferenceID>" + XMLTools.encodeXML(trans.getCreditorReferenceID()) + "</ram:CreditorReferenceID>";
}
if ((trans.getNumber() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>";
if ((trans.getPaymentReference() != null) && (getProfile() != Profiles.getByName("Minimum"))) {
xml += "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getPaymentReference()) + "</ram:PaymentReference>";
}
xml += "<ram:InvoiceCurrencyCode>" + trans.getCurrency() + "</ram:InvoiceCurrencyCode>";
if (this.trans.getPayee() != null) {
@@ -894,7 +900,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
xml += "</ram:InvoiceReferencedDocument>";
}
if (trans.getInvoiceReferencedDocuments() != null) {
for (var doc : trans.getInvoiceReferencedDocuments()) {
for (ReferencedDocument doc : trans.getInvoiceReferencedDocuments()) {
xml += "<ram:InvoiceReferencedDocument>"
+ "<ram:IssuerAssignedID>"
+ XMLTools.encodeXML(doc.getIssuerAssignedID()) + "</ram:IssuerAssignedID>";

View File

@@ -58,6 +58,7 @@ import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.AdobePDFSchema;
@@ -559,6 +560,7 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
// https://github.com/ZUGFeRD/mustangproject/issues/249
COSName cidSet = COSName.getPDFName("CIDSet");
COSName resources = COSName.getPDFName("Resources");
// iterate over all pdf pages
@@ -567,29 +569,45 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
PDPage page = (PDPage) object;
PDResources res = page.getResources();
for (COSName fontName : res.getFontNames()) {
try {
PDFont pdFont = res.getFont(fontName);
if (pdFont instanceof PDType0Font) {
PDType0Font typedFont = (PDType0Font) pdFont;
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
@SuppressWarnings("unused")
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
fontDescriptor.getCOSObject().removeItem(cidSet);
}
}
} catch (IOException e) {
throw e;
// Check for fonts in PDXObjects:
for (COSName xObjectName : res.getXObjectNames()) {
PDXObject xObject = res.getXObject(xObjectName);
COSDictionary d = xObject.getCOSObject().getCOSDictionary(resources);
if (d != null) {
PDResources xr = new PDResources(d);
removeCIDSetFromPDResources(cidSet, xr);
}
// do stuff with the font
}
// Check for fonts in document-resources:
removeCIDSetFromPDResources(cidSet, res);
}
}
}
private void removeCIDSetFromPDResources(COSName cidSet, PDResources res) throws IOException {
for (COSName fontName : res.getFontNames()) {
try {
PDFont pdFont = res.getFont(fontName);
if (pdFont instanceof PDType0Font) {
PDType0Font typedFont = (PDType0Font) pdFont;
if (typedFont.getDescendantFont() instanceof PDCIDFontType2) {
@SuppressWarnings("unused")
PDCIDFontType2 f = (PDCIDFontType2) typedFont.getDescendantFont();
PDFontDescriptor fontDescriptor = pdFont.getFontDescriptor();
fontDescriptor.getCOSObject().removeItem(cidSet);
}
}
} catch (IOException e) {
throw e;
}
// do stuff with the font
}
}
protected void prepareDocument() throws IOException {
PDDocumentCatalog cat = doc.getDocumentCatalog();

View File

@@ -90,9 +90,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
protected byte[] inputstreamToByteArray(InputStream fileInputStream) throws IOException {
byte[] bytes = new byte[fileInputStream.available()];
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
dataInputStream.readFully(bytes);
return bytes;
try (DataInputStream dataInputStream = new DataInputStream(fileInputStream)) {
dataInputStream.readFully(bytes);
return bytes;
}
}
/***

View File

@@ -86,6 +86,7 @@ public class ZUGFeRDImporter extends ZUGFeRDInvoiceImporter {
case "urn:factur-x.eu:1p0:minimum":
return "MINIMUM";
case "urn:ferd:CrossIndustryDocument:invoice:1p0:extended":
case "urn:cen.eu:en16931:2017#conformant#urn:zugferd.de:2p0:extended":
case "urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended":
return "EXTENDED";
default:

View File

@@ -1,5 +1,6 @@
package org.mustangproject.ZUGFeRD;
import javax.xml.XMLConstants;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
@@ -137,9 +138,9 @@ public class ZUGFeRDInvoiceImporter {
return;
}
final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata();
xmpString = new String(XMLTools.getBytesFromStream(XMP), StandardCharsets.UTF_8);
try (final InputStream XMP = doc.getDocumentCatalog().getMetadata().exportXMPMetadata()) {
xmpString = new String(XMLTools.getBytesFromStream(XMP), StandardCharsets.UTF_8);
}
final PDEmbeddedFilesNameTreeNode etn = names.getEmbeddedFiles();
if (etn == null) {
@@ -258,9 +259,25 @@ public class ZUGFeRDInvoiceImporter {
}
private void setDocument() throws ParserConfigurationException, IOException, SAXException, ParseException {
final DocumentBuilderFactory xmlFact = DocumentBuilderFactory.newInstance();
xmlFact.setNamespaceAware(true);
final DocumentBuilder builder = xmlFact.newDocumentBuilder();
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
final DocumentBuilder builder = dbf.newDocumentBuilder();
final ByteArrayInputStream is = new ByteArrayInputStream(rawXML);
/// is.skip(guessBOMSize(is));
document = builder.parse(is);
@@ -288,6 +305,7 @@ public class ZUGFeRDInvoiceImporter {
public Invoice extractInto(Invoice zpp) throws XPathExpressionException, ParseException {
String number = "";
String documentName = null;
String typeCode = null;
String deliveryPeriodStart = null;
String deliveryPeriodEnd = null;
@@ -496,6 +514,9 @@ public class ZUGFeRDInvoiceImporter {
if ((item.getLocalName() != null) && (item.getLocalName().equals("ID"))) {
number = XMLTools.trimOrNull(item);
}
if ((item.getLocalName() != null) && (item.getLocalName().equals("Name"))) {
documentName = XMLTools.trimOrNull(item);
}
if ((item.getLocalName() != null) && (item.getLocalName().equals("TypeCode"))) {
typeCode = XMLTools.trimOrNull(item);
}
@@ -556,13 +577,13 @@ public class ZUGFeRDInvoiceImporter {
}
zpp.addNotes(includedNotes);
String rootNode = extractString("local-name(/*)");
if (rootNode.equals("Invoice")||rootNode.equals("CreditNote")) {
if (rootNode.equals("Invoice") || rootNode.equals("CreditNote")) {
// UBL...
// //*[local-name()="Invoice" or local-name()="CreditNote"]
number = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"ID\"]").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();
if (issueDateStr.length()>0) {
if (issueDateStr.length() > 0) {
issueDate = new SimpleDateFormat("yyyy-MM-dd").parse(issueDateStr);
}
String dueDt = extractString("/*[local-name()=\"Invoice\" or local-name()=\"CreditNote\"]/*[local-name()=\"DueDate\"]").trim();
@@ -651,7 +672,7 @@ public class ZUGFeRDInvoiceImporter {
zpp.setCurrency(currency);
String paymentTermsDescription = extractString("//*[local-name()=\"SpecifiedTradePaymentTerms\"]/*[local-name()=\"Description\"]|//*[local-name()=\"PaymentTerms\"]/*[local-name()=\"Note\"]");
if ((paymentTermsDescription!=null)&&(!paymentTermsDescription.isEmpty())) {
if ((paymentTermsDescription != null) && (!paymentTermsDescription.isEmpty())) {
zpp.setPaymentTermDescription(paymentTermsDescription);
}
@@ -661,6 +682,7 @@ public class ZUGFeRDInvoiceImporter {
List<BankDetails> bankDetails = new ArrayList<>();
String directDebitMandateID = null;
String IBAN = null, BIC = null, paymentMeansCode = null, paymentMeansInformation = null;
String accountName = null;
for (int i = 0; i < headerTradeSettlementNodes.getLength(); i++) {
// XMLTools.trimOrNull(nodes.item(i)))) {
@@ -668,6 +690,12 @@ public class ZUGFeRDInvoiceImporter {
NodeList headerTradeSettlementChilds = headerTradeSettlementNode.getChildNodes();
for (int settlementChildIndex = 0; settlementChildIndex < headerTradeSettlementChilds.getLength(); settlementChildIndex++) {
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("PaymentReference"))) {
String paymentReference = headerTradeSettlementChilds.item(settlementChildIndex).getTextContent();
zpp.setPaymentReference(paymentReference);
}
if ((headerTradeSettlementChilds.item(settlementChildIndex).getLocalName() != null)
&& (headerTradeSettlementChilds.item(settlementChildIndex).getLocalName().equals("SpecifiedTradePaymentTerms"))) {
NodeList paymentTermChilds = headerTradeSettlementChilds.item(settlementChildIndex).getChildNodes();
@@ -712,6 +740,9 @@ public class ZUGFeRDInvoiceImporter {
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("IBANID"))) {//CII
IBAN = XMLTools.trimOrNull(accountChilds.item(accountChildIndex));
}
if ((accountChilds.item(accountChildIndex).getLocalName() != null) && (accountChilds.item(accountChildIndex).getLocalName().equals("AccountName"))) {//CII
accountName = XMLTools.trimOrNull(accountChilds.item(accountChildIndex));
}
}
}
if ((paymentMeansChilds.item(paymentMeansChildIndex).getLocalName() != null) && (paymentMeansChilds.item(paymentMeansChildIndex).getLocalName().equals("PayeeSpecifiedCreditorFinancialInstitution"))) {
@@ -729,6 +760,9 @@ public class ZUGFeRDInvoiceImporter {
if (BIC != null) {
bd.setBIC(BIC);
}
if (accountName!=null) {
bd.setAccountName(accountName);
}
bankDetails.add(bd);
}
}
@@ -758,6 +792,21 @@ public class ZUGFeRDInvoiceImporter {
}
}
xpr = xpath.compile("/*[local-name()=\"Invoice\"]/*[local-name()=\"InvoicePeriod\"]/*"); //UBL only
NodeList periodNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int periodChildIndex = 0; periodChildIndex < periodNodes.getLength(); periodChildIndex++) {
String localName=periodNodes.item(periodChildIndex).getLocalName();
if ((localName != null) && (periodNodes.item(periodChildIndex).getLocalName().equals("StartDate"))) {
deliveryPeriodStart = XMLTools.trimOrNull(periodNodes.item(periodChildIndex));
}
if ((localName != null) && (periodNodes.item(periodChildIndex).getLocalName().equals("EndDate"))) {
deliveryPeriodEnd = XMLTools.trimOrNull(periodNodes.item(periodChildIndex));
}
}
if ((deliveryPeriodStart != null) && (deliveryPeriodEnd != null)) {
zpp.setDetailedDeliveryPeriod(XMLTools.tryDate(deliveryPeriodStart), XMLTools.tryDate(deliveryPeriodEnd));
} else if (deliveryPeriodStart != null) {
@@ -776,12 +825,12 @@ public class ZUGFeRDInvoiceImporter {
&& (paymentMeansChilds.item(meansChildIndex).getLocalName().equals("PayeeFinancialAccount"))) {
NodeList paymentTermChilds = paymentMeansChilds.item(meansChildIndex).getChildNodes();
for (int paymentTermChildIndex = 0; paymentTermChildIndex < paymentTermChilds.getLength(); paymentTermChildIndex++) {
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("Name"))) {
accountName = XMLTools.trimOrNull(paymentTermChilds.item(paymentTermChildIndex));
}
if ((paymentTermChilds.item(paymentTermChildIndex).getLocalName() != null) && (paymentTermChilds.item(paymentTermChildIndex).getLocalName().equals("ID"))) {
IBAN = XMLTools.trimOrNull(paymentTermChilds.item(paymentTermChildIndex));
if (IBAN != null) {
BankDetails bd = new BankDetails(IBAN);
bankDetails.add(bd);
}
}
}
}
@@ -799,10 +848,18 @@ public class ZUGFeRDInvoiceImporter {
}
}
if (IBAN != null) {
BankDetails bd = new BankDetails(IBAN);
if (accountName!=null) {
bd.setAccountName(accountName);
}
bankDetails.add(bd);
}
}
zpp.setIssueDate(issueDate).setDueDate(dueDate).setDeliveryDate(deliveryDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentCode(typeCode);
zpp.setIssueDate(issueDate).setDueDate(dueDate).setDeliveryDate(deliveryDate).setSender(new TradeParty(SellerNodes)).setRecipient(new TradeParty(BuyerNodes)).setNumber(number).setDocumentName(documentName).setDocumentCode(typeCode);
if ((directDebitMandateID != null) && (IBAN != null)) {
DirectDebit d = new DirectDebit(IBAN, directDebitMandateID);
@@ -848,15 +905,14 @@ public class ZUGFeRDInvoiceImporter {
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())))
{
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\"]");
@@ -890,7 +946,7 @@ public class ZUGFeRDInvoiceImporter {
xpr = xpath.compile("//*[local-name()=\"AttachmentBinaryObject\"]|//*[local-name()=\"EmbeddedDocumentBinaryObject\"]");
NodeList attachmentNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < attachmentNodes.getLength(); i++) {
FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
FileAttachment fa = new FileAttachment(attachmentNodes.item(i).getAttributes().getNamedItem("filename").getNodeValue(), attachmentNodes.item(i).getAttributes().getNamedItem("mimeCode").getNodeValue(), "Data", Base64.getMimeDecoder().decode(XMLTools.trimOrNull(attachmentNodes.item(i))));
zpp.embedFileInXML(fa);
// filename = "Aufmass.png" mimeCode = "image/png"
//EmbeddedDocumentBinaryObject cbc:EmbeddedDocumentBinaryObject mimeCode="image/png" filename="Aufmass.png"
@@ -976,6 +1032,38 @@ public class ZUGFeRDInvoiceImporter {
}
}
xpr = xpath.compile("//*[local-name()=\"ApplicableHeaderTradeSettlement\"]/*[local-name()=\"SpecifiedLogisticsServiceCharge\"]");// UBL unknown
chargeNodes = (NodeList) xpr.evaluate(getDocument(), XPathConstants.NODESET);
for (int i = 0; i < chargeNodes.getLength(); i++) {
NodeList chargeNodeChilds = chargeNodes.item(i).getChildNodes();
String chargeAmount = null;
String taxPercent = null;
for (int chargeChildIndex = 0; chargeChildIndex < chargeNodeChilds.getLength(); chargeChildIndex++) {
String chargeChildName = chargeNodeChilds.item(chargeChildIndex).getLocalName();
if (chargeChildName != null) {
if (chargeChildName.equals("AppliedAmount")) {
chargeAmount = XMLTools.trimOrNull(chargeNodeChilds.item(chargeChildIndex));
} else if (chargeChildName.equals("AppliedTradeTax")) {
NodeList taxChilds = chargeNodeChilds.item(chargeChildIndex).getChildNodes();
for (int taxChildIndex = 0; taxChildIndex < taxChilds.getLength(); taxChildIndex++) {
String taxItemName = taxChilds.item(taxChildIndex).getLocalName();
if ((taxItemName != null) && (taxItemName.equals("RateApplicablePercent"))) {
taxPercent = XMLTools.trimOrNull(taxChilds.item(taxChildIndex));
}
}
}
}
//appliedAmount
//AppliedTradeTax
}
if (chargeAmount != null) {
Charge c = new Charge(new BigDecimal(chargeAmount));
if (taxPercent != null) {
c.setTaxPercent(new BigDecimal(taxPercent));
}
zpp.addCharge(c);
}
}
TransactionCalculator tc = new TransactionCalculator(zpp);
@@ -1040,7 +1128,7 @@ public class ZUGFeRDInvoiceImporter {
} else if (rootNode.equals("Invoice")) {
return EStandard.ubl;
} else if (rootNode.equals("CreditNote")) {
return EStandard.ubl;
return EStandard.ubl_creditnote;
} else if (rootNode.equals("CrossIndustryInvoice")) {
return EStandard.facturx;
} else if (rootNode.equals("SCRDMCCBDACIDAMessageStructure")) {
@@ -1085,11 +1173,17 @@ public class ZUGFeRDInvoiceImporter {
*
* @return the file attachments embedded in XML (using base64) decoded as byte array,
* for PDF embedded files in FX use getFileAttachmentsPDF()
* may return empty array
* @deprecated use invoice.getAdditionalReferencedDocuments
*/
@Deprecated
public List<FileAttachment> getFileAttachmentsXML() {
return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments()));
if (importedInvoice.getAdditionalReferencedDocuments()!=null) {
return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments()));
} else {
return new ArrayList<>();
}
}
/***

View File

@@ -21,6 +21,8 @@
package org.mustangproject.ZUGFeRD;
import com.helger.commons.io.stream.StreamHelper;
import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.IOUtils;
import org.apache.fop.apps.*;
import org.apache.fop.apps.io.ResourceResolverFactory;
@@ -45,6 +47,9 @@ import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class ZUGFeRDVisualizer {
@@ -87,7 +92,8 @@ public class ZUGFeRDVisualizer {
* @param fis inputstream (will be consumed)
* @return (facturx = cii)
*/
private EStandard findOutStandardFromRootNode(InputStream fis) {
private EStandard findOutStandardFromRootNode(InputStream fis)
throws ParserConfigurationException {
String zf1Signature = "CrossIndustryDocument";
String zf2Signature = "CrossIndustryInvoice";
@@ -96,6 +102,22 @@ public class ZUGFeRDVisualizer {
String cioSignature = "SCRDMCCBDACIOMessageStructure";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
//REDHAT
//https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf
dbf.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
//OWASP
//https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Disable external DTDs as well
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
try {
DocumentBuilder db = dbf.newDocumentBuilder();
@@ -118,12 +140,15 @@ public class ZUGFeRDVisualizer {
return null;
}
public String visualize(String xmlFilename, Language lang) throws IOException, TransformerException {
FileInputStream fis = new FileInputStream(xmlFilename);
return visualize(fis, lang);
public String visualize(String xmlFilename, Language lang)
throws IOException, TransformerException, ParserConfigurationException {
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
return visualize(fis, lang);
}
}
public String visualize(InputStream inputXml, Language lang) throws IOException, TransformerException {
public String visualize(InputStream inputXml, Language lang)
throws IOException, TransformerException, ParserConfigurationException {
initTemplates(lang);
String fileContent = new String(IOUtils.toByteArray(inputXml), StandardCharsets.UTF_8);
@@ -208,13 +233,15 @@ public class ZUGFeRDVisualizer {
}
protected String toFOP(String xmlFilename)
throws IOException, TransformerException {
FileInputStream fis = new FileInputStream(xmlFilename);
EStandard theStandard = findOutStandardFromRootNode(fis);
fis = new FileInputStream(xmlFilename);//rewind :-(
return toFOP(fis, theStandard);
throws IOException, TransformerException, ParserConfigurationException {
EStandard theStandard;
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
theStandard = findOutStandardFromRootNode(fis);
}
try (FileInputStream fis = new FileInputStream(xmlFilename)) {
return toFOP(fis, theStandard);
}
}
protected String toFOP(InputStream is, EStandard theStandard)
@@ -254,16 +281,61 @@ public class ZUGFeRDVisualizer {
// the writing part
File XMLinputFile = new File(xmlFilename);
String result = null;
String fopInput = null;
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
try {
result = this.toFOP(XMLinputFile.getAbsolutePath());
} catch (TransformerException | IOException e) {
fopInput = this.toFOP(XMLinputFile.getAbsolutePath());
} catch (TransformerException | IOException | ParserConfigurationException e) {
LOGGER.error("Failed to apply FOP", e);
}
toPDFfromFOP(fopInput, () -> {
try {
return new FileOutputStream(pdfFilename);
} catch (FileNotFoundException e) {
LOGGER.error("Failed to create PDF", e);
}
return null;
}, (OutputStream out) -> {});
}
public byte[] toPDF(String xmlContent) {
String fopInput = null;
/* remove file endings so that tests can also pass after checking
out from git with arbitrary options (which may include CSRF changes)
*/
try {
ByteArrayInputStream fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));
EStandard theStandard = findOutStandardFromRootNode(fis);
fis = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8));//rewind :-(
fopInput = toFOP(fis, theStandard);
} catch (TransformerException | IOException | ParserConfigurationException e) {
LOGGER.error("Failed to apply FOP", e);
}
AtomicReference<byte[]> byteHolder = new AtomicReference<>();
ByteArrayOutputStream os = new ByteArrayOutputStream();
toPDFfromFOP(fopInput, () -> new BufferedOutputStream(os), (OutputStream out) -> {
try {
out.flush();
} catch (IOException e) {
LOGGER.error("Failed to create PDF", e);
}
byteHolder.set(os.toByteArray());
});
return byteHolder.get();
}
private void toPDFfromFOP(String fopInput, Supplier<OutputStream> outputStreamDelegate, Consumer<OutputStream> consumerDelegate) {
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
Configuration cfg = null;
@@ -291,24 +363,27 @@ public class ZUGFeRDVisualizer {
// Step 2: Set up output stream.
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) {
try (OutputStream out = new BufferedOutputStream(outputStreamDelegate.get())) {
// Step 3: Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
// Step 4: Setup JAXP using identity transformer
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer transformer = factory.newTransformer(); // identity transformer
// Step 5: Setup input and output for XSLT transformation
// Setup input stream
Source src = new StreamSource(new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8)));
Source src = new StreamSource(new ByteArrayInputStream(fopInput.getBytes(StandardCharsets.UTF_8)));
// Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Step 6: Start XSLT transformation and FOP processing
transformer.transform(src, res);
consumerDelegate.accept(out);
} catch (FOPException | IOException | TransformerException e) {
LOGGER.error("Failed to create PDF", e);