Это преобразование :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kElemById" match="Category"
use="@id"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:call-template name="sortHier">
<xsl:with-param name="pNodes" select=
"*[ParentCategory]"/>
<xsl:with-param name="pParents" select=
"*[not(ParentCategory)]"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="sortHier">
<xsl:param name="pNodes"/>
<xsl:param name="pParents"/>
<xsl:apply-templates select=
"$pParents|$pNodes[not($pParents)]">
<xsl:sort select="@name"/>
</xsl:apply-templates>
<xsl:if test="$pNodes and $pParents">
<xsl:variable name="vNewParents"
select="key('kElemById', $pNodes/ParentCategory)
[not(@id=$pParents/@id)]
"/>
<xsl:variable name="vNewChildren"
select="$pNodes[not(@id=$vNewParents/@id)]"/>
<xsl:call-template name="sortHier">
<xsl:with-param name="pNodes"
select="$vNewChildren"/>
<xsl:with-param name="pParents"
select="$vNewParents"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
при применении к этому документу XML (на основе предоставленного, но перемешанного / несортированного):
<Catalog name="AccessoriesCatalog">
<Category Definition="AccessoriesCategory"
name="16144" id="16144">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="16116" id="16116">
<ParentCategory>16115</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="16126" id="16126">
<ParentCategory>16115</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="16131" id="16131">
<ParentCategory>1532</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="16132" id="16132">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="16136" id="16136">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="16139" id="16139">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="16115" id="16115">
<ParentCategory>1532</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory"
name="1532" id="1532"></Category>
<Category Definition="AccessoriesCategory"
name="16195" id="16195">
<ParentCategory>16131</ParentCategory>
</Category>
</Catalog>
дает желаемый, правильный результат :
<Catalog name="AccessoriesCatalog">
<Category Definition="AccessoriesCategory" name="1532" id="1532"/>
<Category Definition="AccessoriesCategory" name="16115" id="16115">
<ParentCategory>1532</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16131" id="16131">
<ParentCategory>1532</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16116" id="16116">
<ParentCategory>16115</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16126" id="16126">
<ParentCategory>16115</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16132" id="16132">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16136" id="16136">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16139" id="16139">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16144" id="16144">
<ParentCategory>16131</ParentCategory>
</Category>
<Category Definition="AccessoriesCategory" name="16195" id="16195">
<ParentCategory>16131</ParentCategory>
</Category>
</Catalog>
Объяснение
Рекурсивно вызванный именованный шаблон с двумя параметрами: «набор текущих родителей» (или «последний найденный родитель») и набор текущих (все еще не обработанных) узлов.
Условие останова : либо «набор текущих родителей», либо «набор текущих узлов», либо оба они пусты. Здесь мы выводим (и сортируем по @name
) оставшийся непустой набор параметров.
Рекурсивный шаг : непосредственные дети «нынешних родителей» становятся новыми «текущими родителями». Остальные «текущие узлы» становятся новыми «текущими узлами». Скопируйте все текущие родительские элементы или все текущие узлы, если не осталось текущих родительских узлов.
Обновление
В комментариях OP заявляет, что решение работало с небольшими файлами,
"Но когда я пробую это на весь xml
с большим количеством элементов и уровней
не работает. XML у меня есть
около 8 Мб, поэтому я не могу опубликовать это здесь. "
Я попросил его предоставить (в автономном режиме) файлы XML, и когда я их получил, я подтвердил, что это решение без проблем работает как с маленькими, так и с большими (44000 строк, 700 КБ) файлами, которые мне предоставлены.
Производительность файла большего размера была неплохой , за исключением MSXML3.
Вот данные о производительности файла 44000 строк , как видно на моем 8-летнем ПК (2 ГБ ОЗУ, одноядерный 3GHz):
MSXML3: 91 sec.
MSXML6: 6 sec.
AltovaXML (XMLSpy): 6 sec.
Saxon 6.5.4: 2 sec.
Saxon 9.1.05: 1.6 sec.
XslCompiledTransform 1.3 sec.
XQSharp: 0.8 sec.