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.
How this package works
Section titled “How this package works”Activities work on XML text (a string), not on an open file. The typical pattern is:
- Read XML reads the file and returns its contents as a string.
- 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).
- Write XML saves the result to a file.
Read XML path = filePath → output: xmlXML Query Text xml = xml xpath = "//Invoice/Total" → output: totalThe 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.
XPath in a scoop
Section titled “XPath in a scoop”| XPath | Selects |
|---|---|
//Number | Every <Number> element at any level |
//Invoice/Number | The <Number> elements that are direct children of <Invoice> |
//Invoice[@id='5'] | The <Invoice> with attribute id="5" |
//Item[2] | The second <Item> |
//Total/@currency | The 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, not3.0); a boolean astrueorfalse.
Namespaces
Section titled “Namespaces”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 forlocal-name(). - The default namespace (
xmlns="…", no prefix) is written in the XPath with the prefixd:—/d:Invoice/cbc:ID. You can change it withdefaultPrefix(empty meansd). If the document already declares adprefix for another namespace, that one wins: pick anotherdefaultPrefix(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
namespacesparameter (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: totalXML inside XML (electronic invoice)
Section titled “XML inside XML (electronic invoice)”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: containerXML Query Text xml = container xpath = "//cac:Attachment/cac:ExternalReference/cbc:Description" → output: invoiceXML Query Text xml = invoice xpath = "/d:Invoice/cbc:ID" → output: numberXML Query Text xml = invoice xpath = "//cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID" → output: issuerTaxIdXML Query Text xml = invoice xpath = "/d:Invoice/cbc:IssueDate" → output: issuedXML Query Text xml = invoice xpath = "/d:Invoice/cbc:DueDate" → output: dueXML Query Text xml = invoice xpath = "//cac:LegalMonetaryTotal/cbc:PayableAmount" → output: totalXML Query Text xml = invoice xpath = "/d:Invoice/cbc:UUID" → output: cufeXML Query All xml = invoice xpath = "//cac:InvoiceLine/cbc:LineExtensionAmount" → output: lineAmountsIf 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 (<Invoice…) instead of in a CDATA.
Security and limits
Section titled “Security and limits”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.
Common package errors
Section titled “Common package errors”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.
| Message | Cause 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. |
Activities
Section titled “Activities”| Activity | Type | Output | What it does |
|---|---|---|---|
| Read XML | xml-read | string | Reads an XML file as text |
| Write XML | xml-write | string | Saves an XML text to a file |
| XML Query Text | xml-query-text | string | First value matching an XPath |
| XML Query All | xml-query-all | List | All matching values |
| XML to Dictionary | xml-to-dictionary | Dictionary | Converts the XML (or a fragment) into a navigable dictionary |
| XML Set Value | xml-set-value | string | Changes the value of an element or attribute |
| XML Add Element | xml-add-element | string | Adds a child element |
Next steps
Section titled “Next steps”- Read XML — read an XML file.
- XML Query Text — extract a value with XPath.
- XML to Dictionary — all the data at once.
- JSON — for data in JSON format.