如何在XSLT中对含字母数字混合的属性'd'进行排序?
XSLT实现混合格式属性'd'的数字优先升序排序
问题背景
需对一组文档按属性d升序排序,属性值为字母与数字混合的层级格式(示例:11A-1-000003、11-1-000008a)。核心排序规则为数字优先:数字代表主章节,字母代表子章节,层级间按-分割,需先比较每个层级的数字部分(数值大小),再比较字母部分(文本顺序)。
此前尝试的4种方案均未达标:
- 纯文本排序:会出现
11-24-00043a排在11-3-000023前的问题(文本比较中2的ASCII码小于3) - 提取纯数字排序:丢失字母信息,无法区分
11-1与11-1C - 多层substring分割:灵活性差,无法适配不同层级长度或字母位置不固定的场景
- 简单tokenize后排序:未拆分层级内的数字与字母,导致
11-1C-000008排在11-15B-000008后的错误结果
预期排序示例:11-1-000008a → 11-1C-000008 → 11-3-000023 → 11-3-000023a → 11-3-000023b → 11-9-000002 → 11-15-00014 → 11-15B-00014a → 11-16-000009 → 11-24-00043a → 11A-1-000003 → 12-1-000008a → 13-1-000007a
解决方案(XSLT 2.0+)
利用tokenize拆分层级、自定义函数拆分每个层级的数字与字母,实现多维度排序:
完整代码示例
<!-- 自定义函数:拆分单个层级段为数字+字母 --> <xsl:function name="local:split-segment" as="item()*"> <xsl:param name="segment" as="xs:string"/> <xsl:analyze-string select="$segment" regex="^(\d*)(.*)$"> <xsl:matching-substring> <!-- 数字部分转为整数,无数字则返回0 --> <xsl:sequence select="if (regex-group(1) ne '') then xs:integer(regex-group(1)) else 0"/> <!-- 保留字母部分(含空字符串) --> <xsl:sequence select="regex-group(2)"/> </xsl:matching-substring> </xsl:analyze-string> </xsl:function> <!-- 排序模板 --> <xsl:template match="root"> <xsl:copy> <xsl:for-each select="document"> <!-- 按层级拆分后,依次比较每个层级的数字、字母 --> <xsl:sort select="tokenize(@d, '-') ! local:split-segment(.)" data-type="text" order="ascending"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:copy> </xsl:template>
工作原理
- 层级拆分:通过
tokenize(@d, '-')将属性值按-拆分为多个层级段(如11A-1-000003拆分为['11A', '1', '000003']) - 段内拆分:
local:split-segment函数将每个层级段拆分为「数字部分(整数类型)」和「字母部分(文本类型)」,例如:11A→11+A15B→15+B000003→3+ ``(空字符串)
- 多维度排序:排序时按层级顺序依次比较:
- 先比较当前层级的数字部分(数值大小),数字小的条目排前
- 数字部分相同则比较字母部分(文本顺序),字母靠前的排前
- 当前层级完全相同则比较下一层级,以此类推
注意事项
- 需使用支持XSLT 2.0或更高版本的处理器(如Saxon),依赖
tokenize、analyze-string及自定义函数特性 - 若层级段无数字(如
A-1-000001),数字部分默认返回0,确保此类条目排在数字开头的条目之前 - 字母部分排序遵循默认文本规则,如需区分大小写可添加
case-order="upper-first"或lower-first参数
内容的提问来源于stack exchange,提问作者Smile
相关产品推荐
相关产品推荐

