Комбинированный позитивный взгляд назад и взгляд вперед

Я хочу проанализировать массив из пользовательского протокола "ключ-значение". Это похоже на это

RESPONSE GAMEINFO OK
NAME: "gamelobby"
PLAYERS: "alice", "bob", "hodor"
FLAGS: 1, 2, 3

В Java строка выглядит так (в ней используется CRLF как разрыв строки):

RESPONSE GAMEINFO OK\\r\\nNAME: \"gamelobby\"\\r\\nPLAYERS: \"alice\", \"bob\", \"hodor\"FLAGS: 1, 2, 3\\r\\n

Я хочу запечатлеть "alice", "bob", "hodor" как есть. Поэтому я использовал это регулярное выражение, которое было протестировано в Sublime Text и на regex101.com (ключи нечувствительны к регистру)

(?<=(?i:PLAYERS): )([A-Za-z0-9\s\.,:;\?!\n"_-]*)(?=\r\n)

Это снимок экрана из Sublime Text (примечание: я не упомянул здесь \ r):

введите описание изображения здесь

Когда я пытаюсь захватить группу, я тоже получаю следующую строку:

Pattern p = Pattern.compile("(?<=(?i:"+key+"): )([A-Za-z0-9\\s\\.,:;\\?!\\n\"_-]*)(?=\\r\\n)");
Matcher matcher = p.matcher(message);
matcher.find();
String value = new String();
try {
    value = matcher.group(); // = "\"alice\", \"bob\", \"hodor\"\\r\\nFLAGS: 1, 2, 3"
} ...

ПРИМЕЧАНИЕ: \" или \\\", похоже, не имеют значения.

Почему FLAGS: 1, 2, 3 записывается до \\r\\n, но не в строке выше? Возможен ли позитивный взгляд назад и вперед? Какой просмотр вперед / назад оценивается в первую очередь?

РЕДАКТИРОВАТЬ: определение строкового массива

values        = string*("," WSP string)
string        = DQUOTE *(ALPHA / DIGIT / WSP / punctuation / "\n") DQUOTE
punctuation   = "." / ":" / "," / ";" / "?" / "!" / "-" / "_"

person joschuck    schedule 03.02.2015    source источник
comment
Он работает в SublimeText, поскольку в последней строке нет новой строки, а в вашем вводе в Java есть. Если вы можете предположить, что новая строка никогда не может появиться в строке PLAYERS, вам следует удалить \s и \n из вашего регулярного выражения и, вероятно, заменить на \h, если вы используете Java 8.   -  person nhahtdh    schedule 03.02.2015
comment
Я не могу предположить, что моя основная проблема в том, что я захватываю следующую строку, останется   -  person joschuck    schedule 03.02.2015
comment
Значит, вы не можете предположить, что поле PLAYER никогда не может занимать 2 строки и более? Тогда это будет нелегкая задача - разобрать его. Исходя из этого предположения, вы можете просто разделить ввод и проанализировать строку за строкой.   -  person nhahtdh    schedule 03.02.2015
comment
Для псевдонимов существуют ограничения (буквенно-цифровые), я могу предположить это в этом примере. Тем не менее, протокол допускает использование строк с разрывами строк с использованием \n и строковых массивов.   -  person joschuck    schedule 03.02.2015
comment
Можете ли вы включить грамматику вашего протокола? Если вы попросите нас вывести его, просто посмотрев на ваш пример, вы получите наполовину рабочее решение.   -  person nhahtdh    schedule 03.02.2015
comment
Может, я что-то не понимаю, но нельзя ли использовать в выражении скобок ненадежный множитель? См. regex101.com/r/hN6nB5/1.   -  person bgoldst    schedule 03.02.2015
comment
@bgoldst Да, это исправляет совпадение. Спасибо! Я до сих пор не понимаю, почему сопоставление не останавливается на \\r\\n.   -  person joschuck    schedule 03.02.2015
comment
Строка @nhahtdh может быть разделена только ,\s   -  person joschuck    schedule 03.02.2015
comment
\s, кстати, содержит новую строку. Пожалуйста, составьте правильный список пробелов.   -  person nhahtdh    schedule 03.02.2015
comment
Итак, я исправил это (полностью контролировал, что включено \s и \n. (?<=(?i:PLAYERS): )([A-Za-z0-9\n \.,:;\?!"_-]*)(?=\r\n) правильно. \s захвачено \r\n до положительного просмотра. Интересный. Вам также не нужен предложенный @bgoldst не жадный множитель, который не был бы изящным решением думаю.   -  person joschuck    schedule 03.02.2015


Ответы (2)


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

String WHITESPACE_RE = "[ ]"; // Modify this according to your grammar
String PUNCTUATION_RE = "[.:,;?!_-]";
String STRING_RE = "\"(?:[A-Za-z0-9" + WHITESPACE_RE + PUNCTUATION_RE + "\n])*\"";
String VALUES_RE = STRING_RE + "(?:," + WHITESPACE_RE + STRING_RE + ")*";
String PLAYERS_RE = "PLAYERS:" +  WHITESPACE_RE + "(" + VALUES_RE + ")(?=\r\n)";

В настоящее время \r\n используется для проверки разделителя строк в конце записи PLAYERS. Измените его на то, что указано в вашей спецификации.

Предостережение

Это решение работает только для синтаксического анализа действительных входных данных. Анализ недопустимого ввода зависит от вашего алгоритма восстановления и разделителя строк.

Если разделитель строк позволяет использовать как \n, так и \r\n, исправить ошибку будет сложно. Например, если есть пользователь с именем ABC\nFLAGS: 1, 2, 3 (разрешено в соответствии с грамматикой), но закрывающая двойная кавычка отсутствует, список игроков будет разбит, и вы не сможете определить, является ли FLAGS: частью предыдущей строки. или другой заголовок.

RESPONSE GAMEINFO OK
NAME: "gamelobby"
PLAYERS: "alice", "bob", "hodor", "ABC
FLAGS: 1, 2, 3
FLAGS: 1, 2, 3

Полный пример

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SO28290386 {
    public static void main(String[] args) {
        String WHITESPACE_RE = "[ ]"; // Modify this according to your grammar
        String PUNCTUATION_RE = "[.:,;?!_-]";
        String STRING_RE = "\"(?:[A-Za-z0-9" + WHITESPACE_RE + PUNCTUATION_RE + "\n])*\"";
        String VALUES_RE = STRING_RE + "(?:," + WHITESPACE_RE + STRING_RE + ")*";
        String PLAYERS_RE = "PLAYERS:" +  WHITESPACE_RE + "(" + VALUES_RE + ")(?=\r\n)";
        System.out.println(PLAYERS_RE);

        String input = "RESPONSE GAMEINFO OK\r\nNAME: \"gamelobby\"\r\nPLAYERS: \"alice\", \"bob\", \"hodor\", \"new\nline\"\r\nFLAGS: 1, 2, 3\r\n";
        System.out.println("INPUT");
        System.out.println(input);

        Pattern p = Pattern.compile(PLAYERS_RE);
        Matcher m = p.matcher(input);
        while (m.find()) {
            System.out.println(m.group(0));
            System.out.println(m.group(1));
        }
    }
}
person nhahtdh    schedule 03.02.2015
comment
Почему вы использовали (?=\r\n|[\r\n]); есть разница? - person joschuck; 03.02.2015
comment
@joschuck: Используйте все, что указано в вашей спецификации. Вы не указали эту часть. Если это \r\n, используйте только \r\n. - person nhahtdh; 03.02.2015

Вы можете использовать нежадный множитель в выражении скобок:

(?<=(?i:PLAYERS): )([A-Za-z0-9\s\.,:;\?!\n"_-]*?)(?=\r\n)

Причина, по которой сопоставление не заканчивается на \r\n, когда вы используете жадный мультипликатор *, заключается в том, что выражение в скобках содержит \s. Определение \s (согласно документации Pattern < / a> class) равен [ \t\n\x0B\f\r], поэтому выражение в скобках фактически проходит через терминатор строки CRLF и все остальное на своем пути, пока не дойдет до конца всей строки.

Я полагаю, если бы вы были в порядке с явным запретом присутствия одиночных CR в списке цитируемых слов, то другим жизнеспособным решением было бы заменить \s явным [\n\t\f ], но я оставлю это на ваше усмотрение.

Нежадное решение с умножением *? работает, потому что, когда механизм регулярных выражений попадает в первый CRLF, чтобы удовлетворить окончательное утверждение упреждающего просмотра, он перестает соответствовать, даже если выражение в скобках может его поглотить.

тестовый код на regex101 не работает в случае, когда строка содержит новую строку, поскольку сайт не Похоже, он не поддерживает CR, поэтому мы не можем провести там полный тест. Но в реальном регулярном выражении в Java-коде утверждение упреждающего запроса потребовало бы, чтобы CRLF завершил поиск, так что в конечном итоге поиск соответствовал бы всему списку цитируемых слов.

person bgoldst    schedule 03.02.2015
comment
Ваше текущее предложение работает, но вы имеете в виду комментарий, который я сделал в вопросе о вашем сломанном регулярном выражении. Продолжайте обсуждение комментария к комментарию, пожалуйста. - person nhahtdh; 03.02.2015