У меня есть такой XML:
<TEI>
<text>
<div type="scene" n="1">
<sp xml:id="sp1">
<speaker>Julius</speaker>
<l>Lorem ipsum dolor sit amet</l>
<ptr cRef="..." />
<stage>Aside</stage>
<ptr cRef="..." />
<l>consectetur adipisicing elit</l>
<stage>To Antony</stage>
<l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>
</sp>
<sp xml:id="sp2">
...
И мне нужно поднять все элементы <stage>
на один уровень, чтобы они стали братьями и сестрами <sp>
, разбивая <sp>
так, чтобы элементы <stage>
сохраняли свои предшествующие и последующие отношения с другими элементами внутри <sp>
, например
<TEI>
<text>
<div type="scene" n="1">
<sp by="#Julius">
<l>Lorem ipsum dolor sit amet</l>
<ptr cRef="..." />
</sp>
<stage>Aside</stage>
<sp by="#Julius">
<ptr cRef="..." />
<l>consectetur adipisicing elit</l>
</sp>
<stage>To Antony</stage>
<sp by="#Julius">
<l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>
</sp>
Я работал над XSLT, чтобы сделать это. Он включает рекурсивный шаблон, который предназначен для использования всех дочерних элементов <sp>
до (но не включая) первого дочернего элемента <stage>
и выдачи их в результирующем дереве как дочерних элементов нового <sp>
. Затем испустите первый элемент <stage>
. А затем повторите все элементы, следующие за этим первым элементом <stage>
. В конце концов, когда в списке дочерних элементов не останется <stage>
, все оставшиеся элементы выводятся в результирующем дереве внутри нового <sp>
. Вот код, включая отладку <xsl:message>
s:
<xsl:template name="sp-with-stage">
<!-- call with speaker -->
<xsl:param name="speaker" />
<!-- call with an <sp> element -->
<xsl:param name="sp" />
<!-- $content parameter is optional, by default it's the children of the given $sp; this is the parameter whose value is different with each recursive call -->
<xsl:param name="content" select="$sp/*" />
<!-- find the first <stage> element amongst the $content node set -->
<xsl:variable name="stage" select="$content/following-sibling::stage[1]" />
<xsl:message>ID = <xsl:value-of select="$sp/@xml:id" /></xsl:message>
<xsl:message>speaker = "<xsl:value-of select="$speaker" />"</xsl:message>
<xsl:message>content length = <xsl:value-of select="count($content)" /></xsl:message>
<xsl:if test="$stage">
<xsl:message>nodes before $stage = <xsl:value-of select="count($stage/preceding-sibling::*)" /></xsl:message>
<xsl:message>nodes after $stage = <xsl:value-of select="count($stage/following-sibling::*)" /></xsl:message>
</xsl:if>
<xsl:if test="$stage">
<sp by="#{$speaker}">
<!-- process all the nodes in the $content node set before the current <stage> -->
<xsl:message>Processing <xsl:value-of select="count($stage/preceding-sibling::*)" /> nodes before "<xsl:value-of select="$stage/text()" />"</xsl:message>
<xsl:apply-templates select="$stage/preceding-sibling::*" />
</sp>
<xsl:apply-templates select="$stage" />
</xsl:if>
<xsl:choose>
<xsl:when test="$stage/following-sibling::stage">
<!-- if there's another <stage> element in the $content node set then call this template recursively -->
<xsl:message>Call recursively with <xsl:value-of select="count($stage/following-sibling::*)" /> following nodes</xsl:message>
<xsl:call-template name="sp-with-stage">
<xsl:with-param name="speaker"><xsl:value-of select="$speaker" /></xsl:with-param>
<xsl:with-param name="sp" select="$sp" />
<!-- the $content node set for this call is all the nodes after the current <stage> -->
<xsl:with-param name="content" select="$stage/following-sibling::*" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$stage/following-sibling::*">
<!-- if there's no <stage> element in the $content node set, but there are still some elements, emit them in an <sp> element -->
<sp by="#{$speaker}">
<xsl:message>Processing <xsl:value-of select="count($stage/following-sibling::*)" /> trailing nodes</xsl:message>
<xsl:apply-templates select="$stage/following-sibling::*" />
</sp>
</xsl:when>
</xsl:choose>
</xsl:template>
Затем этот шаблон вызывается следующим образом:
<xsl:template match="sp[stage]">
<xsl:call-template name="sp-with-stage">
<xsl:param name="speaker"><xsl:value-of select="speaker" /></xsl:param>
<xsl:param name="sp" select="." />
</xsl:call-template>
</xsl:template>
Проблема заключается в том, что я использую $stage/preceding-sibling::*
, под которым я подразумеваю обработку только узлов из текущего набора узлов $content
, которые предшествуют текущему узлу $stage
. На самом деле происходит следующее: при каждом рекурсивном вызове все узлы, предшествующие текущему $stage
узлу из его исходного <sp>
контекста, выбираются этим $stage/preceding-sibling::*
. И это несмотря на то, что рекурсивные вызовы каждый раз получают правильный новый набор узлов $content
и что узел $stage
берется из этого правильного набора узлов $content
.
Чтобы уточнить, в случае приведенного выше примера XML, когда <stage>To Antony</stage>
является узлом $stage
, а узел $content
содержит только:
<l>consectetur adipisicing elit</l>
<stage>To Antony</stage>
<l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>
выражение $stage/preceding-sibling::*
по-прежнему дает все дочерние элементы исходного <sp>
до <stage>To Antony</stage>
.
Я думаю, должно быть что-то в preceding-sibling
, чего я не совсем понимаю. Какие-либо предложения? Или даже какие-либо предложения совершенно разных способов достижения трансформации?