【问题标题】:Move separator elements upwards in xml hierarchy在 xml 层次结构中向上移动分隔符元素
【发布时间】:2011-04-21 06:25:05
【问题描述】:

我有一个在层次结构中带有分隔符的 xml 文档。

<A>
  <B>
    <C id='1'/>
    <separator/>
    <C id='2'/>
  </B>
  <B>
    <C id='3'/>
    <separator/>
  </B>
  <B>
    <C id='4'/>
  </B>
</A>

我想向上移动分隔符,保持元素有序。所以想要的输出是

<A>
  <B>
    <C id='1'/>
  </B>
</A>
<separator/>
<A>
  <B>
    <C id='2'/>
  </B>
  <B>
    <C id='3'/>
  </B>
</A>
<separator/>
<A>
  <B>
    <C id='4'/>
  </B>
</A>

如何仅使用 xslt 1.0 来完成?可以不使用for-each,只使用模板匹配吗?

更新: 我实际上得到了 4 个不同概括程度的精彩答案,谢谢你们。

【问题讨论】:

  • 很好的问题 (+1)。请参阅我的答案以获得完整的解决方案。
  • +1 我很高兴回答这个问题

标签: xml xslt hierarchy separator


【解决方案1】:

这种转变

<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="kFollowing" match="C"
         use="generate-id(preceding::separator[1])"/>

 <xsl:template match="/">
  <xsl:apply-templates select="*/*/separator"/>
 </xsl:template>

 <xsl:template match="separator" name="commonSep">
  <separator/>
  <xsl:call-template name="genAncestors">
   <xsl:with-param name="pAncs" select="ancestor::*"/>
   <xsl:with-param name="pLeaves"
        select="key('kFollowing', generate-id())"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template match="separator[not(preceding::separator)]">
  <xsl:call-template name="genAncestors">
   <xsl:with-param name="pAncs" select="ancestor::*"/>
   <xsl:with-param name="pLeaves"
        select="key('kFollowing', '')"/>
  </xsl:call-template>
  <xsl:call-template name="commonSep"/>
 </xsl:template>

 <xsl:template name="genAncestors">
   <xsl:param name="pAncs" select="ancestor::*"/>
   <xsl:param name="pLeaves" select="."/>

   <xsl:choose>
    <xsl:when test="not($pAncs[2])">
     <xsl:apply-templates select="$pLeaves" mode="gen"/>
    </xsl:when>
    <xsl:otherwise>
     <xsl:for-each select="$pAncs[1]">
      <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:call-template name="genAncestors">
               <xsl:with-param name="pAncs" select="$pAncs[position()>1]"/>
               <xsl:with-param name="pLeaves" select="$pLeaves"/>
        </xsl:call-template>
      </xsl:copy>
     </xsl:for-each>
    </xsl:otherwise>
   </xsl:choose>
 </xsl:template>

 <xsl:template match="C" mode="gen">
  <xsl:variable name="vCur" select="."/>
  <xsl:for-each select="..">
   <xsl:copy>
    <xsl:copy-of select="@*|$vCur"/>
   </xsl:copy>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

应用于提供的 XML 文档时

<A>
  <B>
    <C id='1'/>
    <separator/>
    <C id='2'/>
  </B>
  <B>
    <C id='3'/>
    <separator/>
  </B>
  <B>
    <C id='4'/>
  </B>
</A>

产生想要的正确结果

<A>
   <B>
      <C id="1"/>
   </B>
</A>
<separator/>
<A>
   <B>
      <C id="2"/>
   </B>
   <B>
      <C id="3"/>
   </B>
</A>
<separator/>
<A>
   <B>
      <C id="4"/>
   </B>
</A>

【讨论】:

  • 不错的解决方案,但从问题来看,我怀疑separator 节点可能出现在任何深度;这个解决方案取决于它们是B 元素的子节点,两层深。有没有更通用的方法来做到这一点?
  • @Flynn1179:只需将 &lt;xsl:apply-templates select="*/*/separator"/&gt; 替换为 &lt;xsl:apply-templates select="//separator"/&gt; 即可。唯一的假设是separator 元素应该都在相同的深度。像往常一样,我会回答具体问题——OP 或任何其他可以提出新的、更通用的问题的人——他们确实必须提供一个很好的例子。 `
  • 公平地说,我倾向于用更一般的术语来考虑问题,以最大限度地提高对其他感兴趣的读者的有用性,同时也因为一个小例子往往无法捕捉到全部可能性。然而,这是我的方法,有时它使解决方案比它需要的更复杂。无论如何,更改该 select 子句是不够的;您的“kFollowing”键专门针对 C 元素。
  • +1 让我很难理解你优雅的“自下而上”方法
【解决方案2】:

这个样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kCByFollSep" match="C"
             use="generate-id(following::separator[1])"/>
    <xsl:template match="A">
        <xsl:for-each select="B/separator|B[last()]/*[last()]">
            <A>
                <xsl:apply-templates
                     select="key('kCByFollSep',
                                 substring(generate-id(),
                                           1 div boolean(self::separator)))"/>
            </A>
            <xsl:copy-of select="self::separator"/>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="C">
        <B>
            <xsl:copy-of select="."/>
        </B>
    </xsl:template>
</xsl:stylesheet>

输出:

<A>
    <B>
        <C id="1" />
    </B>
</A>
<separator />
<A>
    <B>
        <C id="2" />
    </B>
    <B>
        <C id="3" />
    </B>
</A>
<separator />
<A>
    <B>
        <C id="4" />
    </B>
</A>

注意:通过关注separator进行分组,为可能的C添加最后三级元素而不关注separator

编辑:更多拉式风格,更多架构不可知论,此样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kCByFollSep" match="C"
             use="generate-id(following::separator[1])"/>
    <xsl:template match="text()"/>
    <xsl:template match="separator|*[not(*)][not(following::*)]">
        <A>
            <xsl:apply-templates
                     select="key('kCByFollSep',
                                 substring(generate-id(),
                                           1 div boolean(self::separator)))"
                     mode="output"/>
        </A>
        <xsl:copy-of select="self::separator"/>
    </xsl:template>
    <xsl:template match="C" mode="output">
        <B>
            <xsl:copy-of select="."/>
        </B>
    </xsl:template>
</xsl:stylesheet>

编辑 2:更通用的解决方案(我不相信的一件事,ja!)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="node()|@*" name="identity">
        <xsl:param name="pRemains"/>
        <xsl:copy>
            <xsl:apply-templates select="node()[descendant-or-self::node()
                                                   [not(self::separator)]
                                                   [count(following::separator)
                                                    = $pRemains]
                                               ][1]|@*">
                <xsl:with-param name="pRemains" select="$pRemains"/>
            </xsl:apply-templates>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()
                                        [descendant-or-self::node()
                                           [not(self::separator)]
                                           [count(following::separator)
                                            = $pRemains]
                                        ][1]">
            <xsl:with-param name="pRemains" select="$pRemains"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="/*">
        <xsl:variable name="vCurrent" select="."/>
        <xsl:for-each select="descendant::separator|node()[last()]">
            <xsl:variable name="vRemains" select="last()-position()"/>
            <xsl:for-each select="$vCurrent">
                <xsl:copy>
                    <xsl:apply-templates
                         select="node()[descendant::node()
                                          [not(self::separator)]
                                          [count(following::separator)
                                           = $vRemains]
                                       ][1]">
                        <xsl:with-param name="pRemains" select="$vRemains"/>
                    </xsl:apply-templates>
                </xsl:copy>
            </xsl:for-each>
            <xsl:copy-of select="self::separator"/>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="separator"/>
</xsl:stylesheet>

注意:主要是细粒度的遍历。底层层次结构规则(在本例中为根元素)复制自身和分隔符(最后一个组的虚拟节点,没有后面的分隔符)传递剩余分隔符以处理具有足够后续分隔符的第一个子元素来处理。修改后的细粒度遍历身份规则,复制自身并再次处理第一个孩子和后续兄弟姐妹,并有足够的后续分隔符进行处理。最后,一个分隔规则打破了这个过程。

编辑 3:其他更通用的解决方案,现在使用递归身份规则

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:key name="kNodeByFolSep" match="node()[not(self::separator)]"
          use="generate-id((descendant::separator|following::separator)[1])"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:param name="pGroup"/>
        <xsl:copy>
            <xsl:apply-templates
               select="node()[descendant-or-self::node()[count(.|$pGroup)
                                                         = count($pGroup)]]|@*">
                <xsl:with-param name="pGroup" select="$pGroup"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/*">
        <xsl:variable name="vCurrent" select="."/>
        <xsl:for-each select="descendant::separator|node()[last()]">
            <xsl:variable name="vGroup"
                 select="key('kNodeByFolSep',generate-id(self::separator))"/>
            <xsl:for-each select="$vCurrent">
                <xsl:call-template name="identity">
                    <xsl:with-param name="pGroup" select="$vGroup"/>
                </xsl:call-template>
            </xsl:for-each>
            <xsl:copy-of select="self::separator"/>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="separator"/>
</xsl:stylesheet>

编辑 4:现在和以前一样,但使用键测试而不是节点集交集。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:key name="kNodeByFolSep" match="node()[not(self::separator)]"
          use="concat(generate-id(),'+',
                      generate-id((descendant::separator|
                                   following::separator)[1]))"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:param name="pSeparator"/>
        <xsl:copy>
            <xsl:apply-templates
               select="@*|node()[descendant-or-self::node()
                                    [key('kNodeByFolSep',
                                         concat(generate-id(),
                                                '+',
                                                $pSeparator))]]">
                <xsl:with-param name="pSeparator" select="$pSeparator"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/*">
        <xsl:variable name="vCurrent" select="."/>
        <xsl:for-each select="descendant::separator|node()[last()]">
            <xsl:variable name="vSeparator"
                          select="generate-id(self::separator)"/>
            <xsl:for-each select="$vCurrent">
                <xsl:call-template name="identity">
                    <xsl:with-param name="pSeparator" select="$vSeparator"/>
                </xsl:call-template>
            </xsl:for-each>
            <xsl:copy-of select="self::separator"/>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="separator"/>
</xsl:stylesheet>

【讨论】:

  • @Dimitre:谢谢!我很高兴回答这个问题。但是我无法完成一般问题的线性复杂度算法......我正在考虑使用前向和后向模式进行细粒度遍历。
  • +1 用于共享抽象的不同阶段,它更容易理解。最后一个最接近我的想象。
  • @Adam Schmideg:谢谢!你的问题真的很好。另外,我正在添加最后一个使用身份转换模式的示例,但仅使用键测试而不是节点集交集。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-04
  • 1970-01-01
  • 1970-01-01
  • 2013-01-21
  • 2021-07-29
  • 2016-03-28
相关资源
最近更新 更多