如何修复此xml节点合并以使用xslt保留所有唯一属性?

时间:2012-07-13 03:55:00

标签: xml xslt

我有这个输入文件:

<root> 
    <library id="L1">
        <shelf1 id="1">
            <book id="1" action="borrow">
                <attributes>
                    <user>John</user>                    
                </attributes>
                <other1>y</other1>
            </book>  
            <book id="1" action="extend">
                <attributes>
                    <user>Woo</user>           
                    <length>3</length>
                </attributes>
                <other2>y</other2>
            </book>  
            <book id="1" action="extend">
                <attributes>
                    <length>2</length>
                    <condition>ok</condition>
                </attributes>
                <other3>y</other3>
            </book>
            <book id="2" action="borrow">...</book>
        </shelf1>
        <shelf2>...</shelf2>
    </library>
</root>

预期产出:

<root> 
    <library id="L1">
        <shelf1 id="1">
            <book id="1" action="borrow">
                <attributes>
                    <user>Woo</user>           
                    <length>2</length>
                    <condition>ok</condition>
                </attributes>
                <other1>y</other1>
                <other2>y</other2>
                <other3>y</other3>
            </book>
            <book id="2" action="borrow">...</book>
        </shelf1>
        <shelf2>...</shelf2>
    </library>
</root>

我的XSL:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

    <xsl:template match="library/*/*[1]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <attributes>
                <xsl:for-each-group select="attributes/*" group-by="name()">
                    <xsl:sort select="current-grouping-key()"/>
                    <xsl:apply-templates select="."/>
                </xsl:for-each-group>
            </attributes>
            <xsl:apply-templates select="*[not(self::attributes)]"/>
        </xsl:copy>            
    </xsl:template>

    <xsl:template match=
      "library/*/*
        [@action='extend' and following-sibling::*[1][@action='extend']
       or preceding-sibling::*[@action='borrow']]"/>
</xsl:stylesheet>

对于action=borrow后跟一个或多个action=extend

节点的每个节点
  • 将其合并到action=borrow的节点。
  • attributes个孩子合并在一起,使其具有最新值的兄弟姐妹的所有独特属性。
  • 让其他孩子保持不变

请告诉我如何使用XSLT 2.0修复此转换?

非常感谢。

亲切的问候, 约翰

1 个答案:

答案 0 :(得分:3)

你问了很多类似的问题。也许是时候买书并花几天时间阅读?这可能是我最后一次回答你的类似问题。您应该能够从答案中学习,这样您就不必提出类似的问题。如果你不学习,你必须问自己:“什么阻碍了你?”

任何方式,让我们来看看这个样式表。在解释中,我将点作为xml或xpath注释中的数字引用。例如,第1点划分为&lt;! - 1 - &gt; ,或者如果在xpath表达式中,则(:1:)。显然,删除生产的评论。

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:fn="http://www.w3.org/2005/xpath-functions"
  xmlns:John="http://stackoverflow.com/questions/11463900"
  exclude-result-prefixes="xsl xs fn John">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*" />

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

 <xsl:template match="*[starts-with(local-name(),'shelf')]   (: 1 :)">
  <xsl:copy>
   <xsl:apply-templates select="@*" />

    <!-- 2 -->
   <xsl:apply-templates select="
     book[@action='extend']                                  (: 3 :)
         [not( preceding-sibling::book[@action='borrow'])]   (: 4 :)" />

    <!-- 5 -->
   <xsl:for-each-group
    select="book[@action='borrow']     (: 6 :)
             |                         (: 7 :)
            book[@action='extend']
                [preceding-sibling::book[@action='borrow']]   (: 8 :)"
    group-starting-with="book[@action='borrow']">
     <xsl:for-each select="current-group()[1]">
      <xsl:copy>                                            <!-- 9 -->
       <xsl:apply-templates select="@*" />
        <xsl:call-template name="merge-books-deeply">       <!-- 10 -->
         <xsl:with-param name="books" select="current-group()" />
         <xsl:with-param name="name-path" select="()" />
        </xsl:call-template>
      </xsl:copy>
     </xsl:for-each>     
   </xsl:for-each-group>

   <xsl:apply-templates select="                              (: 11 :)
     node()[ not( self::book[@action=('borrow','extend')])]" />

  </xsl:copy>
 </xsl:template>

<xsl:function name="John:children-on-path" as="element()*">
 <xsl:param name="base" as="element()*" />     <!-- 12 -->
 <xsl:param name="path" as="xs:string*" />     <!-- 13 -->
 <xsl:choose>
  <xsl:when test="fn:empty($base)">
   <xsl:sequence select="()" />
  </xsl:when>
  <xsl:when test="fn:empty($path)">
   <xsl:copy-of select="$base/*" />           <!-- 14 -->
  </xsl:when>
  <xsl:otherwise>
   <xsl:sequence select="John:children-on-path(
     $base/*[name()=$path[1]],                      (: 15 :)
     $path[position() ne 1])" /> 
  </xsl:otherwise>  
 </xsl:choose>
</xsl:function>

<xsl:template name="merge-books-deeply">
 <xsl:param name="books" as="element()*" />
 <xsl:param name="name-path" as="xs:string*" />
  <xsl:for-each-group
      select="John:children-on-path($books,$name-path)"
      group-by="name()">                                <!-- 16 -->                 
   <xsl:for-each select="current-group()[last()]" >     <!-- 17 -->
    <xsl:copy>
     <xsl:apply-templates select="@*" />
        <xsl:call-template name="merge-books-deeply">   <!-- 18 -->
         <xsl:with-param name="books" select="$books" />
         <xsl:with-param name="name-path" select="$name-path,name()" />
        </xsl:call-template>
     <xsl:apply-templates select="text()" />        
    </xsl:copy>
   </xsl:for-each>
  </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>

说明

  1. 我们从架子节点的模板开始。不幸的是,您不仅仅使架子节点名称保持不变。它会使匹配表达式更加简单。我们复制货架节点和任何属性。
  2. 现在书籍将按照一些规则合并。所以合并在一起的书籍是一个群体。根据您所说的合并规则,一组书籍都是以借书开头的书籍,包括以下扩展书籍。实现这一目标的自然候选人将是xsl:for-each-group关于借阅和扩展书籍的指令,其中@ group-starting-with设置为借阅书(第5点到第8点)。 xsl:for-each-group 存在一个小问题,如果您有任何孤立的书籍,也就是说扩展的书籍之前没有借书,那么这些将是第一组。我们不希望这样,因为它不符合合并规则。首先,我们只是将它们从分组中排除(见第8点),并在第2点处理任何孤立的书籍。
  3. 此处的谓词选择扩展书籍。
  4. ...这个选择了孤儿书
  5. 现在我们将书架的书籍分组为可合并的组
  6. 当然,如果一本书被借用,它就属于一个团体(实际上它将是该团体的负责人)
  7. 并且我们还需要包含以下扩展的书籍
  8. 但是驱逐孤儿
  9. 此时
  10. current-group()选择一组可合并的书籍。这个序列的头部,current-group()[1]是启动该组的借书。我们选择并复制它及其属性。
  11. 现在为了合并乐趣!从您的示例数据中可以看出,您在两个级别合并,即名为'other1'的节点和名为'attributes'的节点的子节点。虽然你的意图并不清楚这有多远,但我采取了最自由的解释,也就是说你希望所有的book-of-book元素递归地融合到无限深度。为此,我们需要递归调用一个命名模板。 merge-books-deep的工作是沿着一些元素路径($ name-path)合并一组书($ books)。 $ name-path将从空开始。然后它会有('attributes'),('attributes','user')和('other1')等值。
  12. 此时,我们仍在书架上,但我们已经完成了我们的书籍合并。可能有非书籍儿童或其他不参与合并的奇怪事物。由于它们可能有价值,我们在这里复制它们。
  13. 现在我们需要一个支持函数来查找来自货架的给定相对路径的合并组的所有可合并节点。这是John:children-on-path()。它需要两个参数。 $ base是要合并的节点
  14. $ path是从它们向下导航的路径。该函数返回节点序列,这些节点是由$ path下降的$ base的子节点。例如,如果$ base是/ root而$ path是('library'),那么John:children-on-path()应该返回shelf1和shelf2。该函数是递归定义的。首先我们处理那些微不足道的案例,然后如果不是微不足道的话,我们会在路径中向下走一步,并消除我们刚刚从下一条路径中取出的步骤,并继续调用直到我们遍历整个路径。
  15. 一旦我们在组中找到可合并节点,我们就会进行新的分组。我们按名称对它们进行分组例如,所有用户节点都是一个组,然后所有长度节点都是另一个。
  16. 一旦我们有一个小组,说所有用户,我们就无法将它们全部复制 - 整个合并点。所以我们选择一个作为整个组的价值。在样本数据中,OP选择了最后一个值。这就是我们的工作。我们选择该元素,复制它及其属性。
  17. 现在我们可能需要再次合并到另一个级别。所以我们递回到相同的模板中,但是扩展了路径以包括我们刚刚采取的新步骤。
  18. John的研究

    1. Google for xsl:for-each-group并做一些事情。这是一些好的。
    2. 购买XSLT书并阅读。有很多可供选择。