包装一组 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:任何单个或一组 &lt;item&gt; 节点如果尚未包装,则需要由 &lt;list&gt; 节点包装。

注意2:需要保持内容的顺序。

注 3: 如果&lt;anotherNode&gt; 前后有&lt;item&gt; 节点。 它应该改变这个:

<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:如果&lt;anotherNode&gt; 之前和之后 都有&lt;item&gt; 元素会发生什么@ 987654332@?您的解决方案会将它们全部放在&lt;anotherNode&gt; 之前。 @Benjamin,在这种情况下应该怎么办? @LarsH:很好的观察。未指定具有其他名称的兄弟姐妹的所需顺序和项目列表。我的解决方案保留了第一个 item 和任何非项目兄弟之间的相对(文档)排序。我认为这是很自然的。如果需要另外订购,很容易修改解决方案以满足新要求, 如果在 之前和之后有 元素,则这些 组中的每一个都应该有自己的 包装器。应保留内容的顺序。 @Benjamin-Ortuzar 和@LarsH:我已经更新了我的答案,以便解决方案满足最新要求。 :)【参考方案3】:

您没有在原始问题中解决此问题,因此可能不需要。但如果输入有多个需要包装的&lt;item&gt; 元素序列,它们被其他兄弟元素相互分隔,例如:

<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>

我相信,较早的答案会将 &lt;item&gt; 元素集中在一起,改变它们的顺序:

<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 &lt;xsl:for-each-group group-adjacent="name()" /&gt; 构造可能是最简单的。我不知道 PHP 5 是否有 XSLT 2.0 可用,但如果你可以使用这样的东西,请参阅this good article。

【讨论】:

我需要像你的第二个例子一样单独包装它们。

以上是关于包装一组 XML 节点的主要内容,如果未能解决你的问题,请参考以下文章

XML节点InnerText包装方法

用其祖先节点/标签包装 XML 元素

XSLT 1.0 - 用 HTML 包装的多个子节点模板

XSLT 如何将常用标签包装在不同的包装元素中?

用标签包装 XmlNode

jquery的 包装集