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

发布于 2025-01-11 11:39:16 字数 5185 浏览 0 评论 0原文

我正在处理一组具有 区域的文档,该区域定义了原始数据类型组和其他结构的结构,以及 区域,它定义这些数据类型的实例的值。

原始 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,并且不想引入大量对外部库的依赖。

I am acting on a set of documents that have a <DataTypes> area, which defines the structure of groups of primative datatypes and other structures, and a <Tags> area, which defines the values of instances of these datatypes.

Original 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>

Stylesheet

<?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>

Result

<?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>

My operations on the DataTypes section adds nodes and changes the node order. For the section to work correctly, the tag elements must match the contents and order of their respective datatypes, exactly.

If I keep a variable in memory of the final state of the DataSet nodes, is there a simple way to have the tag nodes look up their dataset (via the Structure and StructureMember @DataSet attributes, and sort their members accordingly?

I'm having trouble figuring out where to start.

NOTE: Transformation must be in XSLT 1.0. I'm using .Net, and don't want to introduce a lot of dependencies on external libraries.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

橙味迷妹 2025-01-18 11:39:16

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

It's a bit tricky in XSLT 1.0 (isn't everything?) but a technique that sometimes works is to construct a variable $tokens containing the list of tokens in the required order, for example "|Description|Name|ProcessEntityIndex|Severity|...", and then sort on select="string-length(substring-before($tokens, concat('|',@Name)))".

如梦 2025-01-18 11:39:16

使用 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>

Using Michael Kay's suggesting for sorting, I ended up with the following:

Stylesheet

        <?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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文