36

我正在尝试使用 XSLT 将 XML 文件转换为 dokuwiki 使用的标记。这实际上在某种程度上有效,但是 XSL 文件中的缩进被插入到结果中。目前,我有两个选择:完全放弃这个 XSLT 东西,并找到另一种从 XML 转换为 dokuwiki 标记的方法,或者从 XSL 文件中删除大约 95% 的空白,使其几乎不可读并且是维护的噩梦。

有什么方法可以在不将所有空格传递到最终文档的情况下保留 XSL 文件中的缩进吗?

背景:我正在将一个 autodoc 工具从静态 HTML 页面迁移到 dokuwiki,因此每当应用程序团队遇到文档记录不佳的代码时,应用程序团队可以进一步记录由服务器团队开发的 API。逻辑是为自动文档工具留出每个页面的一部分,并允许在此块之外的任何地方进行评论。我使用 XSLT 是因为我们已经有 XSL 文件可以从 XML 转换为 XHTML,而且我假设重写 XSL 比从头开始推出我自己的解决方案要快。

编辑:啊,对,愚蠢的我,我忽略了缩进属性。(其他背景说明:我是 XSLT 的新手。)另一方面,我仍然需要处理换行符。Dokuwiki 使用管道来区分表格列,这意味着表格行中的所有数据必须在一行中。有没有办法抑制输出换行符(只是偶尔),所以我可以以某种可读的方式为每个表格单元格做一些相当复杂的逻辑?

4

4 回答 4

77

在 XSLT 转换的结果中出现不需要的空白有三个原因:

  1. 来自源文档中节点之间的空白
  2. 来自源文档中节点内的空格
  3. 来自样式表的空格

我将讨论所有这三个,因为很难分辨空白的来源,因此您可能需要使用多种策略。

要解决源文档中节点之间的空白,您应该使用<xsl:strip-space>去除出现在两个节点之间的任何空白,然后使用<xsl:preserve-space>保留可能出现在混合内容中的重要空白。例如,如果您的源文档如下所示:

<ul>
  <li>This is an <strong>important</strong> <em>point</em></li>
</ul>

那么你会想要忽略<ul>and 和 the之间<li>的空白,这并不重要,但保留and元素之间的空白,这重要(否则你会得到“这是一个**重要的** *观点*”)。为此,请使用</li></ul><strong><em>

<xsl:strip-space elements="*" />
<xsl:preserve-space elements="li" />

elementson 属性<xsl:preserve-space>基本上应该列出文档中具有混合内容的所有元素。

另外: using<xsl:strip-space>还减少了内存中源树的大小,并使您的样式表更有效率,因此即使您没有此类空白问题,也值得这样做。

要解决源文档中节点内出现的空白,您应该使用normalize-space(). 例如,如果您有:

<dt>
  a definition
</dt>

并且您可以确定该<dt>元素不会包含您想要使用的任何元素,那么您可以执行以下操作:

<xsl:template match="dt">
  ...
  <xsl:value-of select="normalize-space(.)" />
  ...
</xsl:template>

前导和尾随空格将从<dt>元素的值中删除,您将只得到 string "a definition"

要解决来自样式表的空白(这可能是您遇到的空白),是当您在模板中有这样的文本时:

<xsl:template match="name">
  Name:
  <xsl:value-of select="." />
</xsl:template>

XSLT 样式表的解析方式与它们处理的源文档的解析方式相同,因此上述 XSLT 被解释为一棵树,其中包含<xsl:template>具有属性的元素,该元素match的第一个子节点是文本节点,第二个子节点是<xsl:value-of>具有属性的元素select。文本节点有前导和尾随空格(包括换行符);由于它是样式表中的文字文本,因此它会被逐字复制到结果中,并带有所有前导和尾随空格。

但是XSLT 样式表中的一些空白会被自动去除,即节点之间的空白。您的结果中没有换行符,因为.<xsl:value-of><xsl:template>.

要在结果中仅获取您想要的文本,请使用如下<xsl:text>元素:

<xsl:template match="name">
  <xsl:text>Name: </xsl:text>
  <xsl:value-of select="." />
</xsl:template>

XSLT 处理器将忽略出现在节点之间的换行符和缩进,并且只输出<xsl:text>元素内的文本。

于 2008-10-08T21:46:07.470 回答
4

您在输出标签中使用 indent="no" 吗?

<xsl:output method="text" indent="no" />

此外,如果您使用的是 xsl:value-of,您可以使用 disable-output-escaping="yes" 来帮助解决一些空白问题。

于 2008-10-08T19:26:46.300 回答
3

@JeniT 的回答很棒,我只想指出一个管理空白的技巧。我不确定这是最好的方法(甚至是好方法),但它现在对我有用。

(“s”代表空格,“e”代表空,“n”代表换行。)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:transform [
  <!ENTITY s "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> </xsl:text>" >
  <!ENTITY s2 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>  </xsl:text>" >
  <!ENTITY s4 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>    </xsl:text>" >
  <!ENTITY s6 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>      </xsl:text>" >
  <!ENTITY e "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'></xsl:text>" >
  <!ENTITY n "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
</xsl:text>" >
]>

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
  &e;Flush left, despite the indentation.&n;
  &e;  This line will be output indented two spaces.&n;

      <!-- the blank lines above/below won't be output -->

  <xsl:for-each select="//foo">
    &e;  Starts with two blanks: <xsl:value-of select="@bar"/>.&n;
    &e;  <xsl:value-of select="@baz"/> The 'e' trick won't work here.&n;
    &s2;<xsl:value-of select="@baz"/> Use s2 instead.&n;
    &s2;    <xsl:value-of select="@abc"/>    <xsl:value-of select="@xyz"/>&n;
    &s2;    <xsl:value-of select="@abc"/>&s;<xsl:value-of select="@xyz"/>&n;
  </xsl:for-each>
</xsl:template>
</xsl:transform>

应用于:

<?xml version="1.0" encoding="UTF-8"?>
<foo bar="bar" baz="baz" abc="abc" xyz="xyz"></foo>

输出:

Flush left, despite the indentation.
  This line will be output indented two spaces.
  Starts with two blanks: bar.
baz The 'e' trick won't work here.
  baz Use s2 instead.
  abcxyz
  abc xyz

'e' 技巧在包含至少一个非空白字符的文本节点之前起作用,因为它扩展为:

<xsl:template match="/">
  <xsl:text></xsl:text>Flush left, despite the indentation.<xsl:text>
</xsl:text>

由于去除空格的规则表明只有空格的文本节点会被去除,所以 <xsl:template> 和 <xsl:text> 之间的换行符和缩进会被去除(很好)。由于规则说至少保留一个空白字符的文本节点,因此包含的隐式文本节点" This line will be output indented two spaces."保留其前导空白(但我想这也取决于剥离/保留/规范化的设置)。然后;” 在行尾插入换行符,但它也确保忽略任何后续空格,因为它出现在两个节点之间。

我遇到的问题是当我想输出一个以 <xsl:value-of> 开头的缩进行时。在这种情况下,“&e;” 不会有帮助,因为缩进空格没有“附加”到任何非空格字符。所以对于这些情况,我使用“&s2;” 或“&s4;”,取决于我想要多少缩进。

我敢肯定,这是一个丑陋的 hack,但至少我的 XSLT 中没有冗长的“<xsl:text>”标签,至少我仍然可以缩进 XSLT 本身,使其清晰易读。我觉得我在滥用 XSLT 来实现它不是为它设计的东西(文本处理),这是我能做的最好的事情。


编辑: 回应评论,这是没有“宏”的样子:

<xsl:template match="/">
  <xsl:text>Flush left, despite the indentation.</xsl:text>
  <xsl:text>  This line will be output indented two spaces.</xsl:text>
  <xsl:for-each select="//foo">
    <xsl:text>  Starts with two blanks: </xsl:text><xsl:value-of select="@bar"/>.<xsl:text>
</xsl:text>
    <xsl:text>    </xsl:text><xsl:value-of select="@abc"/><xsl:text> </xsl:text><xsl:value-of select="@xyz"/><xsl:text>
</xsl:text>
  </xsl:for-each>
</xsl:template>

我认为这使得看到预期的输出缩进变得不太清楚,并且它搞砸了 XSL 本身的缩进,因为</xsl:text>结束标记必须出现在 XSL 文件的第 1 列(否则你会在输出文件中得到不需要的空格)。

于 2011-01-16T05:01:57.063 回答
0

关于您对新行的编辑,您可以使用此模板在另一个字符串中递归替换一个字符串,并且可以将其用于换行符:

<xsl:template name="replace.string.section">
  <xsl:param name="in.string"/>
  <xsl:param name="in.characters"/>
  <xsl:param name="out.characters"/>
  <xsl:choose>
    <xsl:when test="contains($in.string,$in.characters)">
      <xsl:value-of select="concat(substring-before($in.string,$in.characters),$out.characters)"/>
      <xsl:call-template name="replace.string.section">
        <xsl:with-param name="in.string" select="substring-after($in.string,$in.characters)"/>
        <xsl:with-param name="in.characters" select="$in.characters"/>
        <xsl:with-param name="out.characters" select="$out.characters"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$in.string"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template> 

如下调用它(此示例将 $some.string 变量中的换行符替换为空格):

    <xsl:call-template name="replace.string.section">
        <xsl:with-param name="in.string" select="$some.string"/>
        <xsl:with-param name="in.characters" select="'&#xA;'"/>
        <xsl:with-param name="out.characters" select="' '"/>
    </xsl:call-template>
于 2008-10-08T21:07:49.547 回答