#xslt #xpath #axes
#xslt #xpath #оси
Вопрос:
Структура XML:
<units>
<unit>
<lesson>
<name>Sample 1</name>
<sections>
<section type="IN">
<title> Sample Title 1 </title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 2</name>
<sections>
<section type="OF">
<title> Sample Title 2 </title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 3</name>
<sections>
<section type="IN">
<title> Sample Title 3</title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 4</name>
<sections>
<section type="AS">
<title> Sample Title 4</title>
</section>
</sections>
</lesson>
<lesson>
<name>Sample 5</name>
<sections>
<section type="IN">
<title> Sample Title 5</title>
</section>
</sections>
</lesson>
</unit>
</units>
Мое требование состоит в том, чтобы получить значения элемента title и отобразить следующим образом (группировка похожих данных и отображение)
IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 5
Я использовал опцию following-sibling, чтобы получить ожидаемый результат. Поскольку структура XML огромна (я вставил только фрагмент), я не могу жестко закодировать путь, используя ../ ../ и все в XSLT. Пожалуйста, помогите мне получить ожидаемый результат.
Комментарии:
1. Группировка с использованием «параметра следующего родственного типа» составляет O (N ^ 2) по временной сложности — это особенно медленно для больших XML-документов, как в вашем случае. Изучите, поймите и используйте более эффективный метод Мюнхена для группировки. Подробнее об этом читайте здесь: jenitennison.com/xslt/grouping/muenchian.html
Ответ №1:
Лучше решить это с помощью группировки, чем любой из родственных осей:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:key name="bySectionType" match="section" use="@type" />
<xsl:template match="/">
<xsl:apply-templates select="units/unit/lesson/sections/section" />
</xsl:template>
<xsl:template match="section" />
<xsl:template
match="section[generate-id()=
generate-id(key('bySectionType', @type)[1])]">
<xsl:value-of select="concat(@type, ':amp;#xA;')" />
<xsl:apply-templates select="key('bySectionType', @type)" mode="out" />
</xsl:template>
<xsl:template match="section" mode="out">
<xsl:value-of select="concat(normalize-space(title), 'amp;#xA;')" />
</xsl:template>
</xsl:stylesheet>
Вывод:
IN:
Sample Title 1
Sample Title 3
Sample Title 5
OF:
Sample Title 2
AS:
Sample Title 4
Для полноты картины в следующей таблице стилей достигается тот же результат с использованием осей preceding
и following
:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:apply-templates select="units/unit/lesson/sections/section" />
</xsl:template>
<xsl:template match="section" />
<xsl:template
match="section[not(preceding::section[@type=current()/@type])]">
<xsl:value-of select="concat(@type, ':amp;#xA;')" />
<xsl:apply-templates select=".|following::section[@type=current()/@type]"
mode="out" />
</xsl:template>
<xsl:template match="section" mode="out">
<xsl:value-of select="concat(normalize-space(title), 'amp;#xA;')" />
</xsl:template>
</xsl:stylesheet>
Это гораздо менее эффективное решение. Обычный способ решить эту проблему — использовать мюнхенский метод группировки, как показано выше.
Комментарии:
1. @lwburk — Разве мы не можем достичь этого результата, используя оси XPATH?
2. @dirin — Да, но тогда хитрость заключается в том, чтобы определить, когда вы находитесь на первом узле с заданным типом, чтобы вы могли выводить заголовок только для этого узла. Это требует от вас проверки родственных узлов каждого узла, что менее эффективно. (И ваш случай немного отличается из-за вложенности. Вы, вероятно, использовали бы
preceding
(неpreceding-sibling
).)3. @lwburk — Как я упоминал ранее, фрагмент кода, вставленный в предыдущее сообщение, является <b> частью </b> исходного XML-файла. Я пытался включить ваш код в свой файл XSLT. И есть так много других шаблонов, которые переопределяют это, и я не получаю ожидаемый результат. Есть ли у вас какой-либо другой вариант, кроме этого? Пожалуйста, дайте мне знать.
4. @dirin: Вы могли бы изолировать ответ @lwburk с помощью режимов (даже с режимами в некотором пространстве имен!). Определенный порядок, такой как OF , AS и IN, означал бы, что вы знаете эти ключи заранее. Тогда вам даже не нужна группировка!
5. @dirin: Пожалуйста, задавайте свои новые вопросы как … НОВЫЕ ВОПРОСЫ. Вы получили отличный ответ на свой вопрос — примите его, затем задайте другой вопрос. @lwburk, кто-либо другой или даже сам @Michael-Kay не могут заменить надлежащую дозу начального обучения, которую вы должны предпринять — в этом отношении XSLT не отличается от любого другого языка программирования.
Ответ №2:
Вот решение в XSLT 2.0:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:for-each-group select="//section" group-by="@type">
<xsl:value-of select="@type, ':amp;#xa;'" separator=""/>
<xsl:value-of select="current-group()/title" separator="amp;#xA;" />
<xsl:value-of select="'amp;#xa;'"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Комментарии:
1. На самом деле мы используем XSLT 1.0. Упомянутая здесь последовательность не относится к порядку возрастания или убывания. Я бы потребовал, чтобы вывод отображался в OF, AS и В (XML может содержать эту информацию в любом порядке)