[xsl] Set difference in xsl:number/@count, xsl:key/@match

Subject: [xsl] Set difference in xsl:number/@count, xsl:key/@match
From: "Deborah Pickett" <debbiep-list-xsl@xxxxxxxxxx>
Date: Wed, 21 Mar 2007 13:59:16 +1100 (EST)
This is in the context of XSLT 1.0, auto-generating numbering for figures
(illustrations) in print documentation.  I have a solution, but I think
it's dirty and wonder if there is a better way.

My XML document is a DITA topic.  For those who don't know DITA, it's in
the family of DocBook and XHTML, but it has funky extensibility mechanisms
that let me define a special kind of <figure>, let's call it <flowchart>,
and insert it into a topic anywhere that <figure> would normally be
allowed.  Base XSLT processing for <figure> automatically applies to
<flowchart> too, unless I write an override template to handle
<flowchart>.  Technically, this is all handled with a "class" attribute
with crafted default values in the DTD; in DITA, the class is more
important than the element name. The XSLT for my flowchart extension will
all be in a different .xsl file, so that it can be imported after the base
XSLT to override the base figure processing.  Not everyone has the same
set of extensions.  This is SOP for DITA.  Example, with explicit class
attribute:

<topic class=" topic ">
  <!-- other stuff -->
  <figure class=" figure "><!-- figure contents --></figure>
  <flowchart class=" figure flowchart "><!-- figure contents --></flowchart>
  <figure class=" figure "><!-- figure contents --></figure>
  <!-- more other stuff -->
</topic>

Auto-generating text to say "Figure 1", "Figure 2", "Figure 3" is easy:
  <xsl:number count="*[contains(@class, ' figure ')]"/>
but I need my flowchart numbering to be independent, and auto-generate
"Figure 1", "Flowchart 1", "Figure 2".  I sort of want to say
  <xsl:number count="*[contains(@class, ' figure ')]
              except *[contains(@class, ' flowchart ')]"/>
only set difference doesn't work in xsl:number/@count, even in XSLT 2.0. 
Besides, the base processing can't necessarily know about flowcharts,
circuitdiagrams, familytrees, or however many specializations of "figure"
I've included.  Each specialization has to have its own processing XSLT. 
Other specializations of figure might want to just use figure numbering,
too, so the absence of text after " figure " in the class isn't a good
indicator.

I worked out all by myself about the additive nature of xsl:key when the
same name is re-used, and that was the basis for my dirty solution.  In
short: set difference via keys.

Base processing has something like this:
  <xsl:key name="figure.count" match="*[contains(@class, ' figure ')]"
           use="'include'"/>
And flowchart processing adds more keys:
  <xsl:key name="figure.count" match="*[contains(@class, ' flowchart ')]"
           use="'exclude'"/>
A shell XSL file imports the base XSLT, then the flowchart XSLT.

To get the figure number, I count all the figures up to that point, and
subtract the ones that have asked to be excluded:
  <xsl:variable name="all">
    <xsl:number count="key('figure.count', 'include')" level="any"/>
  </xsl:variable>
  <xsl:variable name="except">
    <xsl:number count="key('figure.count', 'exclude')" level="any"/>
  </xsl:variable>
  <!-- some code to handle empty $except (= 0) omitted -->
  <xsl:value-of select="number($all) - number($except)"/>

It works a treat, but it would be simpler if there was some mechanism to
"delete" or ignore key members, or use key() in xsl:key to build up a
composite key from parts, or attach a predicate to key() in a pattern, or
have xsl:number/@count know about set differences.

Is there a better way?  Even in XSLT 2.0, which seems to have similar
limitations about keys in patterns?

Current Thread