使用正则表达式修复 Java 中未转义的 XML 实体?

Posted

技术标签:

【中文标题】使用正则表达式修复 Java 中未转义的 XML 实体?【英文标题】:Fixing unescaped XML entities in Java with Regex? 【发布时间】:2011-10-03 00:42:27 【问题描述】:

我必须解析一些格式错误的 XML。无法解决上游问题。

(当前的)问题是和号字符并不总是正确转义,所以我需要将& 转换为&

如果& 已经存在,我不想将其更改为&。一般来说,如果任何结构良好的实体已经存在,我不想破坏它。一般来说,我认为不可能知道可能出现在任何特定 XML 文档中的所有实体,因此我想要一个保留 &<characters>; 之类的解决方案。

其中<characters> 是一些字符集,用于定义初始& 和结束; 之间的实体。特别是,<>not 否则将表示 XML 元素的文字。

现在,在解析时,如果我看到&<characters>,我不知道我会遇到;(空格)、行尾还是另一个&。所以我认为我必须记住<characters>,因为我期待一个能告诉我如何处理原始&的角色。

我认为我需要下推自动机的力量才能做到这一点,我认为有限状态机不会工作,因为我认为这是内存要求 - 对吗? 如果我需要 PDA,那么在调用 String.replaceAll(String, String) 时使用正则表达式将不起作用。或者有没有可以解决这个问题的Java regex?

记住:每行可能有多个替换。

(我知道this question,但它没有提供我正在寻找的答案。)

【问题讨论】:

【参考方案1】:

这是您要查找的正则表达式:&([^;\\W]*([^;\\w]|$)),相应的替换字符串将是 &$1。它匹配&,后跟零个或多个非分号或分号(它需要允许零来匹配独立的&符号),然后是一个分号的分词(或线端)。捕获组允许您使用您正在寻找的& 进行替换。

下面是一些使用它的示例代码:

String s = "& & &nsbp; &tc., &tc. &tc";
final String regex = "&([^;\\W]*([^;\\w]|$))";
final String replacement = "&$1";
final String t = s.replaceAll(regex, replacement);

在沙箱中运行后,我得到以下 t 的结果:

& & &nsbp; &tc., &tc. &tc

如您所见,原来的&  保持不变。但是,如果你用“&&”尝试它,你会得到&&,如果你用“&&&”尝试它,你会得到&&&,我认为这是你所暗示的前瞻问题的症状。但是,如果您替换该行:

final String t = s.replaceAll(regex, replacement);

与:

final String t = s.replaceAll(regex, replacement).replaceAll(regex, replacement);

它适用于所有这些字符串以及我能想到的任何其他字符串。 (在成品中,您可能会编写一个例程来执行此双重 replaceAll 调用。)

【讨论】:

谢谢本!我试过这样的东西,但无法让它适用于所有测试输入。我不知道我的整个测试套件是否适合评论。也许我应该用我想出的东西写一个答案。同时,请尝试使用“&&”(预期“&&”)和“&→&”(预期“&→&”)等输入的解决方案 它适用于“&→&”,但如果您执行两次,则仅适用于“&&”。我会根据所学到的经验相应地扩展我的答案。 是的,我也想过重复应用正则表达式,但这对我来说似乎很奇怪,但也许还不错 - 我想知道在所有情况下只有双重应用就足够了吗? 我还想知道在否定字符类中是否缺少与号。难道你不想说“找到一个 & 符号并扫描,直到你看到空格/分号、分号或另一个 & 符号?”像“&foo&bar;”这样的东西应该产生“&foo&bar;”我认为“&→&”之所以有效,是因为第一个&符号与自身匹配。 一个 & 符号算作一个分词,至少在我的设置下(我可以想象有一个不同的编码它不会工作,但我不知道其中)。我测试了“&foo&bar;”使用双重应用程序,它会按照您的建议生成&foo&bar;。 (我还用双重应用程序测试了“&→&”,它仍然有效。)我还用连续 6 个 & 符号测试了双重应用程序,它取代了所有这些。【参考方案2】:

我认为您还可以使用前瞻来查看 & 字符后是否跟有字符和分号(例如 &(?!\w+;))。这是一个例子:

import java.util.*;
import java.util.regex.*;

public class HelloWorld
    private static final Pattern UNESCAPED_AMPERSAND =
        Pattern.compile("&(?!(#\\d+|\\w+);)");
     public static void main(String []args)
        for (String s : Arrays.asList(
            "http://www.example.com/?a=1&b=2&c=3/",
            "Three in a row: &&&",
            "&lt; is <, &gt; is >, &apos; is ', etc."
        )) 
            System.out.println(
                UNESCAPED_AMPERSAND.matcher(s).replaceAll("&amp;")
            );        
        
     


// Output:
// http://www.example.com/?a=1&amp;b=2&amp;c=3/
// Three in a row: &amp;&amp;&amp;
// &lt; is <, &gt; is >, &apos; is ', etc.

【讨论】:

你刚刚救了我的命。泰! 如果你使用&amp;(?!(#\d+|\w+);),它会处理数字转义,例如&amp;#160; &(?!\w*;) 不对,&(?!\w+;) 更好,但我相信杰克的答案是最好的。 更新答案以纳入上述反馈。【参考方案3】:

首先了解实体的语法:http://www.w3.org/TR/xml/#NT-EntityRef

然后查看FilterInputStream的JavaDoc:http://download.oracle.com/javase/6/docs/api/java/io/FilterInputStream.html

然后实现一个逐字符读取实际输入的方法。当它看到一个 & 符号时,它会切换到“实体模式”并寻找一个有效的实体引用 (&amp; Name ;)。如果它在Name 中不允许的第一个字符之前找到一个,则将其逐字写入输出。否则,它会写入 &amp;amp;,然后是 & 符号之后的所有内容。

【讨论】:

这是一个非常有趣的方法,相当优雅干净。 我真的很想知道 FSM 是否足够好,或者是否需要 PDA。我会在问题中强调这一点。 有限状态机就足够了。您有 '&' 后跟一组定义的字符,然后是 ';'。 顺便说一句,以这种方式使用 InputStream 将限制您使用 ASCII、ISO-8859-X、UTF-8 或其他仅保留 ASCII 子集的字符集。如果您正在处理一个不这样做的字符集(我认为一些亚洲字符集不这样做),则该过程会变得更加困难。 是的,它实际上比这复杂得多。多年前,我曾在一个系统上工作,该系统实现了一些非常复杂的 XML 解析,以处理面对许多字符集的格式错误的文档。一个通用的解决方案并不容易。对于这个问题,我没有做任何与此相关的事情。【参考方案4】:

不要尝试对所有可能的不良数据进行一般性的处理,而是一次处理出现的不良数据。有可能生成 XML 的东西会弄乱一两个字符,但不是所有字符。这当然是一个假设。

尝试将所有 & 替换为 & 除了 & 后跟 amp; 时。如果您遇到的下一个编码不正确的字符是

如果您尝试做太多事情,最终可能会替换您不打算做的事情并自己弄乱数据。

我还想指出,最好的解决方案是鼓励生成 XML 的人自行修复编码。问这个问题可能很尴尬,但如果你专业地向他们解释他们没有生成有效的 XML,他们可能愿意修复错误。这将为下一个必须使用它的人带来额外的好处,不需要做一些疯狂的自定义代码来解决应该从源头解决的问题。至少考虑一下。可能发生的更糟糕的事情是,您提出要求,他们说不,而您现在就在原地。

【讨论】:

感谢您的见解!是的,我试图让它保持小 - 我没有修复坏的 我真的很想知道 FSM 是否足够好,或者是否需要 PDA。我会在问题中强调这一点。【参考方案5】:

很抱歉搅起了老话题: 我遇到了同样的问题,我使用的解决方法分为 3 个步骤:

    识别有效的实体引用并在正则表达式中“隐藏”它们 使用正则表达式替换非转义字符 恢复以前“隐藏”的实体引用

隐藏是通过将实体包含在自定义字符序列中来完成的。例如"#||&lt;ENTITY_NAME&gt;||#"

为了说明,假设我们有这个带有非转义字符 &amp; 的 XML sn-p:

<NAME>Testname</NAME>
<VALUE>
    random words one &amp; two
    I am sad&happy; at the same time!
    its still &lt; ecstatic
    It is two & three words
    Short form is 2&three
    Now for some invalid entity refs: &amp, &gt, and &lt too.
</VALUE>

第 1 步: 我们使用正则表达式将"[&amp;]\(amp|apos|gt|lt|quot\)[;]" 替换为"#||$1||#"。这是因为根据 W3C 的有效 XML 实体引用是 amp,lt,gt,apos & quot。 字符串现在看起来像这样:

<NAME>Testname</NAME>
<VALUE>
    random words one #||amp||# two
    I am sad&happy; at the same time!
    its still #||lt||# ecstatic
    It is two & three words
    Short form is 2&three
    Now for some invalid entity refs: &amp, &gt, and &lt too.
</VALUE>

只有有效的实体引用被隐藏&amp;happy; 保持不变。

第 2 步:"&amp;amp;" 替换正则表达式"[&amp;]"。 字符串现在看起来像这样:

<NAME>Testname</NAME>
<VALUE>
    random words one #||amp||# two
    I am sad&amp;happy; at the same time!
    its still #||lt||# ecstatic
    It is two &amp; three words
    Short form is 2&amp;three
    Now for some invalid entity refs: &amp;amp, &amp;gt, and &amp;lt too.
</VALUE>

第 3 步: 将正则表达式替换为"#\|\|([a-z]+)\|\|#""&amp;$1;"。 最终更正后的字符串现在看起来像这样:

<NAME>Testname</NAME>
<VALUE>
    random words one &amp; two
    I am sad&amp;happy; at the same time!
    its still &lt; ecstatic
    It is two &amp; three words
    Short form is 2&amp;three
    Now for some invalid entity refs: &amp;amp, &amp;gt, and &amp;lt too.
</VALUE>

缺点: 必须仔细选择用于隐藏有效实体的自定义字符序列,以确保没有任何有效内容会偶然包含相同的序列。虽然机会很小,但承认,这不是一个万无一失的解决方案......

【讨论】:

【参考方案6】:

我使用了上面的UNESCAPED_AMPERSAND 解决方案,但我不得不将正则表达式更改为

private static final Pattern UNESCAPED_AMPERSAND =
        Pattern.compile("&(?!(#\\d+|#x[0-9a-fA-F]+|\\w+);)");

添加 |#x[0-9a-fA-F]+ 以说明十六进制字符引用。

(我想对该解决方案发表评论,但显然我不能。)

【讨论】:

以上是关于使用正则表达式修复 Java 中未转义的 XML 实体?的主要内容,如果未能解决你的问题,请参考以下文章

java转义符和正则表达式转义符

java 正则 二次转义

[\s\S]* 有啥区别?和 。*?在 Java 正则表达式中?

java中正则表达式要进行转义的字符。

Java正则表达式转义撇号[重复]

java 和 JS(javaScript)中的反斜杠正则转义