代码之家  ›  专栏  ›  技术社区  ›  jbeard4

如何使用XSLT标记具有唯一、连续、递增整数ID的特定节点?

  •  4
  • jbeard4  · 技术社区  · 14 年前

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

    为了清楚地了解我在说什么,下面是如何使用dom来表示这种转换:

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

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

    这种方法的问题在于position函数只能与上下文节点一起使用,因此类似以下内容是非法的:

    <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来迭代全局变量。对于每个更改的上下文节点,这允许我按照我描述的方式使用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>
    

    我很感激任何人能提供的建议。谢谢。

    2 回复  |  直到 14 年前
        1
  •  3
  •   Dimitre Novatchev    14 年前

    这种转变 :

    <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
  •  0
  •   Tomalak    14 年前

    最简单的方法:

    <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 轴,所以在任何情况下,这都是基准点。