diff --git a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java index 6687297e..275da445 100755 --- a/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java +++ b/Mustang-CLI/src/main/java/org/mustangproject/commandline/Main.java @@ -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; @@ -84,6 +85,7 @@ public class Main { + " [--logAppend ]: text to be added to log line\n" + " Additional parameters (optional - user will be prompted if not defined)\n" + " [--source ]: 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" @@ -357,6 +359,7 @@ public class Main { 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 = ""; @@ -380,6 +383,7 @@ public class Main { 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"); @@ -427,7 +431,7 @@ public class Main { 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"))) { @@ -454,7 +458,7 @@ public class Main { } - 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); @@ -466,7 +470,16 @@ public class Main { 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); diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java new file mode 100644 index 00000000..686daa7c --- /dev/null +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ValidationLogVisualizer.java @@ -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)); + } + } +} diff --git a/library/src/main/resources/stylesheets/result-pdf.xsl b/library/src/main/resources/stylesheets/result-pdf.xsl new file mode 100644 index 00000000..ff64d0cb --- /dev/null +++ b/library/src/main/resources/stylesheets/result-pdf.xsl @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + green + + + red + + + + + + green + + + red + + + + + + Das ZUGFeRD-PDF ist valide. + + + Das ZUGFeRD-PDF ist nicht valide. + + + + + + Es wird empfohlen das Dokument anzunehmen und weiter zu verarbeiten. + + + Es wird empfohlen das Dokument zurückzuweisen. + + + + + + + + + + + + + + + Prüfbericht + + + + + + + + + + + + + + + Referenz: + + + + + + + + + + Zeitpunkt der Prüfung: + + + + + + + + + + Erkannter Dokumenttyp: + + + + + + + + + + + + + Validierungsergebnisse im Detail: + + + + + + + + + Type + + + Code + + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pfad: + + + + + + + + + + + + + + + + + + + + + + + + + + + +