This article demonstrates how to create basic XSLT scripts for data transformations used in Sequential Workflows.
We will demonstrate examples for the following scenarios:
- New record creation
- Update of existing data
- Update of existing data using parameters
- Transformation of existing data into a different output structure
As an example, we will use the simple task manager from the initial tutorial.
Introduction
For sequential workflows, APIs, reports, or menus, ORIGAM needs a tool for data transformations. This tool is XSLT, which is used to transform one data structure into another, usually one XML into another XML.
XSLT stands for Extensible Stylesheet Language (XSL) Transformations and was created and standardized by W3C (World Wide Web Consortium). XSL (eXtensible Stylesheet Language) itself is a styling language for XML. XSL was originally created mainly for transforming XML into HTML, but it can also be used for general XML data transformations, which is the purpose for which ORIGAM uses it.
The reason why the platform uses XSLT is that the application model is stored in XML files, and application data is loaded from the database into XML structures using the Data Structure model element. Once input data is in XML, using XSLT makes perfect sense.
It is also important to mention that XSLT uses the XPath language in its scripts to address specific elements and parameters of the input XML data. XPath is a standalone language, so we will only briefly mention it here.
In the following chapters, we will show how to process the individual cases mentioned above, demonstrating a simple use of XSLT and XPath. But first, let’s look at ORIGAM’s default XSLT template.
If you are not familiar with these technologies at all, you can review these XSLT and XPath introductions by W3C before proceeding.
The default template
When you create a new XSL Transformation (XSLT) in ORIGAM, you get the following template (comments are included for learning purposes):
<!-- HEADER -->
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:AS="http://schema.advantages.cz/AsapFunctions"
xmlns:date="http://exslt.org/dates-and-times" exclude-result-prefixes="AS date">
<!-- MAIN TEMPLATE -->
<xsl:template match="ROOT">
<ROOT>
<xsl:apply-templates select=""/>
</ROOT>
</xsl:template>
<!-- EMBEDDED TEMPLATE -->
<xsl:template match="">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name=""><xsl:value-of select=""/></xsl:attribute>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
<!-- FOOTER -->
</xsl:stylesheet>
This script is essentially a sequence of declarations, selections from input XML data, and writes to the output XML document. XSLT logic is more akin to functional programming than to imperative languages like Java or Python, and it takes some time to master.
To work with XSLT, you need to understand one key point:
Anything that begins with <xsl: is an XSLT command and an instruction for the XSLT processor.
Everything else (that does not begin with <xsl:) is written directly into the output document.
The header
In the header you can see that ORIGAM uses XSLT version 1.0, which outlines the standard XSL functions available for transformations.
The <xsl:stylesheet> element wraps the entire XSLT document. Its parameters also declare additional custom functions under the namespace AS (ASAP was ORIGAM’s previous name), which are used in addition to the standard XSL functions. The complete list of these functions is available in the documentation. Additionally, some 3rd party extensions – EXSLT modules – are also available, more details here.
The main template
This part of the script selects the content of the entire document for further processing. We can therefore view this part as the control mechanism for the entire transformation.
The <xsl:template match="..."> instruction specifies that when the processor finds this element in the input XML, it should execute the instructions inside the template.
Since ROOT is the enclosing element of every XML document in ORIGAM, this selects the entire input document and will also insert it at the beginning (<ROOT>) and at the end (</ROOT>) of the output document. Keep in mind that anything without <xsl: is written directly into the output document.
The <xsl:apply-templates select=""/> instruction specifies which child elements should be processed using embedded templates. For example:
<xsl:apply-templates select="BusinessPartner"/>will select and process allBusinessPartnerelements.<xsl:apply-templates select="Service[@Type='Premium']"/>will process onlyServiceelements withType="Premium".
Example of the output when selecting the Task element:
<ROOT>
<Task Name="Create new user accounts" DueDate="31.12.2025" Assignee="John Boulder" />
<Task Name="Archive documents" DueDate="31.12.2026" Assignee="Joan Bezos" />
</ROOT>
To each existing Task element is then applied the appropriate template from the next section of the script. This is determined by code <xsl:template match="Task">
The embedded template
This part of the code serves to modify all data elements selected in the first part.
<xsl:template match="">is used to select the XML elements to be processed — for example, allTaskelements.<xsl:copy>copies the current element to which the template in the match applies – it copies only the element’s name, not its attributes or content.<xsl:copy-of select="@*"/>copies all existing attributes of the element (@denotes attributes,*stands for all).<xsl:attribute name="">...</xsl:attribute>adds or modifies a specific attribute. For example:<xsl:attribute name="Selected"><xsl:value-of select="True"/></xsl:attribute>.<xsl:copy-of select="*"/>copies all child elements, if there are any.
Example output:
<ROOT>
<Task Name="Create new user accounts" DueDate="31.12.2025" Assignee="John Boulder" Selected="True" />
<Task Name="Archive documents" DueDate="31.12.2026" Assignee="Joan Bezos" Selected="True" />
</ROOT>
This is a copy of the input data with one additional attribute (Selected).
The Entire Flow
Using the Task element as an example, we’ll show how the code works as a whole.
<xsl:template match="ROOT">
<ROOT>
<xsl:apply-templates select="Task"/>
</ROOT>
</xsl:template>
<xsl:template match="Task">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="Status"><xsl:value-of select="New"/></xsl:attribute>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
The flow of the transformation is as follows:
- The processor finds
ROOTand runs the first template. - The first template calls
apply-templateson its child elementTask. - The processor finds a child element
Taskand runs the second template on it. - The second template copies all existing
Taskattributes, adds a new attribute namedStatusand sets it value toNew.
Simple, isn’t it?
XPath
XPath allows navigation through the XML tree and selection of nodes based on position, name, values, relationships, filters, and conditions.
In other words, XPath is a query language for selecting nodes in an XML document. It answers the question: “Which parts of the XML tree do I want right now?” In this context, it is used to select data (XML elements) for transformation with XSLT.
In the default template, XPath is used in the match and select instructions, for example:
<xsl:templatematch="ROOT"><xsl:apply-templatesselect="TASK"/><xsl:value-ofselect="True"/>
This is a very simple use of XPath, but it also supports much more, such as selecting elements based on attribute values:
select="TASK[@Priority = 'High' and Assignee/@Id = '7']"
This selects only tasks with high priority assigned to a specific user. Absolute paths are also supported, e.g., /ROOT/TASK/USER.
In addition to navigation and filtering, XPath also supports conditions and functions, but that is beyond the scope of this article.
Example 1: New record creation
The following XSLT code uses just one template and it creates one new element (record) named Task and uses four different platform’s custom functions (AS) to:
- Generate a new Id (GUID)
- Assign it to the active user (fill in the field
refAssignedToId) - Set the due date to 14 days from today
- Set Priority to “Mid”
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:AS="http://schema.advantages.cz/AsapFunctions"
xmlns:date="http://exslt.org/dates-and-times" exclude-result-prefixes="AS date">
<xsl:template match="ROOT">
<ROOT>
<Task
Id="{AS:GenerateId()}"
refAssignedToId="{AS:ActiveProfileId()}"
DueDate="{AS:AddDays(date:date(), 14)}"
refPriorityId="{AS:GetConstant('Priority_Mid')}"
/>
</ROOT>
</xsl:template>
</xsl:stylesheet>
Note: The constant Priority_Mid you need to create first. This constant contains the Id of your data record with the desired priority.
This transformation creates data for a new task which can be further completed and edited in a UI form.
Example 2: Update of existing data
This transformation is based on the default template described above. It takes all the tasks selected in the UI – any Task element with the attribute Selected='true' in the data, copies their content, and modifies the refAssignedToId attribute to a specified value (GUID):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:AS="http://schema.advantages.cz/AsapFunctions"
xmlns:date="http://exslt.org/dates-and-times" exclude-result-prefixes="AS date">
<xsl:template match="ROOT">
<ROOT>
<xsl:apply-templates select="Task[@Selected='true']"/>
</ROOT>
</xsl:template>
<xsl:template match="Task">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="refAssignedToId">
<xsl:value-of select="a2890720-2ab1-44d8-b86b-0d87a2b842a4"/>
</xsl:attribute>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note: The Id (GUID) of the user stored in the refAssignedToId field is hardcoded in this transformation and you need to get it from your application or database or use the AS:ActiveProfileId() function which returns the Id of the currently logged-in user.
Example 3: Update of existing data using parameters
This example extends the previous case: the value for the attribute is passed as a parameter to the script instead of being hardcoded – the value of the attribute refAssignedToId is set from the parameter UserId:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:AS="http://schema.advantages.cz/AsapFunctions"
xmlns:date="http://exslt.org/dates-and-times" exclude-result-prefixes="AS date">
<!-- PARAMETER DECLARATION -->
<xsl:param name="userId"/>
<xsl:template match="ROOT">
<ROOT>
<xsl:apply-templates select="Task[@Selected='true']"/>
</ROOT>
</xsl:template>
<xsl:template match="Task">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:attribute name="refAssignedToId">
<xsl:value-of select="$userId"/>
</xsl:attribute>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note: Any parameter used in a XSL transformation needs to be declared in its beginning using the <xsl:param name="..."/> function.
Example 4: Transformation of existing data into a different output structure
The last example shows how to transform the output data. Instead of Task, the output element is named TaskOverview, and all three attributes (Name, Due, Assignee) are renamed as well:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:AS="http://schema.advantages.cz/AsapFunctions"
xmlns:date="http://exslt.org/dates-and-times" exclude-result-prefixes="AS date">
<xsl:template match="ROOT">
<ROOT>
<xsl:apply-templates select="Task"/>
</ROOT>
</xsl:template>
<xsl:template match="Task">
<TaskOverview>
<xsl:attribute name="Name">
<xsl:value-of select="@Name"/>
</xsl:attribute>
<xsl:attribute name="Due">
<xsl:value-of select="AS:FormatDate(@DueDate,'dd.MM.yyyy')"/>
</xsl:attribute>
<xsl:attribute name="Assignee">
<xsl:value-of select="AS:LookupValue('b2ff0325-e788-4701-a855-774dfbb1b733', @refAssignedToId)"/>
</xsl:attribute>
</TaskOverview>
</xsl:template>
</xsl:stylesheet>
This transformation can, for example, prepare data for an API in the format expected by its consumer.
Some notes about this example:
- All attributes are addressed with
@in theselect— this is a standard XPath syntax. - The custom AS function
FormatDateformats dates to the desired formdd.MM.yyyy. - Another custom AS function
LookupValuetakes the user ID inrefAssignedToIdand, using the model Lookup with IDb2ff0325-e788-4701-a855-774dfbb1b733, converts it into the user’s Name and Surname. So, instead of the ID, the output data contains, for example, “Joan Bezos”.