XSLT:将空元素对视为 start/end 标记

XSLT: Treat empty elements pair as start/end tags

我有 xml 试图将两个空元素转换为 start/end 标记对。我的 xml 看起来像...

<para>This is a paragraph with text <Emph type="bold" mode="start"/>this text needs to be bolded<Emph type="bold" mode="end"/>, and we might also have some text that needs to be <Emph type="italic" mode="start"/>italicized<Emph type="italic" mode="end"/>.

我正在尝试匹配成对的空元素以将它们变成

<para>This is a paragraph with text <b>this text needs to be bolded</b>, and we might also have some text that needs to be <i>italicized</i>.

我使用的是 XSLT 2.0,我可以匹配空元素,但我不能只将一个转换为开始标记,将另一个转换为结束标记。我正在努力将两者视为一个集合(请记住,它们内部可能还有其他需要处理的元素)。

如有任何建议,我们将不胜感激。

对于将 Emph mode="start"/Emph mode="end" 的匹配对作为兄弟姐妹的常规输入,这里有一个建议:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="2.0">

    <xsl:param name="type-map">
        <map input="bold">b</map>
        <map input="italic">i</map>
    </xsl:param>

    <xsl:key name="type-map" match="map" use="@input"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* |node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*[Emph]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:for-each-group select="node()" group-starting-with="Emph[@mode = 'start']">
                <xsl:choose>
                    <xsl:when test="self::Emph[@mode = 'start']">
                        <xsl:variable name="start" select="."/>
                        <xsl:for-each-group select="current-group() except ." group-ending-with="Emph[@mode = 'end' and @type = $start/@type]">
                            <xsl:choose>
                                <xsl:when test="current-group()[last()][self::Emph[@mode = 'end' and @type = $start/@type]]">
                                    <xsl:element name="{key('type-map', $start/@type, $type-map)}">
                                        <xsl:apply-templates select="current-group()[position() lt last()]"/>
                                    </xsl:element>
                                </xsl:when>
                                <xsl:otherwise>
                                    <xsl:apply-templates select="current-group()"/>
                                </xsl:otherwise>
                            </xsl:choose>
                        </xsl:for-each-group>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each-group>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

实现评论中提到的重叠案例要复杂得多。

我在这里有一个涵盖标记重叠段落的建议:

<xsl:output indent="yes" method="xml"/>

<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="para/text()">
    <xsl:choose>
        <xsl:when test="preceding::Emph[@type = 'bold'][1]/@mode = 'start'
                and following::Emph[@type = 'bold'][1]/@mode = 'end'">
            <b>
                <xsl:value-of select="."/>
            </b>
        </xsl:when>
        <xsl:when test="preceding::Emph[@type = 'italic'][1]/@mode = 'start'
                and following::Emph[@type = 'italic'][1]/@mode = 'end'">
            <i>
                <xsl:value-of select="."/>
            </i>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="."/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="Emph"/>

涵盖重叠或嵌套的粗体和斜体。这里的另一个缺点是它只在文本节点上运行,这使得进一步的处理有点复杂(Martin Honnen 用嵌套组解决了这个问题)。但是,我仍然 post 我的方法,因为它可能很有趣。

好吧,您不是在 XSLT 中创建标签,而是创建节点树。因此,您并不是要将一个空元素标记转换为开始标记,而将另一个空元素标记转换为结束标记,而是要尝试将这两个空元素节点之间的文本转换为新的元素节点。这会影响您思考问题的整个方式:思考节点,而不是标签。

如 Martin Honnen 所示,位置分组是解决此问题的一种方法。另一种方法是 "sibling recursion",它看起来像这样:

<xsl:template match="para">
  <xsl:apply-templates select="node()[1]" mode="traverse"/>
</xsl:template>

<xsl:template match="node()" mode="traverse">
  <xsl:copy-of select="."/>
  <xsl:apply-templates select="following-sibling::node()[1]"
                       mode="traverse"/>
</xsl:template>

<xsl:template match="Emph[@mode = 'start'][@type='bold']"
      mode="traverse">
  <b>
    <xsl:apply-templates match="following-sibling::node()[1]" 
      mode="traverse"/>
  </b>
  <xsl:apply-templates match="following-sibling::Emph[@mode = 'end']
      [@type='bold'][1]/following-sibling::node()[1]" 
      mode="traverse"/>
</xsl:template>

<xsl:template match="Emph[@mode = 'end'][@type='bold']"
      mode="traverse"/>

这里发生的事情是,对于每个兄弟节点,您复制该节点,然后继续处理它的下一个兄弟节点;但是当您点击 "start" 标记时,该递归的结果将添加到新的 b 元素中,并且当您点击结束标记时,递归停止,开始标记的模板再次恢复扫描。

兄弟递归也适用于 XSLT 1.0。