В дополнение к ответу @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 close. Но это должно быть легко изменить, чтобы покрыть любые неудачные случаи. Этот ответ обеспечивает правильную структуру для лексического анализа меток CDATA.