Assume my xml input is a MFMATR element with a few child elements, such as: TRLIST, INTRO, and SBLIST -- in that document order. I am converting to HTML.
I have a template that matches on the MFMATR element, and wants to run xsl:apply-templates
on the 3 child elements, but I want INTRO to be processed first (listed first in the HTML). The other two (TRLIST and SBLIST) should keep their relative document order, as long as INTRO comes before both of them.
So I'd like to run <xsl:apply-templates select="INTRO, *">
but not have INTRO matched twice. (Using this syntax with xsl 3.0 causes dupes for me.) I also don't want to explicitly list every tag in the select expression, so unknown tags will still be processed.
A 2nd real life example is this: <xsl:apply-templates select="TITLE, CHGDESC, *"/>
. Again, right now that is causing dupes I don't want.
I am using Saxon.
This seems to work. If someone has a better answer, let me know and I will change it.
There is no DRY violation here -- no repeated element names or variable names. I want it to look clean at all the call sites I will have.
It seems idiomatic to me since the function was pulled from w3's own website!
<xsl:template match="MFMATR">
<!-- Process INTRO first, no matter where it appears -->
<xsl:variable name="nodes" select="INTRO, *"/>
<xsl:apply-templates select="kp:distinct_nodes_stable($nodes)"/>
</xsl:template>
<xsl:template match="INTRO">
<xsl:variable name="nodes" select="TITLE, CHGDESC, *"/>
<xsl:apply-templates select="kp:distinct_nodes_stable($nodes)"/>
</xsl:template>
<!-- Discard duplicate elements in $seq, but keep their ordering -->
<!-- Adapted from https://www.w3.org/TR/xpath-functions/#func-distinct-nodes-stable -->
<xsl:function name="kp:distinct_nodes_stable" as="node()*">
<xsl:param name="seq" as="node()*"/>
<xsl:sequence select="fold-left($seq, (),
function($foundSoFar as node()*, $this as node()) as node()* {
if ($foundSoFar intersect $this)
then $foundSoFar
else ($foundSoFar, $this)
}) "/>
</xsl:function>