Maps
Transform messages with XSLT 3.0 and full XPath 3.1 support. A map reads XML or JSON as its source; CSV and flat files must be disassembled to XML or JSON first, and any format can be written on the output side.
A map is the transformation step that sits between two message shapes. It accepts a payload in one format, runs an XSLT 3.0 stylesheet against it, and emits a new payload, usually carrying a different message type. Maps are scoped to an Application and run from a hookpoint on a port; they never run on their own.
Maps are authored as XSLT stylesheets in the built-in code editor. Saxon HE 12.9 provides the engine; the XSLT 3.0 / XPath 3.1 function library covers XML, JSON, CSV, and flat text out of the box. There is no compile step, saving a map deploys it.
Maps are scoped to an Application. From the application menu, the Maps list shows every map you have defined inside that application; a map can only be referenced from ports in the same application.
The list view follows the platform's Selected Application behavior, with an Application selected, the list filters to it and the Application column is hidden; with no selection, the list spans every Application you have access to and the column is shown. Creating a new map uses the same flow: with an Application selected, the form opens with that Application preemptively chosen; without one, the form exposes an Application picker.
A map only does work once it is referenced from a hookpoint, most often a map step on a port. The hookpoint itself decides whether the map runs at all and, when several maps are in play, which one runs for a given message. The port’s Map Assignment setting picks between three configurations.
Typed table rows are unique by source Message Type, the UI enforces that. From the map’s own perspective the three modes look the same: write the XSLT once and the hookpoint decides when it runs. The hookpoint-specific rules, how the incoming Message Type is resolved, what happens after the map runs, and which modes are available on a given side of a given port, live with the port: see Receive port, Send port, Loopback port, and Null port.
A map’s output is not just a new payload, it is a brand-new message that carries its own message type. The runtime treats that as an instantiation moment: the new type’s promotions are evaluated against the freshly produced payload and, if a schema is associated with the destination type, the payload is validated against it. This is the same machinery that fires at inbound classification, the second of the two moments documented in When a message type is instantiated.
The Art2link ESB map engine is powered by SaxonHE 12.9, an industry-standard XSLT processor that provides full compliance with the latest W3C specifications.
| Capability | Version |
|---|---|
| XSLT | 3.0 (backwards compatible with 1.0 and 2.0) |
| XPath | 3.1 |
| XQuery | 3.1 |
| XSLT 4.0 | Selective support for proposed features |
A map can read only XML or JSON as its source. XML and JSON are first-class on the input side; CSV and TXT cannot be read by the map directly and must be disassembled to XML or JSON by a pipeline component first. On the output side any format can be written, including CSV and flat text via method="text".
A map navigates only XML or JSON, so a CSV or flat-text body has to be disassembled to XML or JSON by a pipeline component before the map runs; the map never reads the raw delimited string itself. The output side is unconstrained: method="text" lets the XSLT emit whatever character stream you build with string-join() and xsl:text, so a map can still produce CSV or flat text.
The mapping engine includes the full set of XSLT 3.0 / XPath 3.1 functions. Below are the most commonly used ones in integration scenarios.
| Function | Description |
|---|---|
| //element | Descendant axis, selects elements at any depth. |
| path/to/node | Child axis, navigates the document tree. |
| position() | Returns the context position within a sequence. |
| last() | Returns the size of the context sequence. |
| name() | Returns the name of the context node. |
| Function | Description |
|---|---|
| count() | Returns the number of items in a sequence. |
| sum() | Returns the sum of a sequence of numbers. |
| min() / max() | Returns the minimum or maximum value in a sequence. |
| avg() | Returns the average of a sequence of numbers. |
| Function | Description |
|---|---|
| concat() | Concatenates two or more strings. |
| string-join() | Joins a sequence of strings with a separator. |
| substring() | Extracts a portion of a string. |
| replace() | Replaces substrings using a regular expression. |
| tokenize() | Splits a string into a sequence using a regex pattern. |
| matches() | Tests whether a string matches a regular expression. |
| normalize-space() | Strips leading/trailing whitespace and collapses internal whitespace. |
| upper-case() / lower-case() | Case conversion. |
| Function | Description |
|---|---|
| current-dateTime() | Returns the current date and time. |
| format-dateTime() | Formats a dateTime value using a picture string. |
| format-number() | Formats a number using a picture string. |
| number() | Converts a value to a number. |
| Function | Description |
|---|---|
| map:merge() | Merges multiple maps into one. |
| map:get() | Retrieves a value from a map by key. |
| map:keys() | Returns all keys in a map. |
| array:size() | Returns the number of members in an array. |
| array:get() | Retrieves an item from an array by position. |
| array:for-each() | Applies a function to each member of an array. |
| Function | Description |
|---|---|
| for-each() | Applies a function to every item in a sequence. |
| filter() | Returns items from a sequence that match a predicate. |
| fold-left() / fold-right() | Reduces a sequence to a single value using an accumulator. |
| sort() | Sorts a sequence using a custom comparator. |
Beyond the built-in XSLT and XPath library, maps can call user-authored custom functions, compiled C# methods deployed through the Art2link ESB UI. See the Custom functions topic for how to author and deploy them. Once a function is compiled, a map can invoke it in two ways.
The same double-curly binding used elsewhere in the map for variables, promoted properties, and other references. Typing {{ opens intellisense, which exposes available custom functions alongside the other binding sources. Calls written this way are resolved by a preprocessor pass before the XSLT runs, so the function executes outside the XSLT context, no XPath context is shared with it.
<xsl:template match="Order"> <PurchaseOrder> <Active>{{Function.fnParseBool(Promoted.Order.IsActive)}}</Active> </PurchaseOrder> </xsl:template>
The argument is itself a binding token, here a promotion, written without its own {{ }}: once you are inside the outer binding, a nested reference is written bare. It has to be a pre-resolved token like a promotion because the whole call is resolved before the XSLT runs: the preprocessor has no XPath context, so a bare payload field would mean nothing to it. A value that depends on the current node belongs to art:invoke below.
Custom functions can also be called inline from XSLT as native functions. This requires declaring the urn:art2link:functions namespace on the stylesheet and excluding its prefix from the result tree:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:art="urn:art2link:functions" exclude-result-prefixes="xs art">
With the namespace declared, invoke any custom function via art:invoke, passing the function name as the first argument and the function's parameters after it. Parameters use standard XPath nomenclature, so any XPath expression, field references, casts, computed values, can be passed in:
<xsl:template match="Order"> <PurchaseOrder> <CreatedAt> <xsl:value-of select="art:invoke('fnFormatDateTime', string(CreatedAt))"/> </CreatedAt> </PurchaseOrder> </xsl:template>
Four worked examples cover the most common integration shapes. Each one starts from a single XSLT 3.0 stylesheet, no library, no preprocessor, and emits the destination format with the standard xsl:output method.
The output pairings (XML → CSV, JSON → CSV, and so on) work the same way: read the XML or JSON source, then pick the xsl:output method for the destination. To go the other way, from CSV or flat text, disassemble it to XML or JSON in a pipeline component first, then map as usual.
Restructure an XML document into a different schema. This example maps a source <Order> to a canonical <PurchaseOrder> format.
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:template match="Order"> <PurchaseOrder id="{@OrderId}"> <Buyer><xsl:value-of select="Customer/Name"/></Buyer> <Items> <xsl:for-each select="LineItems/Item"> <LineItem sku="{@Code}"> <Description><xsl:value-of select="Name"/></Description> <Qty><xsl:value-of select="Quantity"/></Qty> <Price><xsl:value-of select="UnitPrice"/></Price> </LineItem> </xsl:for-each> </Items> <Total><xsl:value-of select="sum(LineItems/Item/(Quantity * UnitPrice))"/></Total> </PurchaseOrder> </xsl:template> </xsl:stylesheet>
Convert XML input to JSON output using XSLT 3.0 maps and arrays with serialize().
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:map="http://www.w3.org/2005/xpath-functions/map"> <xsl:output method="text"/> <xsl:template match="Order"> <xsl:variable name="result" select=" map { 'orderId': string(@OrderId), 'buyer': string(Customer/Name), 'items': array { for $item in LineItems/Item return map { 'sku': string($item/@Code), 'name': string($item/Name), 'qty': number($item/Quantity), 'price': number($item/UnitPrice) } }, 'total': sum(LineItems/Item/(Quantity * UnitPrice)) }"/> <xsl:value-of select="serialize($result, map { 'method': 'json', 'indent': true() })"/> </xsl:template> </xsl:stylesheet>
Consume a JSON input and produce structured XML. The JSON body arrives as a raw string; the template matches / and parses it with parse-json(), after which the JSON is available as an XPath 3.1 map.
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <!-- JSON arrives as a string; parse-json() turns it into an XPath 3.1 map --> <xsl:template match="/"> <xsl:variable name="in" select="parse-json(.)"/> <Invoice> <InvoiceNumber><xsl:value-of select="$in?invoiceNo"/></InvoiceNumber> <Vendor><xsl:value-of select="$in?vendor?name"/></Vendor> <Lines> <xsl:for-each select="$in?lines?*"> <Line> <Product><xsl:value-of select="?product"/></Product> <Amount><xsl:value-of select="?qty * ?price"/></Amount> </Line> </xsl:for-each> </Lines> </Invoice> </xsl:template> </xsl:stylesheet>
Produce a flat delimited file from JSON input. This example generates pipe-delimited output with a header row, a common pattern for EDI and legacy system integrations.
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:variable name="in" select="parse-json(.)"/> <!-- Header --> <xsl:text>Product|Qty|Price|Total </xsl:text> <!-- Data rows --> <xsl:for-each select="$in?lines?*"> <xsl:value-of select="string-join( (?product, string(?qty), string(?price), string(?qty * ?price)), '|')"/> <xsl:text> </xsl:text> </xsl:for-each> </xsl:template> </xsl:stylesheet>
The XSLT 3.0 and XPath 3.1 function libraries are extensive. Reach for Custom Functions only when built-in capabilities cannot meet the requirement.
Extract common transformation patterns into named templates with <xsl:template name="..."> and call them with <xsl:call-template>.
Use the XPath 3.1 lookup operator (?key) to navigate JSON structures instead of converting JSON to XML first. It is cleaner and more performant.
Use if or choose to guard against missing or empty values. Defensive maps prevent runtime errors when input data varies.
Saving a map overwrites the previous version immediately. Deploy changes when no messages are actively being processed to avoid unpredictable behavior.
Each map should handle one transformation. For complex pipelines that involve multiple transformations, chain them through components rather than building monolithic maps.
Ready to map
Open the map editor, write your XSLT, and save. There is no compile step: the saved map is live on its hookpoint immediately.