DEV Community

Cover image for Modern Invoice Generation with XSLT and React: A Seamless Integration Guide
Serif COLAKEL
Serif COLAKEL

Posted on

Modern Invoice Generation with XSLT and React: A Seamless Integration Guide

Bridge legacy systems and modern web apps through powerful XML transformations


📌 Introduction

In the modern business landscape, the ability to generate invoices quickly and accurately is a critical necessity for businesses of all sizes. Traditional manual invoicing processes are often slow, error-prone, and difficult to scale. That's where automation comes in.

By leveraging technologies like XSLT (Extensible Stylesheet Language Transformations) for transforming XML data and React for rendering dynamic content, we can create a solution that streamlines and simplifies the invoice generation process.

As a front-end developer, I had the opportunity to build a dynamic invoice generation system using React, XSLT, and XML. In this article, I'll take you through the solution I implemented, explain the key technologies, and show how they work together to automate invoice creation.

Let's dive in!

Links


Table of Contents

Why XSLT Still Matters

In an era dominated by JSON and REST APIs, XSLT (Extensible Stylesheet Language Transformations) remains unmatched for:

  • 📄 Complex XML-to-HTML/PDF transformations
  • 🖨️ Print-ready document workflows
  • 🏢 Enterprise invoice systems
  • 📱 Multi-format output from single sources

Integrating XSLT with React

  1. XML and XSLT Files: Begin by creating your XML and XSLT files. The XML file will contain the data for your invoices, while the XSLT file will define how this data should be transformed into an HTML invoice.
  • Example XML Structure:
   <Invoices>
     <Invoice>
       <companyInfo>
         <name>ABC Corp</name>
         <address>123 Main St</address>
       </companyInfo>
       <clientInfo>
         <name>John Doe</name>
         <address>456 Elm St</address>
       </clientInfo>
       <invoiceItems>
         <invoiceItem>
           <name>Product 1</name>
           <quantity>2</quantity>
           <price>50</price>
         </invoiceItem>
       </invoiceItems>
     </Invoice>
   </Invoices>
Enter fullscreen mode Exit fullscreen mode
  • Example XSLT Structure:
    <?xml version="1.0" encoding="UTF-8"?>
   <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="html" indent="yes" encoding="UTF-8" />
     <xsl:template match="/Invoices">
       <html>
         <body>
           <xsl:for-each select="Invoice">
             <div class="invoice">
               <h1>Invoice for <xsl:value-of select="clientInfo/name" /></h1>
               <p>Company: <xsl:value-of select="companyInfo/name" /></p>
               <p>Address: <xsl:value-of select="clientInfo/address" /></p>
               <table>
                 <tr>
                   <th>Product</th>
                   <th>Quantity</th>
                   <th>Price</th>
                 </tr>
                 <xsl:for-each select="invoiceItems/invoiceItem">
                   <tr>
                     <td><xsl:value-of select="name" /></td>
                     <td><xsl:value-of select="quantity" /></td>
                     <td><xsl:value-of select="price" /></td>
                   </tr>
                 </xsl:for-each>
               </table>
             </div>
           </xsl:for-each>
         </body>
       </html>
     </xsl:template>
   </xsl:stylesheet>
Enter fullscreen mode Exit fullscreen mode

Core Implementation

Transforming XML with XSLT in React: Use the DOMParser and XSLTProcessor APIs available in modern browsers to transform your XML data using the XSLT stylesheet.

Full code available on GitHub

1. XSLT Template (Invoice Engine)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" indent="yes"/>

  <xsl:template match="/Invoices">
    <xsl:for-each select="Invoice">
      <html>
        <body>
          <!-- Dynamic Table -->
          <table class="items">
            <xsl:for-each select="invoiceItems/invoiceItem">
              <tr>
                <td><xsl:value-of select="position()"/></td>
                <td><xsl:value-of select="name"/></td>
                <td><xsl:value-of select="quantity * basePrice"/></td>
                <td>
                  <xsl:choose>
                    <xsl:when test="totalNumber > 500">✅ Paid</xsl:when>
                    <xsl:otherwise>❌ Unpaid</xsl:otherwise>
                  </xsl:choose>
                </td>
              </tr>
            </xsl:for-each>
          </table>
        </body>
      </html>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>
Enter fullscreen mode Exit fullscreen mode

Key Features of XSLT

  • Template-Based Transformation: XSLT uses templates to match specific parts of an XML document and transform them into the desired output format. Each template defines rules for how a particular XML element should be processed.

  • XPath Integration: XSLT heavily relies on XPath (XML Path Language) to navigate and select nodes in an XML document. XPath expressions are used to locate elements and attributes within the XML structure.

  • Declarative Language: XSLT is a declarative language, meaning you specify what the output should look like, rather than how to achieve it. This makes it different from procedural programming languages.

  • Cross-Platform: XSLT is platform-independent and can be used in various environments, including web browsers, server-side applications, and standalone processors.

  • Extensibility: XSLT can be extended with custom functions and elements, allowing developers to add specific functionality as needed.

Basic Structure of an XSLT Document

An XSLT document is itself an XML document and typically includes the following components:

XSLT Cheat Sheet

XSLT Cheat Sheet

  • xsl:template: Define a template
  • xsl:for-each: Loop over nodes
  • xsl:value-of: Output node value
  • xsl:choose: Conditional logic
  • xsl:when: If condition is true
  • xsl:otherwise: If condition is false
  • xsl:param: Receive external data
  • xsl:variable: Store temporary data
  • xsl:apply-templates: Call another template
  • xsl:import: Include another XSLT file
  • xsl:output: Define output format
  • xsl:attribute: Add an attribute
  • xsl:comment: Add a comment
  • xsl:copy: Copy a node
  • xsl:element: Create a new element
  • xsl:if: Conditional check
  • xsl:sort: Sort nodes
  • xsl:text: Add text content
  • xsl:processing-instruction: Add processing instruction
  • xsl:strip-space: Remove whitespace
  • xsl:preserve-space: Preserve whitespace
  • xsl:include: Include another XSLT file
  • xsl:key: Define a key
  • xsl:number: Add numbering
  • xsl:namespace-alias: Define namespace alias
  • xsl:document: Create a new document
  • xsl:decimal-format: Define decimal format
  • xsl:sequence: Create a sequence
  • xsl:function: Define a function
  • xsl:result-document: Create a new document
  • fn:escape-html(userContent): Escape HTML content
  • xsl:analyze-string: Analyze a string
  • format-number(): Format a number
  • current(): Get current node
  • document(): Load an external document
  • element-available(): Check if element is available
  • function-available(): Check if function is available
  • generate-id(): Generate a unique ID
  • id(): Get element by ID
  • not(invoiceItems) : Check if node is empty

  • <xsl:stylesheet>: The root element of an XSLT document, which defines the version of XSLT being used and the namespace for XSLT elements.

  • <xsl:template>: Defines a template for transforming a specific part of the XML document. Each template has a match attribute that specifies the XPath expression to match elements in the XML.

  • <xsl:value-of>: Extracts the value of a selected node and inserts it into the output.

  • <xsl:for-each>: Iterates over a set of nodes, applying the transformation rules to each node.

  • <xsl:if> and <xsl:choose>: Provide conditional logic to apply different transformations based on certain conditions.

  • <xsl:apply-templates>: Calls another template to process the selected nodes.

  • <xsl:output>: Specifies the output format of the transformed document, such as HTML, XML, or text.

  • <xsl:import> and <xsl:include>: Allow you to include other XSLT files in your stylesheet.

  • <xsl:param>: Receives external parameters that can be passed to the XSLT stylesheet during transformation.

  • <xsl:variable>: Defines variables that can be used within the XSLT stylesheet.

  • <xsl:comment>: Adds comments to the XSLT document.

  • <xsl:attribute>: Adds attributes to elements in the output document.

  • <xsl:element>: Creates new elements in the output document.

  • <xsl:copy>: Copies nodes from the input document to the output document.

  • <xsl:sort>: Sorts nodes based on specified criteria.

  • <xsl:number>: Adds numbering to nodes in the output document.

⚙️ Problem Definition: The Need for Dynamic Invoice Generation

Invoicing is a routine yet critical task for businesses, involving multiple moving parts: client information, itemized product lists, pricing, taxes, and discounts. To handle this, businesses need a flexible way to manage data and generate invoices that can be tailored to each client. Instead of manually creating each invoice, an automated process allows businesses to generate invoices efficiently based on dynamic data.

The challenge in this specific case was transforming raw XML data into a well-structured HTML invoice format, which could be dynamically updated with different parameters such as client details, items, and invoice totals. This required a solution that could handle these transformations seamlessly.

🛠️ Tools and Technologies Used

To solve this problem, I utilized the following tools and technologies:

  • XSLT (Extensible Stylesheet Language Transformations): A powerful XML-based language for transforming XML data into other formats such as HTML. XSLT allows for applying custom templates to XML data, making it possible to format the information in a way that suits the needs of the application.
  • React: The JavaScript framework for building user interfaces, which helped in managing dynamic data rendering and providing a smooth user experience.
  • DOMParser: A browser-based tool that allows us to parse XML data within the browser.
  • window.XSLTProcessor: A JavaScript object used to apply the XSLT transformation to the XML data.

💡 Solution Approach

1. XSLT Transformation

The core of the solution revolves around the XSLT transformation, where raw XML data is processed and converted into HTML. XSLT is powerful because it enables the dynamic transformation of XML documents into different formats by applying templates defined in an XSLT file.

The XSLT processor works by matching patterns in the XML document and transforming them into the specified output format (HTML, for this case). A key part of this solution is passing parameters to the XSLT, allowing dynamic customization of the output.

XSLT Tags Overview

Some of the important XSLT tags used in the process include:

  • <xsl:template>: This tag defines a template for transforming XML data. It's the foundation of any XSLT document, where you specify what to do with matching elements in the XML source.
  <xsl:template match="/">
    <html>
      <body>
        <xsl:value-of select="invoice/clientName" />
      </body>
    </html>
  </xsl:template>
Enter fullscreen mode Exit fullscreen mode
  • <xsl:value-of>: This tag is used to retrieve the value of an XML node and insert it into the output. For example, to insert the client's name, you would use this tag.
  <xsl:value-of select="invoice/clientName" />
Enter fullscreen mode Exit fullscreen mode
  • <xsl:for-each>: This tag is used to loop through a list of XML elements. It is ideal for processing dynamic lists of items, such as invoice line items or product lists.
  <xsl:for-each select="invoice/items/item">
    <div>
      <xsl:value-of select="name" />
      <xsl:value-of select="price" />
    </div>
  </xsl:for-each>
Enter fullscreen mode Exit fullscreen mode
  • <xsl:if>: This tag allows conditional logic within the transformation. It's useful for handling optional data or applying certain styles conditionally.
  <xsl:if test="invoice/discount">
    <p>Discount Applied</p>
  </xsl:if>
Enter fullscreen mode Exit fullscreen mode

These XSLT tags are key in defining how the XML data is processed and presented in the final output (HTML invoice). The power of XSLT lies in its ability to separate content (XML data) from presentation (HTML structure), making the system scalable and flexible.

2. Building the Invoice Data

To generate the invoice dynamically, we first need to create the necessary data structures. For the example in the code, I used the following approach:

  • Client Information: Details like the client's name, address, and contact information.
  • Company Information: The business's name, address, tax information, etc.
  • Invoice Information: Date, invoice number, and other general info.
  • Invoice Items: A list of products or services provided, including the quantity, price, and total.

Once the data is created, we build the XML string that contains all the necessary information for the invoice.

const xmlString = `
  <Invoices>
    ${Array.from({ length: 1 })
      .map(() => {
        return invoicePageToXml(
          `
          ${variableToXml("createDate", now)}
          ${objectToXml("companyInfo", companyInfo)}
          ${objectToXml("clientInfo", clientInfo)}
          ${objectToXml("headerInfo", headerInfo)}
          ${objectToXml("invoiceInfo", invoiceInfo)}
          ${listToXml("invoiceItems", "invoiceItem", list)}
          `
        );
      })
      .join("")}
  </Invoices>`;
Enter fullscreen mode Exit fullscreen mode

Helper Functions for XML Generation:

To simplify the process of converting data into XML format, I created helper functions that handle the conversion of objects, lists, and variables into XML strings.

import { BaseXmlItem } from "@/types";

/**
 * Transforms an XML string using an XSLT string, with optional parameters.
 * @param {string} xsltString The XSLT string.
 * @param {string} xmlString The XML string.
 * @param {BaseXmlItem} params Optional parameters to pass to the XSLT.
 * @returns The transformed HTML string.
 */
export function transformXml(
  xsltString: string,
  xmlString: string,
  params: BaseXmlItem = {}
): string {
  const parser = new DOMParser();
  const xsltProcessor = new window.XSLTProcessor();

  const xsltDoc = parser.parseFromString(xsltString, "application/xml");
  const xmlDoc = parser.parseFromString(xmlString, "application/xml");

  xsltProcessor.importStylesheet(xsltDoc);

  Object.entries(params).forEach(([key, value]) => {
    xsltProcessor.setParameter(null, key, value);
  });

  xsltProcessor.setParameter(
    null,
    "create-date",
    new Intl.DateTimeFormat("en-US", {
      dateStyle: "full",
      timeStyle: "long",
    }).format(new Date())
  );

  // const date = xsltProcessor.getParameter(null, "create-date");

  const resultDocument = xsltProcessor.transformToDocument(xmlDoc);

  return new XMLSerializer().serializeToString(resultDocument);
}

/**
 * @description Converts a list of items to an XML string.
 * @param {string} tagName The name of the root tag.
 * @param {string} itemName The name of the item tag.
 * @param {BaseXmlItem[]} items The items to convert to XML.
 * @returns {string} The XML string.
 */
export function listToXml(
  tagName: string,
  itemName: string,
  items: BaseXmlItem[]
): string {
  return `<${tagName}>${items
    .map(
      (item) =>
        `<${itemName}>
            ${Object.entries(item).map(
              ([key, value]) => `<${key}>${value}</${key}>`
            )}
        </${itemName}>`
    )
    .join("")}</${tagName}>`;
}

/**
 * @description Converts an object to an XML string.
 * @param {string} tagName The name of the root tag.
 * @param {BaseXmlItem} obj The object to convert to XML.
 * @returns {string} The XML string.
 */
export function objectToXml(tagName: string, obj: BaseXmlItem): string {
  return `<${tagName}>
    ${Object.entries(obj).map(([key, value]) => `<${key}>${value}</${key}>`)}
  </${tagName}>`;
}

export const variableToXml = (name: string, value: string) => {
  return `<${name}>${value}</${name}>`;
};

export const invoicePageToXml = (child: string) => {
  return `
    <Invoice>
        ${child}
    </Invoice>
  `;
};
Enter fullscreen mode Exit fullscreen mode
  • invoicePageToXml: Converts the invoice data into an XML structure.
  • objectToXml: Converts an object into an XML string.
  • listToXml: Converts a list of items into an XML structure.
  • variableToXml: Converts a variable into an XML string.
  • transformXml: Transforms the XML data using the XSLT template.
  • generateInvoiceHtml: Generates the HTML content for the invoice.
  • getNow: Returns the current date and time.

3. Generating the Invoice HTML

After defining the XML structure, the next step is to transform it into HTML. Using the XSLTProcessor, we apply the XSLT template to the XML string, which generates the HTML content dynamically. This is done in the transformXml function, where the transformation is applied, and the final HTML is returned.

Code Example for transformXml:

export function transformXml(xsltString, xmlString, params = {}) {
  const parser = new DOMParser();
  const xsltProcessor = new window.XSLTProcessor();

  const xsltDoc = parser.parseFromString(xsltString, "application/xml");
  const xmlDoc = parser.parseFromString(xmlString, "application/xml");

  xsltProcessor.importStylesheet(xsltDoc);

  Object.entries(params).forEach(([key, value]) => {
    xsltProcessor.setParameter(null, key, value);
  });

  const resultDocument = xsltProcessor.transformToDocument(xmlDoc);
  return new XMLSerializer().serializeToString(resultDocument);
}
Enter fullscreen mode Exit fullscreen mode

🔄 Final Process Flow

Here's the flow of how the dynamic invoice generation works:

  1. Data Creation: Create mock data for the invoice, including client, company, and product information.
  2. XML Generation: Convert the data into a structured XML format.
  3. XSLT Transformation: Apply the XSLT template to transform the XML data into HTML.
  4. HTML Rendering: Render the generated HTML in the browser, allowing users to preview the invoice.
  5. Print: Integrate the react-to-print library for easy printing of the generated invoice.
import { generateInvoiceHtml } from "@/lib/invoice-generator";
import { useRef, useState } from "react";
import {
  generateClientInfo,
  generateCompanyInfo,
  generateHeader,
  generateInvoiceInfo,
  generateInvoiceList,
} from "@/lib/mocks";
import {
  invoicePageToXml,
  listToXml,
  objectToXml,
  variableToXml,
} from "@/helpers/xml";
import { getNow } from "@/helpers/date";
import { useReactToPrint } from "react-to-print";
import { Button } from "@/components/ui/button";

const documentTitle = "Invoice Preview";

export default function InvoicePreviewPage() {
  const [html, setHtml] = useState<string>("");
  const [isPrinting, setIsPrinting] = useState<boolean>(false);
  const componentRef = useRef<HTMLDivElement>(null);
  const handlePrint = useReactToPrint({
    content: () => componentRef.current!,
    documentTitle,
    onBeforePrint() {
      setIsPrinting(true);
    },
    onAfterPrint() {
      setIsPrinting(false);
    },
  });

  const handleSetHtml = async () => {
    const list = generateInvoiceList(5);
    const companyInfo = generateCompanyInfo();
    const clientInfo = generateClientInfo();
    const invoiceInfo = generateInvoiceInfo();
    const headerInfo = generateHeader();
    const now = getNow();
    const len = Math.floor(Math.random() * 10);
    const xmlString = `
        <Invoices>
          ${Array.from({ length: len > 1 ? len : 1 })
            .map(() => {
              return invoicePageToXml(
                `
                    ${variableToXml("createDate", now)}
                    ${objectToXml("companyInfo", companyInfo)}
                    ${objectToXml("clientInfo", clientInfo)}
                    ${objectToXml("headerInfo", headerInfo)}
                    ${objectToXml("invoiceInfo", invoiceInfo)}
                    ${listToXml("invoiceItems", "invoiceItem", list)}
                `
              );
            })
            .join("")}
        </Invoices>
    `;
    const html = await generateInvoiceHtml({
      xsltPath: "test.xslt",
      xmlString,
    });
    setHtml(html);
  };

  return (
    <div className="w-full mx-auto">
      <header className="flex justify-center space-x-4 mb-4">
        <Button
          disabled={isPrinting || !html}
          onClick={handlePrint}
          className=""
        >
          Print
        </Button>
        <Button onClick={handleSetHtml} className="">
          Generate HTML
        </Button>
      </header>
      <div ref={componentRef} className="flex flex-col items-center bg-white">
        <div dangerouslySetInnerHTML={{ __html: html }} />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this React component, we have a button to generate the HTML invoice and another button to print the invoice. The generateInvoiceHtml function is responsible for transforming the XML data into HTML using the XSLT template. The react-to-print library is used to handle the printing functionality.

🌍 Benefits & Impact

  • Efficiency: Automating the invoice generation process saves time, reduces errors, and allows for quick generation of multiple invoices.
  • Customization: The use of XSLT allows for easy modification of the invoice format without altering the data structure.
  • Scalability: The solution can easily handle large volumes of invoices by simply changing the data or XSLT template.
  • User-Friendly: The integration with React and the print functionality offers a smooth, seamless user experience.

🚀 Conclusion & Takeaways

This project demonstrated the power of combining XSLT for XML transformation and React for dynamic rendering. By automating the process of generating invoices, businesses can improve their operational efficiency and focus on higher-value tasks.

  • XSLT is a powerful tool for transforming XML data into various formats, making it a great choice for projects that involve structured data.
  • React enables smooth dynamic rendering of content and integrates well with external libraries like react-to-print for adding printing functionality.
  • Automation of repetitive tasks, like invoice generation, can lead to significant time savings and reduce the chance of human error.

This solution can be extended to various use cases like generating reports, receipts, or any other type of dynamic document generation, making it a versatile tool for developers.

🔗 Related Technologies

  • XML: A markup language that defines rules for encoding documents in a format readable by both humans and machines.
  • XSLT: A language for transforming XML documents into other formats such as HTML, text, or other XML formats.
  • React: A JavaScript library for building user interfaces, particularly for single-page applications.

By leveraging these technologies, developers can build powerful, automated solutions that save time and streamline processes.

Top comments (0)