xslt:通过中间参考节点选择唯一节点?

时间:2013-01-25 03:45:37

标签: xslt xslt-2.0 xslt-grouping

XSLT 2。

嗨,我有一个有3个节点的xml,从“孩子们”的角度命名:Children,Fathers and MothersFathers。从Fathers节点开始,我需要根据子节点中的ID找到子节点MothersFather节点(子节点是连接其他两个节点的中间节点。)

所以,每个父亲都会得到他孩子的独特 MothersFather--这些不是人类,父亲可能有数百个孩子,但只有20个左右的相关MothersFathers:)

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

<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中,我选择'distinct-value',我希望输出如下:

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来传递给'apply-templates'。

无论我尝试过什么,我都会遇到以下错误: 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'都有一个'Name' - 希望for-each'sort'声明将类似地引用上面的问题修复?

感谢您的时间。 布莱斯。

修改

哇!!谢谢你的回答Dimitre。我已经过去了,并希望你能够为我分解一下,因为我没有完全理解它? 答案是:

<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>

我使用了所涉及的密钥。

<xsl:template match="Children|MothersFathers|text()"/>行 - 这条线是怎么做的?如果我通过调试器,它只是跳过这一行。如果我评论它有很多多余的输出,我看不到它的来源。

给予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为'(点点)' - 与父节点有关?哪一个?等于基于ChildByFIdAndMFId键生成的id [1] - 这1只获得匹配生成的id的第一次出现,从而给出我的不同值吗?

(Dimitre的回答也与JLRishie的回答非常相似。他的那种似乎有效,我在Dimitre错过了什么吗?)

问候,布莱斯。

2 个答案:

答案 0 :(得分:2)

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

<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

答案 1 :(得分:1)

此转换 - 更短,格式良好且可读,无需水平/垂直滚动。此外,与其他答案不同,它正确应用排序

<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编辑了这个问题,询问了有关此解决方案的一些问题:

我使用了所涉及的密钥。

  

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

你已经发现了这个带有空体的模板正在做什么 - 它可以防止写入多余的输出。 XSLT处理器具有许多内置模板,在处理给定节点时选择执行这些模板 - 以防XSLT转换未指定与此节点匹配的模板。

任何元素的内置模板都会输出所有text-node-descendants的字符串值的串联 - 这正是你所看到的多余输出。

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

  

提供MothersFather节点的apply-templates行   <xsl:apply-templates select= "key('kMFById', key('kMFByFId',   ID)[generate-id(..) = generate-id(key('ChildByFIdAndMFId',   concat(../FathersID,'+',.))[1] ) ] )"> - 我一直试图打破   这是纸上看到的神奇,但并没有得到它。它是   像key('kMFById', key('kMFByFId', ID)之类的东西意味着获得   按当前MothersFather匹配Father ID个节点   [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的兄弟,与当前ID的{​​{1}}具有相同的值Father节点