【问题标题】:xslt: select unique node via intermediate reference node?xslt:通过中间参考节点选择唯一节点?
【发布时间】:2013-01-25 03:45:37
【问题描述】:

XSLT 2.

嗨,我有一个 xml,它有 3 个节点,从“孩子”的角度命名:孩子、父亲和母亲父亲。从父亲节点开始,我需要根据子节点中的 ID 找到一个孩子的 MothersFather 节点(子节点是连接其他两个节点的中间参考。)

所以,对于每个父亲来说,都有他孩子的不同 MothersFather - 这些不是人类,一个父亲可能有数百个孩子,但只有大约二十个相关的 MothersFathers :)

XML 的简化版本(现实生活中大约有 80 个父亲节点、3000 个子节点和 400 个母亲父亲节点):

<t>
<Children>
    <Child>
        <ID>1</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>200</MothersFatherID>    
    </Child>
    <Child>
        <ID>2</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>    
    </Child>
    <Child>
        <ID>3</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>202</MothersFatherID>    
    </Child>
    <Child>
        <ID>4</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>    
    </Child>
    <Child>
        <ID>5</ID>
        <FathersID>101</FathersID>
        <MothersFatherID>201</MothersFatherID>    
    </Child>
</Children>
<Fathers>
    <Father>
        <ID>100</ID>
    </Father>
    <Father>
        <ID>101</ID>
    </Father>
</Fathers>
<MothersFathers>
    <MothersFather>
        <ID>200</ID>
    </MothersFather>
    <MothersFather>
        <ID>201</ID>
    </MothersFather>
    <MothersFather>
        <ID>202</ID>
    </MothersFather>
</MothersFathers>        
</t>

我的 xslt 看起来像:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kFathersChildren" match="Child" use="FathersID"/>

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

    <xsl:template match="Fathers">
        <xsl:apply-templates select="Father"></xsl:apply-templates>
    </xsl:template>

    <xsl:template match="Father">
        <xsl:text>&#10;FATHER: ID=</xsl:text><xsl:value-of select="ID"/>
        <!-- Now show all this fathers childrens maternal grandfathers based on the ID in the Child node -->

        <!--TRY 1: this works, as in gets the right nodes, but doesn't do distinct values....--> 
        <xsl:for-each select="key('kFathersChildren', ID)">  <!-- get the fathers children --> 
            <xsl:text>&#10; found child: current MFid=</xsl:text><xsl:value-of select="current()/MothersFatherID"/>
            <xsl:text> ID=</xsl:text><xsl:value-of select="ID"/>
            <xsl:apply-templates select="//MothersFathers/MothersFather[ID=current()/MothersFatherID]"></xsl:apply-templates>
        </xsl:for-each>

        <!-- *** THIS IS WHERE I GET LOST??? - Do the same thing but only get distinct MothersFatherID's... -->

        <!--TRY 2: note- won't compile in current state... -->
        <xsl:for-each select="distinct-values(key('kFathersChildren', ID)[MothersFatherID])">  
            <xsl:text>&#10;  Distinct MothersFatherID ???? - don't know what to select </xsl:text><xsl:value-of select="."/>
            <xsl:apply-templates select="//MothersFathers/MothersFather[ID=??????????"></xsl:apply-templates>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="//MothersFathers/MothersFather">
        <xsl:text>&#10;      IN MothersFather template... ID=</xsl:text><xsl:value-of select="ID"/>
    </xsl:template>
</xsl:stylesheet>

在 Try 1 中,我可以获得所有节点和 MothersFatherID。 Try1 的输出是:

FATHER: ID=100
 found child: current MFid=200 ID=1
      IN MothersFather template... ID=200
 found child: current MFid=201 ID=2
      IN MothersFather template... ID=201
 found child: current MFid=202 ID=3
      IN MothersFather template... ID=202
 found child: current MFid=201 ID=4
      IN MothersFather template... ID=201
FATHER: ID=101
 found child: current MFid=201 ID=5
      IN MothersFather template... ID=201

在我选择“不同值”的 Try2 中,我希望输出如下:

FATHER: ID=100
      IN MothersFather template... ID=201
      IN MothersFather template... ID=200
      IN MothersFather template... ID=202
FATHER: ID=101
      IN MothersFather template... ID=201

(不是真正的输出 - 只是调试显示我可以引用正确节点的东西)。

但我不知道我要使用什么来引用唯一的 MothersFatherID 以传递给“应用模板”调用。

无论我尝试过什么,都会遇到以下错误: Required item type of first operand of '/' is node(); supplied value has item type xs:anyAtomicTypeAxis step child::element('':MothersFatherID) cannot be used here: the context item is an atomic value。我认为他们的意思是我正在尝试选择使用字符串值的节点,反之亦然....也许我对 distinct-value() 函数的使用完全错误?

谁能解释一下如何做到这一点? (我一直希望这个 xslt 在我不会被这种事情困住的时候会有一些禅意的时刻)。

此外,一旦我这样做了,我将希望 MothersFather 对每个父亲都按排序顺序排列 - 在真正的 xml 中,每个“ID”都有一个“名称” - 希望是每个“排序”声明将类似于参考什么修复上述问题?

感谢您的宝贵时间。 布莱斯。

编辑:

哇!!谢谢你的回答迪米特。我已经检查过了,希望你能帮我把它分解一下,因为我没有完全理解它? 答案是:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kMFByFId" match="MothersFatherID"
          use="../FathersID"/>

 <xsl:key name="kMFById" match="MothersFather" use="ID"/>

 <xsl:key name="ChildByFIdAndMFId" match="Child"
  use="concat(FathersID, '+', MothersFatherID)"/>

 <xsl:template match="Children|MothersFathers|text()"/>

 <xsl:template match="Father">
   Father ID=<xsl:value-of select="ID"/>
  <xsl:apply-templates select=
   "key('kMFById',
         key('kMFByFId', ID)
          [generate-id(..)
          =
           generate-id(key('ChildByFIdAndMFId',
                            concat(../FathersID,'+',.)
                          )[1]
                       )
          ]
        )">
     <xsl:sort select="ID" data-type="number"/>
   </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="MothersFather">
      MothersFather ID=<xsl:value-of select="ID"/>
 </xsl:template>
</xsl:stylesheet>

我可以使用所涉及的密钥。

&lt;xsl:template match="Children|MothersFathers|text()"/&gt; 这条线——这条线是怎么做的?如果我通过调试器单步执行它,它就会直接跳过这条线。如果我将其注释掉,就会有很多我看不到来源的多余输出。

还有为 MothersFather 节点提供 <xsl:apply-templates select= "key('kMFById', key('kMFByFId', ID)[generate-id(..) =
generate-id(key('ChildByFIdAndMFId', concat(../FathersID,'+',.))[1] ) ] )">
的 apply-templates 行——我一直试图在纸上将其分解以查看其神奇之处,但并没有完全理解它。 类似于key('kMFById', key('kMFByFId', ID) 的意思是通过当前的父亲 ID 获取匹配的 MothersFather 节点,其中[generate-id(..) 生成的 id 为 '(dot dot)' - 与父节点有关吗?哪一个?等于基于 ChildByFIdAndMFId 键 [1] 生成的 id - 这 1 是否只获得匹配生成的 id 的第一次出现,从而给出我的不同值?

(Dimitre 的这个答案也与 JLRishie 的答案非常相似。他的排序似乎有效,我是否遗漏了什么 Dimitre?)

问候,布莱斯。

【问题讨论】:

  • user1840734,JLRishe 的解决方案排序是否正确?是的,它现在正确排序。但是两天前当我发布我的答案时它没有正确排序。从那时起,他两次编辑了他的答案并修复了排序错误。至于“Dimitre 的这个答案也与 JLRishie 的答案非常相似”,我想说它们本质上是不同的,因为我的解决方案从一开始就可以正常工作,而 JLRishe 的排序不正确。与其他人不同,我总是测试我的解决方案。另一个区别在于简洁性和可读性——在这两个方面,我的解决方案显然更好。
  • @DimitreNovaatchev 我不确定你为什么一直说你的答案更短。我的是 23 行(最初是 20 行),而你的是 34 行。也许你的可显示字符少了 10 或 15 个,并且你将一些文本移到了左边,但这并不值得吹嘘。你暗示我没有测试我的答案,但我做了 - 我原始帖子中的输出是实际输出,并且与问题请求的输出相匹配。确实,我没有充分考虑能够在单独的字段上进行排序,但这是我不会再犯的疏忽。
  • @JLRishe,为什么,这不是很明显吗?您的转换占用了很长的行,使其不可读-我相信您可以将整个转换放在一行...当我以与我的格式相同的方式格式化您的转换时-为了使其可读,它占用的空间不23 行,但 37 行。而且,正如我们所知,37 > 34。如果我使用你的“格式化风格,我永远不会,我的答案将只是 19 行代码。除非你停止产生不可读的答案,否则它们对读者来说毫无价值,尽管我相信这些答案具有真正的价值——仅仅因为格式。
  • @DimitreNovaatchev 我猜你和我对什么是“不可读”和“无价值”有不同的概念。在普通的代码编辑器中,我的响应中的行是完全可显示的,并且在可读性范围内(恕我直言)。尽管如此,我会继续努力确保我的台词不会太长。
  • @JLRishe,“在普通的代码编辑器中,我的响应中的行完全可显示并且在可读性范围内”是不正确的。我正在使用 XSLT IDE(它的普通编辑器部分),甚至将整个屏幕分配给它,代码的很大一部分需要向右滚动才能显示。但是,向右滚动会使代码的最左侧部分不可见。您的代码向右移动了 147 个位置——这绝不是“完全可显示且在可读性范围内”。我欢迎您决心改进这一点。

标签: xslt xslt-2.0 xslt-grouping


【解决方案1】:

我相信这应该做你想做的事:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />
  <xsl:key name="kFathersChildren" match="Child" 
           use="concat(FathersID, ' - ', MothersFatherID)"/>
  <xsl:key name="kChildByFatherId" match="Child" use="FathersID"/>
  <xsl:key name="kMothersFatherById" match="MothersFather" use="ID" />

  <xsl:template match="text() | Children | MothersFathers" />

  <xsl:template match="Father">
    <xsl:value-of select="concat('&#10;FATHER: ID=', ID)" />

    <xsl:apply-templates 
      select="key('kMothersFatherById', 
                 key('kChildByFatherId', ID)
                   [generate-id() = 
                     generate-id(
                       key('kFathersChildren', 
                          concat(FathersID, ' - ', MothersFatherID)
                           )[1])
                   ]/MothersFatherID)">
      <xsl:sort select="ID" data-type="number" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="MothersFather">
    <xsl:value-of select="concat('&#10;      IN MothersFather template... ID=', ID)"/>
  </xsl:template>
</xsl:stylesheet>

在您的示例输入上运行时,会产生:

FATHER: ID=100
      IN MothersFather template... ID=200
      IN MothersFather template... ID=201
      IN MothersFather template... ID=202
FATHER: ID=101
      IN MothersFather template... ID=201

【讨论】:

  • 感谢 JLRishie,我接受了 Dimitre 的回答,但它们都非常相似。干杯。
【解决方案2】:

这种转换——更短、格式正确、可读性强,无需水平/垂直滚动。此外,与其他答案不同,它正确应用排序

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:key name="kMFByFId" match="MothersFatherID"
          use="../FathersID"/>

 <xsl:key name="kMFById" match="MothersFather" use="ID"/>

 <xsl:key name="ChildByFIdAndMFId" match="Child"
  use="concat(FathersID, '+', MothersFatherID)"/>

 <xsl:template match="Children|MothersFathers|text()"/>

 <xsl:template match="Father">
   Father ID=<xsl:value-of select="ID"/>
  <xsl:apply-templates select=
   "key('kMFById',
         key('kMFByFId', ID)
          [generate-id(..)
          =
           generate-id(key('ChildByFIdAndMFId',
                            concat(../FathersID,'+',.)
                          )[1]
                       )
          ]
        )">
     <xsl:sort select="ID" data-type="number"/>
   </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="MothersFather">
      MothersFather ID=<xsl:value-of select="ID"/>
 </xsl:template>
</xsl:stylesheet>

应用于此 XML 文档(已提供,但为了测试是否正确排序而稍微打乱):

<t>
<Children>
    <Child>
        <ID>2</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>
    </Child>
    <Child>
        <ID>1</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>200</MothersFatherID>
    </Child>
    <Child>
        <ID>3</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>202</MothersFatherID>
    </Child>
    <Child>
        <ID>4</ID>
        <FathersID>100</FathersID>
        <MothersFatherID>201</MothersFatherID>
    </Child>
    <Child>
        <ID>5</ID>
        <FathersID>101</FathersID>
        <MothersFatherID>201</MothersFatherID>
    </Child>
</Children>
<Fathers>
    <Father>
        <ID>100</ID>
    </Father>
    <Father>
        <ID>101</ID>
    </Father>
</Fathers>
<MothersFathers>
    <MothersFather>
        <ID>200</ID>
    </MothersFather>
    <MothersFather>
        <ID>201</ID>
    </MothersFather>
    <MothersFather>
        <ID>202</ID>
    </MothersFather>
</MothersFathers>
</t>

产生想要的正确结果:

   Father ID=100
      MothersFather ID=200
      MothersFather ID=201
      MothersFather ID=202
   Father ID=101
      MothersFather ID=201

请注意

使用 XSLT 1.0 和 XSLT 2.0 处理器都可以正确执行转换。


更新

OP 已编辑问题,询问有关此解决方案的一些问题:

我可以使用所涉及的密钥。

线&lt;xsl:template match="Children|MothersFathers|text()"/&gt; - 这条线是怎么做的?如果我通过调试器单步执行它 只是直接跳过这条线。如果我评论它有很多 我看不到来源的多余输出。

您已经发现了这个空正文模板的作用——它可以防止编写多余的输出。 XSLT 处理器有许多内置模板,在处理给定节点时选择执行这些模板——以防 XSLT 转换未指定与该节点匹配的模板。

任何元素的内置模板都会输出其所有文本节点后代的字符串值的串联 - 这正是您所看到的多余输出。

为了避免这种情况,我提供了一个匹配thode 元素的模板。这会覆盖(抑制)内置模板。由于此模板没有实体,因此不会产生任何输出。

还有给出MothersFather 节点的apply-templates 行 &lt;xsl:apply-templates select= "key('kMFById', key('kMFByFId', ID)[generate-id(..) = generate-id(key('ChildByFIdAndMFId', concat(../FathersID,'+',.))[1] ) ] )"&gt; - 我一直在尝试打破 这在纸上看到了神奇但不太明白。这是 key('kMFById', key('kMFByFId', ID) 之类的意思是获取 通过当前Father ID 匹配MothersFather 节点,其中 [generate-id(..) the generated id of '(dot dot)' - 有事做 有父节点?哪一个?等于基于生成的 id ChildByFIdAndMFId key [1] - 这样做1 只得到第一次出现 匹配生成的 id 从而赋予我独特的价值?

你的问题是关于这个代码片段的:

  <xsl:apply-templates select=
   "key('kMFById',
         key('kMFByFId', ID)
          [generate-id(..)
          =
           generate-id(key('ChildByFIdAndMFId',
                            concat(../FathersID,'+',.)
                          )[1]
                       )
          ]
        )">
     <xsl:sort select="ID" data-type="number"/>
   </xsl:apply-templates>

为了了解这里发生了什么,您需要熟悉 Muenchian Grouping Method

上面的代码片段的本质是

处理所有MothersFather元素,这些元素是FathersID的兄弟元素,该元素与当前(Father)节点的ID具有相同的值。

【讨论】:

  • 谢谢 Dimitre,我已经编辑了上面的问题,试图更好地理解您的答案。抱歉格式错误,我在一个小屏幕上进行编辑,只是假设窗格会在更大的屏幕上扩大 - 我看它没有。以后我会尝试观看它,以便在不滚动的情况下保持代码可见....
  • 我希望您能对上述编辑发表评论,以寻求理解。还有一件事我也没有得到你的答案:密钥&lt;xsl:key name="kMFByFId" match="MothersFatherID" use="../FathersID"/&gt;。什么是../FathersID。我以为.. 指的是父级,但我看不到如何位于找到FathersID 的较低节点,因为找到FathersID 的“子”节点已经是最低节点?
  • @user1840734, ../FathersID 当使用上下文节点MothersFatherID 评估时,选择上下文节点的所有FathersID 兄弟姐妹(我们知道这只是一个元素)。详细拼写: 1) .. 将上下文节点更改为父节点。 2) ../FathersID 表示选择我父母的所有FathersID 孩子——这会导致选择所有FathersID 兄弟姐妹。我本来可以写:preceding-sibling::FathersID,但我不确定FathersID 是否总是在MothersFatherID 之前。
  • @user1840734,我已经更新了答案并回答了你的新问题。
猜你喜欢
  • 1970-01-01
  • 2015-02-10
  • 2018-03-15
  • 1970-01-01
  • 1970-01-01
  • 2014-02-06
  • 1970-01-01
  • 2017-01-13
  • 1970-01-01
相关资源
最近更新 更多