如何为每次出现的唯一列表调用模板

时间:2013-01-11 05:34:23

标签: xml unique grouping xslt-2.0

我目前正在使用XSLT 2.0来尝试处理大量数据。我的目标是为整个数据中特定节点的唯一出现调用特定模板。我不能使用“应用模板”,因为我有其他模板需要根据不同的逻辑使用。 xml的结构如下:

<root>
  <row>
     <date>date1</date>
     <child>
        <ID>001</ID>
     </child>
  </row>
  <row>
     <date>date2</date>
     <child>
        <ID>002</ID>
     </child>
  </row>
  <row>
     <date>date1</date>
     <child>
        <ID>002</ID>
     </child>
  </row>
  <row>
     <date>date3</date>
     <child>
        <ID>002</ID>
     </child>
  </row>
</root>

理想情况下,我会在这种情况下调用模板两次,因为有两个唯一ID 001和002,无论该ID上显示多少个日期。我见过几个人们使用Keys或者兄弟姐妹的方法;但是,我似乎永远无法使语法正确。我尝试了以下方法:

正在调用模板:

<xsl:template name='doSomething'/>
   <xsl:value-of select='child/ID'/>
   <xsl:value-of select='date'/>
</template>

====================

<xsl:for-each select='/root/row[not(child/ID = current()child/ID)]
   <xsl:call-template name='doSomething'/>
</xsl:for-each>

====================

<xsl:key name='IDs' match='root/row/child/ID' use='.'/>

<xsl:for-each select='root/row[generate-id() = generate-id(key("IDs",.)[1])]>
    <xsl:call-template name='doSomething'/>
</xsl:for-each>

====================

<xsl:for-each select='root/row/child/ID[not(. = preceding-sibling::row/child/ID)]>
   <xsl:call-template name='doSomething'/>
</xsl:for-each>

如果这是可怕的编码,我很抱歉,因为这是我第一次尝试使用XSLT进行任何复杂操作时,我仍然无法理解关键用法以及轴语法的工作原理。我通常在2.0中使用for-each-group。但是,在这里尝试for-each-groups时,我得到所有条目而不是唯一条目,这让我觉得我不完全理解分组是如何工作的。我原来的尝试是这样的:

<xsl:for-each-group select='root/row' group-by='child/ID'>
    <xsl:call-template name='doSomething'/>
</xsl:for-each-group>

====================

我也试过这个:

<xsl:for-each-group select='root/row' group-by='child/ID[1]'>
   <xsl:call-template name='doSomething'/>
</xsl:for-each-group>

====================

我知道这不完全正确,但我的想法用完了,只是在尝试。任何解决这个问题的方向或协助都会非常感激。提前感谢您提供的任何帮助。

这是我想要的输出:

001date1    002date2

但是,我得到了:

001date1    002date2    002date1    002date3

编辑:感谢您的反馈,我意识到我遗漏了相当重要的代码部分。还有与每个ID相关联的日期,表示交易日期。虽然我可以使用for-each-group按ID分组,但我继续获取与该ID相关联的所有日期,我只想为唯一ID调用模板,而不是所有包含该ID的条目 - 这是什么正在发生。我添加了一个示例输出和当前输出。

我相信我可能误解了apply-template的工作原理。我想如果我使用apply-templates它会应用xslt中的所有模板。因为我有4个在特定条件下使用的其他模板。实际输入xml有大约40个子节点,用于处理逻辑和信息。

我希望澄清一下,如果您需要任何进一步的细节,请告诉我。

修改

在做了一些更多的研究后,似乎使用钥匙对我来说是一个好方法;但是,我似乎无法使语法正确。这是我的尝试,有人可以帮我纠正吗?

<xsl:key name='IDs' match='child' use='ID'/>

<xsl:template match='/'>
   <xsl:for-each select='root/row/child[generate-id(.) = generate-id(key("IDs", ID)[1])]>
       <xsl:call-template name='doSomething'/>
   </xsl:for-each>
</xsl:template>

提前感谢您的任何澄清。

3 个答案:

答案 0 :(得分:0)

通过向我们展示您想要的输出,您拥有的代码以及您获得的结果,您还没有真正解释问题所在。

我没有看到使用apply-templates时遇到任何问题,或者您使用call-template与您发布的for-each-group一起坚持使用此问题。

以下是显示两种方法的示例:

<xsl:stylesheet version="2.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes"/>

<xsl:template match="/">
  <apply-templates>
     <xsl:for-each-group select="root/row" group-by="child/ID">
       <xsl:apply-templates select="."/>
     </xsl:for-each-group>
   </apply-templates>
   <call-template>
     <xsl:for-each-group select="root/row" group-by="child/ID">
       <xsl:call-template name="foo"/>
     </xsl:for-each-group>
   </call-template>
</xsl:template>

<xsl:template match="root/row">
  <xsl:copy-of select="."/>
</xsl:template>

<xsl:template name="foo">
  <xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>

应用于输入

<root>
  <row>
     <child>
        <ID>value</ID>
     </child>
  </row>
  <row>
     <child>
        <ID>value2</ID>
     </child>
  </row>
  <row>
     <child>
        <ID>value2</ID>
     </child>
  </row>
</root>

使用Saxon 9.4我得到输出

<apply-templates>
   <row>
     <child>
        <ID>value</ID>
     </child>
  </row>
   <row>
     <child>
        <ID>value2</ID>
     </child>
  </row>
</apply-templates>
<call-template>
   <row>
     <child>
        <ID>value</ID>
     </child>
  </row>
   <row>
     <child>
        <ID>value2</ID>
     </child>
  </row>
</call-template>

因此,就我理解您的描述而言,这两种方法都会处理您感兴趣的row元素。

如果您仍然遇到问题,请显示最小但完整的XSLT示例,您获得的输出,您想要的输出以及您使用的XSLT 2.0处理器的详细信息,以便我们可以重现该问题。

答案 1 :(得分:0)

<强>解

感谢所有提供反馈意见的人,经过周末的一些进一步研究和一些小实验,我得到了解决方案!

输入:

<root>
  <row>
     <date>date1</date>
     <child>
         <id>001</id>
     </child>
  </row>
  <row>
     <date>date1</date>
     <child>
         <id>002</id>
     </child>
  </row>
  <row>
     <date>date2</date>
     <child>
         <id>002</id>
     </child>
  </row>
</root>

XSLT:

<xsl:for-each select='root/row'>
    <xsl:if test='preceding-sibling::row[1]/child/id != child/id'>
         <xsl:call-template name='doSomething'/>
    </xsl:if>
</xsl:for-each>

输出:

001date1,002date1

答案 2 :(得分:0)

对此延迟回复表示歉意。这是我提出的解决方案。我不知道它是否是最优雅的解决方案,但它对我来说效果很好。也许一些反馈虽然这将是一个糟糕的解决方案,将不胜感激。

    <xsl:for-each-group select='root/Row' group-by='concat(child/ID,date)'>
        <xsl:sort select='child/ID'/>
        <xsl:call-template name='DoSomething'/>
    </xsl:for-each-group>

谢谢。

相关问题