Problem with XSL sorting

给你一囗甜甜゛ 提交于 2019-12-07 15:50:32

问题


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);
    }
}

回答1:


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!




回答2:


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




回答3:


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.




回答4:


@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



来源:https://stackoverflow.com/questions/4140360/problem-with-xsl-sorting

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!