Я пытаюсь «разобрать» некоторые данные с помощью регулярного выражения, и я чувствую, как будто я рядом, но я просто не могу принести все это домой.
Данные, требующие синтаксического анализа, обычно выглядят следующим образом: <param>: <value>\n
. Количество параметров может меняться, как и значение. Тем не менее, вот пример:
FooID: 123456 Имя: Чак Когда: 01/02/2013 01:23:45 InternalID: 789654 Сообщение пользователя: Здравствуйте, это негры, но может быть довольно длинным. Текст можно разложить по многим строкам И может начинаться с любого числа \ n. Он тоже может быть пустым. Хуже всего то, что это МОЖЕТ содержать двоеточие (но они «экранированы» _ с использованием `\`) и даже базовые разметки!
Чтобы подтолкнуть этот текст к объекту, я собрал это небольшое выражение
if (preg_match_all('/^([^:\n\\]+):\s*(.+)/m', $this->structuredMessage, $data)) { $data = array_combine($data[1], $data[2]); //$data is assoc array FooID => 123456, Name => Chuck, ... $report = new Report($data); }
Теперь это работает отлично в большинстве случаев, за исключением бит User Message
:. не соответствует новым строкам, потому что если бы я использовал флаг s
, вторая группа соответствовала бы всем после FooID:
до самого конца строки.
Мне нужно использовать грязное обходное решение для этого:
$msg = explode(end($data[1], $string); $data[2][count($data[2])-1] = array_pop($msg);
После некоторого тестирования я понял, что иногда один или два параметра не заполняются (например, InternalID
может быть пустым). В этом случае мое выражение не терпит неудачу, а приводит к:
[1] => Массив ( [0] => FooID [1] => Имя [2] => Когда [3] => InternalID ) [2] => Массив ( [0] => 123465 [1] => Чак [2] => 01/02/2013 01:23:45 [3] => Комментарий пользователя: Здравствуйте, )
Я пробовал разные выражения и придумал следующее:
/^([^:\n\\]++)\s{0,}:(.*+)(?!^[^:\n\\]++\s{0,}:)/m //or: /^([^:\n\\]+)\s{0,}:(.*)(?!^[^:\\\n]+\s{0,}:)/m
Вторая версия немного медленнее.
Это решает проблемы, которые у меня были с InternalID: <void>
, но все равно оставляет мне последнее препятствие: User Message: <multi-line>
. Использование флага s
не делает трюк с моим выражением ATM.
Я могу только думать об этом:
^([^:\n\\]++)\s{0,}:((\n(?![^\n:\\]++\s{0,}:)|.)*+)
Который, по-моему, слишком сложный, чтобы быть единственным вариантом. Идеи, предложения, ссылки, … все было бы очень полезно
Следующее регулярное выражение должно работать, но я больше не уверен, если это правильный инструмент для этого:
preg_match_all( '%^ # Start of line ([^:]*) # Match anything until a colon, capture in group 1 :\s* # Match a colon plus optional whitespace ( # Match and capture in group 2: (?: # Start of non-capturing group (used for alternation) .*$ # Either match the rest of the line (?= # only if one of the following follows here: \Z # The end of the string | # or \r?\n # a newline [^:\n\\\\]* # followed by anything except colon, backslash or newline : # then a colon ) # End of lookahead | # or match (?: # Start of non-capturing group (used for alternation/repetition) [^:\\\\] # Either match a character except colon or backslash | # or \\\\. # match any escaped character )* # Repeat as needed (end of inner non-capturing group) ) # End of outer non-capturing group ) # End of capturing group 2 $ # Match the end of the line%mx', $subject, $result, PREG_PATTERN_ORDER);
Смотрите его в прямом эфире по regex101 .
Я довольно новичок в PHP, поэтому, возможно, это полностью из-за шума, но, возможно, вы могли бы использовать что-то вроде
$data = <<<EOT FooID: 123456 Name: Chuck When: 01/02/2013 01:23:45 InternalID: 789654 User Message: Hello, this is nillable, but can be quite long. Text can be spread out over many lines And can start with any number of \n's. It can be empty, too EOT; if ($key = preg_match_all('~^[^:\n]+?:~m', $data, $match)) { $val = explode('¬', preg_filter('~^[^:\n]+?:~m', '¬', $data)); array_shift($val); $res = array_combine($match[0], $val); } print_r($res);
доходность
Array ( [FooID:] => 123456 [Name:] => Chuck [When:] => 01/02/2013 01:23:45 [InternalID:] => 789654 [User Message:] => Hello, this is nillable, but can be quite long. Text can be spread out over many lines And can start with any number of 's. It can be empty, too )
Итак, вот что я придумал с помощью сложного preg_replace_callback()
:
$string ='FooID: 123456 Name: Chuck When: 01/02/2013 01:23:45 InternalID: 789654 User Message: Hello, this is nillable, but can be quite long. Text can be spread out over many lines And can start with any number of \n\'s. It can be empty, too Yellow:cool'; $array = array(); preg_replace_callback('#^(.*?):(.*)|.*$#m', function($m)use(&$array){ static $last_key = ''; // We are going to use this as a reference if(isset($m[1])){// If there is a normal match (key : value) $array[$m[1]] = $m[2]; // Then add to array $last_key = $m[1]; // define the new last key }else{ // else $array[$last_key] .= PHP_EOL . $m[0]; // add the whole line to the last entry } }, $string); // Anonymous function used thus PHP 5.3+ is required print_r($array); // print
Демо-версия
Недостаток : я использую PHP_EOL
для добавления PHP_EOL
строк, связанных с ОС.
Я думаю, что я бы не использовал регулярное выражение для выполнения этой задачи, а разделил его на подзадачи.
\n
используя explode
:
также используя explode
с пределом 2. Этот алгоритм предполагает, что нет ключей с экранированными двоеточиями. Сбежавшие двоеточия в значениях будут обрабатываться просто отлично (т.е. пользовательский ввод).
$str = <<<EOT FooID: 123456 Name: Chuck When: 01/02/2013 01:23:45 InternalID: User Message: Hello, this is nillable, but can be quite long. Text can be spread out over many lines This\: works too. And can start with any number of \\n's. It can be empty, too. What's worse, though is that this CAN contain colons (but they're _"escaped"_ using `\`) like so `\:`, and even basic markup! EOT; $arr = explode("\n", $str); $prevKey = ''; $split = ': '; $output = array(); for ($i = 0, $arrlen = sizeof($arr); $i < $arrlen; $i++) { $keyValuePair = explode($split, $arr[$i], 2); // ?: Is this a valid key/value pair if (sizeof($keyValuePair) < 2 && $i > 0) { // -> Nope, append the value to the previous key's value $output[$prevKey] .= "\n" . $keyValuePair[0]; } else { // -> Maybe // ?: Did we miss an escaped colon if (substr($keyValuePair[0], -1) === '\\') { // -> Yep, this means this is a value, not a key/value pair append both key and // value (including the split between) to the previous key's value ignoring // any colons in the rest of the string (allowing dates to pass through) $output[$prevKey] .= "\n" . $keyValuePair[0] . $split . $keyValuePair[1]; } else { // -> Nope, create a new key with a value $output[$keyValuePair[0]] = $keyValuePair[1]; $prevKey = $keyValuePair[0]; } } } var_dump($output);
array(5) { ["FooID"]=> string(6) "123456" ["Name"]=> string(5) "Chuck" ["When"]=> string(19) "01/02/2013 01:23:45" ["InternalID"]=> string(0) "" ["User Message"]=> string(293) "Hello, this is nillable, but can be quite long. Text can be spread out over many lines This\: works too. And can start with any number of \n's. It can be empty, too. What's worse, though is that this CAN contain colons (but they're _"escaped"_ using `\`) like so `\:`, and even basic markup!" }
Демо-версия