6. Creating HTML invoices

Please allow me to introduce a short intermezzo. The ZUGFeRD standard doesn't discuss HTML anywhere, yet I think it's useful to dedicate a chapter to the creating of HTML based on the ZUGFeRD data model. In the next chapter, you'll discover why.

An XSL for Comfort XMLs

As explained in chapter 5, the XMLs created to comply with the Basic profile don't contain sufficient information to create the visual appearance of the corresponding invoice. The XML doesn't contain all the information that is needed if we want to render the line items.

This means that we could create an XSL file that takes the info from the ZUGFeRD XML and converts it into HTML. We'll do that in the HtmlInvoicesComfort example. For instance:

<xsl:template match="/rsm:CrossIndustryDocument">
    <head><link rel="stylesheet" type="text/css" href="invoice.css" /></head>
        <img src="logo.png" alt="Das Company - logo" /><br />
        <xsl:apply-templates /></body>

In this snippet, we match the root tag of the ZUGFeRD XML and we introduce <html>, <head> and <body>. We'll use a CSS to apply some styles and colors and an <img> tag to introduce a logo.

The <xsl:apply-templates /> instruction will deal with all the other components. For instance:

<xsl:template match="rsm:HeaderExchangedDocument">
    <h1 id="header"><xsl:value-of select="ram:Name" />
    <xsl:text> </xsl:text><xsl:value-of select="ram:ID" /></h1>
    <xsl:apply-templates select="ram:IssueDateTime" />

This XSL snippet in turn applies the templates for what's inside the <ram:IssueDateTime> tag. In this case, the following template is called:

<xsl:template match="udt:DateTimeString">
   <h2 id="date"><xsl:choose>
       <xsl:when test="@format='610'">
           <xsl:call-template name="YYYYMM">
               <xsl:with-param name="date" select="." />
       <xsl:when test="@format='616'">
           <xsl:call-template name="YYYYWW">
               <xsl:with-param name="date" select="." />
           <xsl:call-template name="YYYYMMDD">
               <xsl:with-param name="date" select="." />

This is what a switch looks like in XSL. Depending on the date format, one of the following templates is called:

<xsl:template name="YYYYMMDD">
    <xsl:param name="date" />
    <xsl:value-of select="substring($date,1,4)" 
    />-<xsl:value-of select="substring($date,5,2)" 
    />-<xsl:value-of select="substring($date,7,2)" />
<xsl:template name="YYYYMM">
    <xsl:param name="date" />
    <xsl:value-of select="substring($date,1,4)" 
    />-<xsl:value-of select="substring($date,5,2)" />
<xsl:template name="YYYYWW">
    <xsl:param name="date" />
    <xsl:value-of select="substring($date,1,4)" 
    />; week <xsl:value-of select="substring($date,5,2)" />

We won't go into further detail, but it shouldn't be difficult to extend the XSL so that it covers all the data you want to visualize. You can get some inspiration by looking at the XSL I wrote for this example and that results in invoices such as the one shown in figure 6.1.

Figure 6.1: HTML version of an invoice
Figure 6.1: HTML version of an invoice

That's not a very attractive invoice yet, so let's introduce some styles and some color. We'll do that using CSS.

Adding some simple CSS

Please compare the result shown in figure 6.1 with the result shown in figure 6.2.

Figure 6.2: HTML + CSS version of an invoice
Figure 6.2: HTML + CSS version of an invoice

Now we're getting somewhere, aren't we? In our XSL, we've added some id and some class attributes, so that we can refer to these elements from a simple CSS file:

body { font-family: FreeSans; }
#header { color: #008080; font-size: 18pt; font-weight: bold; }
#date { font-size: 16pt; }
#addresses { margin-top: 20pt; font-size: 11pt; }
#products { margin-top: 10pt; border: 3px solid #008080; }
#totals { margin-top: 10pt; border: 3px solid #008080; }
#wireinfo { margin-top: 10pt; margin-left: 72pt; }
.name { font-weight: bold; color: #008080; }
.total { font-weight: bold; color: #008080; }
.headerrow { background-color: #008080; color: #FFFFFF; }
.bold { font-weight: bold; } 
.wireheader { font-weight: bold; text-align: left; }
th { padding: 2pt; font-weight: bold; text-align: center; }
td { padding: 2pt; }

Let's take a look at the Java code that was used to produce these HTML pages.

Transforming XML to HTML using XSL and Java

We start by defining some constants, more specifically the pattern for the paths of the resulting HTML files, and the paths to the XSL, CSS, and logo file.

  1. public static final String DEST = "results/zugferd/html/comfort%05d.html";
  2. public static final String XSL = "resources/zugferd/invoice.xsl";
  3. public static final String CSS = "resources/zugferd/invoice.css";
  4. public static final String LOGO = "resources/zugferd/logo.png";

In the main method, we copy the resources (the CSS and the image) to the directory where we'll generate our HTML. The rest of the code is similar to what we had in chapter 5: we loop over the invoices obtained from the PojoFactory and we call a createHtml() method.

  1. public static void main(String[] args)
  2. throws SQLException, IOException,
  3. ParserConfigurationException, SAXException, TransformerException,
  4. DataIncompleteException, InvalidCodeException {
  5. LicenseKey.loadLicenseFile(
  6. System.getenv("ITEXT7_LICENSEKEY")
  7. + "/itextkey-html2pdf_typography.xml");
  8. File file = new File(DEST);
  9. file.getParentFile().mkdirs();
  10. File css = new File(CSS);
  11. copyFile(css, new File(file.getParentFile(), css.getName()));
  12. File logo = new File(LOGO);
  13. copyFile(logo, new File(file.getParentFile(), logo.getName()));
  14. HtmlInvoicesComfort app = new HtmlInvoicesComfort();
  15. PojoFactory factory = PojoFactory.getInstance();
  16. List<Invoice> invoices = factory.getInvoices();
  17. for (Invoice invoice : invoices) {
  18. app.createHtml(invoice,
  19. new FileWriter(String.format(DEST, invoice.getId())));
  20. }
  21. factory.close();
  22. }

In the createHtml() method, we use the InvoiceData class to create an IComfortProfile instance. We then use standard Java code to convert XML using XSL.

  1. public void createHtml(Invoice invoice, Writer writer)
  2. throws IOException, ParserConfigurationException, SAXException,
  3. DataIncompleteException, InvalidCodeException, TransformerException {
  4. IComfortProfile comfort =
  5. new InvoiceData().createComfortProfileData(invoice);
  6. InvoiceDOM dom = new InvoiceDOM(comfort);
  7. StreamSource xml = new StreamSource(new ByteArrayInputStream(dom.toXML()));
  8. StreamSource xsl = new StreamSource(new File(XSL));
  9. TransformerFactory factory = TransformerFactory.newInstance();
  10. Transformer transformer = factory.newTransformer(xsl);
  11. transformer.transform(xml, new StreamResult(writer));
  12. writer.flush();
  13. writer.close();
  14. }

I'm adding the copyFile() method for the sake of completeness. We're copying the files from the resources to the output directory because we defined the paths to the CSS and the image using relative links.

  1. private static void copyFile(File source, File dest) throws IOException {
  2. InputStream input = new FileInputStream(source);
  3. OutputStream output = new FileOutputStream(dest);
  4. byte[] buf = new byte[1024];
  5. int bytesRead;
  6. while ((bytesRead = input.read(buf)) > 0) {
  7. output.write(buf, 0, bytesRead);
  8. }
  9. input.close();
  10. output.close();
  11. }

We could tweak the XSL and the CSS to produce an even nicer HTML document, but as I explained: invoices in HTML are outside the scope of ZUGFeRD. Let's move on to the next chapter to find out why we introduced this intermezzo.