Как написать регулярное выражение для XML, который удаляет неэкранированные символы амперсанда, кроме CDATA? - PullRequest
0 голосов
/ 11 декабря 2019

Например, у меня есть XML как этот:

<title>Very bad XML with & (unescaped)</title>
<title>Good XML with &amp; and &#x3E; (escaped)</title>
<title><![CDATA[ Good XML with & in CDATA ]]></title>

Моя задача состоит в том, чтобы удалить недопустимые символы амперсанда из XML, но исключая те символы амперсанда, которые есть в CDATA. Я нашел регулярное выражение, которое делает это:

&(?!(?:apos|quot|[gl]t|amp);|#)

, но, к сожалению, оно также удаляет символы амперсанда из CDATA. Как я могу изменить это регулярное выражение, чтобы оно удовлетворяло моей задаче?

Ответы [ 2 ]

3 голосов
/ 11 декабря 2019

Как вы знаете, «XML» не является XML из-за неэкранированного & вне CDATA. Таким образом, вы застряли на необходимости предварительной обработки без использования синтаксического анализатора XML, чтобы различать CDATA и PCDATA. Это грубо, и регулярное выражение не соответствует задаче по всем причинам, по которым регулярное выражение не подходит для анализа XML .

Вот один подход, который может помочь:

  1. Использование регулярных выражений для замены всех изолированных (не являющихся частью символьной сущности) & символов &amp;TEMP, включая символы в CDATA.
  2. Использование синтаксического анализатора XML в теперь правильно сформированном XMLвосстановить &amp;TEMP вхождения в CDATA в &.

См. также: Как проанализировать недопустимый (плохой / не правильно сформированный) XML?

  • Общие рекомендации по разбору грязного "XML"
  • Допустимые парсеры
  • Регулярные выражения для сопоставления недопустимых символов и &
2 голосов
/ 11 декабря 2019

В дополнение к ответу @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 &amp; and &#x3E; (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.

...