Создание вложенной древовидной структуры из пути в XSLT - PullRequest
9 голосов
/ 16 мая 2009

У меня есть динамический XML-документ, который представляет древовидную структуру категорий, но делает это с использованием атрибутов, разделенных путем в произвольном порядке - например:

   <data>    
      <record ID="24" Name="category 1\sub category 1"/>   
      <record ID="26" Name="category 1"/>     
      <record ID="25" Name="category 1\sub category 1\sub category 2"/>    
      <record ID="27" Name="category 1\sub category 1\sub category 3"/>    
      ...
   </data> 

Мне нужно придумать решение, которое «нормализует» XML, чтобы он трансформировался во что-то вроде этого:

   <data>    
      <record ID="26" Name="category 1">    
         <record ID="24" Name="sub category 1">    
            <record ID="25" Name="sub category 2"/>
            <record ID="27" Name="sub category 3"/>    
         </record>
      </record>   
      ...
   </data>

По сути, мне было интересно, может ли XSLT решить эту проблему и как, а не делать это программно.

1 Ответ

18 голосов
/ 16 мая 2009

Конечно, нет проблем:

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

  <xsl:output indent="yes" />

  <xsl:template match="/data">
    <!-- copy the document element -->
    <xsl:copy>
      <!-- That's where we start: all "record" nodes that have no "\". -->
      <xsl:apply-templates mode="recurse" select="/data/record[
        not(contains(@Name, '\'))
      ]" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="record" mode="recurse">
    <xsl:param name="starting-path" select="''" />

    <!-- The record node and its ID attribute can be copied. --> 
    <xsl:copy>
      <xsl:copy-of select="@ID" />

      <!-- Create the new "name" attribute. -->
      <xsl:attribute name="Name">
        <xsl:value-of select="substring-after(@Name, $starting-path)" />
      </xsl:attribute>

      <!-- Append a backslash to the current path. -->
      <xsl:variable name="current-path" select="concat(@Name, '\')" />

      <!-- Select all "record" nodes that are one level deeper... -->
      <xsl:variable name="children" select="/data/record[
        starts-with(@Name, $current-path)
        and
        not(contains(substring-after(@Name, $current-path), '\'))
      ]" />

      <!-- ...and apply this template to them. -->
      <xsl:apply-templates mode="recurse" select="$children">
        <xsl:with-param name="starting-path" select="$current-path" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Вывод в моей системе:

<data>
  <record ID="26" Name="category 1">
    <record ID="24" Name="sub category 1">
      <record ID="25" Name="sub category 2"></record>
      <record ID="27" Name="sub category 3"></record>
    </record>
  </record>
</data>

Обратите внимание, что все решение основано на предположении, что все пути являются каноническими и не содержат конечных обратных косых черт.

Также обратите внимание, что любые несопоставленные / потерянные элементы «записи» не будут присутствовать в выходных данных (если, конечно, они не находятся на корневом уровне).

Еще одна вещь: режим шаблона ("recurse") не является строго обязательным. Я включил его, потому что шаблон делает что-то довольно особенное, и может быть шанс, что в вашем решении есть другой шаблон, который соответствует узлам «записи». В этом случае это решение может быть добавлено, не нарушая ничего другого. Для автономного решения режимы шаблонов могут быть отключены в любое время.

Да, и последнее: если вы хотите, чтобы результирующий документ был упорядочен по имени, включите <xsl:sort> элемент с <xsl:apply-templates> (оба вхождения), например так:

<xsl:apply-templates select="...">
  <xsl:sort select="@Name" data-type="text" order="ascending" />
</xsl:apply-templates>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...