#list #xslt
#Список #xslt
Вопрос:
Я преобразую XML в другой, используя преобразование идентификаторов, и во время этого, основываясь на условии, я хочу пометить несколько узлов в новом списке. Предположим, у меня есть XML типа:
<nod>
<a> A</a>
<b> B</b>
<c><p> p1</p></c>
<c><p> p2 </p></c>
<c><p> p3</p></c>
<c><p> p4 </p></c>
</nod>
Из этого XML я хочу обновить имя ‘nod’ на ‘newnod’ и создать упорядоченный список для элемента ‘c’ ; так что выходные данные будут выглядеть следующим образом:
<newnod>
<a> A</a>
<b>B</b>
<orderedlist>
<listitem><p> p1</p></listitem>
<listitem><p> p2</p></listitem>
<listitem><p>p3</p ></listitem>
<listitem><p>p4</p></listitem>
</упорядоченный список>
</newnod>
Кто-нибудь, пожалуйста, может сказать мне, как это сделать.
Спасибо!!!
Комментарии:
1. Вы хотите преобразовать все <c> элементы, или вы хотите преобразовать все элементы, количество которых > 1?
2. Хороший вопрос, 1. Смотрите мое решение, которое полностью выполнено в «push-стиле».
Ответ №1:
Вот истинное решение в стиле push:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/nod">
<newnod>
<xsl:apply-templates select="node()|@*"/>
</newnod>
</xsl:template>
<xsl:template match="c[1]">
<orderedlist>
<xsl:apply-templates mode="wrap"
select=".|following-sibling::c"/>
</orderedlist>
</xsl:template>
<xsl:template match="c" mode="wrap">
<listitem>
<xsl:apply-templates/>
</listitem>
</xsl:template>
<xsl:template match="c"/>
</xsl:stylesheet>
когда это преобразование применяется к предоставленному XML-документу:
<nod>
<a>A</a>
<b>B</b>
<c><p>p1</p></c>
<c><p>p2</p></c>
<c><p>p3</p></c>
<c><p>p4</p></c>
</nod>
получен желаемый, правильный результат:
<newnod>
<a>A</a>
<b>B</b>
<orderedlist>
<listitem>
<p>p1</p>
</listitem>
<listitem>
<p>p2</p>
</listitem>
<listitem>
<p>p3</p>
</listitem>
<listitem>
<p>p4</p>
</listitem>
</orderedlist>
</newnod>
Комментарии:
1. Привет, Дмитрий, хорошее решение, конечно. Проблема только в том, что OP подразумевает сортировку ряда c * (<упорядоченный список>), требование, которое не может быть согласовано с рекурсией родственного типа. С наилучшими пожеланиями, Майкл
2. @Michael-Ludwig: Спасибо за ваш комментарий. Я думаю, что имя элемента (orderedlist) не обязывает решателя текущей проблемы выполнять какую-либо сортировку — это не запрашивается OP, и элементы в предоставленном XML-документе уже отсортированы — они просто не помечены как таковые — и эта маркировка / перенос является сутью вопроса.
3. Похоже, вы правы. Я давно не сталкивался с такого рода проблемами XSLT, и ваше решение является отличным напоминанием для меня о том, как выглядит чистый код XSLT. Спасибо.
Ответ №2:
Это дает желаемый результат для вашего примера:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/nod">
<newnod>
<xsl:copy-of select="*[not( self::c )]"/>
<orderedlist>
<xsl:apply-templates select="c">
<xsl:sort/>
</xsl:apply-templates>
</orderedlist>
</newnod>
</xsl:template>
<xsl:template match="nod/c">
<listitem>
<xsl:copy-of select="node()"/>
</listitem>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Обратите внимание, что для вашего фактического ввода может потребоваться некоторая настройка. Например, детали сортировки. Или последовательность a
, b
и c
. И объединить все c
вместе или нет? Но это работает для вашего примера.
Комментарии:
1. Привет, Майкл, как ты сказал, это работает для этого примера. Но в случае, если после всех элементов <c> есть еще один элемент <d>, то элемент <d> в выходных данных помечается перед списком. Можете ли вы, пожалуйста, посоветовать, что мне делать в этом случае.
2. В этом случае вам нужно указать, используете ли вы XSLT 1.0 или 2.0? В версии 1.0 вы бы прибегли к одноранговой рекурсии, тогда как в версии 2.0 вы бы использовали средство группировки.
3. @Piyush: Проверьте ответ @Dimitre и мой для более общих шаблонов.
Ответ №3:
Вы могли бы использовать что-то вроде:
<xsl:template match="nod">
<newnod>
<xsl:copy-of select="a"/>
<xsl:copy-of select="b"/>
<orderedlist>
<xsl:for-each select="c">
<listitem><p><xsl:value-of select="."/></p></listitem>
</xsl:for-each>
</orderedlist>
</newnod>
</xsl:template>
Ответ №4:
Другой подход заключается в перемещении по следующей оси следующим образом:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|@*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="nod">
<newnod>
<xsl:apply-templates select="node()[1]|@*"/>
</newnod>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="c">
<orderedlist>
<xsl:call-template name="wrap"/>
</orderedlist>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="search"/>
</xsl:template>
<xsl:template match="c" name="wrap" mode="wrap">
<listitem>
<xsl:apply-templates select="node()[1]|@*"/>
</listitem>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="wrap"/>
</xsl:template>
<xsl:template match="node()" mode="wrap"/>
<xsl:template match="c" mode="search">
<xsl:apply-templates select="following-sibling::node()[1]"
mode="search"/>
</xsl:template>
<xsl:template match="node()" mode="search">
<xsl:apply-templates select="."/>
</xsl:template>
</xsl:stylesheet>
Или короче (более «нажимной стиль»):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|@*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="nod">
<newnod>
<xsl:apply-templates select="node()[1]|@*"/>
</newnod>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="c">
<orderedlist>
<xsl:call-template name="wrap"/>
</orderedlist>
<xsl:apply-templates select="following-sibling::node()
[not(self::c)][1]"/>
</xsl:template>
<xsl:template match="c" name="wrap" mode="wrap">
<listitem>
<xsl:apply-templates select="node()[1]|@*"/>
</listitem>
<xsl:apply-templates select="following-sibling::node()[1]
/self::c"
mode="wrap"/>
</xsl:template>
</xsl:stylesheet>
Оба вывода:
<newnod>
<a>A</a>
<b>B</b>
<orderedlist>
<listitem>
<p>p1</p>
</listitem>
<listitem>
<p>p2</p>
</listitem>
<listitem>
<p>p3</p>
</listitem>
<listitem>
<p>p4</p>
</listitem>
</orderedlist>
</newnod>
Примечание: Даже с XSLT 2.0 некоторые сложные последовательности обработки выражаются лучше с помощью этого шаблона.