使用正则表达式修复 Java 中未转义的 XML 实体?
Posted
技术标签:
【中文标题】使用正则表达式修复 Java 中未转义的 XML 实体?【英文标题】:Fixing unescaped XML entities in Java with Regex? 【发布时间】:2011-10-03 00:42:27 【问题描述】:我必须解析一些格式错误的 XML。无法解决上游问题。
(当前的)问题是和号字符并不总是正确转义,所以我需要将&
转换为&
如果&
已经存在,我不想将其更改为&
。一般来说,如果任何结构良好的实体已经存在,我不想破坏它。一般来说,我认为不可能知道可能出现在任何特定 XML 文档中的所有实体,因此我想要一个保留 &<characters>;
之类的解决方案。
其中<characters>
是一些字符集,用于定义初始&amp;amp;
和结束;
之间的实体。特别是,<
和 >
是 not 否则将表示 XML 元素的文字。
现在,在解析时,如果我看到&<characters>
,我不知道我会遇到;
、(空格)、行尾还是另一个
&amp;amp;
。所以我认为我必须记住<characters>
,因为我期待一个能告诉我如何处理原始&amp;amp;
的角色。
我认为我需要下推自动机的力量才能做到这一点,我认为有限状态机不会工作,因为我认为这是内存要求 - 对吗? 如果我需要 PDA,那么在调用 String.replaceAll(String, String)
时使用正则表达式将不起作用。或者有没有可以解决这个问题的Java regex?
记住:每行可能有多个替换。
(我知道this question,但它没有提供我正在寻找的答案。)
【问题讨论】:
【参考方案1】:这是您要查找的正则表达式:&([^;\\W]*([^;\\w]|$))
,相应的替换字符串将是 &amp;$1
。它匹配&amp;
,后跟零个或多个非分号或分号(它需要允许零来匹配独立的&符号),然后是一个分号非的分词(或线端)。捕获组允许您使用您正在寻找的&amp;
进行替换。
下面是一些使用它的示例代码:
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
如您所见,原来的&amp;
和&nbsp;
保持不变。但是,如果你用“&&”尝试它,你会得到&amp;&
,如果你用“&&&”尝试它,你会得到&amp;&&amp;
,我认为这是你所暗示的前瞻问题的症状。但是,如果您替换该行:
final String t = s.replaceAll(regex, replacement);
与:
final String t = s.replaceAll(regex, replacement).replaceAll(regex, replacement);
它适用于所有这些字符串以及我能想到的任何其他字符串。 (在成品中,您可能会编写一个例程来执行此双重 replaceAll
调用。)
【讨论】:
谢谢本!我试过这样的东西,但无法让它适用于所有测试输入。我不知道我的整个测试套件是否适合评论。也许我应该用我想出的东西写一个答案。同时,请尝试使用“&&”(预期“&&”)和“&→&”(预期“&→&”)等输入的解决方案 它适用于“&→&”,但如果您执行两次,则仅适用于“&&”。我会根据所学到的经验相应地扩展我的答案。 是的,我也想过重复应用正则表达式,但这对我来说似乎很奇怪,但也许还不错 - 我想知道在所有情况下只有双重应用就足够了吗? 我还想知道在否定字符类中是否缺少与号。难道你不想说“找到一个 & 符号并扫描,直到你看到空格/分号、分号或另一个 & 符号?”像“&foo&bar;”这样的东西应该产生“&foo&bar;”我认为“&→&”之所以有效,是因为第一个&符号与自身匹配。 一个 & 符号算作一个分词,至少在我的设置下(我可以想象有一个不同的编码它不会工作,但我不知道其中)。我测试了“&foo&bar;”使用双重应用程序,它会按照您的建议生成&amp;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: &&&",
"< is <, > is >, ' is ', etc."
))
System.out.println(
UNESCAPED_AMPERSAND.matcher(s).replaceAll("&")
);
// Output:
// http://www.example.com/?a=1&b=2&c=3/
// Three in a row: &&&
// < is <, > is >, ' is ', etc.
【讨论】:
你刚刚救了我的命。泰! 如果你使用&(?!(#\d+|\w+);)
,它会处理数字转义,例如&#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
然后实现一个逐字符读取实际输入的方法。当它看到一个 & 符号时,它会切换到“实体模式”并寻找一个有效的实体引用 (& Name ;
)。如果它在Name
中不允许的第一个字符之前找到一个,则将其逐字写入输出。否则,它会写入 &amp;
,然后是 & 符号之后的所有内容。
【讨论】:
这是一个非常有趣的方法,相当优雅干净。 我真的很想知道 FSM 是否足够好,或者是否需要 PDA。我会在问题中强调这一点。 有限状态机就足够了。您有 '&' 后跟一组定义的字符,然后是 ';'。 顺便说一句,以这种方式使用InputStream
将限制您使用 ASCII、ISO-8859-X、UTF-8 或其他仅保留 ASCII 子集的字符集。如果您正在处理一个不这样做的字符集(我认为一些亚洲字符集不这样做),则该过程会变得更加困难。
是的,它实际上比这复杂得多。多年前,我曾在一个系统上工作,该系统实现了一些非常复杂的 XML 解析,以处理面对许多字符集的格式错误的文档。一个通用的解决方案并不容易。对于这个问题,我没有做任何与此相关的事情。【参考方案4】:
不要尝试对所有可能的不良数据进行一般性的处理,而是一次处理出现的不良数据。有可能生成 XML 的东西会弄乱一两个字符,但不是所有字符。这当然是一个假设。
尝试将所有 & 替换为 & 除了 & 后跟 amp; 时。如果您遇到的下一个编码不正确的字符是
如果您尝试做太多事情,最终可能会替换您不打算做的事情并自己弄乱数据。
我还想指出,最好的解决方案是鼓励生成 XML 的人自行修复编码。问这个问题可能很尴尬,但如果你专业地向他们解释他们没有生成有效的 XML,他们可能愿意修复错误。这将为下一个必须使用它的人带来额外的好处,不需要做一些疯狂的自定义代码来解决应该从源头解决的问题。至少考虑一下。可能发生的更糟糕的事情是,您提出要求,他们说不,而您现在就在原地。
【讨论】:
感谢您的见解!是的,我试图让它保持小 - 我没有修复坏的 我真的很想知道 FSM 是否足够好,或者是否需要 PDA。我会在问题中强调这一点。【参考方案5】:很抱歉搅起了老话题: 我遇到了同样的问题,我使用的解决方法分为 3 个步骤:
-
识别有效的实体引用并在正则表达式中“隐藏”它们
使用正则表达式替换非转义字符
恢复以前“隐藏”的实体引用
隐藏是通过将实体包含在自定义字符序列中来完成的。例如"#||<ENTITY_NAME>||#
"
为了说明,假设我们有这个带有非转义字符 &
的 XML sn-p:
<NAME>Testname</NAME>
<VALUE>
random words one & two
I am sad&happy; at the same time!
its still < ecstatic
It is two & three words
Short form is 2&three
Now for some invalid entity refs: &, >, and < too.
</VALUE>
第 1 步:
我们使用正则表达式将"[&]\(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: &, >, and < too.
</VALUE>
只有有效的实体引用被隐藏。 &happy;
保持不变。
第 2 步:
用"&amp;"
替换正则表达式"[&]"
。
字符串现在看起来像这样:
<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>
第 3 步:
将正则表达式替换为"#\|\|([a-z]+)\|\|#"
为"&$1;"
。
最终更正后的字符串现在看起来像这样:
<NAME>Testname</NAME>
<VALUE>
random words one & two
I am sad&happy; at the same time!
its still < ecstatic
It is two & three words
Short form is 2&three
Now for some invalid entity refs: &amp, &gt, and &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 实体?的主要内容,如果未能解决你的问题,请参考以下文章