为下一个兄弟调用命名模板

时间:2017-10-16 11:48:25

标签: xml xslt docbook

我有以下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>

我删除了所有非首要元素:

<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="1123">之间的所有<p id="258">必须是第一个<p id="258">的兄弟姐妹和<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)也以同样悲惨的方式失败。

有人可以指出我正确的方向吗?

3 个答案:

答案 0 :(得分:1)

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>

答案 1 :(得分:1)

现在几乎所有问题的正确答案都是“使用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的模板仅为应生成listItem元素的子项调用xsl:apply-templates,即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> 。 (我们仅将模板应用于第一个模板,但第一个模板负责整个序列。)

id="1123"

N.B。在258 elemet的模板中,我们不会尝试使用p将模板应用于所有适当的兄弟元素。我们可以,而且一切都会更简单,如果我们在XPath 1.0中有一个方便的说法,“id="1123"所有紧跟p元素,但不包括第一个id="258" } p,或父元素的结尾“,并确切地知道我们已经做对了。说“立即跟随兄弟,当且仅当它是id="1123"p时,并且让该元素的模板做同样的事情时更容易。当我们达到{{1}时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> ,或者没有后续兄弟,递归就会停止。

从修改后的输入中,产生输出:

emph

我认为这是必需的。 (如果你想在z-index: 2; position: relative; 中包装1123段的内容,应该很容易看到在哪里这样做。)

答案 2 :(得分:1)

<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>