Всегда трудно работать с примерами, и я не уверен, что показанный требуемый результат является последовательным, я бы предложил двухэтапное преобразование, первое идентифицирует li элементы и устанавливает ol/ul
, второе затем проверяет любой внутреннийсписок заключен в предыдущий li
.
. Здесь представлен XSLT 3 (доступен с Saxon 9.8 или 9.9 для Java и .NET, а также в XML IDE, таких как oXygen или Stylus Studio, также реализованный Altova Raptor в ихлинейки продуктов с выпусков 2017 года) подход первого шага:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:param name="list-levels" as="map(xs:integer, xs:integer+)"
select="map { 1 : (560, 561), 2: (562) }"/>
<xsl:param name="list-map" as="map(xs:integer, xs:QName)"
select="map { 560 : QName('', 'ol'), 561 : QName('', 'ul'), 562 : QName('', 'ul') }"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$nodes" group-adjacent="boolean(self::p[@id = $list-levels?($level to 6)])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:element name="{$list-map(xs:integer(@id))}">
<xsl:sequence select="mf:group(current-group(), $level + 1)"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="text">
<xsl:copy>
<xsl:sequence select="mf:group(*, 1)"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[@id = 542]">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[@id = $list-levels?*]">
<li>
<xsl:apply-templates/>
</li>
</xsl:template>
</xsl:stylesheet>
Вы можете установить его на https://xsltfiddle.liberty -development.net / pPzifoZ со следующим результатом:
<text>
<p>This is a parapgraph</p>
<ul>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
</ul>
<p>This is a parapgraph</p>
<ul>
<li>This is a first level bullet</li>
<ul>
<li>This is a second level bullet</li>
<li>This is a second level bullet</li>
</ul>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
</ul>
<p>This is a parapgraph</p>
<p>This is a parapgraph</p>
<ol>
<li>This is a first ordered list</li>
<li>This is a first ordered list</li>
<li>This is a first ordered list</li>
<ul>
<li>This is a second level bullet</li>
<li>This is a second level bullet</li>
</ul>
</ol>
</text>
Для второго шага я добавил режим обработки переноса вложенных ul
или ol
:
<xsl:mode name="wrap" on-no-match="shallow-copy"/>
<xsl:template match="ul | ol" mode="wrap">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="li">
<xsl:copy>
<xsl:apply-templates select="node(), current-group() except ." mode="#current"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<xsl:copy>
<xsl:apply-templates select="mf:group(*, 1)" mode="wrap"/>
</xsl:copy>
</xsl:template>
Пример при https://xsltfiddle.liberty -развитии.net / pPzifoZ / 1 производит вывод
<text>
<p>This is a parapgraph</p>
<ul>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
</ul>
<p>This is a parapgraph</p>
<ul>
<li>This is a first level bullet<ul>
<li>This is a second level bullet</li>
<li>This is a second level bullet</li>
</ul>
</li>
<li>This is a first level bullet</li>
<li>This is a first level bullet</li>
</ul>
<p>This is a parapgraph</p>
<p>This is a parapgraph</p>
<ol>
<li>This is a first ordered list</li>
<li>This is a first ordered list</li>
<li>This is a first ordered list<ul>
<li>This is a second level bullet</li>
<li>This is a second level bullet</li>
</ul>
</li>
</ol>
</text>
Полный код
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:param name="list-levels" as="map(xs:integer, xs:integer+)"
select="map { 1 : (560, 561), 2: (562) }"/>
<xsl:param name="list-map" as="map(xs:integer, xs:QName)"
select="map { 560 : QName('', 'ol'), 561 : QName('', 'ul'), 562 : QName('', 'ul') }"/>
<xsl:function name="mf:group" as="node()*">
<xsl:param name="nodes" as="node()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$nodes" group-adjacent="boolean(self::p[@id = $list-levels?($level to 6)])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:element name="{$list-map(xs:integer(@id))}">
<xsl:sequence select="mf:group(current-group(), $level + 1)"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:mode name="wrap" on-no-match="shallow-copy"/>
<xsl:template match="ul | ol" mode="wrap">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="li">
<xsl:copy>
<xsl:apply-templates select="node(), current-group() except ." mode="#current"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<xsl:copy>
<xsl:apply-templates select="mf:group(*, 1)" mode="wrap"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[@id = 542]">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[@id = $list-levels?*]">
<li>
<xsl:apply-templates/>
</li>
</xsl:template>
</xsl:stylesheet>
Примеры используют XSLT 3, но в XSLT 2 группировка будет такой же, сохраняя толькоотображение p
идентификаторов науровни элементов и списков должны были бы быть выполнены в некоторой структуре XML вместо XPath map
s, который я использовал выше.
Ограничение: в $level to 6
я выбрал некоторый максимальный уровень вложенности, равный шести, но он можетконечно легко подстраиваться.
Тем не менее, выбор более умного представления, такого как массив, позволит нам избежать необходимости определять максимальный уровень, поэтому с <xsl:param name="list-levels" as="array(xs:integer+)" select="[ (560, 561), (562) ]"/>
и затем <xsl:for-each-group select="$nodes" group-adjacent="boolean(self::p[@id = array:subarray($list-levels, $level)])">
, как в https://xsltfiddle.liberty -development.net/ pPzifoZ / 2 , нет необходимости определять максимальный уровень, жестко закодированный в XSLT.