XSLT: прогулка по древовидной структуре - PullRequest
1 голос
/ 12 марта 2009

У меня есть XML-документ со списком категорий:

<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>
    ...
</products>

Если категория продукта равна 12, то она должна быть преобразована в «Конфигурации / Мониторы / ЭЛТ-монитор» (взять категорию 12, затем ее родитель (13) и т. Д.). Если родитель равен 0, остановитесь.

Есть ли элегантный способ сделать это с помощью XSL-преобразования?

Ответы [ 4 ]

4 голосов
/ 12 марта 2009

Я не знаю, будет ли это считаться элегантным, но с этим вводом:

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

Это 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>

Даст вам этот вывод:

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

У путей по-прежнему есть дополнительный завершающий слеш, вам понадобится еще немного условного XSLT, чтобы слеш получался только тогда, когда вы не на первом уровне.

Крайне важно, чтобы иерархия категорий была правильной, иначе ваше преобразование может легко попасть в бесконечный цикл, который остановится только тогда, когда ему не хватит памяти. Если бы я реализовывал что-то подобное в реальной системе, у меня возникло бы желание добавить параметр в шаблон catWalk, который увеличивается при каждом вызове, и добавлять его в тест, чтобы он прекратил цикл после 10 вызовов независимо от того, был ли найден родитель .

3 голосов
/ 12 марта 2009

Рекомендуется использовать <xsl:key>:

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

Это производит:

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

при тестировании на следующем XML:

<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>
1 голос
/ 12 марта 2009

Можно начать с преобразования документа категорий из простого списка узлов в иерархию. Это значительно упрощает задачу преобразования входного документа. Кроме того, если ваш список продуктов большой, он будет работать намного лучше, чем подход, который ищет простой список категорий для каждого шага в иерархии категорий.

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

Это будет производить что-то вроде этого:

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

Если вы передали преобразованный документ категорий в свой XSLT в качестве параметра (или прочитали его в переменную с помощью функции document()), шаблон для продуктов становится довольно простым:

<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>
1 голос
/ 12 марта 2009

Это должно подвести вас достаточно близко (я изо всех сил пытался поместить здесь код xslt, поэтому я избежал его, надеюсь, это работает нормально

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

Извините за примерный код, но он генерирует

/ Конфигурации / Мониторы / ЭЛТ-монитор

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...