Skip to content
Art2link ESB v2.02 LTS HomeDocumentationBlogContact
Core concepts/Maps

Maps

Transform messages between formats using XSLT 3.0 with full XPath 3.1 support — XML, JSON, CSV, and flat files, all from a single mapping language.

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.

INPUT HOOKPOINT MAP OUTPUT Message type: Source XML / JSON / CSV / TXT Map step on a port NONE / TYPED / UNIVERSAL decides which map runs XSLT 3.0 stylesheet Saxon HE XPath 3.1 · maps · arrays New message type: Destination promotions + schema fire The hookpoint decides whether a map runs. The engine runs the XSLT. The output is a new message of the destination type.

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 in Art2link ESB define the transformation logic between source and destination message formats. A map takes an incoming message — XML, JSON, or flat text — and produces a structured output in whatever format the destination system requires.

Maps are authored as XSLT stylesheets using the built-in code editor. XSLT 3.0 provides native support for XML, JSON, CSV, and plain text transformations, making it the ideal language for modern integration scenarios where messages arrive in mixed formats.

There is no compilation or deployment step. Write your map, save it, and reference it from a Port. Art2link ESB handles the rest.


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. There are three configurations.

None MT: X Message in no map MT: X Message out No transform Passes through unchanged Typed MT: B in SOURCE MT → MAP A B C Map table MT: B′ out Picked by source MT Miss → no map applied Universal MT: any Message in One map MT: Y Message out One map, any source Match is author’s responsibility

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.

MAP OUTPUT — INSTANTIATION MOMENT 2 MAP RUNS XSLT executes STAGE 1 Type assigned STAGE 2 Promotions fire STAGE 3 Validate IF SCHEMA BUS New message Map output = a fresh typed message. Promotions and (if attached) schema validation fire on the destination type, not the source type. A pre-map schema is not re-checked after the map runs — it had its turn at the previous instantiation moment. A validation failure on the destination type marks the message errored; recovery means fixing the map, not retrying.
The map authors the destination message. Whatever shape your XSLT emits becomes the payload the rest of the platform sees — routing, downstream maps, tracking, and any schema bound to the destination type all operate on the post-map output. The pre-map message stops existing the moment the map returns.

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.

CapabilityVersion
XSLT3.0 (backwards compatible with 1.0 and 2.0)
XPath3.1
XQuery3.1
XSLT 4.0Selective support for proposed features
Why XSLT 3.0? XSLT 3.0 added native JSON handling, streaming, higher-order functions, maps, arrays, and the try/catch instruction — making it a complete data transformation language, not just an XML tool.
Format support

XSLT 3.0 handles the four message-type formats natively, but not all four can be read or written with the same machinery. XML and JSON are first-class on both sides; CSV is consumed natively and emitted as text; flat text outside CSV needs a small adapter on the way in.

FORMAT READ (input) WRITE (output) XML Native XPath node tree /Order/Customer/Name · @attr · for-each select Literal result elements xsl:output method="xml" JSON XDM maps and arrays ?key · ?items?* · json-doc() map{} / array{} + serialize() { 'method': 'json' } CSV Read as text, parse with XPath unparsed-text() · tokenize() · for-each-line string-join + xsl:text xsl:output method="text" TXT Disassemble to XML or JSON first pipeline component → XPath Hand-built strings xsl:output method="text"

CSV input is read with unparsed-text() and split per line with tokenize() — no pipeline component required. TXT input that is not a delimited grid (fixed-width records, EDI, binary) is the only format that has to be disassembled to XML or JSON by a pipeline component before the map can navigate it. Every format can be written directly because method="text" lets the XSLT emit whatever character stream you build with string-join() and xsl: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.

XPath & Node Selection
FunctionDescription
//elementDescendant axis — selects elements at any depth.
path/to/nodeChild 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.
Aggregation & Counting
FunctionDescription
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.
String Functions
FunctionDescription
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.
Date, Time & Number
FunctionDescription
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.
Maps & Arrays (XPath 3.1)
FunctionDescription
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.
Higher-Order Functions
FunctionDescription
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.
Full reference: For the complete list of functions, see the Saxonica function reference.

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.

Binding syntax {{ … }} PREPROCESSOR Source map {{fn(IsActive)}} C# resolves "true" Rewritten "true" THEN XSLT RUNS Saxon sees a plain string; no function context shared Use when drop a value into the output as a literal — no XPath logic depends on the result art:invoke art:invoke('fn',…) NATIVE XSLT CALL XSLT engine running — XPath context live xsl:if test= art:invoke(…) C# returns typed value XPath xmlns:art="urn:art2link:functions" Use when the result feeds into xsl:if / xsl:choose, a larger XPath expression, or needs a type
Binding syntax (preprocessor)

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.

XSLT 3.0Binding inside a map
<xsl:template match="Order">
  <PurchaseOrder>
    <Active>{{Function.fnParseBool(IsActive)}}</Active>
  </PurchaseOrder>
</xsl:template>
XSLT function call (art:invoke)

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:

XSLT 3.0Stylesheet declaration
<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:

XSLT 3.0art:invoke inside a map
<xsl:template match="Order">
  <PurchaseOrder>
    <CreatedAt>
      <xsl:value-of select="art:invoke('fnFormatDateTime', string(CreatedAt))"/>
    </CreatedAt>
  </PurchaseOrder>
</xsl:template>
When to use which. The binding syntax is the quickest way to drop a function value into the output and is the right pick when the call has no dependence on XSLT context. art:invoke is the right pick when the call needs to participate in XSLT logic — inside xsl:if/xsl:choose, as part of a larger XPath expression, or anywhere the result must be a typed XPath value rather than a preprocessed string.

Unlike XML, JSON does not have elements or attributes. In XPath 3.1, JSON data is represented as maps (objects) and arrays. You navigate them using the lookup operator ? — this is the JSON equivalent of XPath's / for XML.

Lookup Syntax
ExpressionMeaningJSON Equivalent
?keyGet a value from a map by keyobj.key
?key1?key2Navigate nested mapsobj.key1.key2
?items?*Iterate all members of an arrayobj.items[*]
?items?(1)Get the first member of an array (1-based)obj.items[0]
?*Get all values from a mapObject.values(obj)
Example

Given this JSON input:

JSONInput message
{
  "orderId": "PO-1024",
  "customer": {
    "name": "Acme Corp",
    "region": "US-West"
  },
  "items": [
    { "sku": "A100", "qty": 5 },
    { "sku": "B200", "qty": 12 }
  ]
}

You would navigate it in XPath like this:

ExpressionReturns
?orderId"PO-1024"
?customer?name"Acme Corp"
?items?*?sku("A100", "B200")
?items?(1)?qty5
count(?items?*)2
sum(?items?*?qty)17
Think of ? as the JSON equivalent of /. Where XML navigation uses Order/Customer/Name, JSON navigation uses ?order?customer?name. The ?* wildcard iterates arrays, just like Item selects all child elements of that name.

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.

EXAMPLE 1 XML XML Restructure Order → PurchaseOrder method="xml" canonicalize internal shapes EXAMPLE 2 XML JSON Cross-format XML → modern REST map{} + serialize() XML legacy → JSON API EXAMPLE 3 JSON XML Cross-format REST → XML canonical ?key + literal XML JSON inbound → XML core EXAMPLE 4 JSON CSV Flatten JSON → pipe-delimited method="text" REST → EDI/legacy

The reverse pairings (XML → CSV, CSV → XML, CSV → JSON, etc.) work the same way — pick the read pattern for the source format and the xsl:output method for the destination, and the rest is XPath.


Restructure an XML document into a different schema. This example maps a source <Order> to a canonical <PurchaseOrder> format.

XSLT 3.0XML to XML
<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().

XSLT 3.0XML to JSON
<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 map engine can accept JSON input directly using json-doc(). Inside the XSLT, the JSON is available as an XDM map.

XSLT 3.0JSON to XML (initial template)
<xsl:stylesheet version="3.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" indent="yes"/>

  <!-- JSON input is passed as the global context item (a map) -->
  <xsl:template name="xsl:initial-template">
    <Invoice>
      <InvoiceNumber><xsl:value-of select="?invoiceNo"/></InvoiceNumber>
      <Vendor><xsl:value-of select="?vendor?name"/></Vendor>
      <Lines>
        <xsl:for-each select="?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>
The ?key syntax is how you access map entries in XPath 3.1. ?lines?* iterates over all members of the lines array.

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.

XSLT 3.0JSON to pipe-delimited CSV
<xsl:stylesheet version="3.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>

  <xsl:template name="xsl:initial-template">
    <!-- Header -->
    <xsl:text>Product|Qty|Price|Total&#10;</xsl:text>
    <!-- Data rows -->
    <xsl:for-each select="?lines?*">
      <xsl:value-of select="string-join(
        (?product, string(?qty), string(?price), string(?qty * ?price)),
        '|')"/>
      <xsl:text>&#10;</xsl:text>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

Use built-in functions first

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.

Use templates for reuse

Extract common transformation patterns into named templates with <xsl:template name="..."> and call them with <xsl:call-template>.

Leverage the ? operator for JSON

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.

Handle missing data

Use if or choose to guard against missing or empty values. Defensive maps prevent runtime errors when input data varies.

Deploy during quiet periods

Saving a map overwrites the previous version immediately. Deploy changes when no messages are actively being processed to avoid unpredictable behavior.

Keep maps focused

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. Art2link ESB compiles and deploys your transformation automatically.