2

给定一个深度嵌套的 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>

我想在 <ul> (easy peasy) 中找到第 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>
4

2 回答 2

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>
于 2013-08-31T02:34:02.450 回答
0

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

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

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

  • Banana 之外的所有内容都被复制为身份转换;它对应于输出中相同类型的节点,具有相同的名称,但有一个例外,它具有相似的属性和子节点序列。Banana 的父级是个例外:它的子级序列包含两个对应于 Banana 的元素,以及branch它们之间的第三个元素(此处命名为 )。
  • 作为 Bullseye 的祖先和 Banana(或 Banana 本身)的后代的每个元素对应于输出树中的两个元素,具有相似的名称和属性。
  • 在文档顺序中在 Bullseye 之前且不是 Bullseye 的祖先且不是 Bullseye 的祖先的属性的 Banana 的每个后代都被复制一次到输出,就像在 Banana 的第一个副本中进行身份转换一样.
  • 在文档顺序中跟随靶心并且不是靶心的祖先并且不是靶心的祖先的属性的香蕉的每个后代都被复制一次到输出,就像在香蕉的第二个副本中的恒等变换一样. 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>
于 2013-08-31T00:36:52.417 回答