XSL 排序由句点分隔的数字
XSL Sort Numbers Separated by Periods
我对用句点分隔的数字排序有问题(例如 1、2.1、1.1、1.3)。我在这里 XSL recursive sort 找到了解决方案。这就是我需要的,略有不同。
在我的 xml 中,标签就像
<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="level"/>
<name>C</name>
<val>test</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.2.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>
我想根据 "val" 标签对 col/@name = "rank" 的所有行进行排序。是否可以仅修改链接问题中接受的答案来获得输出?如果没有,是否有 xsl 版本 1(如果有 none 则为 2)的任何解决方案。
我需要的输出是这样的:
<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>
</ul>
</li>
</ul>
更新 I:所以根据 michael.hor257k 的回答,这就是我一直在寻找的解决方案:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="5.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<html>
<body>
<ul>
<xsl:apply-templates select="row[not(contains(val, '.'))][contains(col/@name, 'rank')]">
<xsl:sort select="val" data-type="number" order="ascending"/>
</xsl:apply-templates>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="row">
<li>
<xsl:variable name="parent" select="concat(val, '.')" />
<xsl:value-of select="./name"/> - <xsl:value-of select="./val"/>
<xsl:if test="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))][contains(col/@name, 'rank')]">
<ul>
<xsl:apply-templates select="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))][contains(col/@name, 'rank')]">
<xsl:sort select="substring-after(val, $parent)" data-type="number" order="ascending"/>
</xsl:apply-templates>
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
更新 II:感谢 Dimitre Novatchev,我有一个更好的解决方案,我认为这是最好的答案。所以,我试图理解它,之后我会检查它是否为已接受的答案。
更新 III:我接受了 michael.hor257k 发布的答案,因为这是我需要的。我知道我的 xml 中没有排名顺序的跳跃,但正如 Dimitre Novatchev 提到的那样,如果有,比如有 1.3.2 而没有 1.3,这个解决方案就会有问题,你可以使用完整的答案Dimitre Novachev 发布的。
知道关卡的数量吗?如果是这样,在 XSLT 2.0 中您可以使用
<xsl:apply-templates select="row[col/@name = 'rank']">
<xsl:sort select="xs:integer(tokenize(val, '\'.')[1])"/>
<xsl:sort select="xs:integer(tokenize(val, '\'.')[2])"/>
<xsl:sort select="xs:integer(tokenize(val, '\'.')[3])"/>
</xsl:apply-templates/>
三个级别。在 XSLT 3.0 中,您甚至可以使用任何级别的 sort
函数来完成此操作:<xsl:apply-templates select="sort(row[col/@name = 'rank'], function($row) { tokenize($row/val, '\.')!xs:integer(.) })">
尽管您也想嵌套,但我认为使用递归函数来执行
<xsl:for-each-group select="$rows" group-by="xs:integer(tokenize(val, '\.')[1])"><xsl:sort select="current-grouping-key()"/>...</xsl:for-each-group>
比纯排序更适合 XSLT 2.0 或 3.0。
一个完整的样式表是
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf" version="2.0">
<xsl:output method="html" indent="yes"/>
<xsl:function name="mf:nest" as="element()*">
<xsl:param name="rows" as="element(row)*"/>
<xsl:sequence select="mf:nest($rows, 1)"/>
</xsl:function>
<xsl:function name="mf:nest" as="element()*">
<xsl:param name="rows" as="element(row)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$rows" group-by="xs:integer(tokenize(val, '\.')[$level])">
<xsl:sort select="current-grouping-key()"/>
<li>
<xsl:variable name="item" select="current-group()[not(tokenize(val, '\.')[$level + 1])]"/>
<xsl:value-of select="$item/concat(name, ' - ', val)"/>
<xsl:if test="current-group()[2]">
<ul>
<xsl:sequence select="mf:nest(current-group() except $item, $level + 1)"/>
</ul>
</xsl:if>
</li>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="root">
<ul>
<xsl:sequence select="mf:nest(row[col/@name = 'rank'])"/>
</ul>
</xsl:template>
</xsl:stylesheet>
它转换输入
<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="level"/>
<name>C</name>
<val>test</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.2.2</val>
</row>
<row>
<col name="rank"/>
<name>E</name>
<val>1.2.1</val>
</row>
<row>
<col name="rank"/>
<name>foo</name>
<val>2</val>
</row>
<row>
<col name="rank"/>
<name>bar</name>
<val>1.10</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.2</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.10.1</val>
</row>
</root>
进入结果
<ul>
<li>B - 1
<ul>
<li>A - 1.1</li>
<li>F - 1.2
<ul>
<li>E - 1.2.1</li>
<li>D - 1.2.2</li>
</ul>
</li>
<li>bar - 1.10
<ul>
<li>F - 1.10.1</li>
</ul>
</li>
</ul>
</li>
<li>foo - 2</li>
</ul>
考虑以下样式表:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<list>
<xsl:apply-templates select="row[not(contains(val, '.'))]">
<xsl:sort select="val" data-type="number" order="ascending"/>
</xsl:apply-templates>
</list>
</xsl:template>
<xsl:template match="row">
<xsl:variable name="parent" select="concat(val, '.')" />
<item val="{val}">
<xsl:apply-templates select="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))]">
<xsl:sort select="substring-after(val, $parent)" data-type="number" order="ascending"/>
</xsl:apply-templates>
</item>
</xsl:template>
</xsl:stylesheet>
应用于以下输入示例:
XML
<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>E</name>
<val>1.2.1</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.2</val>
</row>
</root>
结果将是:
<?xml version="1.0" encoding="UTF-8"?>
<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.10"/>
</item>
<item val="2"/>
</list>
这是递归工作的,并且对级别数没有限制。但请注意,每个项目(不包含点的 "ancestor" 项目除外)都必须有父项。
I have a problem with sorting numbers, separated by periods (e.g. 1,
2.1, 1.1, 1.3). I found a solution here XSL recursive sort.
第一部分排序
很容易将原来的解决方案应用到新的案例中。 与接受的答案不同,此解决方案正确排序 XML 文档,其中有
<val>1.3.2</val>
但是没有
<val>1.3</val>
有关将排序结果转换为所需的嵌套列表结构的信息,请参见第二部分。
<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="root">
<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:choose>
<!-- end of recursion, there isn't any more row with more chunks -->
<xsl:when test="val = substring($prefix, 1, string-length($prefix)-1)">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="chunk" select=
"substring-before(concat(substring-after(val, $prefix), '.'), '.')"/>
<!-- this tests for grouping row with same prefix, to skip duplicates -->
<xsl:if test=
"not(preceding-sibling::row[starts-with(val, concat($prefix, $chunk))])">
<xsl:variable name="new-prefix"
select="concat($prefix, $chunk, '.')"/>
<xsl:apply-templates select=
"../row[starts-with(val, $new-prefix) or val = concat($prefix, $chunk)]">
<xsl:sort select=
"substring-before(concat(substring-after(val, $new-prefix), '.'), '.')"
data-type="number"/>
<xsl:with-param name="prefix" select="$new-prefix"/>
</xsl:apply-templates>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
当此转换应用于以下 XML 文档时 -- 注意有 <val>1.3.2</val>
但没有 <val>1.3</val>
并且接受的答案不会产生正确的结果——实际上删除了整个 <row>
具有 <val>1.3.2</val>
child:
<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>
第二部分:将排序后的平面结果转换为嵌套列表结构
在这里,我们从第 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>
检查您选择的 XSLT 处理器是否支持数字排序规则(一种将连续数字序列视为数字的字符串排序方式,因此 "Chapter 2" 在 "Chapter 10" 之前排序)。例如,对于 XSLT 3.0 和 XPath 3.1 中定义的 UCA 归类 URI,这将是
<xsl:sort select="val"
collation="http://www.w3.org/2013/collation/UCA?numeric=yes"/>
数字排序规则在撒克逊语中也已经使用了一些年
collation="http://saxon.sf.net/collation?alphanumeric=yes"
如果您能够利用它,这里还有另一个候选者:XPath 3.1 中的新 fn:sort 函数允许使用复合排序键,因此您可以编写
sort(val, function($x){tokenize($x, '\.')!number()})
我对用句点分隔的数字排序有问题(例如 1、2.1、1.1、1.3)。我在这里 XSL recursive sort 找到了解决方案。这就是我需要的,略有不同。 在我的 xml 中,标签就像
<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="level"/>
<name>C</name>
<val>test</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.2.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>
我想根据 "val" 标签对 col/@name = "rank" 的所有行进行排序。是否可以仅修改链接问题中接受的答案来获得输出?如果没有,是否有 xsl 版本 1(如果有 none 则为 2)的任何解决方案。 我需要的输出是这样的:
<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>
</ul>
</li>
</ul>
更新 I:所以根据 michael.hor257k 的回答,这就是我一直在寻找的解决方案:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="5.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<html>
<body>
<ul>
<xsl:apply-templates select="row[not(contains(val, '.'))][contains(col/@name, 'rank')]">
<xsl:sort select="val" data-type="number" order="ascending"/>
</xsl:apply-templates>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="row">
<li>
<xsl:variable name="parent" select="concat(val, '.')" />
<xsl:value-of select="./name"/> - <xsl:value-of select="./val"/>
<xsl:if test="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))][contains(col/@name, 'rank')]">
<ul>
<xsl:apply-templates select="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))][contains(col/@name, 'rank')]">
<xsl:sort select="substring-after(val, $parent)" data-type="number" order="ascending"/>
</xsl:apply-templates>
</ul>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>
更新 II:感谢 Dimitre Novatchev,我有一个更好的解决方案,我认为这是最好的答案。所以,我试图理解它,之后我会检查它是否为已接受的答案。
更新 III:我接受了 michael.hor257k 发布的答案,因为这是我需要的。我知道我的 xml 中没有排名顺序的跳跃,但正如 Dimitre Novatchev 提到的那样,如果有,比如有 1.3.2 而没有 1.3,这个解决方案就会有问题,你可以使用完整的答案Dimitre Novachev 发布的。
知道关卡的数量吗?如果是这样,在 XSLT 2.0 中您可以使用
<xsl:apply-templates select="row[col/@name = 'rank']">
<xsl:sort select="xs:integer(tokenize(val, '\'.')[1])"/>
<xsl:sort select="xs:integer(tokenize(val, '\'.')[2])"/>
<xsl:sort select="xs:integer(tokenize(val, '\'.')[3])"/>
</xsl:apply-templates/>
三个级别。在 XSLT 3.0 中,您甚至可以使用任何级别的 sort
函数来完成此操作:<xsl:apply-templates select="sort(row[col/@name = 'rank'], function($row) { tokenize($row/val, '\.')!xs:integer(.) })">
尽管您也想嵌套,但我认为使用递归函数来执行
<xsl:for-each-group select="$rows" group-by="xs:integer(tokenize(val, '\.')[1])"><xsl:sort select="current-grouping-key()"/>...</xsl:for-each-group>
比纯排序更适合 XSLT 2.0 或 3.0。
一个完整的样式表是
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf" version="2.0">
<xsl:output method="html" indent="yes"/>
<xsl:function name="mf:nest" as="element()*">
<xsl:param name="rows" as="element(row)*"/>
<xsl:sequence select="mf:nest($rows, 1)"/>
</xsl:function>
<xsl:function name="mf:nest" as="element()*">
<xsl:param name="rows" as="element(row)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$rows" group-by="xs:integer(tokenize(val, '\.')[$level])">
<xsl:sort select="current-grouping-key()"/>
<li>
<xsl:variable name="item" select="current-group()[not(tokenize(val, '\.')[$level + 1])]"/>
<xsl:value-of select="$item/concat(name, ' - ', val)"/>
<xsl:if test="current-group()[2]">
<ul>
<xsl:sequence select="mf:nest(current-group() except $item, $level + 1)"/>
</ul>
</xsl:if>
</li>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="root">
<ul>
<xsl:sequence select="mf:nest(row[col/@name = 'rank'])"/>
</ul>
</xsl:template>
</xsl:stylesheet>
它转换输入
<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="level"/>
<name>C</name>
<val>test</val>
</row>
<row>
<col name="rank"/>
<name>D</name>
<val>1.2.2</val>
</row>
<row>
<col name="rank"/>
<name>E</name>
<val>1.2.1</val>
</row>
<row>
<col name="rank"/>
<name>foo</name>
<val>2</val>
</row>
<row>
<col name="rank"/>
<name>bar</name>
<val>1.10</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.2</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.10.1</val>
</row>
</root>
进入结果
<ul>
<li>B - 1
<ul>
<li>A - 1.1</li>
<li>F - 1.2
<ul>
<li>E - 1.2.1</li>
<li>D - 1.2.2</li>
</ul>
</li>
<li>bar - 1.10
<ul>
<li>F - 1.10.1</li>
</ul>
</li>
</ul>
</li>
<li>foo - 2</li>
</ul>
考虑以下样式表:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<list>
<xsl:apply-templates select="row[not(contains(val, '.'))]">
<xsl:sort select="val" data-type="number" order="ascending"/>
</xsl:apply-templates>
</list>
</xsl:template>
<xsl:template match="row">
<xsl:variable name="parent" select="concat(val, '.')" />
<item val="{val}">
<xsl:apply-templates select="../row[starts-with(val, $parent)][not(contains(substring-after(val, $parent), '.'))]">
<xsl:sort select="substring-after(val, $parent)" data-type="number" order="ascending"/>
</xsl:apply-templates>
</item>
</xsl:template>
</xsl:stylesheet>
应用于以下输入示例:
XML
<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>E</name>
<val>1.2.1</val>
</row>
<row>
<col name="rank"/>
<name>F</name>
<val>1.2</val>
</row>
</root>
结果将是:
<?xml version="1.0" encoding="UTF-8"?>
<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.10"/>
</item>
<item val="2"/>
</list>
这是递归工作的,并且对级别数没有限制。但请注意,每个项目(不包含点的 "ancestor" 项目除外)都必须有父项。
I have a problem with sorting numbers, separated by periods (e.g. 1, 2.1, 1.1, 1.3). I found a solution here XSL recursive sort.
第一部分排序
很容易将原来的解决方案应用到新的案例中。 与接受的答案不同,此解决方案正确排序 XML 文档,其中有
<val>1.3.2</val>
但是没有
<val>1.3</val>
有关将排序结果转换为所需的嵌套列表结构的信息,请参见第二部分。
<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="root">
<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:choose>
<!-- end of recursion, there isn't any more row with more chunks -->
<xsl:when test="val = substring($prefix, 1, string-length($prefix)-1)">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="chunk" select=
"substring-before(concat(substring-after(val, $prefix), '.'), '.')"/>
<!-- this tests for grouping row with same prefix, to skip duplicates -->
<xsl:if test=
"not(preceding-sibling::row[starts-with(val, concat($prefix, $chunk))])">
<xsl:variable name="new-prefix"
select="concat($prefix, $chunk, '.')"/>
<xsl:apply-templates select=
"../row[starts-with(val, $new-prefix) or val = concat($prefix, $chunk)]">
<xsl:sort select=
"substring-before(concat(substring-after(val, $new-prefix), '.'), '.')"
data-type="number"/>
<xsl:with-param name="prefix" select="$new-prefix"/>
</xsl:apply-templates>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
当此转换应用于以下 XML 文档时 -- 注意有 <val>1.3.2</val>
但没有 <val>1.3</val>
并且接受的答案不会产生正确的结果——实际上删除了整个 <row>
具有 <val>1.3.2</val>
child:
<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>
第二部分:将排序后的平面结果转换为嵌套列表结构
在这里,我们从第 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>
检查您选择的 XSLT 处理器是否支持数字排序规则(一种将连续数字序列视为数字的字符串排序方式,因此 "Chapter 2" 在 "Chapter 10" 之前排序)。例如,对于 XSLT 3.0 和 XPath 3.1 中定义的 UCA 归类 URI,这将是
<xsl:sort select="val"
collation="http://www.w3.org/2013/collation/UCA?numeric=yes"/>
数字排序规则在撒克逊语中也已经使用了一些年
collation="http://saxon.sf.net/collation?alphanumeric=yes"
如果您能够利用它,这里还有另一个候选者:XPath 3.1 中的新 fn:sort 函数允许使用复合排序键,因此您可以编写
sort(val, function($x){tokenize($x, '\.')!number()})