如何为 XML 编写正则表达式以删除除 CDATA 之外的未转义的符号字符?
How to write regex for XML which removes unescaped ampersand characters except CDATA?
例如,我 XML 是这样的:
<title>Very bad XML with & (unescaped)</title>
<title>Good XML with & and > (escaped)</title>
<title><![CDATA[ Good XML with & in CDATA ]]></title>
我的任务是从 XML 中删除无效的符号字符,但排除 CDATA 中的那些符号字符。
我找到了一个正则表达式:
&(?!(?:apos|quot|[gl]t|amp);|#)
但不幸的是,它还从 CDATA 中删除了 & 字符。如何更改此正则表达式以满足我的任务?
如您所知,"XML" 不是 XML,因为 CDATA 之外的未转义 &
。
因此,您必须在没有 XML 解析器区分 CDATA 和 PCDATA 的情况下进行预处理。这很粗糙,由于 regex isn't up to parsing XML.
的所有原因,正则表达式不能胜任这项任务
这是一种可以提供帮助的方法:
- 使用正则表达式将所有孤立的(不是字符实体的一部分)
&
字符替换为 &TEMP
,包括 CDATA 中的字符。
- 在现在格式正确的 XML 上使用 XML 解析器,将 CDATA 中出现的
&TEMP
恢复为 &
。
另请参阅:
- 关于解析混乱的一般建议"XML"
- 容忍解析器
- 用于匹配无效字符的正则表达式和
&
的
作为对@kjughes 回答的补充,编写一个程序来提取“&”字符是相当简单的,尽管这是一个相当无聊的练习。由于CDATA
s不能嵌套,所以很容易标记标签的开闭。
这是一个这样的程序:
final int NOCDATA = -1;
final int OPEN_CDATA0 = 0; //!
final int OPEN_CDATA1 = 1; //![
final int OPEN_CDATA2 = 2; //![C
final int OPEN_CDATA3 = 3; //![CD
final int OPEN_CDATA4 = 4; //![CDA
final int OPEN_CDATA5 = 5; //![CDAT
final int OPEN_CDATA6 = 6; //![CDATA
final int INSIDE_CDATA = 7; //![CDATA[
final int CLOSE_CDATA0 = 8; //]
String xml = "<title>Very bad XML with & (unescaped)</title>\n" +
"<title>Good XML with & and > (escaped)</title>\n" +
"<title><![CDATA[ Good XML with & in CDATA && ]]></title><title>Very bad XML with ![CDATA[&]] && (unescaped)</title>";
StringBuilder result = new StringBuilder();
Reader reader = new BufferedReader(new StringReader(xml));
int r;
int state = NOCDATA;
while((r = reader.read()) != -1) {
char c = (char)r;
switch(c) {
case '!':
if(state == NOCDATA)
state = OPEN_CDATA0;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case '[':
if(state == OPEN_CDATA0)
state = OPEN_CDATA1;
else if(state == OPEN_CDATA6)
state = INSIDE_CDATA;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'C':
if(state == OPEN_CDATA1)
state = OPEN_CDATA2;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'D':
if(state == OPEN_CDATA2)
state = OPEN_CDATA3;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'A':
if(state == OPEN_CDATA3)
state = OPEN_CDATA4;
else if(state == OPEN_CDATA5)
state = OPEN_CDATA6;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'T':
if(state == OPEN_CDATA4)
state = OPEN_CDATA5;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case ']':
if(state == INSIDE_CDATA)
state = CLOSE_CDATA0;
else if(state == CLOSE_CDATA0)
state = NOCDATA;
break;
default:
break;
}
if(state == CLOSE_CDATA0 && c != ']') {
System.err.println("ERROR CLOSING");
System.out.println(result);
System.exit(1);
}
if(c !='&' || state == INSIDE_CDATA)
result.append(c);
}
System.out.println(result);
此程序为问题中的输入输出以下内容(输入中第一个字符串的副本已附加到整个字符串的末尾,并带有一个额外的 CDATA 标记以检查右括号):
<title>Very bad XML with (unescaped)</title>
<title>Good XML with amp; and #x3E; (escaped)</title>
<title><![CDATA[ Good XML with & in CDATA && ]]></title><title>Very bad XML with ![CDATA[&]] (unescaped)</title>
它实际上是一个使用 switch/case 语句构建的简单状态机。我没有对此进行过广泛的测试,我怀疑嵌套 CDATA 可能会导致失败(无论如何问题中似乎都不允许这样做)。我也懒得在 CDATA 关闭标记中添加最后一个 >
。但修改它以涵盖任何失败案例应该很容易。 This answer 为 CDATA 标签的词法分析提供了正确的结构。
例如,我 XML 是这样的:
<title>Very bad XML with & (unescaped)</title>
<title>Good XML with & and > (escaped)</title>
<title><![CDATA[ Good XML with & in CDATA ]]></title>
我的任务是从 XML 中删除无效的符号字符,但排除 CDATA 中的那些符号字符。 我找到了一个正则表达式:
&(?!(?:apos|quot|[gl]t|amp);|#)
但不幸的是,它还从 CDATA 中删除了 & 字符。如何更改此正则表达式以满足我的任务?
如您所知,"XML" 不是 XML,因为 CDATA 之外的未转义 &
。
因此,您必须在没有 XML 解析器区分 CDATA 和 PCDATA 的情况下进行预处理。这很粗糙,由于 regex isn't up to parsing XML.
这是一种可以提供帮助的方法:
- 使用正则表达式将所有孤立的(不是字符实体的一部分)
&
字符替换为&TEMP
,包括 CDATA 中的字符。 - 在现在格式正确的 XML 上使用 XML 解析器,将 CDATA 中出现的
&TEMP
恢复为&
。
另请参阅:
- 关于解析混乱的一般建议"XML"
- 容忍解析器
- 用于匹配无效字符的正则表达式和
&
的
作为对@kjughes 回答的补充,编写一个程序来提取“&”字符是相当简单的,尽管这是一个相当无聊的练习。由于CDATA
s不能嵌套,所以很容易标记标签的开闭。
这是一个这样的程序:
final int NOCDATA = -1;
final int OPEN_CDATA0 = 0; //!
final int OPEN_CDATA1 = 1; //![
final int OPEN_CDATA2 = 2; //![C
final int OPEN_CDATA3 = 3; //![CD
final int OPEN_CDATA4 = 4; //![CDA
final int OPEN_CDATA5 = 5; //![CDAT
final int OPEN_CDATA6 = 6; //![CDATA
final int INSIDE_CDATA = 7; //![CDATA[
final int CLOSE_CDATA0 = 8; //]
String xml = "<title>Very bad XML with & (unescaped)</title>\n" +
"<title>Good XML with & and > (escaped)</title>\n" +
"<title><![CDATA[ Good XML with & in CDATA && ]]></title><title>Very bad XML with ![CDATA[&]] && (unescaped)</title>";
StringBuilder result = new StringBuilder();
Reader reader = new BufferedReader(new StringReader(xml));
int r;
int state = NOCDATA;
while((r = reader.read()) != -1) {
char c = (char)r;
switch(c) {
case '!':
if(state == NOCDATA)
state = OPEN_CDATA0;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case '[':
if(state == OPEN_CDATA0)
state = OPEN_CDATA1;
else if(state == OPEN_CDATA6)
state = INSIDE_CDATA;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'C':
if(state == OPEN_CDATA1)
state = OPEN_CDATA2;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'D':
if(state == OPEN_CDATA2)
state = OPEN_CDATA3;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'A':
if(state == OPEN_CDATA3)
state = OPEN_CDATA4;
else if(state == OPEN_CDATA5)
state = OPEN_CDATA6;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case 'T':
if(state == OPEN_CDATA4)
state = OPEN_CDATA5;
else if(state != INSIDE_CDATA)
state = NOCDATA;
break;
case ']':
if(state == INSIDE_CDATA)
state = CLOSE_CDATA0;
else if(state == CLOSE_CDATA0)
state = NOCDATA;
break;
default:
break;
}
if(state == CLOSE_CDATA0 && c != ']') {
System.err.println("ERROR CLOSING");
System.out.println(result);
System.exit(1);
}
if(c !='&' || state == INSIDE_CDATA)
result.append(c);
}
System.out.println(result);
此程序为问题中的输入输出以下内容(输入中第一个字符串的副本已附加到整个字符串的末尾,并带有一个额外的 CDATA 标记以检查右括号):
<title>Very bad XML with (unescaped)</title>
<title>Good XML with amp; and #x3E; (escaped)</title>
<title><![CDATA[ Good XML with & in CDATA && ]]></title><title>Very bad XML with ![CDATA[&]] (unescaped)</title>
它实际上是一个使用 switch/case 语句构建的简单状态机。我没有对此进行过广泛的测试,我怀疑嵌套 CDATA 可能会导致失败(无论如何问题中似乎都不允许这样做)。我也懒得在 CDATA 关闭标记中添加最后一个 >
。但修改它以涵盖任何失败案例应该很容易。 This answer 为 CDATA 标签的词法分析提供了正确的结构。