我有一个非常简单的xml,我想使用xmlstarlet重新排列。
的例子:
<myXml description="example 1">
<!-- Comment XXX -->
<randomNodeX>
<randomSubNode1>value1</randomSubNode1>
<randomSubNode2>value2</randomSubNode2>
</randomNodeX>
<!-- Comment YYY1 -->
<!-- Comment YYY2 -->
<randomNodeY attribute1="value3" attribute2="value4"/>
<!-- Comment ZZZ -->
<randomNodeZ attribute1="value5" attribute0="value6">
<randomSubNode3 attribute3="value7" attribute4="value8"/>
</randomNodeZ>
<!-- Comment for node1 first occurrence -->
<node1 attribute1="value9" attribute5="value10" attribute6="value11"/>
<!-- Comment for node2 first occurrence -->
<node2 attribute1="value12" attribute7="value13" attribute8="value14">
<subNode21 attributeX="value15"/>
<subNode22 attributeY="value16" attributeZ="value17"/>
</node2>
<!-- Comment for node3 first occurrence -->
<node3 attribute1="value18" attribute9="value19">
<subNode31 attributeW="value20"/>
</node3>
<!-- Comment for node1 second occurrence -->
<node1 attribute1="value21" attribute5="value22" attribute6="value23"/>
<!-- Comment for node3 second occurrence -->
<node3 attribute1="value24" attribute9="value25">
<subNode31 attributeW="value26"/>
</node3>
<!-- Comment for node2 second occurrence -->
<node2 attribute1="value27" attribute7="value28" attribute8="value29">
<subNode21 attributeX="value30"/>
<subNode22 attributeY="value31" attributeZ="value32"/>
</node2>
</myXml>
我想重新排列xml,以便所有node1, node2和node3元素与它们各自的注释一起出现。此外,我想保留文档的其余部分和注释,而不必知道哪些标记存在. 我的意思是,除了node1, node2和node3之外,xml中还可以有其他标签,我想保留在文档的开头(包括注释)。
预期结果:
<myXml description="example 1">
<!-- Comment XXX -->
<randomNodeX>
<randomSubNode1>value1</randomSubNode1>
<randomSubNode2>value2</randomSubNode2>
</randomNodeX>
<!-- Comment YYY1 -->
<!-- Comment YYY2 -->
<randomNodeY attribute1="value3" attribute2="value4"/>
<!-- Comment ZZZ -->
<randomNodeZ attribute1="value5" attribute0="value6">
<randomSubNode3 attribute3="value7" attribute4="value8"/>
</randomNodeZ>
<!-- Comment for node1 first occurrence -->
<node1 attribute1="value9" attribute5="value10" attribute6="value11"/>
<!-- Comment for node1 second occurrence -->
<node1 attribute1="value21" attribute5="value22" attribute6="value23"/>
<!-- Comment for node2 first occurrence -->
<node2 attribute1="value12" attribute7="value13" attribute8="value14">
<subNode21 attributeX="value15"/>
<subNode22 attributeY="value16" attributeZ="value17"/>
</node2>
<!-- Comment for node2 second occurrence -->
<node2 attribute1="value27" attribute7="value28" attribute8="value29">
<subNode21 attributeX="value30"/>
<subNode22 attributeY="value31" attributeZ="value32"/>
</node2>
<!-- Comment for node3 first occurrence -->
<node3 attribute1="value18" attribute9="value19">
<subNode31 attributeW="value20"/>
</node3>
<!-- Comment for node3 second occurrence -->
<node3 attribute1="value24" attribute9="value25">
<subNode31 attributeW="value26"/>
</node3>
</myXml>
现在我使用这个样式表:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()[not(self::node1|self::node2|self::node3|self::comment())]"/>
<xsl:apply-templates select="node1"/>
<xsl:apply-templates select="node2"/>
<xsl:apply-templates select="node3"/>
</xsl:copy>
</xsl:template>
<xsl:template match="randomNodeX|randomNodeY|randomNodeZ|node1|node2|node3">
<xsl:apply-templates select="preceding-sibling::comment()[generate-id(following-sibling::*[1])=generate-id(current())]"/>
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
问题是我必须指定xml中存在的所有随机标签(randomNodeX, randomNodeY,…)。
是否有一种方法可以在不知道node1, node2和node3之外的标签的情况下做到这一点?
我会这样做:
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:strip-space elements="*"/>
<xsl:key name="my-comments" match="comment()" use="generate-id(following-sibling::*[1])" />
<xsl:template match="/myXml">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="*[not(self::node1 or self::node2 or self::node3)]"/>
<xsl:apply-templates select="node1"/>
<xsl:apply-templates select="node2"/>
<xsl:apply-templates select="node3"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy-of select="key('my-comments', generate-id())"/>
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
InXSLT 2.0这可以简化为:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="my-comments" match="comment()" use="generate-id(following-sibling::*[1])" />
<xsl:template match="/myXml">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:variable name="my-nodes" select="node1, node2, node3" />
<xsl:apply-templates select="* except $my-nodes, $my-nodes"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy-of select="key('my-comments', generate-id())"/>
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
这是XPath 2中的except
操作XSLT 1中的except
操作符可能表示为
<xsl:template match="/*">
<xsl:copy>
<xsl:variable name="nodes" select="node1 | node2 | node3"/>
<xsl:variable name="trailers" select="$nodes | $nodes/preceding-sibling::comment()[1]"/>
<xsl:apply-templates select="node()[count(. | $trailers) > count($trailers)]"/>
<xsl:apply-templates select="$trailers"/>
</xsl:copy>
</xsl:template>
这假设所有的node1
,node2
和node3
元素都恰好有一个前面的兄弟注释节点。
我不太确定,然而,为什么你不使用match="/*/*"
而不是match="randomNodeX|randomNodeY|randomNodeZ|node1|node2|node3"
。
XSLT 2.0+解决方案:
我将从一个分组操作开始,该操作将元素与它们的"关联"进行分组。注释,以便
<!-- Comment ZZZ -->
<randomNodeZ attribute1="value5" attribute0="value6">
<randomSubNode3 attribute3="value7" attribute4="value8"/>
</randomNodeZ>
是
<group>
<!-- Comment ZZZ -->
<randomNodeZ attribute1="value5" attribute0="value6">
<randomSubNode3 attribute3="value7" attribute4="value8"/>
</randomNodeZ>
</group>
,然后在阶段2中,根据所包含的元素名称对组进行分组(同时删除group
包装器)。
在你的例子中,每个元素前面都有一个或多个"associated"评论,但我们能指望总是这样吗?为了更能容忍输入的变化,我们可以说一个组从任何注释或元素开始,而这些注释或元素的前面没有立即有注释。如果我们假设空格已经使用xsl:strip-space剥离,我们可以使用
进行第一次分组。<xsl:for-each-group select="child::node()"
group-starting-with="(comment()|*)[not(preceding-sibling::*[1][self::comment()]">
<group><xsl:copy-of select="current-group()"/></group>
</xsl:for-each-group>
第二个是
<xsl:for-each-group select="group" group-by="name(*[1])">
<xsl:copy-of select="current-group()/child::node()"/>
</xsl:for-each-group>
但是你可能想重新注入一些空格。