Вопрос по php, regex, parsing – PHP и RegEx: разбить строку запятыми, которые не находятся в квадратных скобках (а также во вложенных скобках)

7

Два дня назад я начал работать над анализатором кода, и я застрял.

Как можно разделить строку запятыми, которые не заключены в квадратные скобки, позвольте мне показать вам, что я имею в виду:

У меня есть эта строка для анализа:

one, two, three, (four, (five, six), (ten)), seven

Я хотел бы получить этот результат:

array(
 "one"; 
 "two"; 
 "three"; 
 "(four, (five, six), (ten))"; 
 "seven"
)

но вместо этого я получаю:

array(
  "one"; 
  "two"; 
  "three"; 
  "(four"; 
  "(five"; 
  "six)"; 
  "(ten))";
  "seven"
)

Как я могу сделать это в PHP RegEx.

Заранее спасибо

Ваш Ответ

7   ответов
10

Ты можешь сделать это проще:

preg_match_all('/[^(,\s]+|\([^)]+\)/', $str, $matches)

Но было бы лучше, если бы вы использовали настоящий парсер. Может быть, что-то вроде этого:

$str = 'one, two, three, (four, (five, six), (ten)), seven';
$buffer = '';
$stack = array();
$depth = 0;
$len = strlen($str);
for ($i=0; $i<$len; $i++) {
    $char = $str[$i];
    switch ($char) {
    case '(':
        $depth++;
        break;
    case ',':
        if (!$depth) {
            if ($buffer !== '') {
                $stack[] = $buffer;
                $buffer = '';
            }
            continue 2;
        }
        break;
    case ' ':
        if (!$depth) {
            continue 2;
        }
        break;
    case ')':
        if ($depth) {
            $depth--;
        } else {
            $stack[] = $buffer.$char;
            $buffer = '';
            continue 2;
        }
        break;
    }
    $buffer .= $char;
}
if ($buffer !== '') {
    $stack[] = $buffer;
}
var_dump($stack);
Да, это проще, но не работает в случае вложенных скобок, например: один, два, три, (четыре, (пять, шесть), (десять)), семь Cristian Toma
В этот момент вы должны использовать настоящий парсер. Регулярные выражения не могут считать или обрабатывать состояния. Gumbo
Я должен использовать регулярные выражения. Регулярные выражения являются рекурсивными и жадными, вы можете сделать это, используя их. Cristian Toma
Нет, ты не можешь. Несомненно, в современных реализациях есть функции, которые могут достигнуть этого, такие как .NET's Балансировочная группа (?<name1-name2> … ) Msdn.microsoft.com / bs2twtah.aspx. Но они используют конечный автомат, и это больше не является регулярным выражением в классической манере. Gumbo
Это более правильно, но все еще не работает для вложенных скобок / [^ (,] * (?: ([^)] +))? [^),] * / DarkSide
7

но так как вы попросили простое решение, я все же попробую:

<?php
  $test = "one, two, three, , , ,(four, five, six), seven, (eight, nine)";
  $split = "/([(].*?[)])|(\w)+/";
  preg_match_all($split, $test, $out);
  print_r($out[0]);              
  die();
?>

Выхо

Array
(
    [0] => one
    [1] => two
    [2] => three
    [3] => (four, five, six)
    [4] => seven
    [5] => (eight, nine)
)
Большое спасибо, ваша помощь очень ценится. Но теперь я понимаю, что я также столкнусь с вложенными скобками, и ваше решение не применимо. Cristian Toma
5

как минимум, просмотр сзади с переменной шириной, и, наконец, я знал, что PHP PCRE имеет только просмотр с фиксированной шириной.

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

Да, это был хак, который я собирался использовать. Замените скобки на $ 1, $ 2 или что-то подобное, разбейте строку и затем восстановите скобки в результате. Спасибо Cristian Toma
Дело в том, что то, что вы описываете, не является регулярным языком, поэтому регулярные выражения плохо подходят. Таким образом, анализ всех вложенных частей в первую очередь - это не «взлом», а самая разумная вещь. Svante
2

как сделать это с помощью одного регулярного выражения, но довольно легко собрать то, что работает:

function process($data)
{
        $entries = array();
        $filteredData = $data;
        if (preg_match_all("/\(([^)]*)\)/", $data, $matches)) {
                $entries = $matches[0];
                $filteredData = preg_replace("/\(([^)]*)\)/", "-placeholder-", $data);
        }

        $arr = array_map("trim", explode(",", $filteredData));

        if (!$entries) {
                return $arr;
        }

        $j = 0;
        foreach ($arr as $i => $entry) {
                if ($entry != "-placeholder-") {
                        continue;
                }

                $arr[$i] = $entries[$j];
                $j++;
        }

        return $arr;
}

Если вы вызываете это так:

$data = "one, two, three, (four, five, six), seven, (eight, nine)";
print_r(process($data));

Это выводит:

Array
(
    [0] => one
    [1] => two
    [2] => three
    [3] => (four, five, six)
    [4] => seven
    [5] => (eight, nine)
)
Большое спасибо, это должно сработать. Именно так я планировал сделать это в первую очередь, но я подумал, что существует более простой способ. Cristian Toma
Вы методом не можете разобрать "один, два, три, ((пять), (четыре (шесть))), семь, восемь, девять". Я думаю, что правильный RegEx будет рекурсивным: /(([^()]+|(?R))*)/ Cristian Toma
Вы не упомянули, что когда я впервые написал этот ответ, он должен был иметь возможность анализировать рекурсивные выражения. Тем не менее, другие определенно предложили лучшие решения после того, как я написал это. Emil H
Да, прости. Виноват Cristian Toma
2

Clumsy, но это делает работу ...

<?php

function split_by_commas($string) {
  preg_match_all("/\(.+?\)/", $string, $result); 
  $problem_children = $result[0];
  $i = 0;
  $temp = array();
  foreach ($problem_children as $submatch) { 
    $marker = '__'.$i++.'__';
    $temp[$marker] = $submatch;
    $string   = str_replace($submatch, $marker, $string);  
  }
  $result = explode(",", $string);
  foreach ($result as $key => $item) {
    $item = trim($item);
    $result[$key] = isset($temp[$item])?$temp[$item]:$item;
  }
  return $result;
}


$test = "one, two, three, (four, five, six), seven, (eight, nine), ten";

print_r(split_by_commas($test));

?>
1

что может быть очень сложно разобрать вложенные скобки, такие какone, two, (three, (four, five)) только с RegExp.

1

что стоит отметить, что вы всегда должны избегать регулярных выражений, когда это возможно. Для этого вы должны знать, что для PHP 5.3+ вы можете использоватьstr_getcsv (). Однако, если вы работаете с файлами (или файловыми потоками), такими как файлы CSV, тогда функцияfgetcsv () может быть то, что вам нужно, и он был доступен с PHP4.

Наконец, я удивлен, что никто не использовалpreg_split () или это не сработало?

Да, Кен, я хочу использовать preg_split (), но что за RegEx игнорирует запятые в скобках? Cristian Toma
Ах да, хорошая мысль, после попытки в течение минуты или 2 я вижу, что это сложно с условиями, изложенными. ken
Да, ты прав, я тоже попробовал твоё решение и не работает. Спасибо еще. Cristian Toma
str_getcsv() работал как шарм! Rob

Похожие вопросы