2009-03-12 11 views
1

J'ai un document XML, qui a une liste de catégories:XSLT: Marcher une structure arborescente

<categories> 
    <category id="1" parent="0">Configurations</category> 
    <category id="11" parent="13">LCD Monitor</category> 
    <category id="12" parent="13">CRT Monitor</category> 
    <category id="13" parent="1"">Monitors</category> 
    <category id="123" parent="122">Printer</category> 
    ... 
</categories> 

Et une liste de produits:

<products> 
    <product> 
    ... 
    <category>12</category> 
    ... 
    </product> 
    ... 
</products> 

Si la catégorie de produit est égal à 12, alors il devrait être transformé en "Configurations/Moniteurs/moniteur CRT" (prendre la catégorie 12, puis c'est parent (13), etc.). Si le parent est 0, arrêtez.

Existe-t-il une façon élégante de faire cela en utilisant une transformation XSL?

Répondre

4

Je ne sais pas si cela serait considéré comme élégant, mais avec cette entrée:

<root> 
    <categories> 
     <category id="1" parent="0">Configurations</category> 
     <category id="11" parent="13">LCD Monitor</category> 
     <category id="12" parent="13">CRT Monitor</category> 
     <category id="13" parent="1">Monitors</category> 
     <category id="123" parent="122">Printer</category> 
    </categories> 
    <products> 
     <product> 
      <category>12</category> 
     </product> 
     <product> 
      <category>11</category> 
     </product> 
    </products> 
</root> 

Ce XSLT:

<?xml version="1.0"?> 

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<xsl:template match="/"> 
    <root> 
    <xsl:apply-templates select="//product"/> 
    </root> 
</xsl:template> 

<xsl:template match="product"> 
    <product> 
    <path> 
     <xsl:call-template name="catwalk"> 
     <xsl:with-param name="id"><xsl:value-of select="category"/> 
     </xsl:with-param> 
     </xsl:call-template> 
    </path> 
    </product> 
</xsl:template> 

<xsl:template name="catwalk"> 
    <xsl:param name="id"/> 
    <xsl:if test="$id != '0'"> 
    <xsl:call-template name="catwalk"> 
     <xsl:with-param name="id"><xsl:value-of select="//category[@id = $id]/@parent"/> 
     </xsl:with-param> 
    </xsl:call-template> 
    <xsl:value-of select="//category[@id = $id]"/><xsl:text>/</xsl:text> 
    </xsl:if> 
</xsl:template> 

</xsl:stylesheet> 

vous donnera cette sortie:

<?xml version="1.0" encoding="utf-8"?> 
    <root> 
    <product> 
    <path>Configurations/Monitors/CRT Monitor/ 
    </path> 
    </product> 
    <product> 
    <path>Configurations/Monitors/LCD Monitor/ 
    </path> 
    </product> 
    </root> 

Les chemins ont toujours une barre oblique finale supplémentaire, vous aurez besoin d'un autre petit peu de XSLT conditionnel pour que la barre oblique émis lorsque vous n'êtes pas au premier niveau.

Il est essentiel que votre hiérarchie de catégorie soit correcte, sinon votre transformation peut facilement entrer dans une boucle sans fin qui ne s'arrêtera que lorsqu'elle sera à court de mémoire. Si j'implémais quelque chose comme ça dans un système réel, je serais tenté d'ajouter un paramètre au modèle catWalk qui s'incrémenterait à chaque appel et l'ajouterait au test afin qu'il arrête de boucler après 10 appels si le parent a été trouvé .

1

Cela devrait vous obtenir assez près (je l'ai stuggled avec mettre le code xslt ici, donc je l'ai échappé, espérons que fonctionne bien

<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

    <xsl:output omit-xml-declaration="yes"/> 

    <xsl:template match="/"> 
    <xsl:call-template name="OutputCategoryTree"> 
     <xsl:with-param name="productId" select="12"/> 
    </xsl:call-template> 
    </xsl:template> 

    <xsl:template name="OutputCategoryTree"> 
    <xsl:param name="productId"/> 
    <xsl:variable name="parentId" select="/categories/category[@id=$productId]/@parent"/> 
    <xsl:if test="$parentId!=0"> 
     <xsl:call-template name="OutputCategoryTree"> 
     <xsl:with-param name="productId" select="/categories/category[@id=$productId]/@parent"/> 
     </xsl:call-template> 
    </xsl:if>/ 
    <xsl:value-of select="/categories/category[@id=$productId]"/> 
    </xsl:template> 
</xsl:stylesheet> 

Désolé pour le code rugueux exemple, mais il ne génère

/Configurations/Moniteurs/CRT Moniteur

3

L'utilisation d'un <xsl:key> est conseillé:

<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

    <xsl:output method="text" /> 

    <xsl:key name="category" match="categories/category" use="@id" /> 

    <xsl:template match="/"> 
    <xsl:apply-templates select="//products/product" /> 
    </xsl:template> 

    <xsl:template match="product"> 
    <xsl:apply-templates select="key('category', category)" /> 
    <xsl:text>&#10;</xsl:text> 
    </xsl:template> 

    <xsl:template match="category"> 
    <xsl:if test="@parent &gt; 0"> 
     <xsl:apply-templates select="key('category', @parent)" /> 
     <xsl:text>/</xsl:text> 
    </xsl:if> 
    <xsl:value-of select="."/> 
    </xsl:template> 

</xsl:stylesheet> 

Ce produit:

 
Configurations/Monitors/LCD Monitor 
Configurations/Monitors/CRT Monitor 

lorsqu'il est porté sur XML suivant:

<data> 
    <categories> 
    <category id="1" parent="0">Configurations</category> 
    <category id="11" parent="13">LCD Monitor</category> 
    <category id="12" parent="13">CRT Monitor</category> 
    <category id="13" parent="1">Monitors</category> 
    <category id="123" parent="122">Printer</category> 
    </categories> 
    <products> 
    <product> 
     <category>11</category> 
    </product> 
    <product> 
     <category>12</category> 
    </product> 
    </products> 
</data> 
+0

+1 Beaucoup plus élégant que ma réponse. – andynormancx

1

Vous pourriez envisager de commencer par transformer vos catégories documents à partir d'une liste plate des noeuds à une hiérarchie. Cela simplifie considérablement le problème de la transformation de votre document d'entrée. En outre, si votre liste de produits est volumineuse, elle fonctionnera beaucoup mieux qu'une approche qui recherche la liste de catégories à chaque étape de la hiérarchie de catégories.

<xsl:template match="categories"> 
    <categories> 
     <xsl:apply-templates select="category[@parent='0']"/> 
    </categories> 
</xsl:template> 

<xsl:template match="category"> 
    <category id='{@id}'> 
     <xsl:value-of select="text()"/> 
     <xsl:apply-templates select="/categories/category[@parent=current()/@id]"/> 
    </category> 
</xsl:template> 

Cela produira quelque chose comme ceci:

<categories> 
    <category id="1">Configurations 
     <category id="13">Monitors 
      <category id="11">LCD Monitor</category> 
      <category id="12">CRT Monitor</category> 
     </category> 
    </category> 
    ... 
</categories> 

En supposant que vous avez passé les catégories transformées document dans votre XSLT en tant que paramètre (ou le lire dans une variable en utilisant la fonction document()), la modèle pour les produits devient assez simple:

<xsl:template match="product"/> 
    <xsl:variable name="c" select="$categories/categories/category[@id=current()/category]"/> 
    <xsl:foreach select="$c/ancestor-or-self::category"> 
     <xsl:value-of select="text()"/> 
     <xsl:if test="position() != last()"> 
     <xsl:text>/</xsl:text> 
     </xsl:if> 
    </xsl:foreach> 
</xsl:template> 
Questions connexes