使用Regex修复Java中未转义的XML实体?

时间:2011-07-11 18:13:02

标签: java xml regex entities automata

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

(当前)问题是&符号并不总是正确转义,因此我需要将&转换为&

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

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

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

我认为我需要Push Down Automaton的强大功能才能做到这一点,我认为有限状态机不会因为我认为是内存需求而有效 - 这是正确的吗?如果我需要PDA,那么调用String.replaceAll(String, String)时的正则表达式将无效。或者是否有可以解决此问题的Java正则表达式?

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

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

6 个答案:

答案 0 :(得分:8)

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

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

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

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

的结果
&amp; &amp; &nsbp; &amp;tc., &amp;tc. &amp;tc

如您所见,原始&amp;&nbsp;保持不变。但是,如果您使用“&amp;&amp;”进行尝试,则会获得&amp;&,如果您使用“&amp;&amp;&amp;”进行尝试,则会获得&amp;&&amp;,我将其视为您提到的前瞻性问题的症状。但是,如果您替换该行:

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

使用:

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

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

答案 1 :(得分:5)

我认为你也可以使用前瞻来查看&个字符是否后跟字符&amp;分号(例如&(?!\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&amp;c=3/",
            "Three in a row: &amp;&&amp;",
            "&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.

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:1)

不要试图在所有可能的坏数据上做一些事情,而是一次一个地处理坏数据的出现。有可能生成XML的任何东西都会弄乱一两个字符,但不是所有的字符。这当然是假设。

尝试更换所有&amp;与&amp;除了&amp;之后是amp;。如果您遇到的下一个编码不正确的字符是&lt;,则将它们全部替换为&lt;。保持规则集小而易于管理,只处理你知道错误的事情。

如果您尝试做很多事情,最终可能会更换您不想要的内容并自行处理数据。

我只想指出,最好的解决方案是鼓励生成XML的人在其结束时修复编码。这可能很难问,但如果你专业地向他们解释他们没有生成有效的XML,他们可能愿意修复这些bug。这将有下一个必须消耗它的人的额外好处,不需要做一些疯狂的自定义代码来解决应该在源头解决的问题。至少考虑一下。可能发生的更糟糕的事情是,你问,他们说不,你就在现在的位置。

答案 4 :(得分:0)

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

  1. 从regex
  2. 中识别有效的实体引用并“隐藏
  3. 使用正则表达式替换未转义的字符
  4. 恢复以前的“隐藏”实体参考
  5. 通过将实体包含在自定义字符序列中来完成隐藏。例如“#||<ENTITY_NAME>||#

    为了说明,我们说这个带有未转义字符&的XML代码段:

    <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|apos|gt|lt|quot\)[;]""#||$1||#"。这是因为根据W3C的有效XML实体引用是 amp,lt,gt,apos&amp; 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>
    

    只有有效的实体引用隐藏&happy;未受影响。

    <强>步骤2:
    正则表达式是否将"[&]"替换为"&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]+)\|\|#"替换为"&$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>
    


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

答案 5 :(得分:0)

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

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

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

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