包装一组 XML 节点
Posted
技术标签:
【中文标题】包装一组 XML 节点【英文标题】:Wrap group of XML nodes 【发布时间】:2011-04-08 19:23:17 【问题描述】:我正在使用 php5,我需要将 XML 转换为以下形式:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<anotherNode>some text</anotherNode
<item label="a">some text</item>
<item label="b">some text</item>
</item>
</list>
变成这样:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<anotherNode>some text</anotherNode>
<list> <!-- opening new wrapper node-->
<item label="a">some text</item>
<item label="b">some text</item>
</list> <!-- closing new wrapper node-->
</item>
</list>
正如您在上面看到的,我需要将包装器节点添加到尚未被“列表”节点包装的任何“项目”节点。
将源 xml 转换为目标 xml 的可能解决方案是什么?
更新:
注意 1:任何单个或一组 <item>
节点如果尚未包装,则需要由 <list>
节点包装。
注意2:需要保持内容的顺序。
注 3:
如果<anotherNode>
前后有<item>
节点。
它应该改变这个:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<item label="a">some text</item>
<item label="b">some text</item>
<anotherNode>some text</anotherNode>
<item label="c">some text</item>
<item label="d">some text</item>
</item>
</list>
进入这个:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<list> <!-- opening new wrapper node-->
<item label="a">some text</item>
<item label="b">some text</item>
</list> <!-- closing new wrapper node-->
<anotherNode>some text</anotherNode>
<list> <!-- opening new wrapper node-->
<item label="c">some text</item>
<item label="d">some text</item>
</list> <!-- closing new wrapper node-->
</item>
</list>
谢谢,
【问题讨论】:
好问题 (+1)。请参阅我的答案以获取本着 XSLT 精神的简短解决方案。 @Benjamin-Ortuzar:你忘记接受答案了吗?还是不完全满意? 【参考方案1】:这个样式表:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@*|node()[1]" />
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]" />
</xsl:template>
<xsl:template match="*[not(self::list)]
/item[not(preceding-sibling::*[1][self::item])]">
<list>
<xsl:call-template name="identity"/>
</list>
<xsl:apply-templates select="following-sibling::node()
[not(self::item)][1]" />
</xsl:template>
<xsl:template match="*[not(self::list)]
/item[not(following-sibling::*[1][self::item])]">
<xsl:copy>
<xsl:apply-templates select="@*|node()[1]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
输出:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<anotherNode>some text</anotherNode>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</item>
</list>
另外,这个样式表:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kItemByFirstSibling"
match="item[preceding-sibling::*[1][self::item]]"
use="generate-id(preceding-sibling::item
[not(preceding-sibling::*[1][self::item])][1])"/>
<xsl:template match="@*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(self::list)]/item"/>
<xsl:template match="*[not(self::list)]
/item[not(preceding-sibling::*[1][self::item])]"
priority="1">
<list>
<xsl:for-each select=".|key('kItemByFirstSibling',generate-id())">
<xsl:call-template name="identity"/>
</xsl:for-each>
</list>
</xsl:template>
</xsl:stylesheet>
注意:第一个样式表使用最细粒度的横向(它将包裹第一个item
之后的任何节点)。第二个样式表完全递归身份转换。
编辑:解决新要求,使用新输入,两个样式表输出:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
<anotherNode>some text</anotherNode>
<list>
<item label="c">some text</item>
<item label="d">some text</item>
</list>
</item>
</list>
【讨论】:
@Alejandro:请参阅我对 Dimitre 的评论...假设所有需要包装的项目元素(即同一个非列表元素的子元素)都是连续的,则此方法工作正常。 @LarsH:正如我在笔记中所写,第一个样式表在第一个item
之后包装任何节点。第二个样式表包装 any item
没有 list
父级,并且相对顺序(兄弟之间)将是第一个 item
的相对顺序。我认为这是一个公平的问题,但只有当 OP 想要输出兄弟姐妹 list
parents 时才重要。【参考方案2】:
这种转变:
<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="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item/item[1]">
<list>
<xsl:apply-templates mode="copy"
select=".| following-sibling::item"/>
</list>
</xsl:template>
<xsl:template match="item" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="item/item[not(position()=1)]"/>
</xsl:stylesheet>
应用于提供的 XML 文档时:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<anotherNode>some text</anotherNode>
<item label="a">some text</item>
<item label="b">some text</item>
</item>
</list>
产生想要的正确结果:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<anotherNode>some text</anotherNode>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</item>
</list>
请注意:
身份规则的使用和覆盖。
抑制某些元素。
使用不同的模式处理某些元素。
更新:
OP 增加了额外的要求:
"如果在anothernode
之前和之后有item
元素,那么每个这样的item
元素组必须包含在单独的list
中"
<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:key name="kfollnonitem" match="item"
use="generate-id(preceding-sibling::*[not(self::item)][1])"/>
<xsl:key name="kprecnonitem" match="item"
use="generate-id(following-sibling::*[not(self::item)][1])"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(self::list)]/item[1]">
<list>
<xsl:apply-templates mode="copy"
select="key('kprecnonitem',
generate-id(following-sibling::*[not(self::item)][1])
)"/>
</list>
</xsl:template>
<xsl:template match=
"*[not(self::list) and item]/*[not(self::item)]">
<xsl:call-template name="identity"/>
<list>
<xsl:apply-templates mode="copy"
select="key('kfollnonitem', generate-id())"/>
</list>
</xsl:template>
<xsl:template match="item" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="item/item[not(position()=1)]"/>
</xsl:stylesheet>
当对以下 XML 文档执行此转换时:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<item label="a">some text</item>
<item label="b">some text</item>
<anotherNode>some text</anotherNode>
<item label="c">some text</item>
<item label="d">some text</item>
</item>
</list>
产生想要的正确结果:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
<anotherNode>some text</anotherNode>
<list>
<item label="c">some text</item>
<item label="d">some text</item>
</list>
</item>
</list>
【讨论】:
@Dimitre: +1 模式解决方案。 @Dimitre:如果<anotherNode>
之前和之后 都有<item>
元素会发生什么@ 987654332@?您的解决方案会将它们全部放在<anotherNode>
之前。 @Benjamin,在这种情况下应该怎么办?
@LarsH:很好的观察。未指定具有其他名称的兄弟姐妹的所需顺序和项目列表。我的解决方案保留了第一个 item
和任何非项目兄弟之间的相对(文档)排序。我认为这是很自然的。如果需要另外订购,很容易修改解决方案以满足新要求,
如果在 之前和之后有 您没有在原始问题中解决此问题,因此可能不需要。但如果输入有多个需要包装的<item>
元素序列,它们被其他兄弟元素相互分隔,例如:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<item label="a">some text</item>
<item label="b">some text</item>
<anotherNode>some text</anotherNode>
<item label="c">some text</item>
<item label="d">some text</item>
</item>
</list>
我相信,较早的答案会将 <item>
元素集中在一起,改变它们的顺序:
<list>
<item label="(1)">some text</item>
<item label="(2)">
<list> <!-- opening new wrapper node-->
<item label="a">some text</item>
<item label="b">some text</item>
<item label="c">some text</item>
<item label="d">some text</item>
</list> <!-- closing new wrapper node-->
<anotherNode>some text</anotherNode>
</item>
</list>
你想要那个,还是像这样分开包装?
<list>
<item label="(1)">some text</item>
<item label="(2)">
<list> <!-- opening new wrapper node-->
<item label="a">some text</item>
<item label="b">some text</item>
</list> <!-- closing new wrapper node-->
<anotherNode>some text</anotherNode>
<list> <!-- opening new wrapper node-->
<item label="c">some text</item>
<item label="d">some text</item>
</list> <!-- closing new wrapper node-->
</item>
</list>
如果是后者,使用 XSLT 2.0 <xsl:for-each-group group-adjacent="name()" />
构造可能是最简单的。我不知道 PHP 5 是否有 XSLT 2.0 可用,但如果你可以使用这样的东西,请参阅this good article。
【讨论】:
我需要像你的第二个例子一样单独包装它们。以上是关于包装一组 XML 节点的主要内容,如果未能解决你的问题,请参考以下文章