Dans XSLT 1.0, le mieux est d'utiliser une approche en deux étapes.
- tokenize l'entrée de délimité par des virgules dans des éléments séparés
groupe
- sur les éléments distincts
Etape n ° 1 tokenizes l'entrée:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/root">
<countries>
<xsl:apply-templates select="entry" />
</countries>
</xsl:template>
<xsl:template match="entry">
<xsl:call-template name="tokenize">
<xsl:with-param name="input" select="countries" />
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="input" />
<xsl:variable name="list" select="concat($input, ',')" />
<xsl:variable name="head" select="substring-before($list, ',') " />
<xsl:variable name="tail" select="substring-after($list, ',') " />
<xsl:if test="normalize-space($head) != ''">
<country>
<xsl:value-of select="normalize-space($head)" />
</country>
<xsl:call-template name="tokenize">
<xsl:with-param name="input" select="$tail" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
produit:
<countries>
<country>USA</country>
<country>Australia</country>
<country>Canada</country>
<country>USA</country>
<country>Australia</country>
<country>Australia</country>
<country>Belgium</country>
<country>Croatia</country>
</countries>
Étape # 2 applique le regroupement Muenchian au résultat intermédiaire:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:key name="kCountry" match="country" use="." />
<xsl:template match="/countries">
<xsl:apply-templates select="country">
<xsl:sort select="count(key('kCountry', .))" data-type="number" order="descending" />
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="country">
<xsl:if test="generate-id() = generate-id(key('kCountry', .)[1])">
<xsl:value-of select="." />
<xsl:text>	</xsl:text>
<xsl:value-of select="count(key('kCountry', .))" />
<xsl:text> </xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
produit le résultat souhaité (le formatage est laissé comme un exercice pour le lecteur):
Australia 3
USA 2
Belgium 1
Canada 1
Croatia 1
Le processus peut se faire en une seule transformation, à l'aide de la fonction d'extension node-set()
. Cependant, vous perdriez la possibilité d'utiliser une clé XSL, ce qui pourrait ralentir les performances pour les grosses entrées. YMMV.
La modification nécessaire de l'étape 1 serait (en utilisant les extensions de msxsl, d'autres fournisseurs diffèrent dans la déclaration d'espace de noms, ce qui réduit la portabilité de cette approche):
<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"
>
<xsl:template match="/root">
<!-- store the list of <country>s as a result-tree-fragment -->
<xsl:variable name="countries">
<xsl:apply-templates select="entry" />
</xsl:variable>
<!-- convert the result-tree-fragment to a usable node-set -->
<xsl:variable name="country" select="msxsl:node-set($countries)/country" />
<!-- iteration, sorting and grouping in one step -->
<xsl:for-each select="$country">
<xsl:sort select="count($country[. = current()])" data-type="number" order="descending" />
<xsl:sort select="." data-type="text" order="ascending" />
<xsl:if test="generate-id() = generate-id($country[. = current()][1])">
<xsl:value-of select="." />
<xsl:text>	</xsl:text>
<xsl:value-of select="count($country[. = current()])" />
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<!-- ... the remainder of the stylesheet #1 is unchanged ... -->
</xsl:stylesheet>
Avec cette approche, une étape distincte # 2 devient inutile. Le résultat est le même que ci-dessus. Pour les petits intrants, la différence de performance ne sera pas perceptible.
J'ai mis à jour la réponse avec quelques points supplémentaires sur la façon de résoudre ce problème. –
Quel processeur est-ce, et, plus important encore, est-ce XSLT 1.0 ou XSLT 2.0? –