2009-03-12 4 views
1

ich ein XML-Dokument habe, die eine Liste von Kategorien hat:XSLT: Geht eine baumartige Struktur

<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> 

und eine Liste der Produkte:

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

Wenn die Kategorie Produkt ist gleich 12, dann sollte es in "Konfigurationen/Monitore/CRT Monitor" umgewandelt werden (nehmen Sie Kategorie 12, dann ist es Elternteil (13), etc.). Wenn das Elternteil 0 ist, hör auf.

Gibt es eine elegante Möglichkeit, dies mit einer XSL-Transformation zu tun?

Antwort

4

Ich weiß nicht, ob dies würde elegant in Betracht gezogen werden, aber mit diesem Eingang:

<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> 

Dieses 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> 

Werden Sie diese Ausgabe geben:

<?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> 

Die Pfade haben immer noch einen zusätzlichen Schrägstrich, Sie benötigen ein weiteres bisschen bedingtes XSLT, damit der Schrägstrich nur erhalten wird emittiert, wenn Sie nicht auf der ersten Ebene sind.

Es ist wichtig, dass die Kategoriehierarchie korrekt ist. Andernfalls kann Ihre Transformation leicht in eine Endlosschleife geraten, die nur dann gestoppt wird, wenn der Arbeitsspeicher knapp wird. Wenn ich so etwas in einem echten System implementieren würde, wäre ich versucht, der catWalk-Vorlage einen Parameter hinzuzufügen, der bei jedem Aufruf erhöht und dem Test hinzugefügt wird, sodass nach zehn Aufrufen keine Schleife mehr angezeigt wird, unabhängig davon, ob das übergeordnete Element gefunden wurde .

1

Dies sollte man nahe genug bekommen (ich habe mit Putting XSLT-Code hier stuggled, also habe ich es entgangen, die hoffentlich funktioniert ok

<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> 

Sorry für die grobe Beispielcode, aber es generiert

/Konfigurationen/Monitore/CRT Monitor

3

Die Verwendung eines <xsl:key> ist ratsam:

<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> 

Dies erzeugt:

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

, wenn sie auf der folgenden XML getestet:

<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 Deutlich eleganter als meine Antwort. – andynormancx

1

Sie beginnen könnte prüfen, indem Sie Ihre Kategorien Dokument aus einer flachen Liste von Knoten zu einer Hierarchie zu verwandeln. Das vereinfacht das Problem der Umwandlung Ihres Eingabedokuments erheblich. Wenn Ihre Produktliste groß ist, wird sie viel besser funktionieren als ein Ansatz, der die flache Kategorieliste für jeden Schritt in der Kategoriehierarchie durchsucht.

<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> 

Dieses so etwas wie dieses produzieren:

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

Vorausgesetzt, dass Sie die transformierten Kategorien dokumentieren in Ihre XSLT als Parameter übergeben haben (oder es in eine Variable mit der document() Funktion lesen), die Vorlage für Produkte wird ziemlich einfach:

<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>