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:

    &lt;?xml version="1.0" encoding="utf-8"?&gt;
       &lt;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"&gt;

     &lt;xsl:output method="xml" indent="yes"/&gt;
     &lt;xsl:strip-space elements="*"/&gt;


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

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

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


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:

&lt;Data&gt;
  &lt;Batch&gt;
    &lt;Page&gt;
      &lt;Fields&gt;
        &lt;Barcode&gt;|||||||||||&lt;/Barcode&gt;
      &lt;/Fields&gt;
    &lt;/Page&gt;
    &lt;Page&gt;
      &lt;Fields&gt;
        &lt;Barcode&gt;|RETURN|||||||||&lt;/Barcode&gt;
      &lt;/Fields&gt;
    &lt;/Page&gt;
    &lt;Page&gt;
      &lt;Fields&gt;
        &lt;Barcode&gt;||5454|||||||||&lt;/Barcode&gt;
      &lt;/Fields&gt;
    &lt;/Page&gt;
  &lt;/Batch&gt;
&lt;/Data&gt;


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 &lt;xsl:key ...&gt; index, like so (at top level):

&lt;xsl:key name="contains_RETURN" match="Barcode" use="contains(text(), 'RETURN')"/&gt;


and then rewrite your hotspot to:

&lt;xsl:when test="count(key('contains_RETURN', 'true')) &gt; 0"&gt;
  ...
&lt;/xsl:when&gt;


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.