使用 XSLT,是否有办法使节点集的排序顺序与第二个节点集的顺序相匹配?

Using XSLT, is there a way to make the sort order of a nodeset match the order of a second nodeset?

我正在处理一组文档,这些文档有一个 <DataTypes> 区域,它定义了原始数据类型和其他结构组的结构,还有一个 <Tags> 区域,它定义了值这些数据类型的实例。

原版XML

<?xml version="1.0" encoding="utf-8" ?>

<Program>
  <DataTypes>
    <DataType Name="String20">
      <Member Name="LEN" DataType="INTEGER" Dimension="0" />
      <Member Name="DATA" DataType="BYTE" Dimension="20" />
    </DataType>
    <DataType Name="UDT_Params">
      <Member Name="InAlarm" DataType="BIT" Dimension="0" />
      <Member Name="SetPoint" DataType="FLOAT" Dimension="0" />
      <Member Name="DwellTime" DataType="INTEGER" Dimension="0" />
      <Member Name="UserName" DataType="String20" Dimension="0" />
    </DataType>
  </DataTypes>
  <Tags>
    <Tag Name="MyParameters" DataType="UDT_Params">
      <Data Name="InAlarm" DataType="BIT" Value="0" />
      <Data Name="SetPoint" DataType="FLOAT" Value="4.5" />
      <Data Name="DwellTime" DataType="INTEGER" Value="10" />
      <Data Name="UserName" DataType="String20">
        <Data Name="LEN" DataType="INTEGER" Value="3" />
        <Data Name="DATA" DataType="String20" >         <!--The system I'm working in shows strings as arrays of BYTES in DataType, -->
          Bob                                           <!--but calls them out as Strings when they are used as tags.  I cannot change it.-->
        </Data>
      </Data>
    </Tag>
  </Tags>
</Program>

样式表

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>

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

<!--Packing algorithm.  Works fine on datatypes, but not on Tags.-->
<xsl:template name="pack-nodes">
    <xsl:param name="nodes" />
    <!--Omitted for brevity-->
</xsl:template>

  <!--Pack DataTypes-->
  <xsl:variable name="datatypes-packed">
    <xsl:call-template name="pack-nodes">
      <xsl:with-param name="nodes" select="/Program/DataTypes/DataType" />
    </xsl:call-template>
  </xsl:variable>

  <!--Write DataTypes to output.-->
  <xsl:template match="/Program/DataTypes">
    <xsl:copy>
      <xsl:for-each select="msxsl:node-set($datatypes-packed)">
        <xsl:copy-of select="."/>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
  
  <!--Pack tags-->
  <xsl:variable name="tags-packed">
    <xsl:call-template name="pack-nodes">
      <xsl:with-param name="nodes" select="/Program/Tags/Tag" />
    </xsl:call-template>
  </xsl:variable>
  
  <!--Write Tags to output.-->
  <xsl:template match="/Program/Tags">
    <xsl:copy>
      <xsl:for-each select="msxsl:node-set($tags-packed)">
        <xsl:copy-of select="."/>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

结果

<?xml version="1.0" encoding="utf-8"?>
<Program>
    <DataTypes>
        <DataType Name="String20">
            <Member Name="LEN" DataType="INTEGER" Dimension="0"/>
            <Member Name="DATA" DataType="BYTE" Dimension="20"/>
        </DataType>
        <DataType Name="Parameters" DataType="UDT_Params">
            <Member Name="UserName" DataType="String20" Dimension="0"/>
            <Member Name="SetPoint" DataType="FLOAT" Dimension="0"/>
            <Member Name="DwellTime" DataType="INTEGER" Dimension="0"/>
            <Member Name="InAlarm" DataType="BIT" Dimension="0"/>
        </DataType>
    </DataTypes>
    <Tags>
        <Tag Name="MyParameters" DataType="UDT_Params">
            <Data Name="UserName" DataType="String20">
                <Data Name="DATA" DataType="String20">      <!--Note that DATA comes before LEN -->
                    Bob                                     
                </Data>
                <Data Name="LEN" DataType="INTEGER" Value="3"/>
            </Data>
            <Data Name="SetPoint" DataType="FLOAT" Value="4.5"/>
            <Data Name="DwellTime" DataType="INTEGER" Value="10"/>
            <Data Name="InAlarm" DataType="BIT" Value="0"/>
        </Tag>
    </Tags>
</Program>

我对 DataTypes 部分的操作添加了节点并更改了节点顺序。要使该部分正常工作,标记元素必须与其各自数据类型的内容和顺序完全匹配。

如果我在 DataSet 节点的最终状态的内存中保留一个变量,是否有一种简单的方法让标签节点查找它们的数据集(通过 Structure 和 StructureMember @DataSet 属性,并相应地对它们的成员进行排序?

我不知道从哪里开始。

注意:转换必须在 XSLT 1.0 中。我正在使用 .Net,不想引入很多对外部库的依赖。

它在 XSLT 1.0 中有点棘手(不是一切吗?)但有时可行的技术是构造一个变量 $tokens,其中包含按要求顺序排列的标记列表,例如“|Description |Name|ProcessEntityIndex|Severity|...”,然后按 select="string-length(substring-before($tokens, concat('|',@Name)))".

排序

根据 Michael Kay 的排序建议,我得到了以下结果:

样式表

        <?xml version="1.0" encoding="utf-8"?>
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
        >
            <xsl:output method="xml" indent="yes"/>
        
    <!--Skipped to new part -->
        
          <xsl:template name="sort-by-datatype">
            <xsl:param name="tags" />
            <xsl:param name="datatypes" />
            <xsl:for-each select="msxsl:node-set($tags)">
              <!--First, do an edge-check.-->
              <xsl:variable name="edge">
                <xsl:call-template name="edge-check">
                  <xsl:with-param name="n1" select="." />
                </xsl:call-template>
              </xsl:variable>
              <xsl:choose>
                <!--No children, nothing to sort.  Just do a deep copy.-->
                <xsl:when test="$edge = 'true'">
                  <xsl:copy-of select="."/>
                </xsl:when>
                <xsl:otherwise>
                  <!--Search for datatype in the DataTypes nodeset, and use it to create a list of members in order.-->
                  <xsl:variable name="tag-datatype" select="./@DataType" />
                  <xsl:variable name="tokens-untrimmed">
                    <xsl:for-each select="msxsl:node-set($datatypes)/DataType[@Name = $tag-datatype]/Member">
                      <xsl:value-of select="concat(' | ', @Name)"/>
                    </xsl:for-each>
                  </xsl:variable>
                  <xsl:variable name="tokens" select="substring-after($tokens-untrimmed, '|')" />
                  <xsl:choose>
                    <!--If tokens string is empty (maybe because we couldn't find the datatype?), just copy the tag as it is, then recurse.-->
                    <xsl:when test="string-length($tokens) = 0">
                      <xsl:copy>
                        <xsl:copy-of select="@*"/>
                        <xsl:call-template name="sort-by-datatype">
                          <xsl:with-param name="tags" select="." />
                          <xsl:with-param name="datatypes" select="$datatypes" />
                        </xsl:call-template>
                      </xsl:copy>
                    </xsl:when>
                    <!--Otherwise, sort members in the same order as datatype-->
                    <xsl:otherwise>
                      <!--Build variable with sorted members.-->
                      <xsl:variable name="tag-members-sorted">
                        <xsl:for-each select="*">
                          <xsl:sort data-type="number" order="ascending" select="string-length(substring-before($tokens, concat(' | ', @Name)))" />   <!--Magic Sort Algorithm-->-->
                          <xsl:copy-of select="."/>
                        </xsl:for-each>
                      </xsl:variable>
                      <!--Copy the parent node node.-->
                      <xsl:copy>
                        <xsl:copy-of select="@*"/>
                        <!--Now sort and copy the children.-->
                        <xsl:for-each select="msxsl:node-set($tag-members-sorted)/*">
                          <!--Recurse.  This copies the child node.-->
                          <xsl:call-template name="sort-by-datatype">
                            <xsl:with-param name="tags" select="." />
                            <xsl:with-param name="datatypes" select="$datatypes" />
                          </xsl:call-template>
                        </xsl:for-each>
                      </xsl:copy>
                    </xsl:otherwise>
                  </xsl:choose>
                </xsl:otherwise>
              </xsl:choose>
            </xsl:for-each>
          </xsl:template>
          
          <!--Pack tags-->
          <xsl:variable name="tags-packed">
            <xsl:call-template name="sort-by-datatype">
              <xsl:with-param name="tags" select="/Program/Tags/Tag" />
              <xsl:with-param name="datatypes" select="$datatypes-packed" />
            </xsl:call-template>
          </xsl:variable>
          
<!--Skipped for brevity-->

        </xsl:stylesheet>