XSLT:将空元素对视为开始/结束标记

时间:2016-12-06 13:54:18

标签: xslt xslt-2.0

我有xml,我正在尝试将两个空元素转换为开始/结束标记对。我的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,我可以匹配空元素,但我不能只将一个转换为开始标记,另一个转换为结束标记。我正在努力将两者视为一组(请记住,他们可能有其他元素需要处理)。

任何建议都将受到赞赏。

3 个答案:

答案 0 :(得分:1)

这是一个建议,应该对于匹配的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>

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

答案 1 :(得分:1)

好吧,你没有在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"/>

这里发生的是,对于每个兄弟节点,您复制节点,然后继续处理其下一个兄弟节点;但是当你点击一个“开始”标记时,该递归的结果会被添加到一个新的b元素中,当你点击结束标记时,递归就会停止,扫描会被模板的初始标记再次恢复。

兄弟递归也适用于XSLT 1.0。

答案 2 :(得分:0)

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

<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用嵌套组解决了这个问题)。但是,我仍然在这里发布我的方法,因为它可能是有意义的。