У меня проблема с сортировкой чисел, разделенных точками (например, 1, 2.1, 1.1, 1.3). Я нашел решение здесь XSL recursive sort.
Часть I. Сортировочный
Это очень легко адаптировать оригинальное решение к новому делу. В отличии от принятого ответа, это решение сортирует правильно XML документы, где есть
<val>1.3.2</val>
но нет
<val>1.3</val>
См Части II для преобразования отсортированного результата в требуемых вложенной структуру списка.
Когда это преобразование применяется на следующий документ XML - обратите внимание, что <val>1.3.2</val>
но нет <val>1.3</val>
и принятый ответ не дает правильный результат - фактически удаляет весь <row>
имея <val>1.3.2</val>
ребенка:
<root>
<row>
<col name="rank"/>
<name>A</name>
<val>1.1</val>
</row>
<row>
<col name="rank"/>
<name>B</name>
<val>1</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.10</val>
</row>
<row>
<col name="level"/>
<name>C</name>
<val>2</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.2.2</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.3.2</val>
</row>
<row>
<col name="rank"/>
<name>E</name>
<val>1.2.1</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.2</val>
</row>
</root>
разыскиваемый, правильно отсортирован результат получается:
<root>
<row>
<col name="rank"/>
<name>B</name>
<val>1</val>
</row>
<row>
<col name="rank"/>
<name>A</name>
<val>1.1</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.2</val>
</row>
<row>
<col name="rank"/>
<name>E</name>
<val>1.2.1</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.2.2</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.3.2</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.10</val>
</row>
<row>
<col name="level"/>
<name>C</name>
<val>2</val>
</row>
</root>
Наконец, еще один рефакторинг: устранение всех XSLT условных операторов:
<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:template match="/*">
<xsl:copy>
<xsl:apply-templates select="row">
<xsl:sort select="substring-before(concat(val, '.'), '.')"
data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="row">
<xsl:param name="prefix" select="''"/>
<xsl:variable name="vHasChildren" select=
"not(val = substring($prefix, 1, string-length($prefix)-1))"/>
<xsl:copy-of select="self::node()[not($vHasChildren)]"/>
<xsl:variable name="chunk"
select="substring-before(concat(substring-after(val, $prefix), '.'), '.')"/>
<xsl:variable name="new-prefix" select="concat($prefix, $chunk, '.')"/>
<xsl:apply-templates select= "self::node()
[$vHasChildren
and not(preceding-sibling::row[starts-with(val, concat($prefix, $chunk))])
]
/../row[starts-with(val, $new-prefix) or val = concat($prefix, $chunk)]">
<xsl:with-param name="prefix" select="$new-prefix"/>
<xsl:sort data-type="number" select=
"substring-before(concat(substring-after(val, $new-prefix), '.'), '.')"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Часть II: Преобразование отсортированного, плоский результата в вложенную структуру списка
Здесь мы начнем с результата преобразования, полученного в части I, и из него мы создаем нужную структуру списка вложенных списков.Отсортирован плоский результат мы имеем до сих пор:
<root>
<row>
<col name="rank"/>
<name>B</name>
<val>1</val>
</row>
<row>
<col name="rank"/>
<name>A</name>
<val>1.1</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.2</val>
</row>
<row>
<col name="rank"/>
<name>E</name>
<val>1.2.1</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.2.2</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.3.2</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.10</val>
</row>
<row>
<col name="level"/>
<name>C</name>
<val>2</val>
</row>
</root>
Мы используем это преобразование:
<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:template match="/*">
<list>
<xsl:apply-templates select=
"row[not(substring-before(concat(val, '.'), '.')
= substring-before(concat(preceding-sibling::row[1]/val,'.'),'.'))]">
<xsl:with-param name="pPrefix" select="''"/>
</xsl:apply-templates>
</list>
</xsl:template>
<xsl:template match="row">
<xsl:param name="pPrefix"/>
<item val="{val}">
<xsl:variable name="vnewPrefix" select="concat($pPrefix, val, '.')"/>
<xsl:variable name="vcurrentVal" select="val"/>
<xsl:apply-templates select="following-sibling::row
[starts-with(val, concat($vcurrentVal,'.'))
and
(string-length(val) - string-length(translate(val,'.',''))
= 1 + string-length($vcurrentVal) - string-length(translate($vcurrentVal,'.','')
)
or
not(starts-with(val,
concat($vnewPrefix,
substring-before(concat(substring-after(preceding-sibling::row[1]/val, $vnewPrefix),'.'),'.'),
'.')
)
)
)
]">
<xsl:with-param name="pPrefix" select="$vnewPrefix"/>
</xsl:apply-templates>
</item>
</xsl:template>
</xsl:stylesheet>
Результат применения этого преобразования на документе выше XML является хотел структура вложенного списка :
<list>
<item val="1">
<item val="1.1"/>
<item val="1.2">
<item val="1.2.1"/>
<item val="1.2.2"/>
</item>
<item val="1.3.2"/>
<item val="1.10"/>
</item>
<item val="2"/>
</list>
Мы также можем создать желаемый HTML-код, используя этот трансформер мация:
<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:template match="/*">
<ul>
<xsl:apply-templates select=
"row[not(substring-before(concat(val, '.'), '.')
= substring-before(concat(preceding-sibling::row[1]/val,'.'),'.'))]">
<xsl:with-param name="pPrefix" select="''"/>
</xsl:apply-templates>
</ul>
</xsl:template>
<xsl:template match="row">
<xsl:param name="pPrefix"/>
<li> <xsl:value-of select="concat(val, ' - ', name, '
')"/>
<xsl:variable name="vnewPrefix" select="concat($pPrefix, val, '.')"/>
<xsl:variable name="vcurrentVal" select="val"/>
<xsl:variable name="vnextInChain" select=
"following-sibling::row
[starts-with(val, concat($vcurrentVal,'.'))
and
(string-length(val) - string-length(translate(val,'.',''))
= 1 + string-length($vcurrentVal) - string-length(translate($vcurrentVal,'.','')
)
or
not(starts-with(val,
concat($vnewPrefix,
substring-before(concat(substring-after(preceding-sibling::row[1]/val, $vnewPrefix),'.'),'.'),
'.')
)
)
)
]"/>
<xsl:if test="$vnextInChain">
<ul>
<xsl:apply-templates select="following-sibling::row
[starts-with(val, concat($vcurrentVal,'.'))
and
(string-length(val) - string-length(translate(val,'.',''))
= 1 + string-length($vcurrentVal) - string-length(translate($vcurrentVal,'.','')
)
or
not(starts-with(val,
concat($vnewPrefix,
substring-before(concat(substring-after(preceding-sibling::row[1]/val, $vnewPrefix),'.'),'.'),
'.')
)
)
)
]">
<xsl:with-param name="pPrefix" select="$vnewPrefix"/>
</xsl:apply-templates>
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется на плоской отсортированный результат, полезный результат HTML производится: поддерживает
<ul>
<li>1 - B
<ul>
<li>1.1 - A
</li>
<li>1.2 - F
<ul>
<li>1.2.1 - E
</li>
<li>1.2.2 - D
</li>
</ul></li>
<li>1.3.2 - D
</li>
<li>1.10 - F
</li>
</ul></li>
<li>2 - C
</li>
</ul>
Кажется, вы хотите гораздо больше, чем просто ** соберите ** их : вы все поэтому хотите ** вложить ** их в соответствии с их иерархией. –
Если вы их отсортируете, я постараюсь сделать все остальное. : -D На самом деле, это не так важно. также значения тегов «name». (Я просто хочу показать, как цифры должны быть отсортированы) @ michael.hor257k – Warjeh
Существует лучшее решение, чем принятое - оно не накладывает никаких ограничений на документ XML. Наслаждайтесь! –