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。
我有 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。