如何使用XSLT拆分嵌套结构?

时间:2013-08-30 00:09:44

标签: xml xslt xslt-2.0

鉴于深度嵌套的XML树,我想找到一个特定的元素。在那一点上,我想将X包装在一个与更高元素处于同一级别的新元素中。然后,我想继续使用“特定”元素后面的原始树的其余部分。

例如,给定此输入:

<root>
    <branch att="yo">
        <div stuff="no">
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
            </ul>
        </div>        
    </branch>
</root>

我想在&lt; ul&gt;中找到第2项(十分简单)。我想在第2项之前插入一个新的分支级元素。然后我想继续第2项(继续祖先节点)。也就是说,我想要这个输出:

<root>
    <branch att="yo">
        <div stuff="no">
            <ul>
                <li>Item 1</li>
            </ul>
        </div>
    </branch>
    <branch>
        <div>
            <p>New branch here</p>
        </div>
    </branch>
    <branch att="yo">
        <div stuff="no">
            <ul>
                <li>Item 2</li>
                <li>Item 3</li>
            </ul>
        </div>        
    </branch>
</root>

为了制作通用解决方案,我遇到了一个难题。我认为它将涉及多个模式或键以及祖先节点的处理以查找节点名称和属性。任何帮助表示赞赏。

好的,这是我到目前为止所拥有的。它部分工作(例如,我复制一些节点,但没有属性;我也复制了太多的节点,但我认为这是一个开始)。我的想法是我需要一个递归函数。我从我关注的最远的祖先(分支)开始,并且对于每个子节点,其最终后代是特定元素(li“item 2”),我复制每个节点及其属性,将其附加到前一个节点(即newNewTree是什么)。我递归直到达到某个元素(li的第2项),此时我返回NewNewTree变量,我的意图是它是一个右祖先元素的树(没有文本)。为了完成这项工作,我认为我需要做的是通过身份模板覆盖来处理函数中的每个节点,该覆盖复制节点及其属性。但今晚太累了,无法尝试。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:my="http://www.example.com"
                exclude-result-prefixes="xs my"
                version="2.0">
  <xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="/">
    <xsl:sequence select="$origNodesNoText" />
  </xsl:template>
  <xsl:variable name="ancestorElemName" select="'div'" />
  <xsl:variable name="origNodesNoText">
    <xsl:apply-templates select="/" mode="origNodesNoText"/>
  </xsl:variable>
  <xsl:template match="text()" mode="origNodesNoText"/>
  <xsl:template match="li[.='Item 2']" mode="origNodesNoText">
    <xsl:variable name="newTree">
      <xsl:element name="{local-name(ancestor::*[local-name()=$ancestorElemName][1])}">
        <xsl:for-each select="ancestor::*[local-name()=$ancestorElemName][1]/attribute::*">
          <xsl:attribute name="{local-name()}" select="."/>
        </xsl:for-each>
      </xsl:element>
    </xsl:variable>
    <xsl:sequence select="my:split(ancestor::*[local-name()=$ancestorElemName][1],.,$newTree)" />
  </xsl:template>

  <xsl:function name="my:split">
    <xsl:param name="ancestorElemNode" />
    <xsl:param name="callingNode" />
    <xsl:param name="newTree" />
    <xsl:message>Calling my split</xsl:message>
    <xsl:choose>
        <xsl:when test="$ancestorElemNode ne $callingNode">
            <xsl:message>Found a node to copy its name and attributes</xsl:message>
            <xsl:variable name="newNewTree">
                <xsl:for-each select="$newTree/node()">
                    <xsl:copy /> <!-- doesn't copy attribute nodes so not what we want -->
                </xsl:for-each>
                <xsl:element name="{local-name($ancestorElemNode)}">
                    <xsl:for-each select="$ancestorElemNode/attribute::*">
                        <xsl:attribute name="{local-name()}" select="."/>
                    </xsl:for-each>
                </xsl:element>    
            </xsl:variable>
            <xsl:sequence select="my:split($ancestorElemNode/child::*[descendant::*[. eq $callingNode]][1],$callingNode,$newNewTree)" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:message>Found the end point</xsl:message>
            <xsl:sequence select="$newTree" />
        </xsl:otherwise>
    </xsl:choose>
  </xsl:function>

</xsl:stylesheet>

2 个答案:

答案 0 :(得分:0)

您希望以下列方式处理输入的节点。首先,让我们为经常需要引用的节点定义两个术语:

  • 第二个列表项是我们的目标元素;我们称之为Bullseye。
  • 包围Bullseye的分支元素需要拆分;我们称之为香蕉。

现在我们可以根据它们与输出树的对应关系在输入树中定义几个类节点。

  • 香蕉以外的所有东西都被复制为身份变换;它对应于输出中具有相同名称的相同类型的节点,并且除了一个例外,它具有类似的属性和子序列。例外是香蕉的父亲:它的子序列包含两个与香蕉相对应的元素,以及它们之间的第三个元素(此处命名为branch)。
  • 作为Bullseye的祖先和Banana(或香蕉本身)的后代的每个元素对应于输出树中的两个元素,具有相似的名称和属性。
  • Banana的每个后代都以文档顺序位于Bullseye之前,并且不是Bullseye的祖先,并且不是Bullseye祖先的属性,在第一个副本中被复制一次到输出中的身份变换香蕉。
  • Banana的每个后代都按照文档顺序跟随Bullseye并且不是Bullseye的祖先,并且不是Bullseye的祖先的属性被复制一次到输出,如同在身份变换中,在第二个副本中香蕉Bullseye本身也是如此。

我用近乎身份的变换做到这一点。我们需要对香蕉及其后代进行特殊处理。其中一些需要复制两次,有些只需复制一次,但最简单的做法就是两次处理整个子树。

整个样式表可能如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0">

  <xsl:output method="xml" indent="yes"/>

  <xsl:variable name="Bullseye" select="//li[.='Item 2']"/>

  <!--* Default identity transform *-->
  <xsl:template match="comment()">
    <xsl:comment><xsl:value-of select="."/></xsl:comment>
  </xsl:template>

  <xsl:template match="processing-instruction()">
    <xsl:variable name="pitarget" select="name()"/>
    <xsl:processing-instruction name="{$pitarget}">
      <xsl:value-of select="."/>
    </xsl:processing-instruction>
  </xsl:template>

  <xsl:template match="@*|*">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!--* Banana *-->
  <xsl:template match="branch[descendant::* intersect $Bullseye]">    
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" mode="pre-bullseye"/>
    </xsl:copy>
    <xsl:call-template name="insertion"/>
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" mode="post-bullseye"/>
    </xsl:copy>
  </xsl:template>

  <!--* first pass over subtree rooted at Banana *-->
  <xsl:template match="comment()" mode="pre-bullseye">
    <xsl:apply-templates select="."/>
  </xsl:template>
  <xsl:template match="processing-instruction()" mode="pre-bullseye">
    <xsl:apply-templates select="."/>
  </xsl:template>
  <xsl:template match="@*|*|text()" mode="pre-bullseye">
    <xsl:choose>
      <xsl:when test="descendant::* intersect $Bullseye">
        <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:apply-templates select="node()" mode="pre-bullseye"/>
        </xsl:copy>
      </xsl:when>
      <xsl:when test=". &lt;&lt; $Bullseye">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:when>
      <xsl:when test=". is $Bullseye"/>      
      <xsl:when test=". >> $Bullseye"/>
      <xsl:otherwise>
        <xsl:message terminate="yes"
          >Unexpected case in mode pre-bullseye, dying now.</xsl:message>
      </xsl:otherwise>
    </xsl:choose>

  </xsl:template>

  <!--* Second pass over subtree rooted at Banana *-->
  <xsl:template match="comment()" mode="post-bullseye">
    <xsl:apply-templates select="."/>
  </xsl:template>
  <xsl:template match="processing-instruction()" mode="post-bullseye">
    <xsl:apply-templates select="."/>
  </xsl:template>
  <xsl:template match="@*|*|text()" mode="post-bullseye">
    <xsl:choose>
      <xsl:when test="descendant::* intersect $Bullseye">
        <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:apply-templates select="node()" mode="post-bullseye"/>
        </xsl:copy>
      </xsl:when>
      <xsl:when test=". &lt;&lt; $Bullseye"/>

      <xsl:when test=". is $Bullseye">
        <xsl:copy-of select="."/>
      </xsl:when>
      <xsl:when test=". >> $Bullseye">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message terminate="yes"
         >Unexpected case in post-bullseye, dying now.</xsl:message>
      </xsl:otherwise>
    </xsl:choose>

  </xsl:template>

  <xsl:template name="insertion">
    <branch>
      <div>
        <p>New branch here.</p>
      </div>
    </branch>
  </xsl:template>

</xsl:stylesheet>

在您提供的输入上运行时,此样式表会生成以下输出。

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <branch att="yo">
        <div stuff="no">
            <ul>
                <li>Item 1</li>
                </ul>
      </div>
   </branch>
   <branch>
      <div>
         <p>New branch here.</p>
      </div>
   </branch>
   <branch>
      <div stuff="no">
         <ul>
            <li>Item 2</li>
                <li>Item 3</li>
            </ul>
        </div>        
    </branch>
</root>

答案 1 :(得分:0)

这是另一种选择。基本上你:

  • 定义目标元素的值(target参数)。
  • 标识目标元素(键branch)并应用该元素的模板。 (如果有多个具有该值的元素,则在第一次出现时分支。)
  • 从顶部开始为前一个兄弟模板应用模板。 注意:“顶部”是/*/*。如果根元素有多个子元素,则此样式表需要进行一些调整。
  • 将模板应用于目标元素。我们传递一个参数(branch),因此我们知道这是插入新分支的地方。
  • 再次将模板应用于目标元素(不包含branch参数)以及以下兄弟。

XML输入

<root>
    <branch att="yo">
        <div stuff="no">
            <ul>
                <li>Item 1</li>
                <li>Item 2</li>
                <li>Item 3</li>
            </ul>
        </div>        
    </branch>
</root>

XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:param name="target" select="'Item 2'"/>
    <xsl:key name="branch" match="*[.=$target]" use="."/>

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="@*|(//*[key('branch',.)])[1]"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*">
        <xsl:variable name="precID" select="for $id in preceding-sibling::* return generate-id($id)"/>
        <xsl:variable name="follID" select="for $id in (self::*,following-sibling::*) return generate-id($id)"/>
        <xsl:apply-templates select="/*/*" mode="ident">
            <xsl:with-param name="restricted2IDs" select="$precID" tunnel="yes"/>           
        </xsl:apply-templates>
        <xsl:apply-templates select="/*/*" mode="ident">
            <xsl:with-param name="restricted2IDs" select="generate-id(current())" tunnel="yes"/>
            <xsl:with-param name="branch" select="';-)'" tunnel="yes"/>
        </xsl:apply-templates>
        <xsl:apply-templates select="/*/*" mode="ident">
            <xsl:with-param name="restricted2IDs" select="$follID" tunnel="yes"/>           
        </xsl:apply-templates>      
    </xsl:template>

    <xsl:template match="@*|node()" mode="ident">
        <xsl:param name="restricted2IDs" tunnel="yes"/>
        <xsl:param name="branch" tunnel="yes"/>
            <xsl:choose>
                <xsl:when test="*[generate-id()=$restricted2IDs]">
                    <xsl:choose>
                        <xsl:when test="$branch">
                            <p>New branch here</p>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:copy>
                                <xsl:apply-templates select="@*|node()[generate-id()=$restricted2IDs]" mode="ident"/>                                                       
                            </xsl:copy>                         
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:when>
                <xsl:when test="descendant::*[generate-id()=$restricted2IDs]">
                    <xsl:copy>
                        <!--If you want the attributes in the new branch, remove the "if" and just use "@*|node".-->
                        <xsl:apply-templates select="if ($branch) then node() else @*|node()" mode="ident"/>                        
                    </xsl:copy>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy>
                        <xsl:apply-templates select="@*|node()" mode="ident"/>
                    </xsl:copy>
                </xsl:otherwise>
            </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

XML输出

<root>
   <branch att="yo">
      <div stuff="no">
         <ul>
            <li>Item 1</li>
         </ul>
      </div>
   </branch>
   <branch>
      <div>
         <p>New branch here</p>
      </div>
   </branch>
   <branch att="yo">
      <div stuff="no">
         <ul>
            <li>Item 2</li>
            <li>Item 3</li>
         </ul>
      </div>
   </branch>
</root>