Intereting Posts
Динамическое шифрование кнопки Paypal Есть ли проблемы с использованием ключевого слова static в простой PHP-функции? DENY прямая загрузка файла с использованием php php: используя DomDocument, когда я пытаюсь написать UTF-8, он записывает шестнадцатеричную нотацию PDO: Недопустимый номер параметра: смешанные имена и позиционные параметры – знак вопроса в комментариях Как передать URL-адрес в контроллер CodeIgniter? Как я могу прочитать ответ GZIP-ed от API Stackoverflow в PHP? Как я могу обмениваться контентом со своей внутренней страницы на странице Facebook? Как использовать Elastic Search поверх ранее существующей базы данных SQL? PHP, если стенография и эхо в одной строке – возможно? Создание PDF-файла и отправка по электронной почте Удалять каждый второй элемент из массива и ключей перегруппировки? Как преобразовать массив массивов или объектов в ассоциативный массив? Видеоролик Vline для кросс-браузера не работает Простой пример для публикации на странице фан-сайта Facebook через PHP?

Regex с возможными пустым совпадением и многострочным совпадением

Я пытаюсь «разобрать» некоторые данные с помощью регулярного выражения, и я чувствую, как будто я рядом, но я просто не могу принести все это домой.
Данные, требующие синтаксического анализа, обычно выглядят следующим образом: <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 строк, связанных с ОС.

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

Основной алгоритм

  1. Разделить строку на \n используя explode
  2. Петля по результирующему массиву
    1. Разделите результирующие строки на : также используя explode с пределом 2.
    2. Если длина полученного массива меньше 2, добавьте все данные в значение предыдущего ключа
    3. Else, используйте первый индекс массива в качестве вашего ключа, второй – как значение, если только двоеточие split не было экранировано (в этом случае вместо этого добавьте значение ключа + split + в значение предыдущего ключа)

Этот алгоритм предполагает, что нет ключей с экранированными двоеточиями. Сбежавшие двоеточия в значениях будут обрабатываться просто отлично (т.е. пользовательский ввод).

Код

 $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!" } 

Демо-версия