XSL 排序问题

发布于 2024-10-01 15:23:22 字数 2446 浏览 0 评论 0原文

我在尝试使用 CLR4.0 中的 XslCompiledTransform 对 XSL 文件进行排序时遇到问题。这是我的示例 XML 文件(注意:第二个 元素后面有一个空格):

<?xml version="1.0" encoding="utf-8"?>
<reflection> 
  <apis>
    <api id="A">
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <foos>
        <foo/> 
      </foos>
    </api>     
  </apis>
</reflection>

当我应用以下 XSL 文件时:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">
  <xsl:template match="/">
    <html>
      <body>
        <table>
          <xsl:apply-templates select="/reflection/apis/api">
                        <xsl:sort select="@id" />
                    </xsl:apply-templates>          
        </table>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="api">
    <tr>
      <td>
        <xsl:value-of select="@id" />
      </td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

我得到以下结果:

<html>
  <body>
    <table>
      <tr>
        <td>B</td>
      </tr>
      <tr>
        <td>A</td>
      </tr>
    </table>
  </body>
</html>

但是,如果我删除如果第二个 元素后面有空格,则结果文件排序正确。这看起来可能是 XslCompiledTransform 中的一个错误,但我希望有人可能有解决方法。

编辑:如果有人在复制时遇到问题,这是我正在使用的代码:

XslCompiledTransform xslt = new XslCompiledTransform();
XsltSettings transformSettings = new XsltSettings(true, true);
xslt.Load("CreateVSToc.xsl", transformSettings, new XmlUrlResolver());

XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.IgnoreWhitespace = true;
Stream readStream = File.Open("reflection.xml", FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
using (XmlReader reader = XmlReader.Create(readStream, readerSettings))
{
    Stream outputStream = File.Open("toc.xml", FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete);
    using (XmlWriter writer = XmlWriter.Create(outputStream, xslt.OutputSettings))
    {

        XsltArgumentList arguments = new XsltArgumentList();
        xslt.Transform(reader, arguments, writer);
    }
}

I am having a problem trying to sort with an XSL file using the XslCompiledTransform in CLR4.0. Here is my sample XML file (Note: there is a space after the second <foo> element):

<?xml version="1.0" encoding="utf-8"?>
<reflection> 
  <apis>
    <api id="A">
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <foos>
        <foo/> 
      </foos>
    </api>     
  </apis>
</reflection>

When I apply the following XSL file:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">
  <xsl:template match="/">
    <html>
      <body>
        <table>
          <xsl:apply-templates select="/reflection/apis/api">
                        <xsl:sort select="@id" />
                    </xsl:apply-templates>          
        </table>
      </body>
    </html>
  </xsl:template>
  <xsl:template match="api">
    <tr>
      <td>
        <xsl:value-of select="@id" />
      </td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

I get the following result:

<html>
  <body>
    <table>
      <tr>
        <td>B</td>
      </tr>
      <tr>
        <td>A</td>
      </tr>
    </table>
  </body>
</html>

However, if I remove the space after the second <foo> element, the resulting file is sorted correctly. This seems like it's probably a bug in the XslCompiledTransform, but I was hoping someone might have a workaround.

Edit: If anyone is having trouble reproducing it, here is the code I am using:

XslCompiledTransform xslt = new XslCompiledTransform();
XsltSettings transformSettings = new XsltSettings(true, true);
xslt.Load("CreateVSToc.xsl", transformSettings, new XmlUrlResolver());

XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.IgnoreWhitespace = true;
Stream readStream = File.Open("reflection.xml", FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
using (XmlReader reader = XmlReader.Create(readStream, readerSettings))
{
    Stream outputStream = File.Open("toc.xml", FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete);
    using (XmlWriter writer = XmlWriter.Create(outputStream, xslt.OutputSettings))
    {

        XsltArgumentList arguments = new XsltArgumentList();
        xslt.Transform(reader, arguments, writer);
    }
}

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

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

发布评论

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

评论(4

时常饿 2024-10-08 15:23:22

可疑的是,如果每个 api 元素的 XML 修改为以下内容,结果将按预期排序:

<api id="apiId">
   <id>apiId</id>
   <foos>
      <foo />
   </foo>       
</api>

此外,如果 a) 每个 api 元素的 XML 被修改完全删除 id 属性

<api>
   <id>apiId</id>
   <foos>
      <foo />
   </foo>       
</api>

,并且 b) 仅将 XSL 文件中对 @id第二个引用更改为 id,结果仍然会按字母顺序排序!


XslCompiledTransform 可能会尝试对名为 id 的子元素而不是名为 id 的属性进行排序,或者这可能只是愚蠢的运气。不管怎样,我已经验证它愿意对名为 id 的子元素进行正确排序。

考虑到这一点,我可以想到两种解决方法,但这两种方法都要求您对转换过程有一定程度的控制。

方法 1:您可以更改 XML

更改编写原始 XML 的流程,将 id 指定为 api 包含的第一个元素元素。然后更新 XSL,将对 @id 的引用替换为 id

方法 2:您可以在应用 XSL 之前预处理 XML

使用 XSL 转换将 id 属性的值移动到 的子元素中api,然后将与方法 1 中相同的 XSL 应用到中间 XML 文档。在处理大型 XML 文件时,将文档转换两次显然不太理想。

以下 XSL 将使您从原始 XML 过渡到中间 XML:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">  
   <!-- recursively copy each element (including root) -->
   <xsl:template match="*|/">
      <xsl:copy>      
         <!-- xsl:copy ignores attributes, copy those as well -->
         <xsl:copy-of select="@*"/>      
         <!-- continue to deep copy the element -->
         <xsl:apply-templates />      
      </xsl:copy>
   </xsl:template>    
   <!-- for api elements, move the id attribute into an element -->
   <xsl:template match="api">
      <api>
         <id>
            <xsl:value-of select="@id"/>
         </id>      
         <!-- continue deep copy of api element contents -->
         <xsl:apply-templates />
      </api>
   </xsl:template>   
</xsl:stylesheet>

我希望这会有所帮助!

Suspiciously, if the XML for each api element is modified to the following, the result will be sorted as expected:

<api id="apiId">
   <id>apiId</id>
   <foos>
      <foo />
   </foo>       
</api>

Furthermore, if a) the XML for each api element is modified to remove the id attribute entirely

<api>
   <id>apiId</id>
   <foos>
      <foo />
   </foo>       
</api>

and b) only the second reference to @id in the XSL file is changed to id, the result will still be sorted alphabetically!


It is possible that the XslCompiledTransform is attempting to sort on a child element named id instead of an attribute named id, or this could just be dumb luck. Either way, I've verified it is willing to sort correctly on a child element named id.

With this in mind, I can think of two workarounds, but both require that you have some level of control over the transform process.

Approach 1: You are able to change the XML

Change the process writing the original XML to specify the id as the first element contained by an api element. Then update the XSL to replace references to @id with id.

Approach 2: You are able to pre-process the XML before applying your XSL

Use an XSL transform to move the value of the id attribute into a child element of api, then apply the same XSL as you would in Approach 1 to the intermediate XML document. Transforming the document twice would obviously be less desirable when processing large XML files.

The following XSL will get you from the original XML to the intermediate XML:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">  
   <!-- recursively copy each element (including root) -->
   <xsl:template match="*|/">
      <xsl:copy>      
         <!-- xsl:copy ignores attributes, copy those as well -->
         <xsl:copy-of select="@*"/>      
         <!-- continue to deep copy the element -->
         <xsl:apply-templates />      
      </xsl:copy>
   </xsl:template>    
   <!-- for api elements, move the id attribute into an element -->
   <xsl:template match="api">
      <api>
         <id>
            <xsl:value-of select="@id"/>
         </id>      
         <!-- continue deep copy of api element contents -->
         <xsl:apply-templates />
      </api>
   </xsl:template>   
</xsl:stylesheet>

I hope this helps!

尴尬癌患者 2024-10-08 15:23:22

如果将样式表版本更改为任何> = 2.0 的“1.0”,它就可以工作

it works if change the sytyle sheet version to '1.0' of anything >= 2.0

无妨# 2024-10-08 15:23:22

我没有尝试重现该问题,并且我没有发现您的 XSL 有任何明显的问题。以下是我要探索的内容,看看这是您的问题还是 XSL 引擎中的错误:尝试删除 后面的空格并将 id“A”更改为“C”。你得到的输出顺序是“B”、“C”吗?换句话说,确保它实际上是按 id 排序的。

I haven't tried to reproduce the problem, and I don't see anything obviously wrong with your XSL. Here's what I would explore to see if it's your problem or a bug in the XSL engine: try removing the space after <foo> and change id "A" to "C". Do you get the output in the order "B", "C"? In other words, make sure that it's actually sorting by id.

我家小可爱 2024-10-08 15:23:22

@Russ Ferri,感谢您的回答。它为我指明了正确的方向。 XslCompiledTransform 中的错误似乎是,当您想要按元素的属性排序时,它实际上是按该元素的第一个子元素的值排序。因此,正如 Russ 指出的那样,这将与我的原始转换文件正确排序:

<reflection>
  <apis>
    <api id="C">
      <id>C</id>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <id>B</id>
      <foos>
        <foo/>
      </foos>
    </api>
  </apis>
</reflection>

但是这也会正确排序:

<reflection>
  <apis>
    <api id="C">
      <anyElementName>C</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <anyElementName>B</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
  </apis>
</reflection>

事实上,属性名称完全被忽略,所以如果我对这样的东西运行转换:

<reflection>
  <apis>
    <api id="C">
      <anyElementName>Z</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="A">
      <anyElementName>Y</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <anyElementName>X</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
  </apis>
</reflection>

结果看起来像这样

<html>
  <body>
    <table>
      <tr>
        <td>B</td>
      </tr>
      <tr>
        <td>A</td>
      </tr>
      <tr>
        <td>C</td>
      </tr>
    </table>
  </body>
</html>

:如果您按 元素排序,则正确的排序

@Russ Ferri, thanks for your answer. It pointed me in the right direction. It appears the bug in the XslCompiledTransform is that when you want to sort by an attribute of an element, it actually sorts by the value of the first child element of that element. So as Russ pointed out, this will sort correctly with my original transform file:

<reflection>
  <apis>
    <api id="C">
      <id>C</id>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <id>B</id>
      <foos>
        <foo/>
      </foos>
    </api>
  </apis>
</reflection>

But so will this:

<reflection>
  <apis>
    <api id="C">
      <anyElementName>C</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <anyElementName>B</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
  </apis>
</reflection>

In fact, the attribute name is completely ignored, so if I run the transform on something like this:

<reflection>
  <apis>
    <api id="C">
      <anyElementName>Z</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="A">
      <anyElementName>Y</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
    <api id="B">
      <anyElementName>X</anyElementName>
      <foos>
        <foo/>
      </foos>
    </api>
  </apis>
</reflection>

The result looks like this:

<html>
  <body>
    <table>
      <tr>
        <td>B</td>
      </tr>
      <tr>
        <td>A</td>
      </tr>
      <tr>
        <td>C</td>
      </tr>
    </table>
  </body>
</html>

Which is the correct sorting if you were sorting by the <anyElementName> elements

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