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

This commit is contained in:
Jochen Stärk
2026-04-10 15:42:01 +02:00
9 changed files with 67 additions and 19 deletions

View File

@@ -115,7 +115,7 @@ corrections
- support parsing of BT-90 CreditorReferenceID
- #871 schema validation does not ignore external entities
- #868 Fix wrong version in History.md
- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans
- #729 Updates about SpecifiedTradeSettlementHeaderMonetarySummation and SpecifiedTradeSettlementPaymentMeans. **Breaking Change:** The hardcoded values of ram:TypeCode (58) and ram:Information ("SEPA credit transfer") in the getSettlementXML() method of the IZUGFeRDTradeSettlementPayment interface have been removed. Starting with this version, any code utilizing this interface must explicitly set the corresponding attributes.
- #863 LineCalculator throws NPE if product is null (since 2.17.0)
- #731 Got a broken translation key when visualizing XML into PDF (xr:Business_process_type)
- #865 Add sevdesk signature to PDF creators

View File

@@ -136,6 +136,7 @@ public class DXExporterFromA1 extends DXExporterFromA3 {
public DXExporterFromA1 setConformanceLevel(PDFAConformanceLevel newLevel) {
return (DXExporterFromA1) super.setConformanceLevel(newLevel);
}
@Override
public DXExporterFromA1 setProducer(String producer){
return (DXExporterFromA1) super.setProducer(producer);

View File

@@ -71,6 +71,7 @@ import jakarta.activation.FileDataSource;
public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
protected boolean compressionEnabled = false;
protected ArrayList<FileAttachment> fileAttachments = new ArrayList<>();
/**
@@ -296,8 +297,8 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
dict.setString("Desc", description);
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
// ef.addCompression();
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
ef.setSubtype(subType);
ef.setSize(data.length);
ef.setCreationDate(new GregorianCalendar());
@@ -417,7 +418,6 @@ public class DXExporterFromA3 extends ZUGFeRDExporterFromA3 {
return this;
}
@Override
public DXExporterFromA3 setCreator(String creator) {
this.creator = creator;

View File

@@ -56,6 +56,7 @@ public interface IZUGFeRDExporter extends Closeable, IExporter {
public IZUGFeRDExporter load(InputStream pdfSource) throws IOException;
public IZUGFeRDExporter setCreator(String creator);
public IZUGFeRDExporter setConformanceLevel(PDFAConformanceLevel newLevel);
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean enablePDFAttachmentCompression);
public IZUGFeRDExporter setProducer(String producer);
public IZUGFeRDExporter setZUGFeRDVersion(int version);
public boolean ensurePDFIsValid(final DataSource dataSource) throws IOException;

View File

@@ -129,6 +129,9 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
private boolean attachZUGFeRDHeaders = true;
/** Defines whether attachments to the PDF should be using FLATE compression */
private boolean compressionEnabled = false;
/**
* Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the
* metadata level, this will not e.g. convert graphics to JPG-2000)
@@ -296,8 +299,8 @@ public class OXExporterFromA3 extends ZUGFeRDExporterFromA3 {
dict.setString("Desc", description);
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
// ef.addCompression();
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
ef.setSubtype(subType);
ef.setSize(data.length);
ef.setCreationDate(new GregorianCalendar());

View File

@@ -861,7 +861,7 @@ public class ZUGFeRD2PullProvider implements IXMLProvider {
}
if ((trans.getZFAllowances() != null) && (trans.getZFAllowances().length > 0)) {
if (profile == Profiles.getByName("XRechnung")) {
if ((profile == Profiles.getByName("XRechnung")) || (profile == Profiles.getByName("EN16931")) || (profile == Profiles.getByName("EXTENDED"))) {
for (IZUGFeRDAllowanceCharge allowance : trans.getZFAllowances()) {
xml += "<ram:SpecifiedTradeAllowanceCharge>" +
"<ram:ChargeIndicator>" +

View File

@@ -104,6 +104,9 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
private Profile profile = null;
protected boolean documentPrepared = false;
/** Defines whether attachments to the PDF should be using FLATE compression */
private boolean compressionEnabled = false;
/**
* Data (XML invoice) to be added to the ZUGFeRD PDF. It may be externally set,
* in which case passing a IZUGFeRDExportableTransaction is not necessary. By
@@ -395,7 +398,8 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
dict.setString("Desc", description);
ByteArrayInputStream fakeFile = new ByteArrayInputStream(data);
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile);
COSName filter = compressionEnabled ? COSName.FLATE_DECODE : null;
PDEmbeddedFile ef = new PDEmbeddedFile(doc, fakeFile, filter);
// ef.addCompression();
ef.setSubtype(subType);
ef.setSize(data.length);
@@ -506,6 +510,12 @@ public class ZUGFeRDExporterFromA3 extends XRExporter implements IZUGFeRDExporte
return this;
}
@Override
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) {
this.compressionEnabled = compressionEnabled;
return this;
}
public ZUGFeRDExporterFromA3 setCreator(String creator) {
this.creator = creator;

View File

@@ -188,6 +188,10 @@ public class ZUGFeRDExporterFromPDFA implements IZUGFeRDExporter {
return getExporter().setConformanceLevel(newLevel);
}
public IZUGFeRDExporter setEnablePDFAttachmentCompression(boolean compressionEnabled) {
return getExporter().setEnablePDFAttachmentCompression(compressionEnabled);
}
public IZUGFeRDExporter setProducer(String producer) {
return getExporter().setProducer(producer);

View File

@@ -23,7 +23,11 @@ import junit.framework.TestSuite;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
@@ -413,10 +417,11 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos);
ze.close();
String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.BASIC) >= 0);
assertFalse(pdfContent.indexOf(DocumentContextParameterTypeConstants.EXTENDED) >= 0);
assertTrue(pdfContent.indexOf(DocumentContextParameterTypeConstants.COMFORT) >= 0);
byte[] targetBytes = baos.toByteArray();
String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "ZUGFeRD-invoice.xml");
assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.BASIC));
assertFalse(xmlContent.contains(DocumentContextParameterTypeConstants.EXTENDED));
assertTrue(xmlContent.contains(DocumentContextParameterTypeConstants.COMFORT));
} catch (IOException e) {
fail("IOException should not happen in testZExport");
@@ -440,7 +445,8 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
result.write(buffer, 0, length);
}
ze.addAdditionalFile("test.pdf", result.toByteArray());
var bytes = result.toByteArray();
ze.addAdditionalFile("test.pdf", bytes);
ze.setTransaction(this);
ze.disableAutoClose(true);
@@ -449,12 +455,14 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ze.export(baos);
ze.close();
String pdfContent = baos.toString(StandardCharsets.UTF_8);
assertFalse(pdfContent.indexOf("(via mustangproject.org") == -1);
byte[] targetBytes = baos.toByteArray();
String pdfContent = new String(targetBytes, StandardCharsets.UTF_8);
String xmlContent = readEmbeddedXmlFromPdf(targetBytes, "factur-x.xml");
assertTrue(pdfContent.contains("(via mustangproject.org"));
// check for pdf-a schema extension
assertFalse(pdfContent.indexOf("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>") == -1);
assertFalse(pdfContent.indexOf("<pdfaSchema:prefix>fx</pdfaSchema:prefix>") == -1);
assertFalse(pdfContent.indexOf("urn:cen.eu:en16931:2017") == -1);
assertTrue(pdfContent.contains("<fx:ConformanceLevel>EN 16931</fx:ConformanceLevel>"));
assertTrue(pdfContent.contains("<pdfaSchema:prefix>fx</pdfaSchema:prefix>"));
assertTrue(xmlContent.contains("urn:cen.eu:en16931:2017"));
}
// now check the contents (like MustangReaderTest)
@@ -468,6 +476,27 @@ public class MustangReaderWriterTest extends MustangReaderTestCase {
assertEquals(getNumber(), zi.getForeignReference());
}
private static String readEmbeddedXmlFromPdf(byte[] targetBytes, String xmlFilename) throws IOException {
// Read attached (compressed) XML file
try (PDDocument pdDocument = Loader.loadPDF(targetBytes)) {
PDDocumentNameDictionary names = new PDDocumentNameDictionary(pdDocument.getDocumentCatalog());
PDEmbeddedFilesNameTreeNode embeddedFiles = names.getEmbeddedFiles();
Map<String, PDComplexFileSpecification> fileMap = embeddedFiles.getNames();
PDComplexFileSpecification fileSpec = fileMap.get(xmlFilename);
if (fileSpec == null) {
fail("Files does not contain a file named " + xmlFilename);
return null;
}
PDEmbeddedFile embeddedFile = fileSpec.getEmbeddedFile();
String xmlContent = new String(embeddedFile.toByteArray(), StandardCharsets.UTF_8);
System.out.println(xmlContent);
return xmlContent;
}
}
public void testExceptionOnPDF14() {
final String TARGET_PDF = "./target/testout-MustangGnuaccountingBeispielRE-20170509_505new.pdf";