Skip to content
invopopPublic

About

GOBL conversion into Universal Business Language (UBL) XML format and vice versa.

Resources

Stars

4 stars

Watchers

2 watching

Forks

Latest commit

 

History

473 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GOBL.UBL

GOBL conversion into UBL XML format and vice versa.

codecov Ask DeepWiki

Copyright Invopop Ltd. 2025. Released publicly under the Apache License Version 2.0. For commercial licenses, please contact the dev team at invopop. To accept contributions to this library, we require transferring copyrights to Invopop Ltd.

Usage

Go Package

Usage of the GOBL to UBL conversion library is straightforward and supports bidirectional conversion:

  1. Convert GOBL to UBL XML: You must first have a GOBL Envelope, including an invoice, ready to convert. There are some samples in the test/data directory.

  2. Parse UBL XML into GOBL: You need to have a valid UBL XML document that you want to convert to GOBL format.

Both conversion directions are supported, allowing you to seamlessly transform between GOBL and UBL XML formats as needed.

Convert GOBL to UBL

package main

import (
    "os"

    "github.com/invopop/gobl"
    ubl "github.com/invopop/gobl.ubl"
)

func main() {
    data, _ := os.ReadFile("./test/data/invoice-sample.json")

    env := new(gobl.Envelope)
    if err := json.Unmarshal(data, env); err != nil {
        panic(err)
    }

    // Prepare the UBL Invoice document
    doc, err := ubl.ConvertInvoice(env)
    if err != nil {
        panic(err)
    }

    // Create the XML output
    out, err := doc.Bytes()
    if err != nil {
        panic(err)
    }

}

The ubl package also supports using specific of custom contexts that can be used to generate documents with specific customization and profile identifiers. To use something other than the default, add the options during conversion. For example:

doc, err := ubl.ConvertInvoice(env, ubl.WithContext(ubl.ContextPeppol))

UBL to GOBL

package main

import (
    "encoding/json"
    "os"

    ubl "github.com/invopop/gobl.ubl"
)

func main() {
    // Read the UBL XML file
    inData, err := os.ReadFile("path/to/ubl_invoice.xml")
    if err != nil {
        panic(err)
    }

    // Parse the UBL document
    doc, err := ubl.Parse(inData)
    if err != nil {
        panic(err)
    }

    // Type assert to the appropriate document type
    inv, ok := doc.(*ubl.Invoice)
    if !ok {
        panic("expected an invoice document")
    }

    // Convert to GOBL envelope
    env, err := inv.Convert()
    if err != nil {
        panic(err)
    }

    // Extract binary attachments if needed
    attachments := inv.ExtractBinaryAttachments()

    // Marshal to JSON
    outputData, err := json.MarshalIndent(env, "", "  ")
    if err != nil {
        panic(err)
    }
}

Command Line

The GOBL to UBL tool includes a command-line helper. You can install it manually in your Go environment with:

go install ./cmd/gobl.ubl

Once installed, usage is straightforward. The tool automatically detects the input file type and performs the appropriate conversion:

  • If the input is a JSON file (GOBL format), it will convert it to UBL XML.
  • If the input is an XML file (UBL format), it will convert it to GOBL JSON.

For example:

gobl.ubl convert ./test/data/invoice-sample.json

Testing

testify

The library uses testify for testing. To run the tests, you can use the following command:

go test ./...

Schematron validation

Beyond the golden-file comparisons, the generated XML can be pushed through the real EN 16931 / Peppol / XRechnung / French CTC / ZATCA schematron rule sets. Validation runs against phorm, the standalone validation service that replaced the now-archived invopop/phive gRPC wrapper, using the invopop/phorm HTTP client.

Start a service locally:

docker run -d --name phorm -p 8080:8080 phelger/phorm

Use phelger/phorm-arm64 on Apple Silicon. Note the image is phelger/phorm, not phax/phorm — the latter does not exist, despite what the invopop/phorm README says.

It takes a few seconds to boot. It is ready once this returns HTTP 200:

curl -s -o /dev/null -w '%{http_code}\n' \
  -H 'X-Token: phorm-dev-token' \
  'http://localhost:8080/api/get/vesids?include-deprecated=true'

Then run the suite with -validate:

go test ./... -validate

Without -validate the validating tests are skipped, so the plain go test ./... never needs a service and CI stays offline.

Pointing somewhere else

PHORM_URL and PHORM_TOKEN default to http://localhost:8080 and phorm's stock development token. Override them for a shared instance, or when port 8080 is already taken locally:

docker run -d --name phorm -p 8085:8080 phelger/phorm
PHORM_URL=http://localhost:8085 go test ./... -validate

Known failures

These fixtures do not currently pass schematron. They are long-standing gaps in the conversion rather than regressions, so a run is "clean" when only these fail:

Rule Fixtures Cause
BR-KSA-33 all zatca/ invoices The invoice counter value (KSA-16) is never emitted: ZATCA needs an ICV cac:AdditionalDocumentReference.
PEPPOL-EN16931-R061 peppol/invoice-prices-include-vat.json The fixture pays by SEPA direct debit but carries no mandate reference (BT-89).

Notes

  • A failed validation is not an error. phorm answers a document that breaks a rule with an HTTP 400 carrying the report, which it also uses for a request it rejects outright, so invopop/phorm separates the two by whether the body is a validation report (fixed in v0.1.5). An error from ValidateXml therefore means the validation never ran — unreachable service, rejected token, unresolvable VESID, or a body that is not XML — and the tests treat it as fatal, since nothing was checked.
  • phorm normalises VESID versions, so the fr.ctc:ubl-invoice:1.4.0-03 spelling in context.go resolves to its published fr.ctc:ubl-invoice:1.4-03 rule set. The resolved id comes back as ves.vesid, which is worth checking when a rule set behaves unexpectedly.
  • phive-rules keeps only a rolling window of releases. VESIDs that pass today are dropped a few releases later, so context.go needs periodic updating; GET /api/get/vesids?include-deprecated=true lists what a given phorm build actually carries, along with a deprecated flag.

Considerations

There are certain assumptions and lost information in the conversion from UBL to GOBL that should be considered:

  1. GOBL does not currently support additional embedded documents, so the AdditionalReferencedDocument field (BG-24 in EN 16931) is not supported and lost in the conversion.
  2. GOBL only supports a single period in the ordering, so only the first InvoicePeriod (BG-14) in the UBL is taken.
  3. Fields ProfileID (BT-23) and CustomizationID (BT-24) in UBL are not supported and lost in the conversion.
  4. The AccountingCost (BT-19, BT-133) fields are added as notes.
  5. Payment advances do not include their own tax rate, they use the global tax rate of the invoice.

Development

The main source of information for this project comes from the EN 16931 standard, developed by the EU for electronic invoicing. Part 1 of the standard defines the semantic data model that forms an invoice, but does not provide a concrete implementation. Part 3.2 defines the mappings from the semantic data model to the UBL 2.1 XML format covered in this repository.

Useful links:

About

GOBL conversion into Universal Business Language (UBL) XML format and vice versa.

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages