Skip to content

XML Activities

Version: 1.1.0  ·  ID: zoan-packages-xml  ·  Author: Zoan Software

The XML package reads, queries and modifies XML documents, a data exchange format widely used in integrations (electronic invoicing, SOAP web services, configurations, reports exported by other systems). It lets you extract data from an XML and modify it without parsing the text by hand.

Activities work on XML text (a string), not on an open file. The typical pattern is:

  1. Read XML reads the file and returns its contents as a string.
  2. You query values with XML Query Text / XML Query All, convert everything to a dictionary with XML to Dictionary, or modify it with XML Set Value / XML Add Element (which return the modified XML).
  3. Write XML saves the result to a file.
Read XML path = filePath → output: xml
XML Query Text xml = xml xpath = "//Invoice/Total" → output: total

The activities that modify (XML Set Value, XML Add Element) return the document indented and without the <?xml …?> declaration. Write XML writes the declaration again, with the file’s encoding, when saving.

XPathSelects
//NumberEvery <Number> element at any level
//Invoice/NumberThe <Number> elements that are direct children of <Invoice>
//Invoice[@id='5']The <Invoice> with attribute id="5"
//Item[2]The second <Item>
//Total/@currencyThe value of the currency attribute of <Total>
count(//Item)How many <Item> there are

The XPath (1.0) is evaluated over the whole document. It can select elements, attributes, text nodes or compute a value (count(…), sum(…), a comparison); the query activities always return its text:

  • An element with children returns the text of all its descendants, joined.
  • An integer comes out without decimals (3, not 3.0); a boolean as true or false.

Many integration XMLs (UBL invoices, SOAP) use namespaces: names carry a prefix, like cbc:ID or cac:InvoiceLine, declared with xmlns:cbc="…". Since version 1.1.0, the activities with xpath register them automatically:

  • Prefixes declared in the document (at the root or deeper) are used as-is in the XPath: //cbc:ID, //cac:LegalMonetaryTotal/cbc:PayableAmount. No need for local-name().
  • The default namespace (xmlns="…", no prefix) is written in the XPath with the prefix d: — /d:Invoice/cbc:ID. You can change it with defaultPrefix (empty means d). If the document already declares a d prefix for another namespace, that one wins: pick another defaultPrefix (e.g. n) to reach the default namespace.
  • If the XPath finds nothing, the document has a default namespace and the XPath doesn’t use its prefix, the log reminds you with a warning: The XML has a default namespace: in the XPath, write its elements with the prefix ‘d:’ (e.g. //d:Invoice).
  • With the optional namespaces parameter (dictionary prefix → URI) you add your own prefixes or override the document’s; yours take precedence.
  • If the XPath uses a prefix that doesn’t exist, the error tells you which one and which prefixes are available.
  • If the same prefix is declared with two different URIs, the first one in document order wins; to use the other, define it in namespaces.
XML Query Text
xml = document
xpath = "//inv:Total"
namespaces = dictSet(newDict(), "inv", "urn:example:invoices")
→ output: total

Sometimes an XML carries another XML as text, inside a CDATA. That is the case of Colombia’s electronic invoice (DIAN, UBL 2.1): it arrives as an AttachedDocument and the real invoice is in cac:Attachment/cac:ExternalReference/cbc:Description. It takes two steps: extract that text and query it directly (no need to save it to a file).

Read XML path = filePath → output: container
XML Query Text xml = container
xpath = "//cac:Attachment/cac:ExternalReference/cbc:Description" → output: invoice
XML Query Text xml = invoice xpath = "/d:Invoice/cbc:ID" → output: number
XML Query Text xml = invoice
xpath = "//cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID" → output: issuerTaxId
XML Query Text xml = invoice xpath = "/d:Invoice/cbc:IssueDate" → output: issued
XML Query Text xml = invoice xpath = "/d:Invoice/cbc:DueDate" → output: due
XML Query Text xml = invoice xpath = "//cac:LegalMonetaryTotal/cbc:PayableAmount" → output: total
XML Query Text xml = invoice xpath = "/d:Invoice/cbc:UUID" → output: cufe
XML Query All xml = invoice xpath = "//cac:InvoiceLine/cbc:LineExtensionAmount" → output: lineAmounts

If you need a lot of data, convert the invoice once with XML to Dictionary. The text coming out of the CDATA may start with line breaks, spaces or a BOM before <?xml …?>: the activities ignore them. It also works if the embedded XML comes escaped (&lt;Invoice…) instead of in a CDATA.

Since version 1.1.0, every activity reads XML safely (including Read XML and Write XML, which validate the document):

  • DTDs are not allowed (<!DOCTYPE …>), nor are entities: an XML with a DTD is rejected with a clear error. This prevents external entity attacks (XXE), which try to read files on the machine, and entity-expansion “bombs”.
  • Maximum size: 50 MB per document (50 M characters when it arrives as text).
  • Maximum nesting: 256 levels.

These messages can come from any activity that reads XML, and they stop the activity. If the XML comes from outside and may arrive damaged, wrap the reading in a Try Catch.

MessageCause and fix
XML parser message with line and position (e.g. Data at the root level is invalid. Line 1, position 1.)The text is not well-formed XML: unclosed tags, text before the root, unescaped &. Check where the text comes from.
The XML declares a DTD (<!DOCTYPE>). DTDs and entities are not allowed for security reasons (XXE); remove the <!DOCTYPE> declaration.The document has a <!DOCTYPE …>. Ask for the file without a DTD or strip it from the text before processing.
The XML is too large (… MB); the limit is 50 MB.The document exceeds the limit. Ask for smaller files or split them at the source.
The XML is nested more than 256 levels deep.Abnormally deep structure.
The XPath expression «…» is not valid: …Syntax error in the XPath (unclosed brackets or quotes, unknown function).
The prefix 'x:' is not declared in the XML or in 'namespaces'. Available prefixes: ….The XPath uses a prefix the document doesn’t declare. Use one from the list or define it in namespaces.
'namespaces' must be a dictionary of prefix → namespace URI.You passed a text or a list in namespaces. Use a dictionary.
'…' is not a valid namespace prefix in 'namespaces'. / The prefix '…' in 'namespaces' has no namespace URI.A key in namespaces is not a valid XML name (or is xml/xmlns), or its URI is empty.
'…' is not a valid prefix for the default namespace.defaultPrefix is not a valid XML name (no spaces or :), or is xml/xmlns.
ActivityTypeOutputWhat it does
Read XMLxml-readstringReads an XML file as text
Write XMLxml-writestringSaves an XML text to a file
XML Query Textxml-query-textstringFirst value matching an XPath
XML Query Allxml-query-allListAll matching values
XML to Dictionaryxml-to-dictionaryDictionaryConverts the XML (or a fragment) into a navigable dictionary
XML Set Valuexml-set-valuestringChanges the value of an element or attribute
XML Add Elementxml-add-elementstringAdds a child element