为下一个兄弟调用命名模板
Call named template for next sibling
我有以下 XML:
<Text>
<p id="258">Step.</p>
<p id="1123">Step info.</p>
<p id="258">Step.</p>
<p id="1123">Step info.</p>
<p id="258">Step.</p>
<p id="1123">Step info:</p>
<p id="1123">- Comment.</p>
<p id="1123">- Comment.</p>
<p id="1123">- Comment.</p>
</Text>
我必须把它变成 DocBook <orderedlist>
:
<orderedlist>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info:</emphasis>
</para>
<para>
<emphasis>- Comment:</emphasis>
</para>
<para>
<emphasis>- Comment:</emphasis>
</para>
<para>
<emphasis>- Comment:</emphasis>
</para>
</listitem>
</orderedlist>
我先把所有<p id="258">
个元素变成<listitem><para>
:
<xsl:template match="AIT:p[@id='258'][1]">
<orderedlist>
<xsl:for-each select="../AIT:p[@id='258']">
<xsl:call-template name="stepNoLine"/>
</xsl:for-each>
</orderedlist>
</xsl:template>
<xsl:template name="stepNoLine">
<listitem>
<para>
<xsl:apply-templates select="*|node()"/>
</para>
</listitem>
</xsl:template>
然后我删除所有 non-first 个元素:
<xsl:template match="AIT:p[@id='258'][position() > 1]"/>
目前一切顺利:
<orderedlist>
<listitem>
<para>Step.</para>
</listitem>
<listitem>
<para>Step.</para>
</listitem>
<listitem>
<para>Step.</para>
</listitem>
</orderedlist>
但现在我不知道如何处理 <p id="1123">
个元素。两个 <p id="258">
之间的所有 <p id="1123">
必须是第一个 <p id="258">
的兄弟姐妹,children 必须是 <listitem>
的兄弟姐妹。再次:
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info.</emphasis>
</para>
</listitem>
我微不足道的尝试可耻地以耻辱的方式失败了:
<xsl:template name="stepNoLine">
<listitem>
<para>
<xsl:apply-templates select="*|node()"/>
</para>
<xsl:if test="following-sibling::AIT:p/@id='1123'">
<xsl:call-template name="stepInfo"/>
</xsl:if>
</listitem>
</xsl:template>
<xsl:template name="stepInfo">
<para>
<emphasis>
<xsl:apply-templates select="*|node()"/>
</emphasis>
</para>
</xsl:template>
我得到类似的东西:
<orderedlist>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step.</emphasis>
</para>
</listitem>
</orderedlist>
换句话说,每个 <p id="258">
元素被复制两次。我以为 <xsl:if>
使下一个兄弟节点成为当前节点,但我显然错了。
其他尝试(例如使用 xsl:for-each
而不是 xsl:if
)以同样悲惨的方式失败。
有人能给我指出正确的方向吗?
您可以使用 XSLT 2.0 或 3.0 for-each-group group-starting-with
:
<xsl:template match="Text">
<orderedlist>
<xsl:for-each-group select="*" group-starting-with="p[@id = 258]">
<listitem>
<xsl:apply-templates select="current-group()"/>
</listitem>
</xsl:for-each-group>
</orderedlist>
</xsl:template>
<xsl:template match="Text/p[@id = 258]">
<para>
<xsl:apply-templates/>
</para>
</xsl:template>
<xsl:template match="Text/*[not(self::p[@id = 258])]">
<para>
<emphasis>
<xsl:apply-templates/>
</emphasis>
</para>
</xsl:template>
现在几乎所有此类问题的正确答案都是 "use XSLT 2.0",但是在只有 XSLT 1.0 可用的环境中,能够在 1.0 中解决问题也很有用。
考虑以下输入,与所示示例同构,但具有不同的字符数据,以便更容易看出发生了什么:
<Text>
<p id="258">First step.</p>
<p id="1123">Step info for step 1.</p>
<p id="258">Step two.</p>
<p id="1123">Step info for second step.</p>
<p id="258">Step three.</p>
<p id="1123">Step info for third step:</p>
<p id="1123">- Comment on step 3.</p>
<p id="1123">- Comment 2 on step 3.</p>
<p id="1123">- Comment 3 on step 3.</p>
</Text>
通过此输入,可以更容易地看出 (a) 您的样式表草稿(假设我已根据您的描述正确重建了它)确实设法为每个步骤获得一个 listItem
(尽管略有它使用的绕屋方法),而且 (b) 它没有匹配 Text
元素或任何 p
元素与 id="1123"
的模板,这意味着默认模板会触发,我们会在前 258 段的模板输出之后得到 1123 段字符数据的转储。
<orderedlist>
<listitem>
<para>First step.</para>
<para><emphasis>First step.</emphasis></para>
</listitem>
<listitem>
<para>Step two.</para>
<para><emphasis>Step two.</emphasis></para>
</listitem>
<listitem>
<para>Step three.</para>
<para><emphasis>Step three.</emphasis></para>
</listitem>
</orderedlist>
Step info for step 1.
Step info for second step.
Step info for third step:
- Comment on step 3.
- Comment 2 on step 3.
- Comment 3 on step 3.
代码中的直接问题是 stepNoLine
调用 stepinfo
时没有做任何改变上下文节点的事情,所以那里的模板应用程序处理 258 段落的子项,而不是下面的 1123段落。
您正在处理的输入在元素序列中嵌入了大量信息;您的拉式样式表忽略了该信息并试图从无到有地重新创建它。如果您让样式表以输入为导向,您的样式表将会更简单并且会做得更好,这在 XSLT 编程中通常被称为 'push' 样式。
在下面的 XSLT 样式表中,Text
的模板调用 xsl:apply-templates 仅为应该生成 listItem
元素的子元素,即 p
元素 id="258"
。 1123 段未被该指令处理。
258 个段落元素的模板依次创建列表项的所有内容:首先是 258 个段落,然后是所有后续 p
个元素的序列 id="1123"
。 (我们只将模板应用于第一个,但第一个负责整个序列。)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="Text">
<xsl:element name="orderedList">
<xsl:apply-templates select="p[@id='258']"/>
</xsl:element>
</xsl:template>
<xsl:template match="p[@id='258']">
<xsl:element name="listitem">
<xsl:element name="para">
<xsl:apply-templates/>
</xsl:element>
<xsl:apply-templates select="following-sibling::*[1]/self::p[@id='1123']"/>
</xsl:element>
</xsl:template>
<xsl:template match="p[@id='1123']">
<xsl:element name="para">
<xsl:apply-templates/>
</xsl:element>
<xsl:apply-templates select="following-sibling::*[1]/self::p[@id='1123']"/>
</xsl:template>
</xsl:stylesheet>
N.B。在 258 个元素的模板中,我们不会尝试将模板应用于所有具有 id="1123"
的适当同级元素。如果我们在 XPath 1.0 中有一种方便的方式说 "all the immediately following p
elements with id="1123"
up to but not including the first p
with id="258"
, or the end of the parent element",并且确信我们做对了,我们可以,而且一切都会更简单。更简单的说法是“当且仅当它是 p
和 id="1123"
时,然后让该元素的模板做同样的事情。当我们到达 p
与 id="1123"
紧随其后的兄弟不是 p
与 id="1123"
,或者没有后续兄弟,递归停止。
根据修改后的输入,这产生了输出:
<?xml version="1.0" encoding="UTF-8"?>
<orderedList>
<listitem>
<para>First step.</para>
<para>Step info for step 1.</para>
</listitem>
<listitem>
<para>Step two.</para>
<para>Step info for second step.</para>
</listitem>
<listitem>
<para>Step three.</para>
<para>Step info for third step:</para>
<para>- Comment on step 3.</para>
<para>- Comment 2 on step 3.</para>
<para>- Comment 3 on step 3.</para>
</listitem>
</orderedList>
我认为这是必需的。 (如果要将 1123 个段落的内容包装在 emph
中,应该很容易看出在哪里做。)
<xsl:template match="Text">
<xsl:element name="orderedlist">
<xsl:apply-templates select="p[@id='258']"/>
</xsl:element>
</xsl:template>
<xsl:template match="p">
<xsl:choose>
<xsl:when test="@id='258'">
<xsl:element name="listitem">
<xsl:element name="para"><xsl:apply-templates /></xsl:element>
<xsl:apply-templates select="following-sibling::p[1][@id = '1123']"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="para">
<xsl:element name="emphasis">
<xsl:apply-templates />
</xsl:element>
</xsl:element>
<xsl:apply-templates select="following-sibling::p[1][@id = '1123']"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
我有以下 XML:
<Text>
<p id="258">Step.</p>
<p id="1123">Step info.</p>
<p id="258">Step.</p>
<p id="1123">Step info.</p>
<p id="258">Step.</p>
<p id="1123">Step info:</p>
<p id="1123">- Comment.</p>
<p id="1123">- Comment.</p>
<p id="1123">- Comment.</p>
</Text>
我必须把它变成 DocBook <orderedlist>
:
<orderedlist>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info:</emphasis>
</para>
<para>
<emphasis>- Comment:</emphasis>
</para>
<para>
<emphasis>- Comment:</emphasis>
</para>
<para>
<emphasis>- Comment:</emphasis>
</para>
</listitem>
</orderedlist>
我先把所有<p id="258">
个元素变成<listitem><para>
:
<xsl:template match="AIT:p[@id='258'][1]">
<orderedlist>
<xsl:for-each select="../AIT:p[@id='258']">
<xsl:call-template name="stepNoLine"/>
</xsl:for-each>
</orderedlist>
</xsl:template>
<xsl:template name="stepNoLine">
<listitem>
<para>
<xsl:apply-templates select="*|node()"/>
</para>
</listitem>
</xsl:template>
然后我删除所有 non-first 个元素:
<xsl:template match="AIT:p[@id='258'][position() > 1]"/>
目前一切顺利:
<orderedlist>
<listitem>
<para>Step.</para>
</listitem>
<listitem>
<para>Step.</para>
</listitem>
<listitem>
<para>Step.</para>
</listitem>
</orderedlist>
但现在我不知道如何处理 <p id="1123">
个元素。两个 <p id="258">
之间的所有 <p id="1123">
必须是第一个 <p id="258">
的兄弟姐妹,children 必须是 <listitem>
的兄弟姐妹。再次:
<listitem>
<para>Step.</para>
<para>
<emphasis>Step info.</emphasis>
</para>
</listitem>
我微不足道的尝试可耻地以耻辱的方式失败了:
<xsl:template name="stepNoLine">
<listitem>
<para>
<xsl:apply-templates select="*|node()"/>
</para>
<xsl:if test="following-sibling::AIT:p/@id='1123'">
<xsl:call-template name="stepInfo"/>
</xsl:if>
</listitem>
</xsl:template>
<xsl:template name="stepInfo">
<para>
<emphasis>
<xsl:apply-templates select="*|node()"/>
</emphasis>
</para>
</xsl:template>
我得到类似的东西:
<orderedlist>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step.</emphasis>
</para>
</listitem>
<listitem>
<para>Step.</para>
<para>
<emphasis>Step.</emphasis>
</para>
</listitem>
</orderedlist>
换句话说,每个 <p id="258">
元素被复制两次。我以为 <xsl:if>
使下一个兄弟节点成为当前节点,但我显然错了。
其他尝试(例如使用 xsl:for-each
而不是 xsl:if
)以同样悲惨的方式失败。
有人能给我指出正确的方向吗?
您可以使用 XSLT 2.0 或 3.0 for-each-group group-starting-with
:
<xsl:template match="Text">
<orderedlist>
<xsl:for-each-group select="*" group-starting-with="p[@id = 258]">
<listitem>
<xsl:apply-templates select="current-group()"/>
</listitem>
</xsl:for-each-group>
</orderedlist>
</xsl:template>
<xsl:template match="Text/p[@id = 258]">
<para>
<xsl:apply-templates/>
</para>
</xsl:template>
<xsl:template match="Text/*[not(self::p[@id = 258])]">
<para>
<emphasis>
<xsl:apply-templates/>
</emphasis>
</para>
</xsl:template>
现在几乎所有此类问题的正确答案都是 "use XSLT 2.0",但是在只有 XSLT 1.0 可用的环境中,能够在 1.0 中解决问题也很有用。
考虑以下输入,与所示示例同构,但具有不同的字符数据,以便更容易看出发生了什么:
<Text>
<p id="258">First step.</p>
<p id="1123">Step info for step 1.</p>
<p id="258">Step two.</p>
<p id="1123">Step info for second step.</p>
<p id="258">Step three.</p>
<p id="1123">Step info for third step:</p>
<p id="1123">- Comment on step 3.</p>
<p id="1123">- Comment 2 on step 3.</p>
<p id="1123">- Comment 3 on step 3.</p>
</Text>
通过此输入,可以更容易地看出 (a) 您的样式表草稿(假设我已根据您的描述正确重建了它)确实设法为每个步骤获得一个 listItem
(尽管略有它使用的绕屋方法),而且 (b) 它没有匹配 Text
元素或任何 p
元素与 id="1123"
的模板,这意味着默认模板会触发,我们会在前 258 段的模板输出之后得到 1123 段字符数据的转储。
<orderedlist>
<listitem>
<para>First step.</para>
<para><emphasis>First step.</emphasis></para>
</listitem>
<listitem>
<para>Step two.</para>
<para><emphasis>Step two.</emphasis></para>
</listitem>
<listitem>
<para>Step three.</para>
<para><emphasis>Step three.</emphasis></para>
</listitem>
</orderedlist>
Step info for step 1.
Step info for second step.
Step info for third step:
- Comment on step 3.
- Comment 2 on step 3.
- Comment 3 on step 3.
代码中的直接问题是 stepNoLine
调用 stepinfo
时没有做任何改变上下文节点的事情,所以那里的模板应用程序处理 258 段落的子项,而不是下面的 1123段落。
您正在处理的输入在元素序列中嵌入了大量信息;您的拉式样式表忽略了该信息并试图从无到有地重新创建它。如果您让样式表以输入为导向,您的样式表将会更简单并且会做得更好,这在 XSLT 编程中通常被称为 'push' 样式。
在下面的 XSLT 样式表中,Text
的模板调用 xsl:apply-templates 仅为应该生成 listItem
元素的子元素,即 p
元素 id="258"
。 1123 段未被该指令处理。
258 个段落元素的模板依次创建列表项的所有内容:首先是 258 个段落,然后是所有后续 p
个元素的序列 id="1123"
。 (我们只将模板应用于第一个,但第一个负责整个序列。)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="Text">
<xsl:element name="orderedList">
<xsl:apply-templates select="p[@id='258']"/>
</xsl:element>
</xsl:template>
<xsl:template match="p[@id='258']">
<xsl:element name="listitem">
<xsl:element name="para">
<xsl:apply-templates/>
</xsl:element>
<xsl:apply-templates select="following-sibling::*[1]/self::p[@id='1123']"/>
</xsl:element>
</xsl:template>
<xsl:template match="p[@id='1123']">
<xsl:element name="para">
<xsl:apply-templates/>
</xsl:element>
<xsl:apply-templates select="following-sibling::*[1]/self::p[@id='1123']"/>
</xsl:template>
</xsl:stylesheet>
N.B。在 258 个元素的模板中,我们不会尝试将模板应用于所有具有 id="1123"
的适当同级元素。如果我们在 XPath 1.0 中有一种方便的方式说 "all the immediately following p
elements with id="1123"
up to but not including the first p
with id="258"
, or the end of the parent element",并且确信我们做对了,我们可以,而且一切都会更简单。更简单的说法是“当且仅当它是 p
和 id="1123"
时,然后让该元素的模板做同样的事情。当我们到达 p
与 id="1123"
紧随其后的兄弟不是 p
与 id="1123"
,或者没有后续兄弟,递归停止。
根据修改后的输入,这产生了输出:
<?xml version="1.0" encoding="UTF-8"?>
<orderedList>
<listitem>
<para>First step.</para>
<para>Step info for step 1.</para>
</listitem>
<listitem>
<para>Step two.</para>
<para>Step info for second step.</para>
</listitem>
<listitem>
<para>Step three.</para>
<para>Step info for third step:</para>
<para>- Comment on step 3.</para>
<para>- Comment 2 on step 3.</para>
<para>- Comment 3 on step 3.</para>
</listitem>
</orderedList>
我认为这是必需的。 (如果要将 1123 个段落的内容包装在 emph
中,应该很容易看出在哪里做。)
<xsl:template match="Text">
<xsl:element name="orderedlist">
<xsl:apply-templates select="p[@id='258']"/>
</xsl:element>
</xsl:template>
<xsl:template match="p">
<xsl:choose>
<xsl:when test="@id='258'">
<xsl:element name="listitem">
<xsl:element name="para"><xsl:apply-templates /></xsl:element>
<xsl:apply-templates select="following-sibling::p[1][@id = '1123']"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="para">
<xsl:element name="emphasis">
<xsl:apply-templates />
</xsl:element>
</xsl:element>
<xsl:apply-templates select="following-sibling::p[1][@id = '1123']"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>