Let’s clarify XSLT template priority rules

Reading time: 3 minutes

In the frame of the preparation of a DITA Open Toolkit course, I have to explain how template priority rules work in XSLT. Before preparing this course, I had a vague idea of how it worked. I knew how import precedence worked, but for templates located in the same file, I didn’t really know which @match patterns had priority over others. I just fixed the conflicts when they popped up.

I had a look at the dedicated section in the XSLT 2.0 specifications, but it didn’t help me much as I wasn’t able to draw simple rules out of sub-section 2).

The best thing I could do was to experiment and see what happened. And I did it.

In this article I only deal with the precedence of templates within the same XSLT file, with no @priority specified. Import precedence is clear enough to be left aside.

How I tested

  1. I downloaded Saxon HE 9.6.0.5J from Sourceforge (the only JAR we need is saxon9he.jar)
  2. I created a test XML file (it’s actually a DITA XML file)
  3. I created a test XSLT 2.0 file
  4. I ran saxon9he.jar many times via command line, always leaving only two templates active (= not commented out). And I took note of the results. The command looks like java -jar saxon9he.jar -s:test.xml -xsl:test.xslt -expand:on.

The input XML (DITA XML) and the XSLT

A few comments

In the XML, the content of the @class attribute is normally extracted upon transformation from the schema (thanks to the -expand:on command line parameter). I pre-filled the @class values in the XML to make the XSLT easier to understand. The DTD was available locally for better performance and to give a rest to OASIS servers. If you want to reproduce the test and not bother downloading the DTD, you can use this doctype declaration instead: <!DOCTYPE topic PUBLIC "-//OASIS//DTD DITA Topic//EN" "http://docs.oasis-open.org/dita/v1.2/os/dtd1.2/technicalContent/dtd/topic.dtd">.

In the stylesheet, we only have templates whose purpose is to match the <b> element (/topic/body/p/b). And they actually all match it, but using different patterns.

The results

I save you the individual results, here is the outcome:

  • Templates A, B, C, D, E, F and G have the same priority level (0.5)
  • Template H has a lower priority level than the others (0)
  • If two or more templates share the highest priority level, the last one is used (here it’s G) and the processor issues a warning:
Recoverable error
  XTRE0540: Ambiguous rule match for /topic/body[1]/p[1]/b[1]
Matches both "b[@outputclass != 'weak']" on line 32 of
  file:/C:/Users/colin/dita/dita-ot-course/xslt/XSLT/04-template_priority.xsl
and "p/b[. = 'all']" on line 29 of
  file:/C:/Users/colin/dita/dita-ot-course/xslt/XSLT/04-template_priority.xsl
Recoverable error
  XTRE0540: Ambiguous rule match for /topic/body[1]/p[1]/b[1]
Matches both "b[@outputclass != 'weak']" on line 32 of
  file:/C:/Users/colin/dita/dita-ot-course/xslt/XSLT/04-template_priority.xsl
and "*[contains(@class, ' topic/ph ')]" on line 17 of
  file:/C:/Users/colin/dita-ot-course/xslt/XSLT/04-template_priority.xsl

Looking back at the priority rules in the XSLT spec, we see in the table that element(E), which is equivalent to our template H, has priority 0. Removing template H and setting the priority of template A at 0.51 makes of A the highest priority template, while setting it to 0.49 yields the same result as without the priority attribute: all other templates have the highest priority and template G is used. This means that without priority attribute, templates A to G have a priority of 0.5.

Conclusion

If you want to rule the priority between templates that have the same priority level:

  • assign @priority to force a lower or higher priority
  • OR move the templates that should have a lower priority higher in the import sequence (and vice versa for a higher priority)