diff --git a/History.md b/History.md index ae453c11..2650dcbd 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +2.8.0 +======= + +Improvement of included notes #331 + 2.7.3 ======= 2023-06-16 diff --git a/NOTICE b/NOTICE index c13b8c75..2292b99c 100644 --- a/NOTICE +++ b/NOTICE @@ -33,3 +33,6 @@ Based on https://jcthiele.github.io/xrechnung-visualization-codelist-resolve/ un Based on Saxon-HE https://sourceforge.net/projects/saxon/ licensed under the MPL 2.0 Based on DOM4j https://dom4j.github.io/ licensed under the BSD license Based on EN16931 validation artefacts https://github.com/ConnectingEurope/eInvoicing-EN16931 licensed under the EUPL 2.0 +Based on https://xmlgraphics.apache.org/fop under the APL2.0 +Based on https://github.com/saucecontrol/Compact-ICC-Profiles/ under the Public Domain +http://ftp.gnu.org/gnu/freefont/ diff --git a/library/pom.xml b/library/pom.xml index 9e2d19ac..b824ed90 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -63,6 +63,12 @@ jackson-databind 2.14.2 + + + org.apache.xmlgraphics + fop + 2.8 + javax.activation diff --git a/library/src/main/java/org/mustangproject/ClasspathResolverURIAdapter.java b/library/src/main/java/org/mustangproject/ClasspathResolverURIAdapter.java new file mode 100644 index 00000000..721c45de --- /dev/null +++ b/library/src/main/java/org/mustangproject/ClasspathResolverURIAdapter.java @@ -0,0 +1,37 @@ +package org.mustangproject; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; + +import org.apache.fop.apps.io.ResourceResolverFactory; +import org.apache.xmlgraphics.io.Resource; +import org.apache.xmlgraphics.io.ResourceResolver; + +public class ClasspathResolverURIAdapter implements ResourceResolver { + + private final ResourceResolver wrapped; + + + public ClasspathResolverURIAdapter() { + this.wrapped = ResourceResolverFactory.createDefaultResourceResolver(); + } + + + @Override + public Resource getResource(URI uri) throws IOException { + if (uri.getScheme().equals("classpath")) { + URL url = getClass().getClassLoader().getResource(uri.getSchemeSpecificPart()); + + return new Resource(url.openStream()); + } else { + return wrapped.getResource(uri); + } + } + + @Override + public OutputStream getOutputStream(URI uri) throws IOException { + return wrapped.getOutputStream(uri); + } + +} diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java index 28083a13..42e58dd7 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDVisualizer.java @@ -20,11 +20,23 @@ */ package org.mustangproject.ZUGFeRD; +import org.apache.fop.apps.*; +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.CII.CIIToUBL; +import org.mustangproject.ClasspathResolverURIAdapter; +import org.xml.sax.SAXException; + 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.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -172,7 +184,7 @@ public class ZUGFeRDVisualizer { return baos.toString("UTF-8"); } - public String toFOP(String xmlFilename) + protected String toFOP(String xmlFilename) throws FileNotFoundException, TransformerException, UnsupportedEncodingException { try { @@ -240,6 +252,99 @@ public class ZUGFeRDVisualizer { return baos.toString("UTF-8"); } + public void toPDF(String xmlFilename, String pdfFilename) { + + // the writing part + CIIToUBL c2u = new CIIToUBL(); + String sourceFilename = "factur-x.xml"; + File CIIinputFile = new File(xmlFilename); + + String expected = null; + String result = null; + + ZUGFeRDVisualizer zvi = new ZUGFeRDVisualizer(); + /* remove file endings so that tests can also pass after checking + out from git with arbitrary options (which may include CSRF changes) + */ + try { + result = zvi.toFOP(CIIinputFile.getAbsolutePath()); + } catch (FileNotFoundException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + } catch (TransformerException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + } catch (UnsupportedEncodingException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + } + /* + FopConfParser parser = null; + try { + //parsing configuration + parser = new FopConfParser(CLASS_LOADER.getResourceAsStream("fop-config.xconf"), new URI("file:///")); + + } catch (SAXException e) { + throw new UncheckedIOException(new IOException(e)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (URISyntaxException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + }*/ +// FopFactoryBuilder builder = parser.getFopFactoryBuilder(); + 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.newInstance(new File("c:\\Users\\jstaerk\\temp\\fop-config.xconf")); + + FOUserAgent userAgent = fopFactory.newFOUserAgent(); + + userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-1b"); + +// 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); + + //Files.write(Paths.get("C:\\Users\\jstaerk\\temp\\fop.pdf"), res.toString().getBytes(StandardCharsets.UTF_8)); + + } catch (FOPException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + } catch (TransformerConfigurationException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + } catch (IOException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + } catch (TransformerException e) { + Logger.getLogger(ZUGFeRDVisualizer.class.getName()).log(Level.SEVERE, null, e); + } + + + } + protected void applyZF2XSLT(final InputStream xmlFile, final OutputStream HTMLOutstream) throws TransformerException { Transformer transformer = mXsltXRTemplate.newTransformer(); diff --git a/library/src/main/resources/AdobeCompat-v2.icc b/library/src/main/resources/AdobeCompat-v2.icc new file mode 100644 index 00000000..47fb1b1e Binary files /dev/null and b/library/src/main/resources/AdobeCompat-v2.icc differ diff --git a/library/src/main/resources/FreeSans.ttf b/library/src/main/resources/FreeSans.ttf new file mode 100644 index 00000000..2072cdaf Binary files /dev/null and b/library/src/main/resources/FreeSans.ttf differ diff --git a/library/src/main/resources/FreeSerif.ttf b/library/src/main/resources/FreeSerif.ttf new file mode 100644 index 00000000..889c594f Binary files /dev/null and b/library/src/main/resources/FreeSerif.ttf differ diff --git a/library/src/main/resources/fop-config.xconf b/library/src/main/resources/fop-config.xconf new file mode 100644 index 00000000..35993a98 --- /dev/null +++ b/library/src/main/resources/fop-config.xconf @@ -0,0 +1,37 @@ + + + + true + + + true + + + ./ + + + ./ + + + 72 + + 72 + + + + + + + + + + + + + + classpath:AdobeCompat-v2.icc + + + + diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java index 94ddfdee..5a25099c 100644 --- a/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/VisualizationTest.java @@ -19,10 +19,7 @@ *********************************************************************** */ package org.mustangproject.ZUGFeRD; -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.*; import org.apache.xmlgraphics.util.MimeConstants; import org.junit.FixMethodOrder; import org.junit.runners.MethodSorters; @@ -35,7 +32,6 @@ import javax.xml.transform.stream.StreamSource; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Paths; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class VisualizationTest extends ResourceCase { @@ -162,76 +158,16 @@ public class VisualizationTest extends ResourceCase { /* remove file endings so that tests can also pass after checking out from git with arbitrary options (which may include CSRF changes) */ - result = zvi.toFOP(CIIinputFile.getAbsolutePath()); - File expectedResult=getResourceAsFile("fop.xml"); - ByteArrayOutputStream fopout = new ByteArrayOutputStream(); - //FileOutputStream outfile = new FileOutputStream("c:\\Users\\jstaerk\\temp\\fop2.pdf"); - // File out=new File("c:\\Users\\jstaerk\\temp\\fop.xml"); - -// Step 1: Construct a FopFactory by specifying a reference to the configuration file -// (reuse if you plan to render multiple documents!) - FopFactory fopFactory = FopFactory.newInstance(new File("c:\\Users\\jstaerk\\temp\\fop.xconf")); - FOUserAgent userAgent = fopFactory.newFOUserAgent(); - userAgent.getRendererOptions().put("pdf-a-mode", "PDF/A-1b"); - -// Step 2: Set up output stream. -// Note: Using BufferedOutputStream for performance reasons (helpful with FileOutputStreams). - OutputStream out = new BufferedOutputStream(new FileOutputStream(new File("c:\\Users\\jstaerk\\temp\\fop3.pdf"))); - - try { - // 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); - - //Files.write(Paths.get("C:\\Users\\jstaerk\\temp\\fop.pdf"), res.toString().getBytes(StandardCharsets.UTF_8)); - - } finally { - //Clean-up - out.close(); - } - -// Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, fopout); -/* - Transformer transformer = TransformerFactory.newInstance().newTransformer( - new StreamSource(new File(args[1]))); - transformer.transform(new StreamSource(new File(args[0])), - new SAXResult(((Fop) fop).getDefaultHandler())); -*/ - // fopout.toByteArray(); - - expected = new String(Files.readAllBytes(expectedResult.toPath()), StandardCharsets.UTF_8).replace("\r","").replace("\n",""); - // remove linebreaks as well... - + zvi.toPDF(CIIinputFile.getAbsolutePath(), "c:\\users\\jstaerk\\temp\\fopy2.pdf"); } catch (UnsupportedOperationException e) { fail("UnsupportedOperationException should not happen: "+e.getMessage()); } catch (IllegalArgumentException e) { fail("IllegalArgumentException should not happen: "+e.getMessage()); - } catch (TransformerException e) { - fail("TransformerException should not happen: "+e.getMessage()); - } catch (IOException e) { - fail("IOException should not happen: "+e.getMessage()); - } catch (FOPException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); } - assertNotNull(result); +// assertNotNull(result); // Reading ZUGFeRD - assertEquals(expected, result); +// assertEquals(expected, result); } } diff --git a/library/src/test/resources/fop.fo b/library/src/test/resources/fop.fo new file mode 100644 index 00000000..c334bfc6 --- /dev/null +++ b/library/src/test/resources/fop.fo @@ -0,0 +1 @@ +RE-20201121/508Bereiche ohne Inhalte werden nicht dargestellt!DE136695976/RE-20201121/508Seite / ÜbersichtInformationen zum KäuferLeitweg-ID:AB321Name:Theodor EstAdresszeile 1:Bahnstr. 42PLZ:88802Ort:SpielkreisLändercode:DE (Deutschland)Kennung:2Informationen zum VerkäuferFirmenname:Bei Spiel GmbHAdresszeile 1:Ecke 12PLZ:12345Ort:StadthausenLändercode:DE (Deutschland)RechnungsdatenRechnungsnummer:RE-20201121/508Rechnungsdatum:21.11.2020Rechnungsart:380 (Commercial invoice)Währung:EUR (Euro)Gesamtbeträge der RechnungSumme aller Positionen496,00Summe Nachlässe0,00Summe Zuschläge0,00Gesamtsumme496,00Summe Umsatzsteuer in Abrechnungswährung75,04Gesamtsumme571,04Gezahlter Betrag0,00Summe Fremdforderungen0,00Fälliger Betrag571,04Aufschlüsselung der Umsatzsteuer auf Ebene der RechnungUmsatzsteuerkategorie: S (Standard rate)Gesamtsumme160,00Umsatzsteuersatz7,00%Umsatzsteuerbetrag11,20Umsatzsteuerkategorie: S (Standard rate)Gesamtsumme336,00Umsatzsteuersatz19,00%Umsatzsteuerbetrag63,84ZahlungsdatenSkonto; weitere Zahlungsbedingungen:Zahlbar ohne Abzug bis 12.12.2020Fälligkeitsdatum:12.12.2020Code für das Zahlungsmittel:42 (Payment to bank account)Zahlungsmittel:Bank transferVerwendungszweck:RE-20201121/508ÜberweisungKontoinhaber:Max MustermannIBAN:DE88200800000970375700BIC:COBADEFFXXXDetails1Position:1PreiseinzelheitenMenge1.0000EinheitHUR (hour)Preis pro Einheit (netto)160Gesamtpreis (netto)160,00Listenpreis (netto):160Basismenge zum Artikelpreis:1.0000Code der Maßeinheit:HUR (hour)Umsatzsteuer:S (Standard rate)Umsatzsteuersatz:7,00%ArtikelinformationenBezeichnung:Design (hours)Beschreibung:Of a sample invoice2Position:2PreiseinzelheitenMenge400.0000EinheitH87 (piece)Preis pro Einheit (netto)0,79Gesamtpreis (netto)316,00Listenpreis (netto):0,79Basismenge zum Artikelpreis:1.0000Code der Maßeinheit:H87 (piece)Umsatzsteuer:S (Standard rate)Umsatzsteuersatz:19,00%ArtikelinformationenBezeichnung:BallonsBeschreibung:various colors, ~2000ml3Position:3PreiseinzelheitenMenge800.0000EinheitLTR (litre)Preis pro Einheit (netto)0,025Gesamtpreis (netto)20,00Listenpreis (netto):0,025Basismenge zum Artikelpreis:1.0000Code der Maßeinheit:LTR (litre)Umsatzsteuer:S (Standard rate)Umsatzsteuersatz:19,00%ArtikelinformationenBezeichnung:Hot air „heiße Luft“ (litres)ZusätzeInformationen zum VerkäuferUmsatzsteuer-ID:DE136695976Informationen zum VertragSpezifikationskennung:urn:cen.eu:en16931:2017 \ No newline at end of file