XSLT技术实现:为特定属性元素及后续特殊元素添加父节点
问题描述
现有如下XML结构:
<egXML> <measure type="Divison" n="1"> <!-- The element with this attribute value can appear n times --> </measure> <measure type="End_Cycle" n="4"> <!-- This element appears once after the previous elements with @type="Division" --> </measure> <measure type="Divison" n="5"> <!-- This element with this attribute value can appear n times --> </measure> <measure type="End_Cycle" n="10"> <!-- This element appears once after the previous elements with @type="Division"--> </measure> </egXML>
需求是将若干个@type="Division"的measure元素,加上紧随其后的一个@type="End_Cycle"的measure元素组成一个主题块,用<section>标签包裹,最终输出结构如下:
<egXML> <section n="A"> <measure type="Division" n="1"> </measure> <measure type="Division" n="2"> </measure> <!-- And as many as there are until the one with @type="End_Cycle" appears --> <measure type="End_Cycle" n="4"> </measure> </section> <section n="B"> <measure type="Division" n="5"> </measure> <!-- And so on --> <measure type="End_Cycle" n="10"> </measure> </section> </egXML>
尝试过多种分组方式,但要么把所有@type="Division"元素放进同一个<section>,要么每个元素单独一个<section>,需要正确的XSLT实现方案。
解决方案
方案1:XSLT 2.0/3.0(推荐,语法更简洁)
利用for-each-group的group-ending-with特性,直接指定分组的结束标记是@type="End_Cycle"的measure元素,逻辑清晰:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <!-- 复制根节点 --> <xsl:template match="egXML"> <egXML> <!-- 按End_Cycle元素分组 --> <xsl:for-each-group select="measure" group-ending-with="measure[@type='End_Cycle']"> <!-- 生成section标签,n属性用字母序列A、B、C... --> <section n="{translate(position(), '1234567890', 'ABCDEFGHIJ')}"> <!-- 输出当前组内的所有measure元素 --> <xsl:copy-of select="current-group()"/> </section> </xsl:for-each-group> </egXML> </xsl:template> </xsl:stylesheet>
逻辑说明:
group-ending-with="measure[@type='End_Cycle']":表示每遇到一个@type="End_Cycle"的measure,就将它和之前所有未分组的measure(也就是@type="Division"的元素)组成一个分组。current-group():获取当前分组内的所有元素,直接复制输出即可。position()生成的数字转字母:用translate函数把1-10转成A-J,如果需要更多字母可以扩展映射规则。
方案2:XSLT 1.0(兼容旧版本处理器)
XSLT 1.0没有原生分组函数,需要用Muenchian分组结合轴定位来实现:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <!-- 定义键:按每个End_Cycle元素的位置,标记对应的分组 --> <xsl:key name="group-by-end-cycle" match="measure[@type!='End_Cycle']" use="generate-id(following-sibling::measure[@type='End_Cycle'][1])"/> <!-- 复制根节点 --> <xsl:template match="egXML"> <egXML> <!-- 遍历所有End_Cycle元素,每个对应一个section --> <xsl:for-each select="measure[@type='End_Cycle']"> <xsl:variable name="current-end" select="."/> <!-- 生成section的n属性,用字母序列 --> <section n="{translate(position(), '1234567890', 'ABCDEFGHIJ')}"> <!-- 输出当前组内的Division元素 --> <xsl:copy-of select="key('group-by-end-cycle', generate-id($current-end))"/> <!-- 输出当前End_Cycle元素 --> <xsl:copy-of select="$current-end"/> </section> </xsl:for-each> </egXML> </xsl:template> </xsl:stylesheet>
逻辑说明:
- 用
key为每个Division元素绑定它后面最近的End_Cycle元素的ID,这样所有属于同一个End_Cycle前面的Division会被归为一组。 - 遍历每个
End_Cycle元素,通过key找到对应的所有Division元素,再加上自身,组成一个<section>。
内容的提问来源于stack exchange,提问作者OttoMan
相关产品推荐
相关产品推荐

