2009-07-28 10 views
0

Disons que j'ai la structure XML suivante:Calculer le nombre de mots-clés dans XSLT

<entry> 
    <countries>USA, Australia, Canada</countries> 
</entry> 
<entry> 
    <countries>USA, Australia</countries> 
</entry> 
<entry> 
    <countries>Australia, Belgium</countries> 
</entry> 
<entry> 
    <countries>Croatia</countries> 
</entry> 

Je voudrais compter le nombre de cas pour chaque pays apparaît dans ces entrées. Je ne peux utiliser que XSLT côté client (aucun code de serveur personnalisé autorisé). Les résultats finaux doit ressembler à ceci:

 
Country | Count 
-----------|-------- 
Australia |  3 
USA  |  2 
Belgium |  1 
Canada  |  1 
Croatia |  1 

Comme Mike a-cette structure XML pourrait être améliorée, mais il est produit par le système 3e parti et je ne peux pas le changer.

Est-il possible d'atteindre ce XSLT et si oui, comment?

+0

J'ai mis à jour la réponse avec quelques points supplémentaires sur la façon de résoudre ce problème. –

+0

Quel processeur est-ce, et, plus important encore, est-ce XSLT 1.0 ou XSLT 2.0? –

Répondre

0

Y at-il une raison que vous n'utilisez pas le format:

<entry> 
    <countries> 
    <country>USA</country> 
    <country>Australia</country> 
    <country>Canada</country> 
    </countries> 
</entry> 

Votre actuelle ne correspond pas vraiment comment les données XML doivent être stockés.

Comme vous l'avez dit que vous ne pouvez pas changer le format de données essayer une combinaison de tokenize() et count() (pourvu que vous ayez XSLT2 soutien, sinon je pense que vous êtes hors de la chance).

+0

Je sais, et je suis d'accord, mais cette sortie est produite par un système tiers et je ne peux pas changer cela. –

2

Dans XSLT 1.0, le mieux est d'utiliser une approche en deux étapes.

  1. tokenize l'entrée de délimité par des virgules dans des éléments séparés
  2. groupe
  3. 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>&#9;</xsl:text> 
     <xsl:value-of select="count(key('kCountry', .))" /> 
     <xsl:text>&#10;</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>&#9;</xsl:text> 
     <xsl:value-of select="count($country[. = current()])" /> 
     <xsl:text>&#10;</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.

0
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="xsl str msxsl" version="1.0"> 
    <xsl:import href="str.split.template.xsl"/> 
    <xsl:output indent="yes"/> 

    <xsl:template match="/"> 
    <xsl:variable name="countries"> 
     <xsl:call-template name="get-counties" /> 
    </xsl:variable> 

    <table> 
    <xsl:for-each select="msxsl:node-set($countries)/country[not(. = preceding::country)]"> 
     <xsl:variable name="name" select="./text()"/> 
     <tr> 
     <td> 
      <xsl:value-of select="$name" /> 
     </td> 
     <td> 
      <xsl:value-of select="count(msxsl:node-set($countries)/country[. = $name])" /> 
     </td> 
     </tr> 
    </xsl:for-each> 
    </table> 
    </xsl:template> 

    <xsl:template name="get-counties"> 
    <xsl:for-each select="//countries"> 
     <xsl:variable name="countries-raw"> 
     <xsl:call-template name="str:split"> 
      <xsl:with-param name="string" select="text()"/> 
      <xsl:with-param name="pattern" select="','" /> 
     </xsl:call-template> 
     </xsl:variable> 

     <xsl:for-each select="msxsl:node-set($countries-raw)/token"> 
     <country> 
      <xsl:value-of select="normalize-space(.)"/> 
     </country> 
     </xsl:for-each> 
    </xsl:for-each> 
    </xsl:template> 
</xsl:stylesheet> 

str.split.template.xsl est une partie du module de str EXSLT (http://www.exslt.org/download.html).

+0

Je pense que trier la sortie faisait partie de la tâche. – Tomalak

Questions connexes