首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >XslCompiledTransform忽略XPathNodeIterator的排序

XslCompiledTransform忽略XPathNodeIterator的排序
EN

Stack Overflow用户
提问于 2015-03-20 20:32:05
回答 2查看 178关注 0票数 1

我有一个XSLT样式表,它使用文档并输出SOAP消息,其中主体是WCF数据契约定义的特定格式(此处未指定)。问题是,WCF对什么是字母顺序有一个特殊的概念,并认为以下顺序是正确的:

  • 交流
  • Ab

这是因为它在内部使用序号字符串比较。细节并不有趣,只需说明XSLT <sort>本机不支持这种顺序,但是为了将格式可能不同的输入文档转换为可接受的SOAP消息,样式表必须能够根据这种特殊的顺序对输出元素进行排序。因此,我决定在脚本块中实现节点排序。这是C#解决方案的一部分,并且使用XslCompiledTransform,因此msxsl:script是可用的。

给定样式表:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fn="urn:functions"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                exclude-result-prefixes="msxsl exsl"
                xmlns:exsl="http://exslt.org/common"
>
  <msxsl:script implements-prefix="fn" language="C#">
    <![CDATA[

      public class OrdinalComparer : IComparer
      {
          public int Compare(object x, object y)
          {
              return string.CompareOrdinal((string)x, (string)y);
          }
      }

      public XPathNodeIterator OrdinalSort(XPathNavigator source)
      {
        var query = source.Compile("/*");
        query.AddSort(source.Compile("local-name()"), new OrdinalComparer());
        return source.Select(query);
      }    
    ]]>
  </msxsl:script>

  <xsl:template match="Stuff">
    <xsl:element name="Body">
      <xsl:element name="Request">
        <xsl:variable name="sort">
          <xsl:apply-templates select="*"/>
        </xsl:variable>
        <xsl:for-each select="fn:OrdinalSort($sort)">
          <xsl:copy-of select="."/>
        </xsl:for-each>
      </xsl:element>
    </xsl:element>
  </xsl:template>

  <xsl:output method="xml" indent="yes"/>

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

</xsl:stylesheet>

和一份投入文件:

代码语言:javascript
复制
<?xml version='1.0' encoding='utf-8'?>
<Root>
  <Stuff>
    <Age></Age>
    <AIS></AIS>
    <Something></Something>
    <BMI></BMI>
  </Stuff>
</Root>

我期望输出将最内部的元素排序如下:

  • 认可机构
  • 年龄
  • 体质量指数
  • 某物

这是不可能的。相反,元素是按照它们进入的顺序发出的。调试到样式表执行时,我可以看到调用了OrdinalSort函数,它返回的迭代器按所需的顺序枚举元素,但是XSLT处理器不知怎么地忽略了这一点,并按遇到的顺序释放元素。

此外,我还验证了在控制台应用程序中解析文档和运行相同的迭代器查询会以正确的顺序发出元素。

为什么,我能做什么?我目前唯一的预感是XSLT引擎将迭代器的父导航器(与传递给排序函数的内容没有改变)解释为要再现的元素,而忽略了迭代器的内容。

EN

回答 2

Stack Overflow用户

发布于 2015-03-21 11:24:14

我不知道如何用XPathNodeIteratorXPathNavigator来解决这个问题,我甚至从XPathNodeIterator创建了一个XPathNavigator[],以避免任何懒惰的评估效果,但不知怎么的,我总是得到与您相同的结果。

因此,作为另一种选择,我编写了一些代码,使用.NET框架中的DOM实现来按照正确的排序顺序创建一些新节点:

代码语言:javascript
复制
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:mf="urn:functions"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                exclude-result-prefixes="msxsl exsl mf"
                xmlns:exsl="http://exslt.org/common"
>
  <msxsl:script implements-prefix="mf" language="C#">
    <![CDATA[

      public class OrdinalComparer : IComparer
      {
          public int Compare(object x, object y)
          {
              return string.CompareOrdinal((string)x, (string)y);
          }
      }

      public XPathNavigator OrdinalSort(XPathNavigator source)
      {
        var query = source.Compile("/root/*");
        query.AddSort("local-name()", new OrdinalComparer());
        XPathNodeIterator result = source.Select(query);
        XmlDocument resultDoc = new XmlDocument();
        XmlDocumentFragment frag = resultDoc.CreateDocumentFragment();
        foreach (XPathNavigator item in result)
        {
          frag.AppendChild(resultDoc.ReadNode(item.ReadSubtree()));
        }
        return frag.CreateNavigator();
      }    
    ]]>
  </msxsl:script>

  <xsl:output method="xml" indent="yes"/>

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

  <xsl:template match="Stuff">
    <Body>
      <Request>
        <xsl:variable name="sort-rtf">
          <root>
            <xsl:copy-of select="*"/>
          </root>
        </xsl:variable>
        <xsl:variable name="sort" select="exsl:node-set($sort-rtf)"/>
        <xsl:variable name="sorted" select="mf:OrdinalSort($sort)"/>
        <xsl:copy-of select="$sorted"/>
      </Request>
    </Body>
  </xsl:template>

</xsl:stylesheet>

使用这种方法,结果是

代码语言:javascript
复制
<Root>
  <Body><Request><AIS /><Age /><BMI /><Something /></Request></Body>
</Root>

我稍微简化了代码

代码语言:javascript
复制
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:mf="urn:functions"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                exclude-result-prefixes="msxsl exsl mf"
                xmlns:exsl="http://exslt.org/common"
>
  <msxsl:script implements-prefix="mf" language="C#">
    <![CDATA[

      public class OrdinalComparer : IComparer
      {
          public int Compare(object x, object y)
          {
              return string.CompareOrdinal((string)x, (string)y);
          }
      }

      public XPathNavigator OrdinalSort(XPathNavigator source)
      {
        var query = source.Compile("*");
        query.AddSort("local-name()", new OrdinalComparer());
        XPathNodeIterator result = source.Select(query);
        XmlDocument resultDoc = new XmlDocument();
        XmlDocumentFragment frag = resultDoc.CreateDocumentFragment();
        foreach (XPathNavigator item in result)
        {
          frag.AppendChild(resultDoc.ReadNode(item.ReadSubtree()));
        }
        return frag.CreateNavigator();
      }    
    ]]>
  </msxsl:script>

  <xsl:output method="xml" indent="yes"/>

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

  <xsl:template match="Stuff">
    <Body>
      <Request>
        <xsl:variable name="sorted" select="mf:OrdinalSort(.)"/>
        <xsl:copy-of select="$sorted"/>
      </Request>
    </Body>
  </xsl:template>

</xsl:stylesheet>

必须在扩展函数XmlNode中构造C#“脚本”似乎是一种开销,但我不知道如何解决它。

票数 1
EN

Stack Overflow用户

发布于 2015-03-21 12:52:36

对于最初的问题,我设计了一个可怕的解决方法--让XSLT支持字符序号排序。我认为这个是一个答案,但肯定不是一个好答案。下面的代码片段说明了此解决方案:

代码语言:javascript
复制
<xsl:template match="Stuff">
    <xsl:element name="Body">
      <xsl:element name="Request">

        <xsl:variable name="source">
          <xsl:apply-templates select="*"/>
        </xsl:variable>

        <xsl:for-each select="exsl:node-set($source)/*">
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 1, 1))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 2, 2))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 3, 3))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 4, 4))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 5, 5))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 6, 6))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 7, 7))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 8, 8))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 9, 9))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 10, 10))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 11, 11))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 12, 12))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 13, 13))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 14, 14))"/>
          <xsl:sort data-type="number" select="fn:GetOrdinal(substring(local-name(.), 15, 15))"/>
          <xsl:copy-of select="."/>
        </xsl:for-each>
      </xsl:element>
    </xsl:element>
  </xsl:template>

其中扩展函数GetOrdinal如下所示:

代码语言:javascript
复制
    public int GetOrdinal(string s)
    {
        return s.Length == 1 ? (char)s[0] : 0;
    }

坦率地说,这是可耻的,我不主张做任何这样劣质的事情。但很管用。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29175349

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档