Проблема с сортировкой XSL
-
30-09-2019 - |
Вопрос
У меня возникла проблема, пытаясь сортировать с помощью файла 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);
}
}
Решение 4
@Russ ferri, спасибо за ваш ответ. Это указало меня в правильном направлении. Похоже, что ошибка в 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>
элементы
Другие советы
Подозрительно, если XML для каждого api
Элемент изменен до следующего, результат будет отсортирован, как ожидается:
<api id="apiId">
<id>apiId</id>
<foos>
<foo />
</foo>
</api>
Кроме того, если A) 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>
Надеюсь, это поможет!
Это работает, если изменение версии Sytyle Lists на «1.0» чего-либо> = 2.0
Я не пытался воспроизвести проблему, и я не вижу ничего, что явно не так с вашим XSL. Вот что я бы исследовал, чтобы увидеть, если ваша проблема или ошибка в двигателе XSL: попробуйте удалить пространство после <foo>
и изменить идентификатор «А» на «C». Вы получаете вывод в порядке «B», «C»? Другими словами, убедитесь, что это на самом деле сортировка по ID.