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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/184431/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 12:08:41  来源:igfitidea点击:

Converting XML to plain text - how should I ignore/handle whitespace in the XSLT?

xmlxsltwhitespacedokuwiki

提问by PotatoEngineer

I'm trying to convert an XML file into the markup used by dokuwiki, using XSLT. This actually works to some degree, but the indentation in the XSL file is getting inserted into the results. At the moment, I have two choices: abandon this XSLT thing entirely, and find another way to convert from XML to dokuwiki markup, or delete about 95% of the whitespace from the XSL file, making it nigh-unreadable and a maintenance nightmare.

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

Is there some way to keep the indentation in the XSL file without passing all that whitespace on to the final document?

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

Background: I'm migrating an autodoc tool from static HTML pages over to dokuwiki, so the API developed by the server team can be further documented by the applications team whenever the apps team runs into poorly-documented code. The logic is to have a section of each page set aside for the autodoc tool, and to allow comments anywhere outside this block. I'm using XSLT because we already have the XSL file to convert from XML to XHTML, and I'm assuming it will be faster to rewrite the XSL than to roll my own solution from scratch.

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

Edit: Ah, right, foolish me, I neglected the indent attribute. (Other background note: I am new to XSLT.) On the other hand, I still have to deal with newlines. Dokuwiki uses pipes to differentiate between table columns, which means that all of the data in a table line must be on one line. Is there a way to suppress newlines being outputted (just occasionally), so I can do some fairly complex logic for each table cell in a somewhat readable fasion?

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

回答by JeniT

There are three reasons for getting unwanted whitespace in the result of an XSLT transformation:

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

  1. whitespace that comes from between nodes in the source document
  2. whitespace that comes from within nodes in the source document
  3. whitespace that comes from the stylesheet
  1. 来自源文档中节点之间的空白
  2. 来自源文档中节点内的空白
  3. 来自样式表的空白

I'm going to talk about all three because it can be hard to tell where whitespace comes from so you might need to use several strategies.

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

To address the whitespace that is between nodes in your source document, you should use <xsl:strip-space>to strip out any whitespace that appears between two nodes, and then use <xsl:preserve-space>to preserve the significant whitespace that might appear within mixed content. For example, if your source document looks like:

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

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

then you will want to ignore the whitespace between the <ul>and the <li>and between the </li>and the </ul>, which is not significant, but preserve the whitespace between the <strong>and <em>elements, which issignificant (otherwise you'd get "This is an **important***point*"). To do this use

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

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

The elementsattribute on <xsl:preserve-space>should basically list all the elements in your document that have mixed content.

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

Aside: using <xsl:strip-space>also reduces the size of the source tree in memory, and makes your stylesheet more efficient, so it's worth doing even if you don't have whitespace problems of this sort.

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

To address the whitespace that appears within nodes in your source document, you should use normalize-space(). For example, if you have:

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

<dt>
  a definition
</dt>

and you can be sure that the <dt>element won't hold any elements that you want to do something with, then you can do:

并且您可以确定该<dt>元素不会包含您想要对其进行处理的任何元素,然后您可以执行以下操作:

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

The leading and trailing whitespace will be stripped from the value of the <dt>element and you will just get the string "a definition".

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

To address whitespace coming from the stylesheet, which is perhaps the one you're experiencing, is when you have text within a template like this:

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

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

XSLT stylesheets are parsed in the same way as the source documents that they process, so the above XSLT is interpreted as a tree that holds an <xsl:template>element with a matchattribute whose first child is a text node and whose second child is a <xsl:value-of>element with a selectattribute. The text node has leading and trailing whitespace (including line breaks); since it's literal text in the stylesheet, it gets literally copied over into the result, with all the leading and trailing whitespace.

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

But somewhitespace in XSLT stylesheets get stripped automatically, namely those between nodes. You don't get a line break in your result because there's a line break between the <xsl:value-of>and the close of the <xsl:template>.

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

To get only the text you want in the result, use the <xsl:text>element like this:

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

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

The XSLT processor will ignore the line breaks and indentation that appear between nodes, and only output the text within the <xsl:text>element.

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

回答by Lindsay

Are you using indent="no" in your output tag?

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

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

Also if you're using xsl:value-of you can use the disable-output-escaping="yes" to help with some whitespace issues.

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

回答by Dan

@JeniT's answer is great, I just want to point out a trick for managing whitespace. I'm not certain it's the best way (or even a good way), but it works for me for now.

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

("s" for space, "e" for empty, "n" for newline.)

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

Applied to:

应用于:

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

Outputs:

输出:

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

The 'e' trick works prior to a text node containing at least one non-whitespace character because it expands to this:

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

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

Since the rules for stripping whitespacesay that whitespace-only text nodes get stripped, the newline and indentation between the <xsl:template> and <xsl:text> get stripped (good). Since the rules say a text node with at least one whitespace character is preserved, the implicit text node containing " This line will be output indented two spaces."keeps its leading whitespace (but I guess this also depends on the settings for strip/preserve/normalize). The "&n;" at the end of the line inserts a newline, but it also ensures that any following whitespace is ignored, because it appears between two nodes.

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

The trouble I have is when I want to output an indented line that begins with an <xsl:value-of>. In that case, the "&e;" won't help, because the indentation whitespace isn't "attached" to any non-whitespace characters. So for those cases, I use "&s2;" or "&s4;", depending on how much indentation I want.

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

It's an ugly hack I'm sure, but at least I don't have the verbose "<xsl:text>" tags littering my XSLT, and at least I can still indent the XSLT itself so it's legible. I feel like I'm abusing XSLT for something it was not designed for (text processing) and this is the best I can do.

我敢肯定这是一个丑陋的黑客,但至少我没有冗长的“<xsl:text>”标签乱扔我的 XSLT,至少我仍然可以缩进 XSLT 本身,所以它是清晰的。我觉得我在滥用 XSLT,因为它不是为(文本处理)设计的,这是我能做的最好的事情。



Edit:In response to comments, this is what it looks like without the "macros":

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

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

I think that makes it less clear to see the intended output indentation, and it screws up the indentation of the XSL itself because the </xsl:text>end tags have to appear at column 1 of the XSL file (otherwise you get undesired whitespace in the output file).

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

回答by Odilon Redo

Regarding your edit about new lines, you can use this template to recursively replace one string within another string, and you can use it for line breaks:

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

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

Call it as follows (this example replaces line breaks in the $some.string variable with a space):

按如下方式调用它(此示例用空格替换 $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>