用 XSLT 替换 XML 属性中的换行符
Posted
技术标签:
【中文标题】用 XSLT 替换 XML 属性中的换行符【英文标题】:Replacing newlines in XML attributes with XSLT 【发布时间】:2013-06-29 10:49:51 【问题描述】:我需要一些 XSLT(或 something - 见下文)将所有属性中的换行符替换为替代字符。
我必须处理将所有数据存储为属性的遗留 XML,并使用换行符来表达基数。例如:
<sample>
<p att="John
Paul
Ringo"></p>
</sample>
当我在 Java 中解析文件时(根据 XML 规范),这些换行符被替换为空格,但是我希望将它们视为一个列表,因此这种行为并不是特别有用。
我的“解决方案”是使用 XSLT 将所有属性中的所有换行符替换为其他分隔符 - 但我对 XSLT 的了解为零。到目前为止,我看到的所有示例要么非常具体,要么替换了节点内容而不是属性值。
我已经涉足 XSLT 2.0 的 replace()
,但很难将所有内容放在一起。
XSLT 甚至是正确的解决方案吗?使用下面的 XSLT:
<xsl:template match="sample/*">
<xsl:for-each select="@*">
<xsl:value-of select="replace(current(), '\n', '|')"/>
</xsl:for-each>
</xsl:template>
使用 Saxon 应用于示例 XML 输出以下内容:
John Paul Ringo
显然这种格式不是我所追求的——这只是为了试验replace()
——但是当我们进行 XSLT 处理时,换行符是否已经被规范化了?如果是这样,是否有任何其他方法可以使用 Java 解析器将这些值解析为书面形式?到目前为止,我只使用过 JAXB。
【问题讨论】:
我有一种非常讨厌的感觉,我可能需要戴上橡胶手套并在解析之前在 XML 字符串上实现一个肮脏的正则表达式。不幸的是,我无法控制正在生成的 XML。 实际上不,这太可怕了,无法考虑。 如果属性值中的空格在语义上很重要,那么您就不是在处理 XML,您需要使用非 XML 工具来处理它。 Per spec 属性值中的所有换行符必须由解析器转换为空格,如果您希望解析后看到的值中有换行符,则必须将其作为字符引用转义(&#10;
)
我不同意你的观点。 XML 是从将保持无名的应用程序中导出的。这不是完全应用程序的错,尽管在属性中填充所有数据可能是一种有点可疑的方法。我怀疑用户通过使用应用程序盲目地将其原封不动地导出到 XML 的换行符来解决此特定字段缺乏 1:M 基数的问题。
我可能会对任何为可疑 XML 设计的 Java 库进行一些研究——这不可能是一个孤立的实例,所以我确信那里有人编写了一个故意松散/宽容的解析器。
【参考方案1】:
我已经通过使用JSoup 预处理XML 解决了这个问题(这是对@Ian Roberts 关于使用非XML 工具解析XML 的评论的一个点头)。 JSoup 是(或曾经)为 html 文档设计的,但在这种情况下效果很好。
我的代码如下:
@Test
public void verifyNewlineEscaping()
final List<Node> nodes = Parser.parseXmlFragment(FileUtils.readFileToString(sourcePath.toFile(), "UTF-8"), "");
fixAttributeNewlines(nodes);
// Reconstruct XML
StringBuilder output = new StringBuilder();
for (Node node : nodes)
output.append(node.toString());
// Print cleansed output to stdout
System.out.println(output);
/**
* Replace newlines and surrounding whitespace in XML attributes with an alternative delimiter in
* order to avoid whitespace normalisation converting newlines to a single space.
*
* <p>
* This is useful if newlines which have semantic value have been incorrectly inserted into
* attribute values.
* </p>
*
* @param nodes nodes to update
*/
private static void fixAttributeNewlines(final List<Node> nodes)
/*
* Recursively iterate over all attributes in all nodes in the XML document, performing
* attribute string replacement
*/
for (final Node node : nodes)
final List<Attribute> attributes = node.attributes().asList();
for (final Attribute attribute : attributes)
// JSoup reports whitespace as attributes
if (!StringUtils.isWhitespace(attribute.getValue()))
attribute.setValue(attribute.getValue().replaceAll("\\s*\r?\n\\s*", "|"));
// Recursively process child nodes
if (!node.childNodes().isEmpty())
fixAttributeNewlines(node.childNodes());
对于我的问题中的示例 XML,此方法的输出是:
<sample>
<p att="John|Paul|Ringo"></p>
</sample>
请注意,我没有使用&#10;
,因为 JSoup 在其字符转义和属性值中转义 everything 时相当警惕。它还将现有的数字实体引用替换为等效的 UTF-8,因此时间会证明这是否是一个可以通过的解决方案。
【讨论】:
请注意,使用 JSoup 的缺点是它目前将属性名称转换为小写。有一个open bug 详细说明了这一点。【参考方案2】:XSLT 仅在 XML 解析器处理 XML 后才能看到它,这将完成属性值规范化。
我认为一些 XML 解析器可以选择抑制属性值规范化。如果您无法访问这样的解析器,我认为在解析之前用&#x0A;
对 (\r?\n) 进行文本替换可能是您最好的逃生路线。以这种方式转义的换行符不会被属性值规范化处理。
【讨论】:
谢谢迈克尔。在进行了合理的挖掘之后,我想出了一个空白,试图找到一个基于 Java 的解析器,它允许抑制属性值规范化。文本替换很困难,因为我无法控制正在生成的 XML。这意味着我不能将替换限制为属性值。【参考方案3】:似乎很难做到这一点。正如我在Are line breaks in XML attribute values allowed? 中发现的那样 - 属性中的换行符是有效的,但 XML 解析器对其进行了规范化(https://***.com/a/8188290/1324394),因此它可能在处理之前丢失(因此在替换之前)。
【讨论】:
我也看到了,但我希望他们仍然会在那里进行一些 XSLT 修复。从那以后,我发现jdom.org 通过不声称自己是 XML 解析器来绕过这个问题,这大概可以减轻它必须遵守 XML 规范的麻烦。现在要试一试... 只是大声思考,你可以做这样的事情replace(/data/@value, '\s2,10','|')
- 它不是绝对正确的,因为它依赖于会有多个空格而不是换行符,但它可以完成工作。跨度>
@JirkaŠ。不,那是行不通的,因为 XML 解析器在数据到达 XPath 数据模型之前会将属性值中的所有连续空格折叠到一个空格中。
我对此感到害怕,但我在 Altova 中尝试过,它奏效了。可能只是 Altova 的特异性。
啊,我发现我错过了spec 中的关键语句:“所有未读取声明的属性都应该由非验证处理器处理,就像声明了 CDATA 一样。” - 因此,如果您没有 DTD,解析器将用空格替换换行符,但 不会 将连续的空格折叠成一个空格。以上是关于用 XSLT 替换 XML 属性中的换行符的主要内容,如果未能解决你的问题,请参考以下文章