2
votes

I have an XSL stylesheet which imports a number of other XSL stylesheets, and is used to process a variety of XML files. I would like to check that none of the imported templates conflict.

For example, I have a stylesheet that imports import_one.xsl and import_two.xsl.

import_one.xsl contains a template:

<xsl:template match="Foo">
   One
</xsl:template>

and import_two.xsl contains a template:

<xsl:template match="Foo">
    Two
</xsl:template>

Both these templates will match Foo elements with equal priority, which is an error that XSL processors can recover from by using the template that was imported last.

For a given set of XML files, I would like to check that no such error will occur.

The XSL stylesheets contain JavaScript functions and use MSXSL node-sets, which prevent me from using e.g. Saxon, which would give warnings in the event of such conflicts. The MSXSL processor can process the stylesheets, but does not give warnings.

What are my options?

1
If the stylesheets are imported, rather than included, then the rules have different import precedence, so there cannot be a conflict in the way you describe (you can have two matching rules, but one will always take precedence). Perhaps this is why you are getting no error. But apart from the fact that it's not an error, I can see why you might want to know what's going on. - Michael Kay

1 Answers

1
votes

I think you may need to consider writing some simple analysis tools. This is where the fact that XSLT uses XML syntax can really start to give benefits: manipulating XSLT code is much easier than manipulating code in most other languages. If you only need to know about identical match patterns, then you could analyze your code using a 2.0 stylesheet that does something like

<xsl:function name="f:all-modules" as="document-node()*">
  <xsl:param name="root" as="document-node()"/>
  <xsl:sequence select="$root, document($root/*/(xsl:include|xsl:import)/@href)/f:all-modules(.)"/>
</xsl:function>

<xsl:template name="duplicate-patterns">
  <xsl:for-each-group select="f:all-modules(.)//xsl:template" 
        group-by="@match">
    <xsl:if test="current-group()[2]">
      <xsl:copy-of select="current-group()"/>
    </xsl:if>
  </xsl:for-each-group>
</xsl:template>

If you want to detect "overlapping" patterns where there is more than one match (e.g. para[1] and para[@class='header']) then this can't be done by static analysis, you will need to do some dynamic processing. One way might be to transform your existing stylesheet into an XSLT 2.0 stylesheet that tests every element against every pattern. You could do this by generating a unique mode for every pattern. It might look something like this:

<xsl:template match="para[1]" mode="mode-7826">1</xsl:template>
<xsl:template match="*" mode="mode-7826"/>

<xsl:template match="para[@class='header']" mode="mode-7827" as="xs:integer">1</xsl:template>
<xsl:template match="*" mode="mode-7827"/>

<xsl:template match="/">
    <xsl:for-each select="//*">
      <xsl:variable name="matches">
        <xsl:apply-templates select="." mode="mode-7826"/>
        <xsl:apply-templates select="." mode="mode-7827"/>
        <xsl:apply-templates select="." mode="mode-7828"/>
        ....
      </xsl:variable>
      <xsl:if test="string-length($matches) gt 1">
        Conflict!!
      </xsl:if>
    </xsl:for-each>
</xsl:template>