题 将XML转换为纯文本 - 我应该如何忽略/处理XSLT中的空格?


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

有没有办法在XSL文件中保留缩进而不将所有空格传递给最终文档?

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

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


36
2017-10-08 19:22


起源




答案:


在XSLT转换的结果中获得不需要的空格有三个原因:

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

我将谈论所有三个,因为很难说出空白来自哪里,所以你可能需要使用几种策略。

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

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

那么你会想要忽略之间的空白 <ul> 和 <li> 和之间 </li> 和 </ul>,这并不重要,但保留了之间的空白 <strong> 和 <em> 元素,其中  显着(否则你会得到“这是一个**重要的***点*”)。要做到这一点

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

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

旁白:使用 <xsl:strip-space> 还减少了内存中源代码树的大小,使得样式表更有效率,所以即使你没有这种空白问题也值得去做。

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

<dt>
  a definition
</dt>

你可以肯定的 <dt> element不会包含你想要做的任何元素,那么你可以这样做:

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

前导和尾随空格将从中删除 <dt> 元素,你将得到字符串 "a definition"

要解决来自样式表的空白区域(也许就是您正在经历的那个),就是当您在模板中包含文本时,如下所示:

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

XSLT样式表的解析方式与它们处理的源文档的解析方式相同,因此上面的XSLT被解释为一个包含它的树。 <xsl:template> 元素与 match 第一个子节点是文本节点,第二个子节点是a的属性 <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> 元件。


76
2017-10-08 21:46



这非常有帮助!谢谢。 - Black
这确实很有帮助,但我对你使用“节点之间”这个短语感到困惑。是不是所有的空格都包含在文本节点中? “节点之间”是什么意思?如果我没有认出你的名字,我会认为你需要一个关于XML文档结构的讲座。 - LarsH
好文章,谢谢!但严格来说,你使用的术语“节点”实际上意味着“元素”。 - rustyx
@LarsH:我在这里不在我的领域(而且已经晚了几个月),但我认为这回答了你的问题: w3.org/TR/xslt#strip “...剥离了一些文本节点。除非文本节点只包含空白字符,否则永远不会剥离文本节点。” “如果文本节点包含至少一个非空白字符,则保留文本节点。” - Dan


你在输出标签中使用indent =“no”吗?

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

此外,如果你正在使用xsl:value-of,你可以使用disable-output-escaping =“yes”来帮助解决一些空白问题。


4
2017-10-08 19:26



大多数时候,使用 disable-output-escaping 是做错事的错误方式。它只适用于非常有限的情况。以这种一般方式向不熟悉的人宣传d-o-e可能比有用更有害。看到 dpawson.co.uk/xsl/sect2/N2215.html#d3702e223 - LarsH


@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." 保持其领先的空白(但我想这也取决于strip / preserve / normalize的设置)。然后;”在行的末尾插入换行符,但它也确保忽略任何后续空格,因为它出现在两个节点之间。

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

这是一个丑陋的黑客,我敢肯定,但至少我没有乱七八糟的“<xsl:text>”标签乱丢我的XSLT,至少我仍然可以缩进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列(否则您会在输出文件中得到不需要的空格)。


3
2018-01-16 05:01



@Dan:首先, xsl:text 它不是冗长的,你总是可以使用concat xsl:value-of。其次,你没有处理文本,你的输出是纯文本。
@Dan:最后。您的解决方案是针对XSLT的,因为这些实体(正确声明)是XML文档的表面语法(在本例中为样式表)的一部分。因此,在到达XSLT处理器之前,替换在解析fase中需要时间。一旦完成替换并且有 新元素 在样式表中,应用剥离/保留空白的规则仅应用文本节点。从读者的角度来看,你的样式表结果是什么并不清楚。
@Alejandro:感谢您的反馈。如果你已经习惯了XML,我认为它并不冗长...我的背景更多是lex / yacc / C ++所以我肯定感觉不到我的元素。我想使用XML编辑器和文本编辑器可能有所帮助。 - Dan
@Alejandro:关于它是否清楚......我想这是一个意见问题。要么使用 xsl:text或者 &e; 类型“宏”比问题中提出的替代方案更好:“从XSL文件中删除大约95%的空白,使其难以理解并且是维护噩梦。” - Dan
@Dan:什么表明这不是一个意见问题是需要 &s2; 代替 &e; 在某些情况下,为了相同的效果。


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

<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>

0
2017-10-08 21:07