使用XSL Key基于属性的通配符查找唯一元素

时间:2013-01-27 04:35:24

标签: xml xslt indexing key

XSLT 2是首选,我希望这更容易。

给出类似于

的文件
<doc xmlns:bob="bob.com">
    <element bob:name="fred" bob:occupation="Dr">Stuff</element>
    <element bob:name="Bill" bob:occupation="Dr" bob:birthMonth="Jan"/>
    <element>Kill Me</element>
    <element bob:name="fred" bob:occupation="Dr">different Stuff</element>
</doc>

我想拥有基于bob命名空间中所有属性的所有唯一元素。这是一个有代表性的样本,但我会有更深的嵌套,所以我希望它遍历树

*[@bob:*] and get the unique set of those.

希望输出看起来像

<doc xmlns:bob="bob.com">
    <element bob:name="fred" bob:occupation="Dr">Stuff</element>
    <element bob:name="Bill" bob:occupation="Dr" bob:birthMonth="Jan"/>
</doc>

其中一个元素被移除,因为没有任何@bob:*属性而另一个元素被删除,因为它们只是基于属性而复制第一个元素。

我试图使用密钥,但似乎没有做对

<xsl:key name="BobAttributes" match="//*" use="./@bob:*" />

我还尝试创建一个连接所有@bob属性的函数,但这似乎也不符合我的期望。

<xsl:key name="BobAttributes" match="//*" use="functx:AllBobConcat(.)" />

 <xsl:function name="functx:AllBobConcat" as="xs:string*" 
    xmlns:functx="http://www.functx.com" >
    <xsl:param name="nodes" as="node()*"/> 

    <xsl:for-each select="$nodes/@bob:*">
        <xsl:value-of select="local-name(.)"/>
        <xsl:value-of select="."/>
    </xsl:for-each>
</xsl:function>

在这两种情况下,我都使用“简单”的XSL过滤出独特的XSL,也许我在这里吹了它? 这里添加了变量以尝试和调试。

  <xsl:template match="*[@ism:*]" priority="100">
        <xsl:variable name="concat">
            <xsl:value-of select="functx:AllBobConcat(.)"/>
        </xsl:variable>

        <xsl:variable name="myID">
            <xsl:value-of select="generate-id() "/>
        </xsl:variable>

        <xsl:variable name="Keylookup">
            <xsl:value-of select="key('BobAttributes', $concat)"/>
        </xsl:variable>
        <xsl:value-of select="concat($concat, $Keylookup, $myID)"/>
        <xsl:if test="generate-id() = generate-id(key('BobAttributes', $concat)[1])">

            <xsl:apply-templates select="." mode="copy"/>
        </xsl:if>
    </xsl:template>

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

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

期待听到我应该忽略的一件简单的事情,或者我应该采取的完全不同的方法。

2 个答案:

答案 0 :(得分:2)

我会像你一样定义函数AllBobConcat,然后将其用作分组键:

<xsl:for-each-group select="element" group-by="f:AllBobConcat(.)">
  <xsl:if test="current-grouping-key() != ''">
    <xsl:copy-of select="current-group()[1]"/>
  </xsl:if>
</xsl:for-each-group>

除了AllBobConcat需要确保属性是规范顺序之外,所以:

<xsl:function name="f:AllBobConcat" as="xs:string">
    <xsl:param name="node" as="element(element)"/> 
    <xsl:value-of>
      <xsl:for-each select="$node/@bob:*">
        <xsl:sort select="local-name()"/>
        <xsl:value-of select="local-name(.)"/>
        <xsl:value-of select="'='"/>
        <xsl:value-of select="."/>
        <xsl:value-of select="' '"/>
      </xsl:for-each>
    </xsl:value-of>
</xsl:function>

此外,您不应该将您的函数放在属于其他人的命名空间中。

答案 1 :(得分:1)

几乎可以肯定有更好的方法,但是FWIW,这是一个蛮力,令人担忧的程序性,非常低效的解决方案:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    version="1.0"
    xmlns:bob="bob.com"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    >

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

  <xsl:template match="element">
    <xsl:variable name="this" select="."/>
    <xsl:variable name="match">
      <xsl:for-each select="preceding::element">
        <xsl:variable name="diff">
          <xsl:variable name="that" select="."/>
          <xsl:for-each select="$this/@bob:*">
            <xsl:variable name="att-name" select="name()"/>
            <xsl:variable name="att-val" select="."/>
            <xsl:for-each select="$that/@bob:*[name()=$att-name]">
              <xsl:if test=". != $att-val">
                DIFF
              </xsl:if>
            </xsl:for-each>
          </xsl:for-each>
        </xsl:variable>
        <xsl:if test="$diff = ''">MATCH</xsl:if>
      </xsl:for-each>
    </xsl:variable>

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

</xsl:stylesheet>

对于每个元素,遍历所有前面的元素。对于每个属性,检查值是否相等,如果不是则引发DIFF标志。任何没有DIFF标志的前面元素都会引发一个MATCH标志。只有在没有引发MATCH标志的情况下才传递一个元素。

感觉就像我用汇编语言编程一样。现在我们坐下来等待迈克尔使用deep-equal或其他人给我们他的单线。