避免使用XSLT / XPath重复的属性名称

时间:2016-11-24 20:25:24

标签: xml xslt recursion xpath duplicates

说我有这样的XML:

<parole>
   <parola id="a">1</parola>
   <parola id="b">2</parola>
   <parola id="c">3</parola>
   <parola id="a">4</parola>
   <parola id="a">5</parola>
   <parola id="b">6</parola>
</parole>

现在,我知道generate-id()函数存在。但是,出于学习目的,我想知道如何使用XSLT更改名为&#34; id&#34;的属性值。

我已经考虑过&#34;算法&#34;喜欢:

  

考虑属性的以下和前一个兄弟   如果您遇到当前属性的副本,请添加&#34; f&#34;到这个名字的末尾和(递归地)添加另一个&#34; f&#34;直到我们没有具有相同值的属性。

所以我的最终XML就像:

<parole>
  <parola id="a">1</parola>
  <parola id="b">2</parola>
  <parola id="c">3</parola>
  <parola id="af">4</parola>
  <parola id="aff">5</parola>
  <parola id="bf">6</parola>
</parole>

现在,我尝试使用递归函数获得此结果,如:

<xsl:variable name="following-siblings-ids" select="/parole/parola/following-sibling::parola/@id"/>
<xsl:variable name="preceding-siblings-ids" select="/parole/parola/preceding-sibling::parola/@id"/>

<xsl:function name="du:check" as="xs:string">
    <xsl:param name="id" /> 
    <xsl:choose>
        <xsl:when test="$id  = $following-siblings-ids and $preceding-siblings-ids">
            <xsl:value-of select="du:check(concat($id, 'f'))"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$id"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

<xsl:template match="parola">
  <xsl:value-of select="du:check(@id)"/>
</xsl:template match="parola">

但我得到了像

这样的结果
  

&#34; AF&#34; &#34; BF&#34; &#34; CF&#34; &#34; AF&#34; &#34; AF&#34; &#34; BF&#34;

而不是所需的那个。

任何提示?在使用动态变量和for-each的非声明性语言中,这将是一项简单的任务,但我不知道如何在XSLT中实现它...

2 个答案:

答案 0 :(得分:1)

这是一个XSLT 1解决方案。以下转型

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

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

  <xsl:template name="make-f">
    <xsl:param name="n"/>
    <xsl:if test="$n > 0">f<xsl:call-template name="make-f">
    <xsl:with-param name="n" select="$n - 1"/></xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template match="parola/@id">
    <xsl:variable name="id" select="."/>
    <xsl:variable name="f">
      <xsl:call-template name="make-f">
    <xsl:with-param name="n" select="count(../preceding-sibling::parola/@id[.=$id])"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:attribute name="id">
      <xsl:value-of select="concat(., $f)"/>
    </xsl:attribute>
  </xsl:template>

</xsl:stylesheet>

应用于输入样本时,会产生以下输出:

$ xsltproc test.xsl test.xml
<?xml version="1.0"?>
<parole>
  <parola id="a">1</parola>
  <parola id="b">2</parola>
  <parola id="c">3</parola>
  <parola id="af">4</parola>
  <parola id="aff">5</parola>
  <parola id="bf">6</parola>
</parole>

答案 1 :(得分:0)

我会使用一个键通过id属性“分组”元素,然后识别该组中的位置以附加索引,或者如果您想要f个字母的数量:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs mf"
    version="2.0">

    <xsl:param name="n" select="'f'"/>

    <xsl:function name="mf:node-index-of" as="xs:integer">
        <xsl:param name="node" as="node()"/>
        <xsl:param name="sequence" as="node()*"/>
        <xsl:sequence select="for $pos in 1 to count($sequence) return $pos[$node is $sequence[$pos]]"/>
    </xsl:function>

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

    <xsl:key name="p-by-id" match="parola" use="@id"/>

    <xsl:template match="parola[not(. is key('p-by-id', @id)[1])]/@id">
        <xsl:attribute name="id" select="concat(., string-join(for $i in 1 to mf:node-index-of(.., key('p-by-id', .)) - 1 return $n, ''))"/>
    </xsl:template>

</xsl:stylesheet>

带有match="@* | node()"的模板是所谓的身份转换模板,它可以逐层复制所有未更改的内容,允许我们为要转换的节点添加更多模板。

您要转换的唯一节点是id元素的parola属性,其中存在多个具有特定ID值的元素,因此我为此编写了一个模板match="parola[not(. is key('p-by-id', @id)[1])]/@id",使用键<xsl:key name="p-by-id" match="parola" use="@id"/>来标识具有相同值的所有元素。

然后通过将现有值与包含concat(., string-join(for $i in 1 to mf:node-index-of(.., key('p-by-id', .)) - 1 return $n, ''))的序列连接起来,计算新属性值$n,该序列比其父parola的位置索引的次数少一个组。

如果您不熟悉XSLT和XPath 2.0,请花些时间阅读有关身份转换,密钥和用户定义函数的教程或书籍。