XSLT当元素说明时,如何修剪元素前后的空间?

时间:2012-04-12 16:25:01

标签: xslt xpath

在使用TEI标记(www.tei-c.org)格式化文本文档时会出现此问题。它超出了我的XSLT / XPATH技能。 (需要XSLT / XPATH 1.0中的解决方案。)

有一个标记元素<lb>,用于标记换行符。它可以采用属性@break。如果@break="no",则在生成输出时应忽略<lb>与周围文本之间的任何空格。

所以

This little tea <lb break="no" />
pot, short and stout.

应理解为

This little teapot, short and stout.

也就是说,“tea”之后的空格和“pot”之前的换行符不应该在输出流中呈现。

对于<lb>之前的空格,这可能有效:

<xsl:template match="text()[following-sibling::*[1][self::lb[@break='no']]">
    <!-- Do something about the space here. -->
</xsl:template> 

类似的内容适用于<lb>之后的换行符。

行。但这更棘手:

This <emph>little <ref>tea </ref> </emph>
<lb break="no" />
pot, short and stout.

现在<ref>元素中的文字不是<lb>的兄弟。并且</ref>之前的空格,</emph>之前的空格以及<lb>之前和之后的换行都需要从输出流中删除。

如何?

2 个答案:

答案 0 :(得分:3)

这是一个经过测试的工作实现,包括如何从文本节点的右侧或左侧修剪空白:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:template match="node() | @*">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*"/>
        </xsl:copy>
    </xsl:template>

    <!-- Match if the preceding node (not necessarily sibling) that is either
      a non-empty-space-text node or an <lb> is an <lb break='no'> -->
    <xsl:template match="text()[
        (preceding::node()[
            self::text()[normalize-space() != ''] or
            self::lb])
                [last()]
        [self::lb[@break='no']]
        ]">

        <!-- Trim whitespace on the left. Thanks to Alejandro,
            http://stackoverflow.com/a/3997107/423105 -->
        <xsl:variable name="firstNonSpace"
            select="substring(normalize-space(), 1, 1)"/>
        <xsl:value-of select="concat($firstNonSpace,
            substring-after(., $firstNonSpace))"/>
    </xsl:template>

    <!-- Match if the next node (not necessarily sibling) that is either
      a non-empty-space-text node or an <lb> is an <lb break='no'> -->
    <xsl:template match="text()[
        following::node()[
            self::text()[normalize-space() != ''] or
            self::lb]
               [1]
        [self::lb[@break='no']]
        ]">

        <xsl:variable name="normalized" select="normalize-space()"/>
        <xsl:if test="$normalized != ''">
            <xsl:variable name="lastNonSpace"
                select="substring($normalized, string-length($normalized))"/>
            <xsl:variable name="trimmedSuffix">
                <xsl:call-template name="substring-after-last">
                    <xsl:with-param name="string" select="."/>
                    <xsl:with-param name="delimiter" select="$lastNonSpace"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:value-of select="substring(., 1, string-length(.) -
               string-length($trimmedSuffix))"/>
        </xsl:if>
        <!-- otherwise output nothing. -->
    </xsl:template>


    <!-- Thanks to Jeni Tennison:
        http://www.stylusstudio.com/xsllist/200111/post00460.html -->
    <xsl:template name="substring-after-last">
        <xsl:param name="string" />
        <xsl:param name="delimiter" />
        <xsl:choose>
            <xsl:when test="contains($string, $delimiter)">
                <xsl:call-template name="substring-after-last">
                    <xsl:with-param name="string"
                        select="substring-after($string, $delimiter)" />
                    <xsl:with-param name="delimiter" select="$delimiter" />
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise><xsl:value-of select="$string" /></xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

我的假设是,在上面的“下一个含糊不清”评论的答案之前,如果有一个<lb>元素没有 break="no",那么{{1}构成“周围文本”,意思是它作为忽略空格的边界。

样本输入:

<lb>

输出:

<test>
    <t1>
        This <emph>little <ref>tea </ref> </emph>
        <lb break="no" />
        pot, short and stout.        
    </t1>    
    <t2>
        This <emph>little <ref>tea </ref> </emph>
        <lb />
        <lb break="no" />
        pot, short and stout.        
    </t2>    
</test>

此输出是正确的AFAICT。如果没有,请告诉我原因,我会看到修复它。

答案 1 :(得分:1)

尝试如下选择器:

text()[matches(., '\S?\s*$') and not following::text()[matches('\S')] and following::lb[@break="no"]]

这当然是丑陋而低效的。但是可能有用。不起作用,因为正如已经指出的那样,你没有匹配()。我还有另一个去:

好的,我们正在寻找四种不同的场景:

  • 第一个前置非空文本元素,如果它以空格结尾:

    lb [@ break ='no'] / preceding :: text()[normalize-space()!=''和string-length(substring-after(。,normalize-space()))!= 0 ] [1]

  • 在前面的第一个非空文本元素后面的空文本元素:

    lb [@ break ='no'] / preceding :: text()[normalize-space()=''and preceding :: text()[normalize-space()!='']]

  • 在第一个非空文本元素之前的空文本元素:

    lb [@ break ='no'] / following :: text()[normalize-space()!=''和string-length(substring-before(。,normalize-space()))!= 0 ] [1]

  • 首先跟随非空文本元素,如果它在空格中开始:

    lb [@ break ='no'] / following :: text()[normalize-space()=''and following :: text()[normalize-space()!='']]

因为您无法在xpath 1.0中使用union,所以您必须使用此方法从上述每个匹配项中调用模板。