Группировать список узлов в дерево узлов с XSL
Я разбираю огромный файл слов с описаниями тестов, и у меня проблема с областью видимости узлов. Word в основном создает список абзацев, и я хочу сгруппировать их в родительский узел. Поэтому для каждого узла "A" я хочу сгруппировать все следующие узлы до следующего узла "A" в "A".
Как это можно сделать с помощью XSL?
Пример: я получил:
<A/>
<ab/>
<ac/>
<A/>
<ab/>
<ac/>
Но нужно:
<A>
<ab/>
<ac/>
</A>
<A>
<ab/>
<ac/>
</A>
Спасибо!
3 ответа
Если вы хотите, чтобы соответствовать всем узлам, следующим <A>
, но до следующего <A>
Я думаю, вы можете использовать что-то вроде этого:
<xsl:template match="A">
<xsl:copy>
<!-- start of range -->
<xsl:variable name="start" select="count(preceding-sibling::*) + 1" />
<!-- end of range -->
<xsl:variable name="stop">
<xsl:choose>
<!-- either just before the next A node -->
<xsl:when test="following-sibling::A">
<xsl:value-of select="count(following-sibling::A[1]/preceding-sibling::*) + 1" />
</xsl:when>
<!-- or all the rest -->
<xsl:otherwise>
<xsl:value-of select="count(../*) + 1" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- this for debugging only -->
<xsl:attribute name="range">
<xsl:value-of select="concat($start + 1, '-', $stop - 1)" />
</xsl:attribute>
<!-- copy all nodes in the calculated range -->
<xsl:for-each select="../*[position() > $start and position() < $stop]">
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:copy>
</xsl:template>
Для вашего ввода:
<root>
<A />
<ab />
<ac />
<A />
<ab />
<ac />
</root>
Я получаю (я оставил атрибут "диапазон", чтобы сделать вычисления видимыми):
<A range="2-3">
<ab />
<ac />
</A>
<A range="5-6">
<ab />
<ac />
</A>
Существует простое и очень мощное решение с использованием ключей.
Это преобразование:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kFollowing" match="*[not(self::A)]"
use="generate-id(preceding-sibling::A[1])"/>
<xsl:template match="/*">
<t>
<xsl:apply-templates select="A"/>
</t>
</xsl:template>
<xsl:template match="A">
<A>
<xsl:copy-of select=
"key('kFollowing',generate-id())"/>
</A>
</xsl:template>
</xsl:stylesheet>
при применении к исходному документу XML:
<t>
<A/>
<ab/>
<ac/>
<A/>
<ab/>
<ac/>
</t>
дает желаемый результат:
<t>
<A>
<ab/>
<ac/>
</A>
<A>
<ab/>
<ac/>
</A>
</t>
Обратите внимание, как определение <xsl:key>
в сочетании с использованием key()
Функция делает наиболее простым и естественным собирать все элементы одного и того же между двумя соседними <A/>
элементы.
Решение XSLT 2.0:
<xsl:for-each-group select="*" group-starting-with="A">
<xsl:element name="{name(current-group()[1])}">
<xsl:copy-of select="current-group()[position() gt 1]"/>
</xsl:element>
</xsl:for-each-group>