【问题标题】:How to use XSLT to tag specific nodes with unique, sequential, increasing integer ids?如何使用 XSLT 用唯一的、连续的、递增的整数 id 标记特定节点?
【发布时间】:2010-06-08 07:08:04
【问题描述】:

我正在尝试使用 XSLT 通过使用整数 id 标记一组 XML 节点来转换文档,从 0 开始,并为组中的每个节点增加一个。传递到样式表的 XML 应该被回显出来,但要包含这些额外的信息。

为了清楚我在说什么,这里是如何使用 DOM 来表达这种转换:

states = document.getElementsByTagName("state");
for( i = 0; i < states.length; i++){
    states.stateNum = i;
}

这对于 DOM 来说非常简单,但我在使用 XSLT 时遇到了更多麻烦。我设计的当前策略是从身份转换开始,然后创建一个全局变量来选择并存储我希望编号的所有节点。然后我创建一个与那种节点匹配的模板。那么,这个想法是,在模板中,我会在全局变量节点列表中查找匹配节点的位置,这会给我一个唯一的数字,然后我可以将其设置为属性。

这种方法的问题是位置函数只能与上下文节点一起使用,所以像下面这样的东西是非法的:

<template match="state">
    <variable name="stateId" select="@id"/>
    <variable name="uniqueStateNum" select="$globalVariable[@id = $stateId]/position()"/>
</template>

以下情况也是如此:

<template match="state">
    <variable name="stateId" select="@id"
    <variable name="stateNum" select="position($globalVariable[@id = $stateId])/"/>
</template>

为了使用 position() 在 $globalVariable 中查找元素的位置,必须更改上下文节点。

我找到了一个解决方案,但它非常不理想。基本上,在模板中,我使用 for-each 来遍历全局变量。 For-each 更改上下文节点,因此这允许我以我描述的方式使用 position()。问题是这会将通常的 O(n) 操作转换为 O(n^2) 操作,其中 n 是节点列表的长度,因为这需要在模板匹配时遍历整个列表。我认为必须有一个更优雅的解决方案。

总而言之,这是我当前的(稍微简化的)xslt 样式表:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:s="http://www.w3.org/2005/07/scxml"
    xmlns="http://www.w3.org/2005/07/scxml"
    xmlns:c="http://msdl.cs.mcgill.ca/"
    version="1.0">
    <xsl:output method="xml"/>

    <!-- we copy them, so that we can use their positions as identifiers -->
    <xsl:variable name="states" select="//s:state" />


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

    <xsl:template match="s:state">

        <xsl:variable name="stateId">
            <xsl:value-of select="@id"/>
        </xsl:variable>

        <xsl:copy>
            <xsl:apply-templates select="@*"/>

            <xsl:for-each select="$states">
                <xsl:if test="@id = $stateId">
                    <xsl:attribute name="stateNum" namespace="http://msdl.cs.mcgill.ca/">
                        <xsl:value-of select="position()"/>
                    </xsl:attribute>
                </xsl:if>
            </xsl:for-each>

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

</xsl:stylesheet>

如果有人能提供任何建议,我将不胜感激。谢谢。

【问题讨论】:

  • 好问题 (+1)。请参阅我的答案以获得非常简单但正确的解决方案。 :)

标签: xslt


【解决方案1】:

这种转变

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:s="http://www.w3.org/2005/07/scxml"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

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

 <xsl:template match="s:state">
  <xsl:variable name="vNum">
   <xsl:number level="any" count="s:state"/>
  </xsl:variable>

  <xsl:copy>
   <xsl:copy-of select="@*"/>

   <xsl:attribute name="stateId">
    <xsl:value-of select="@id"/>
   </xsl:attribute>

   <xsl:attribute name="id">
     <xsl:value-of select="$vNum -1"/>
   </xsl:attribute>

   <xsl:apply-templates/>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

应用于提供的 XML 文档时

<scxml xmlns="http://www.w3.org/2005/07/scxml">
    <state id="Compound1">
        <state id="Basic1"/>
        <state id="Basic2"/>
        <state id="Basic3"/>
    </state>
</scxml>

产生想要的、正确的输出

<scxml xmlns="http://www.w3.org/2005/07/scxml">
    <state stateId="Compound1" id="0">
        <state stateId="Basic1" id="1"/>
        <state stateId="Basic2" id="2"/>
        <state stateId="Basic3" id="3"/>
    </state>
</scxml>

【讨论】:

    【解决方案2】:

    最简单的方法:

    <xsl:template match="s:state">
      <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:attribute name="stateNum" namespace="http://msdl.cs.mcgill.ca/">
          <xsl:value-of select="count(preceding::s:state)" />
        </xsl:attribute>
        <xsl:apply-templates select="node()"/>
      </xsl:copy>
    </xsl:template>
    

    不确定您的 XSLT 处理器如何处理 preceding 轴,因此无论如何都要进行基准测试。

    【讨论】:

    • Tomalak,根据我对前面轴的理解,这似乎是一种合理的方法。然而不幸的是,它似乎不起作用。请参阅以下 XML 文档: w3.org/2005/07/scxml"> 这将创建以下状态编号:stateId: Compound1, stateNum: 0; stateId:Basic1,stateNum:0; stateId:Basic2,stateNum:1; stateId:Basic3,stateNum:2 使用 Xalan、4xslt 和 xsltproc 检查。有什么想法吗?
    • 嗯。我明白你的意思了。尝试添加count(ancestor::s:state)count(preceding::s:state),但这也可能是错误的。现在没有机会测试,我现在在我的手机上。 ;-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多