Introduction
Following on from a previous article I posted, I thought I'd share another use for XML and XSLT, albeit an uncommon one.
The Problem
You have an application which imports data from an XML file. Odds are that the schema changes or a new namespace is introduced. You will need to rewrite your loader code and update the application at the client. Alternatively, you don't enjoy writing lots of loopy code using XPathNavigators
and XPathNodeIterators
, or worse the XMLDocument
.
The Solution
Use XSLT to transform the source XML data into XSLT extension calls to a object which will persist the imported data to a backing store. This way, you can package the XML importers as external files to the application, and just deploy a new XSLT whenever the source changes. Since XSLT is designed to manipulate XML, it offers a more elegant approach to extracting the data than writing loopy code. Other nasties like supporting multiple namespaces are easy to do using XSLT. An interesting side effect is that the output of the transform can be used as an activity log of the imports activities.
How it Works
The attached sample application imports order data from an XML file to a backing store. The source XML data is transformed by an XSLT which invokes methods on an extension object to do the work of creating the order and details in the backing store.
An instance of the OrderEntry()
class is created and added to the XslTransform
as an extension under the urn:chrisstefano.xslt.orderentry
namespace.
XmlDocument ordersDoc = new XmlDocument();
ordersDoc.Load("Orders.xml");
XslTransform orderProcessor = new XslTransform();
orderProcessor.Load("LoadOrder.xslt");
XsltArgumentList args = new XsltArgumentList();
args.AddExtensionObject("urn:chrisstefano.xslt.orderentry",
new OrderEntry());
XmlTextWriter output = new XmlTextWriter("output.xml",
System.Text.Encoding.UTF8);
orderProcessor.Transform(ordersDoc.CreateNavigator(), args, output, null);
output.Close();
The OrderEntry
class contains two methods for interfacing with the backing store.
The int CreateOrder(string)
method creates the order header record and returns an int
which represents the order identifier. The decimal CreateOrderLine(int, string, int, decimal)
method takes the order identifier and some other information and creates the order line. It returns a decimal value which represents the value of the order line. This value is included in the output of the transformation.
This extension namespace is declared in the XSLT with the prefix ext
. Method calls on the extension are written using the prefix and the method name.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="urn:chrisstefano.xslt.orderentry">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<OrderProcessing>
<xsl:apply-templates select="Orders"/>
</OrderProcessing>
</xsl:template>
<xsl:template match="Orders">
<xsl:apply-templates select="Order">
<xsl:sort select="@date" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Order">
<xsl:variable name="order-id">
<xsl:value-of select="ext:CreateOrder(@customer)"/></xsl:variable>
<OrderProcessed order-id="{$order-id}" customer="{@customer}">
<xsl:apply-templates select="OrderLine">
<xsl:with-param name="order-id" select="$order-id" />
</xsl:apply-templates>
</OrderProcessed>
</xsl:template>
<xsl:template match="OrderLine">
<xsl:param name="order-id">0</xsl:param>
<xsl:variable
name="consideration"
select="ext:CreateOrderLine
($order-id, @product, number(@quantity), number(@price))" />
<ProcessedOrderLine consideration="{$consideration}" />
</xsl:template>
</xsl:stylesheet>
Conclusion
XSLT is an elegant way to query and manipulate XML data, and coupled with XSLT extensions provides a way to interface with other data stores.
Systems builder and developer, DotNet C#, Ruby, Golang, Linux, Cloud, Open Source contributor.