Создание вложенной древовидной структуры из пути в XSLT
у меня есть динамический XML-документ, который представляет древовидную структуру категорий, но делает это с помощью атрибутов, разделенных путем в произвольном порядке - вроде этого:
<data>
<record ID="24" Name="category 1sub category 1"/>
<record ID="26" Name="category 1"/>
<record ID="25" Name="category 1sub category 1sub category 2"/>
<record ID="27" Name="category 1sub category 1sub 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 ответов
конечно, нет проблем:
<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>
обратите внимание, что все решение основано на предположении, что все пути являются каноническими и не содержат конечных обратных косых черт.
также обратите внимание, что любые непревзойденные/потерянные элементы "записи" не будут в выходных данных (если они не находятся на корневом уровне, конечно).
еще одна вещь: режим шаблона ("рекурс") не является строго необходимым. Я включил его, потому что template делает что-то особенное, и может быть шанс, что в вашем решении есть другой шаблон, который соответствует узлам "запись". В этом случае это решение может быть отброшено, не нарушая ничего другого. Для автономного решения режимы шаблонов могут быть удалены в любое время.
О, И последнее: если вы хотите, чтобы документ результата был заказан по имени, включите <xsl:sort>
элемент С <xsl:apply-templates>
(в обоих случаях), как Итак:
<xsl:apply-templates select="...">
<xsl:sort select="@Name" data-type="text" order="ascending" />
</xsl:apply-templates>