Разбор текстового файла с регулярным выражением

Я пытаюсь использовать регулярное выражение для анализа файла путем извлечения определенных фрагментов текста. Регулярные выражения, которые мне нужно использовать, не поддерживаются стандартными пакетами java.util.regex (так как мне нужно сопоставить вложенные конструкции, такие как вложенные скобки {} и другие подобные вещи), поэтому я решил попробовать JRegex, который утверждает, что полностью обрабатывает синтаксис regex Perl 5.6. Тем не менее, я столкнулся с проблемой при попытке использовать этот пакет с рекурсивным регулярным выражением в соответствии с вложенными скобками {}:

Pattern p = new Pattern("(\\{(?:(?1)*|[^{}]*)+\\}|\\w+)");  // jregex.Pattern
Exception in thread "main" jregex.PatternSyntaxException: wrong char after "(?": 1

Аналогичное регулярное выражение /(\{(?:(?1)*|[^{}]+)+\}|\w+)/sg работает, как ожидалось, в Perl. Итак, моя следующая идея заключалась в том, чтобы найти способ проанализировать файл в Perl, а затем передать результаты на Java (желательно в виде массива строк или что-то подобное), а мой вопрос: что это лучший способ сделать это в этом случае? Или, есть ли еще одна более простая альтернатива, которую я пропускаю?

Ответ 1

JRegex, похоже, не поддерживает рекурсивное сопоставление, поэтому я предлагаю вам просто использовать java.util.regex и установить ограничение на количество уровней вложенности.

Например, чтобы разрешить до пятидесяти уровней вложенности с "неограниченным" числом пар скобок на каждом уровне (кроме самого глубокого), вы можете использовать

// Set the maximum number of nested levels required.
int max = 50;
String regex = "(?R)";

while (--max > 0) {
    regex = regex.replace("(?R)", "(?>\\{(?:[^{}]*+|(?R))+\\})");
}

// Ensure no (?R) in the final and deepest replacement.
regex = regex.replace("(?R)", "\\{[^{}]*+\\}") + "|\\w+";

String str = " {{}{}} {abc} {{de}{fg}} hij {1{2{3{4{5{6{7{8{9{10{11{12{13{14{15{16{17{18{19{20{21{22{23{24{25{26{27{28{29{30{31{32{33{34{35{36{37{38{39{40{41{42{43{44{45{46{47{48{49{50}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} {end}";
Matcher m = Pattern.compile(regex).matcher(str);

while (m.find()) {
    System.out.println(m.group());
}

/*
 {{}{}}
 {abc}
 {{de}{fg}}
 hij
 {1{2{3{4{5{6{7{8{9{10{11{12{13{14{15{16{17{18{19{20{21{22{23{24{25{26{27{28{29{30{31{32{33{34{35{36{37{38{39{40{41{42{43{44{45{46{47{48{49{50}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 {end}
*/

Вышеописанное строит регулярное выражение, беря значение, которое можно было бы использовать, если было рекурсивное сопоставление поддерживалось (?>\\{(?:[^{}]*+|(?R))+\\}) и многократно подставляя (?R) для всего шаблона.

Поскольку в выраженном выражении есть много вложенных кванторов, атомарная группировка (?>) и квантификатор притяжения + используются для ограничения обратного отслеживания и обеспечения быстрого выполнения регулярного выражения, если он не может найти совпадение. Хотя регулярное выражение может быть длинным, оно будет эффективным.

Если вы не хотите или не можете установить ограничение на вложенность, или если идея длинного регулярного выражения вызывает беспокойство, вы можете разобрать вложенные скобки, просто перебирая текст файла и отслеживая количество открытий и закрывающие скобки, например

List<String> list = new ArrayList<String>();
int strLen = str.length();

for (int i = 0; i < strLen; i++) {
    char c = str.charAt(i);

    if (c == '{') {
        int b = 1;
        StringBuilder sb = new StringBuilder("{");

        while (b > 0 && i < strLen - 1) {
            sb.append( c = str.charAt(++i) );

            if (c == '}') b--;
            else if (c == '{') b++;
        }
        list.add(sb.toString());
    }
}

for (String s : list) { System.out.println(s); }

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

Ответ 2

Лучшим способом является токенизация ввода и отправка его через токен-поток в ваш синтаксический анализатор, а затем анализ его сверху вниз/разворота в зависимости от ваших потребностей. Регулярное выражение не всегда полезно для разбора вложенных структур.


Утилита JLex основана на модели генератора лексического анализатора Lex. JLex принимает файл спецификации, подобный принятому Lex, затем создает исходный файл Java для соответствующего лексического анализатора.

Посмотрите JLex, поскольку это может помочь вам генерировать лексический анализатор для вашего случая из очень простого кода.

Ответ 3

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