1

XSLT 2。

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

因此,对于每个父亲来说,他的孩子都有不同的MothersFather - 这些不是人类,一个父亲可能有数百个孩子,但只有大约 20 个相关的 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>

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

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

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

(Dimitre 的这个答案也与 JLRishie 的答案非常相似。他的那种似乎很有效,我在 Dimitre 那里遗漏了什么吗?)

问候,布莱斯。

4

2 回答 2

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
于 2013-01-25T04:54:03.323 回答
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 转换未指定与该节点匹配的模板。

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

为避免这种情况,我提供了一个匹配 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)获取匹配节点- 与父节点有关吗?哪一个?等于基于生成的 id - 这是否只获得匹配生成的 id 的第一次出现,从而给出我的不同值?MothersFatherFather ID[generate-id(..) the generated id of '(dot dot)'ChildByFIdAndMFId key [1]1

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

  <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第一个这样的元素的所有元素,这些元素是 a 的同级元素,并且具有与当前 ( ) 节点FathersID的 相同的值。IDFather

于 2013-01-25T05:17:33.043 回答