Skip to content

Commit

Permalink
Merge branch 'ZUGFeRD:master' into issues/628-invoice-referenced-docu…
Browse files Browse the repository at this point in the history
…ments
  • Loading branch information
christoph-sommer authored Dec 20, 2024
2 parents 5d74158 + 9b846ff commit 0b6f269
Show file tree
Hide file tree
Showing 24 changed files with 1,055 additions and 602 deletions.
6 changes: 6 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@

2.15.2
=======
2024-12-19
- correcly write charge reason codes also for non-Xrechnung #617
- correctly import additional referenced documents into invoice/corrected setting of attachments from jackson
- corrected parseException structure
- allow 1p0 as potential xmp version number
- #618 import BT-20
- #599 add tax category code for free export
- #600 Fixes a problem where a stream was not safely closed

2.15.1
=======
Expand Down
6 changes: 3 additions & 3 deletions Mustang-CLI/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId>
Expand All @@ -12,7 +12,7 @@
should also work for XRechnung/CII.
</name>
<packaging>jar</packaging>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
Expand All @@ -23,7 +23,7 @@
<dependency>
<groupId>org.mustangproject</groupId>
<artifactId>validator</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
<!-- prototypes of new mustangproject versions can be installed by referring to them and installed to the local repo from a jar file with
mvn install:install-file -Dfile=mustang-1.5.4-SNAPSHOT.jar -DgroupId=org.mustangproject.ZUGFeRD -DartifactId=mustang -Dversion=1.5.4 -Dpackaging=jar -DgeneratePom=true
-->
Expand Down
19 changes: 16 additions & 3 deletions Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.mustangproject.commandline;

import org.apache.commons.cli.*;
import org.apache.commons.io.FilenameUtils;
import org.mustangproject.CII.CIIToUBL;
import org.mustangproject.EStandard;
import org.mustangproject.FileAttachment;
Expand Down Expand Up @@ -84,6 +85,7 @@ private static String getUsage() {
+ " [--logAppend <text>]: text to be added to log line\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
+ " [--source <filename>]: input PDF or XML file\n"
+ " [--log-as-pdf]: save log output as pdf\n"
+ " --action validateExpectInvalid validate directory expecting negative results \n"
+ " [--no-notices]: refrain from reporting notices\n"
+ " Additional parameters (optional - user will be prompted if not defined)\n"
Expand Down Expand Up @@ -357,6 +359,7 @@ public static void main(String[] args) {
options.addOption(new Option("d", "directory", true, "which directory to operate on"));
options.addOption(new Option("i", "ignorefileextension", false, "ignore non-matching file extensions"));
options.addOption(new Option("l", "listfromstdin", false, "take list of files from commandline"));
options.addOption(new Option("log-as-pdf", "log-as-pdf", false, "saving log output to pdf file"));

boolean optionsRecognized = false;
String action = "";
Expand All @@ -380,6 +383,7 @@ public static void main(String[] args) {
String format = cmd.getOptionValue("format");
String lang = cmd.getOptionValue("language");
Boolean noNotices = cmd.hasOption("no-notices");
Boolean LogAsPDF = cmd.hasOption("log-as-pdf");

String zugferdVersion = cmd.getOptionValue("version");
String zugferdProfile = cmd.getOptionValue("profile");
Expand Down Expand Up @@ -427,7 +431,7 @@ public static void main(String[] args) {
performUBL(sourceName, outName);
optionsRecognized = true;
} else if ((action != null) && (action.equals("validate"))) {
optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"));
optionsRecognized = performValidate(sourceName, noNotices, cmd.getOptionValue("logAppend"), LogAsPDF);
} else if ((action != null) && (action.equals("validateExpectValid"))) {
optionsRecognized = performValidateExpect(true, directoryName);
} else if ((action != null) && (action.equals("validateExpectInvalid"))) {
Expand All @@ -454,7 +458,7 @@ public static void main(String[] args) {

}

private static boolean performValidate(String sourceName, boolean noNotices, String logAppend) {
private static boolean performValidate(String sourceName, boolean noNotices, String logAppend, boolean createLogAsPDF) {
boolean optionsRecognized;
if (sourceName == null) {
sourceName = getFilenameFromUser("Source PDF or XML", "invoice.pdf", "pdf|xml", true, false);
Expand All @@ -466,7 +470,16 @@ private static boolean performValidate(String sourceName, boolean noNotices, Str
if (noNotices) {
zfv.disableNotices();
}
System.out.println(zfv.validate(sourceName));

String validationResultXML = zfv.validate(sourceName);
System.out.println(validationResultXML);

if( createLogAsPDF) {
ValidationLogVisualizer vlvi = new ValidationLogVisualizer();
String fileBasename = FilenameUtils.getBaseName(sourceName);

vlvi.toPDF(validationResultXML, fileBasename + "_result.pdf");
}
optionsRecognized = !zfv.hasOptionsError();
if (!zfv.wasCompletelyValid()) {
System.exit(-1);
Expand Down
44 changes: 37 additions & 7 deletions library/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
<parent>
<groupId>org.mustangproject</groupId>
<artifactId>core</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<groupId>org.mustangproject</groupId>
<artifactId>library</artifactId>
<version>2.15.2-SNAPSHOT</version>
<version>2.15.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Library to write, read and validate e-invoices (Factur-X, ZUGFeRD, Order-X, XRechnung/CII)</name>
<description>FOSS Java library to read, write and validate european electronic invoices and orders in the UN/CEFACT
Expand Down Expand Up @@ -200,21 +200,51 @@
</manifest>
<manifestSections>
<manifestSection>
<name>FreeSans.ttf</name>
<name>SourceSansPro-Regular.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>FreeSerif.ttf</name>
<name>SourceSansPro-It.ttf</name>
<manifestEntries>
<Content-Type>application/x-font</Content-Type>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSansPro-Bold.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSansPro-BoldIt.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>Times-Bold.ttf</name>
<name>SourceSerifPro-Regular.ttf</name>
<manifestEntries>
<Content-Type>application/x-font</Content-Type>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSerifPro-It.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSerifPro-Bold.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
<manifestSection>
<name>SourceSerifPro-BoldIt.ttf</name>
<manifestEntries>
<Content-Type>font/ttf</Content-Type>
</manifestEntries>
</manifestSection>
</manifestSections>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package org.mustangproject.ZUGFeRD;

import org.apache.fop.apps.*;
import org.apache.fop.apps.io.ResourceResolverFactory;
import org.apache.fop.configuration.Configuration;
import org.apache.fop.configuration.ConfigurationException;
import org.apache.fop.configuration.DefaultConfigurationBuilder;
import org.apache.xmlgraphics.util.MimeConstants;
import org.mustangproject.ClasspathResolverURIAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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 {
public enum Language {
EN,
FR,
DE
}

static final ClassLoader CLASS_LOADER = ValidationLogVisualizer.class.getClassLoader();
private static final String RESOURCE_PATH = "";
private static final Logger LOGGER = LoggerFactory.getLogger(ValidationLogVisualizer.class);

private TransformerFactory mFactory = null;
private Templates mXsltPDFTemplate = null;


public ValidationLogVisualizer() {
mFactory = new net.sf.saxon.TransformerFactoryImpl();
// fact = TransformerFactory.newInstance();
mFactory.setURIResolver(new ValidationLogVisualizer.ClasspathResourceURIResolver());
}

protected void applyXSLTToPDF(final String xmlContent, final OutputStream PDFOutstream)
throws TransformerException {
Transformer transformer = mXsltPDFTemplate.newTransformer();

transformer.transform(new StreamSource(new StringReader(xmlContent)), new StreamResult(PDFOutstream));
}

protected String toFOP(final String xmlContent)
throws TransformerException {

try {
if (mXsltPDFTemplate == null) {
mXsltPDFTemplate = mFactory.newTemplates(
new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/result-pdf.xsl")));
}
} catch (TransformerConfigurationException ex) {
LOGGER.error("Failed to init XSLT templates", ex);
}

ByteArrayOutputStream baos = new ByteArrayOutputStream();

try {

applyXSLTToPDF(xmlContent, baos);

} catch (Exception e1) {
LOGGER.error("Failed to create PDF", e1);
}

return baos.toString(StandardCharsets.UTF_8);
}

public void toPDF(String xmlLogfileContent, String pdfFilename) {

// the writing part

String result = 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(xmlLogfileContent);
} catch ( TransformerException e) {
LOGGER.error("Failed to apply FOP", e);
}
DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();

Configuration cfg = null;
try {
cfg = cfgBuilder.build(CLASS_LOADER.getResourceAsStream("fop-config.xconf"));
} catch (ConfigurationException e) {
throw new RuntimeException(e);
}

FopFactoryBuilder builder = new FopFactoryBuilder(new File(".").toURI(), new ClasspathResolverURIAdapter()).setConfiguration(cfg);
// Step 1: Construct a FopFactory by specifying a reference to the configuration file
// (reuse if you plan to render multiple documents!)

FopFactory fopFactory = builder.build();

fopFactory.getFontManager().setResourceResolver(
ResourceResolverFactory.createInternalResourceResolver(
new File(".").toURI(),
new ClasspathResolverURIAdapter()));

FOUserAgent userAgent = fopFactory.newFOUserAgent();

userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-3b");

// Step 2: Set up output stream.
// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams).

try (OutputStream out = new BufferedOutputStream(new FileOutputStream(pdfFilename))) {

// 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();
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)));

// 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);

} catch (FOPException | IOException | TransformerException e) {
LOGGER.error("Failed to create PDF", e);
}
}

private static class ClasspathResourceURIResolver implements URIResolver {
ClasspathResourceURIResolver() {
// Do nothing, just prevents synthetic access warning.
}

@Override
public Source resolve(String href, String base) throws TransformerException {
return new StreamSource(CLASS_LOADER.getResourceAsStream(RESOURCE_PATH + "stylesheets/" + href));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ public IZUGFeRDExporter getExporter() {
}

protected byte[] filenameToByteArray(String pdfFilename) throws IOException {
FileInputStream fileInputStream = new FileInputStream(pdfFilename);
return inputstreamToByteArray(fileInputStream);
try (FileInputStream fileInputStream = new FileInputStream(pdfFilename)) {
return inputstreamToByteArray(fileInputStream);
}
}

protected byte[] inputstreamToByteArray(InputStream fileInputStream) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ private PostalTradeAddress getAddressFromNodeList(NodeList nl) {
*
* @return a List of LineItem instances
*/
@Deprecated
public List<Item> getLineItemList() {
final List<Node> nodeList = getLineItemNodes();
final List<Item> lineItemList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ public String getUTF8() {
* for PDF embedded files in FX use getFileAttachmentsPDF()
* @deprecated use invoice.getAdditionalReferencedDocuments
*/
@Deprecated
public List<FileAttachment> getFileAttachmentsXML() {
return new ArrayList<>(Arrays.asList(importedInvoice.getAdditionalReferencedDocuments()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class TaxCategoryCodeTypeConstants {
public static final String ZEROTAXPRODUCTS = "Z";
public static final String UNTAXEDSERVICE = "O";
public static final String INTRACOMMUNITY = "K";

public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT).collect(Collectors.toSet());
public static final String FREEEXPORT = "G";

public static Set<String> CATEGORY_CODES_WITH_EXEMPTION_REASON = Stream.of(INTRACOMMUNITY, REVERSECHARGE, TAXEXEMPT, FREEEXPORT).collect(Collectors.toSet());
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 0b6f269

Please sign in to comment.