# Transforming XML as it is being generated on a scanner

At work I've recently been tasked with creating an XSLT to transform some XML as it is being generated on a scanner. The point being to disregard some pages that we are not interested in for further processing, and this is what I've come up with:

    <?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">

<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- Do an indentity transform for all root nodes/attributes -->
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>

<!-- Strip out the sheet with RETURN barcode by replacing it with nothing (blank template) -->
<xsl:template match="Page[contains(Fields/Barcode, 'RETURN')]" />

<!-- Check if there is a page containing RETURN in the barcode field.
If yes append 'return' to all barcodes
If no just copy everything -->
<xsl:template match="Barcode">
<xsl:choose>
<xsl:when test="count(../../../Page[Fields/Barcode[contains(text(), 'RETURN')]]) > 0">
<xsl:element name="Barcode">
<xsl:value-of select="concat(ancestor::Page/Fields/Barcode, 'Return')"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>


Running on XML files with the following simplified structure, the actual files have around 100-3000 pages on average with some 40 fields under Fields:

<Data>
<Batch>
<Page>
<Fields>
<Barcode>|||||||||||</Barcode>
</Fields>
</Page>
<Page>
<Fields>
<Barcode>|RETURN|||||||||</Barcode>
</Fields>
</Page>
<Page>
<Fields>
<Barcode>||5454|||||||||</Barcode>
</Fields>
</Page>
</Batch>
</Data>


It is working, but I'm a bit worried about the Barcode template running too slow as it must be $O(n^2)$. A quick profiling showed my concern to be correct.

Hot path during execution

As this will be running on somewhat limited hardware, does anyone have any suggestions for improvements?

I'd go with a <xsl:key ...> index, like so (at top level):

<xsl:key name="contains_RETURN" match="Barcode" use="contains(text(), 'RETURN')"/>


and then rewrite your hotspot to:

<xsl:when test="count(key('contains_RETURN', 'true')) > 0">
...
</xsl:when>


The idea is to partition Barcode nodes into two sets, the ones that contain 'RETURN' and the ones that don't. The key function as shown then evaluates to a nodeset that only contains Barcode nodes that contain 'RETURN' and hence count works as expected.

• Sounds exactly like what i need. Thanks :) – Frank Aug 16 '12 at 16:31

I wonder if your contains() should really be an "="? It's common to find people translating the english requirement "the element contains 'RETURN'" into a call on contains() when it should be a call on "=" (i.e. Fields/Barcode[.='RETURN']).

Either way, keys are the way to go, but the solution will be simpler if the test is '=' rather than contains().

• Yes, it is necessary to use contains, as the barcode fields contains a pipe delimited string and the 'RETURN' part can be within 10 of them. – Frank Aug 16 '12 at 16:30