Rendering a UBL invoice into something a human can read involves more decisions than most teams expect when they start the project. The source XML is deeply nested, multi-party, and carries enough optional fields that any two production invoices may exercise completely different template paths. This guide covers the practical rendering pipeline for UBL invoices, from initial structure analysis through XSLT template design to formatted output, building on the UBL formatting reference and the template patterns in the XSLT workflows page.

Understanding UBL Invoice Structure

Before writing templates, understand what you are transforming. A UBL invoice contains several major sections:

Header. Invoice number, issue date, due date, document currency, tax point date, and various administrative identifiers. These are straightforward to extract and render.

Parties. Supplier, customer, payee, tax representative, and delivery party. Each party has a structured address, identifiers (tax ID, registration number), and contact details. The challenge is that each party structure uses the same element vocabulary but appears in different contexts.

Line items. Each invoice line carries quantity, unit price, item description, tax category, allowances, charges, and item properties. Lines can reference different tax categories and carry different pricing structures.

Tax summary. Tax subtotals grouped by category, with amounts, rates, and taxable base amounts. The tax total must reconcile with the sum of line-level tax amounts.

Monetary totals. Line extension amount, tax exclusive amount, tax inclusive amount, payable amount, and potentially allowance and charge totals.

Each of these sections requires dedicated template logic. The common mistake is underestimating the template count: a production-quality UBL invoice stylesheet typically needs 40 to 80 template rules to handle the full range of elements and optional structures.

Template Design Patterns

The template structure for UBL invoice rendering follows a few reliable patterns:

Top-down section templates. Start with a template matching the Invoice root element that establishes the page structure and calls named templates or applies templates for each major section. This gives you control over the visual layout and section ordering.

Party rendering templates. Create a reusable template (or template mode) that renders party information. Apply it to AccountingSupplierParty, AccountingCustomerParty, PayeeParty, etc. The template handles the common party structure; the context determines which party it renders.

Line item iteration. Use xsl:for-each or xsl:apply-templates with a mode to process InvoiceLine elements. Each line needs quantity, description, price, and tax rendering. Build the line template to handle missing optional fields gracefully.

Tax summary tables. Iterate over TaxSubtotal elements within TaxTotal. For each subtotal, render the tax category, rate, taxable amount, and tax amount. Include a total row that matches the invoice-level TaxAmount.

Template Tip Use template modes to separate rendering contexts. A party template in "header" mode might render a compact address block, while the same party structure in "detail" mode might render a full contact card. Modes prevent template matching conflicts when the same element type appears in different visual contexts.

Handling Multi-Party Invoices

Multi-party invoices are where template design gets tested. A single invoice may include:

  • Supplier (the entity sending the invoice)
  • Customer (the entity receiving the invoice)
  • Payee (a different entity receiving payment, if not the supplier)
  • Tax representative (an entity handling tax obligations on behalf of the supplier)
  • Delivery party (the entity at the delivery address)

Each party has the same structural elements (name, address, identifiers) but needs to render in the correct section of the formatted invoice with the correct heading. The template must also handle cases where optional parties are absent, which means checking for element existence before rendering section headers.

In practice, I use a single party-rendering template with a parameter for the section label:

<xsl:template name="render-party">
  <xsl:param name="party"/>
  <xsl:param name="label"/>
  <xsl:if test="$party">
    <div class="party-block">
      <h3><xsl:value-of select="$label"/></h3>
      <xsl:apply-templates select="$party/cac:Party" mode="party-detail"/>
    </div>
  </xsl:if>
</xsl:template>

This keeps the rendering logic DRY and ensures consistent formatting across all party types.

Currency and Amount Formatting

Currency formatting is one of the most error-prone aspects of invoice rendering. UBL amounts are decimal values with a currencyID attribute. The rendering must:

  • Display the correct currency symbol or code
  • Use the correct decimal separator for the target locale
  • Apply appropriate rounding rules
  • Align amounts in tables for readability

XSLT 1.0 has limited formatting capabilities. format-number() handles basic decimal formatting but does not support locale-aware currency display. For production-quality output, you may need XSLT 2.0’s format-number() with picture strings, or a post-processing step that handles locale-specific formatting.

Watch Out Never assume the decimal separator. UBL amounts use a period as the decimal separator in the XML, but the rendered output may need to show a comma for European locales. Mismatching these breaks both readability and legal compliance.

Tax Rendering

Tax rendering requires care because the numbers must reconcile. The rendered tax summary should show:

  • Each tax category with its rate
  • The taxable base amount for each category
  • The tax amount for each category
  • The total tax amount

The template should also handle rounding differences. In some jurisdictions, line-level rounding differs from summary-level rounding, and small discrepancies are allowed within tolerance. Your rendering template should match the rounding approach used in the source data rather than recalculating from line items.

HTML vs PDF Output

For HTML output, XSLT produces semantic markup that CSS styles for presentation. This is straightforward and gives you full control over responsive layout, typography, and print styles.

For PDF output, the typical approach is XSLT to XSL-FO, then an FO processor (Apache FOP, Antenna House, RenderX) to PDF. XSL-FO stylesheets are more complex than HTML stylesheets because they must handle page layout, headers, footers, page breaks, and absolute positioning.

An alternative PDF approach is generating HTML and then converting to PDF using a headless browser or HTML-to-PDF tool. This trades FO complexity for HTML/CSS control but may produce less precise page layout.

Testing with Real Data

The single most important practice for UBL invoice rendering is testing with real data. Synthetic test invoices exercise a narrow range of element combinations. Real invoices from production systems expose optional fields, unusual party structures, multi-page line item lists, and currency edge cases that synthetic data does not.

Build a test corpus of real invoices from each jurisdiction and industry you support. Run the full corpus on every stylesheet change. Diff the output against expected results. This is the only reliable way to catch rendering regressions.