用 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 属性值中的所有换行符必须由解析器转换为空格,如果您希望解析后看到的值中有换行符,则必须将其作为字符引用转义( &amp;#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>

请注意,我没有使用&amp;#10;,因为 JSoup 在其字符转义和属性值中转义 everything 时相当警惕。它还将现有的数字实体引用替换为等效的 UTF-8,因此时间会证明这是否是一个可以通过的解决方案。

【讨论】:

请注意,使用 JSoup 的缺点是它目前将属性名称转换为小写。有一个open bug 详细说明了这一点。【参考方案2】:

XSLT 仅在 XML 解析器处理 XML 后才能看到它,这将完成属性值规范化。

我认为一些 XML 解析器可以选择抑制属性值规范化。如果您无法访问这样的解析器,我认为在解析之前用&amp;#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 属性中的换行符的主要内容,如果未能解决你的问题,请参考以下文章

如何在 XML 属性中保存换行符?

PHP SimpleXML 不保留 XML 属性中的换行符

LINQ to XML 忽略属性中的换行符

用XSLT替换f:facet属性

用XSLT替换xml中的break元素

替换换行符[重复]