Проблема с сортировкой XSL

У меня возникла проблема с сортировкой файла XSL с помощью XslCompiledTransform в CLR4.0. Вот мой образец XML-файла (Примечание: после второго элемента <foo> есть пробел):

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

Однако, если я удалю пробел после второго элемента <foo>, результирующий файл будет правильно отсортирован. Похоже, что это, вероятно, ошибка в 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);
    }
}

person Mike Dour    schedule 10.11.2010    source источник
comment
Проблема кажется невоспроизводимой (протестировано с MSXSL 4 и 3). Если есть ошибка в XslCompiledTransform, это должен быть вопрос с тегом xsltprocessor.   -  person    schedule 10.11.2010
comment
Если кто-то хочет отследить это, я также отправил это в Microsoft. Я использовал немного другое содержимое файла, но результат тот же: connect.microsoft.com/VisualStudio/feedback/details/620628/   -  person Mike Dour    schedule 11.11.2010
comment
Я очень старался и не могу воспроизвести это. !!!   -  person Dimitre Novatchev    schedule 12.11.2010


Ответы (4)


Подозрительно, если XML для каждого элемента api изменить на следующий, результат будет отсортирован, как и ожидалось:

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

Кроме того, если а) XML для каждого элемента api изменен, чтобы полностью удалить атрибут id

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

и б) только вторая ссылка на @id в файле XSL изменена на id, результат все равно будет отсортирован по алфавиту!


Возможно, XslCompiledTransform пытается отсортировать дочерний элемент с именем id вместо атрибута с именем id, или это может быть просто везением. В любом случае, я убедился, что он готов правильно сортировать дочерний элемент с именем id.

Имея это в виду, я могу придумать два обходных пути, но оба требуют, чтобы у вас был некоторый уровень контроля над процессом преобразования.

Подход 1: вы можете изменить XML

Измените процесс записи исходного XML, чтобы указать id в качестве первого элемента, содержащегося в элементе api. Затем обновите XSL, заменив ссылки на @id на id.

Подход 2. Вы можете предварительно обработать XML перед применением XSL.

Используйте преобразование XSL, чтобы переместить значение атрибута id в дочерний элемент api, затем примените тот же XSL, что и в Подходе 1, к промежуточному 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>

Надеюсь, это поможет!

person Russ Ferri    schedule 10.11.2010
comment
Похоже, это работает, но на самом деле это предотвращает сортировку. Когда я меняю A на C (который помещает начальные элементы в несортированный порядок) и удаляю пробелы, в результирующем файле порядок никогда не меняется, с пробелом или без него. - person Mike Dour; 10.11.2010
comment
Вы правы, извините за это. Вскоре я обновлю этот ответ некоторыми вещами, которые я заметил после того, как немного поиграл с этой проблемой. - person Russ Ferri; 11.11.2010

это работает, если изменить версию листа sytyle на «1.0» для чего-либо >= 2.0

person Hubery    schedule 29.12.2010

Я не пытался воспроизвести проблему и не вижу ничего явно плохого в вашем XSL. Вот что я хотел бы изучить, чтобы увидеть, является ли это вашей проблемой или ошибкой в ​​​​движке XSL: попробуйте удалить пробел после <foo> и изменить идентификатор «A» на «C». Вы получаете вывод в порядке «B», «C»? Другими словами, убедитесь, что он действительно сортируется по идентификатору.

person Don Kirkby    schedule 10.11.2010
comment
Хорошие вопросы. Они должны быть представлены как комментарий, а не ответ. - person Mads Hansen; 10.11.2010
comment
Спасибо за отзыв, @Mads. Я перефразировал это как предложение о том, как отладить проблему. - person Don Kirkby; 10.11.2010
comment
Когда я меняю A на C, порядок сортировки получается правильным с пробелом и неправильным без пробела, так что это действительно не имеет смысла. - person Mike Dour; 10.11.2010
comment
Вы воспроизвели эту проблему? Если нет, подумайте о других людях с похожей (но реальной) проблемой, которых может смутить ваш ответ. - person ; 10.11.2010
comment
Хорошо, @Alejandro, я пояснил, что не воспроизвел проблему, и это просто предложение по отладке. - person Don Kirkby; 10.11.2010

@ Расс Ферри, спасибо за ответ. Это указало мне правильное направление. Похоже, ошибка в XslCompiledTransform заключается в том, что когда вы хотите отсортировать по атрибуту элемента, он фактически сортируется по значению первого дочернего элемента этого элемента. Итак, как указал Расс, это правильно отсортируется с моим исходным файлом преобразования:

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

Какая сортировка является правильной, если вы сортировали по элементам <anyElementName>

person Mike Dour    schedule 15.11.2010