PHP: разделите строку на запятую, но НЕ, когда между скобками или кавычками?

В PHP у меня есть следующая строка:

$str = "AAA, BBB, (CCC,DDD), 'EEE', 'FFF,GGG', ('HHH','III'), (('JJJ','KKK'), LLL, (MMM,NNN)) , OOO"; 

Мне нужно разбить эту строку на следующие части:

 AAA BBB (CCC,DDD) 'EEE' 'FFF,GGG' ('HHH','III') (('JJJ','KKK'),LLL, (MMM,NNN)) OOO , AAA BBB (CCC,DDD) 'EEE' 'FFF,GGG' ('HHH','III') (('JJJ','KKK'),LLL, (MMM,NNN)) OOO 

Я попробовал несколько регулярных выражений, но не смог найти решение. Есть идеи?

ОБНОВИТЬ

Я решил использовать regex на самом деле не лучшее решение, имея дело с искаженными данными, скрытыми кавычками и т. Д.

Благодаря предложениям, сделанным здесь, я нашел функцию, которая использует синтаксический анализ, который я переписал в соответствии с моими потребностями. Он может обрабатывать различные типы скобок, а разделитель и цитата – это параметры.

  function explode_brackets($str, $separator=",", $leftbracket="(", $rightbracket=")", $quote="'", $ignore_escaped_quotes=true ) { $buffer = ''; $stack = array(); $depth = 0; $betweenquotes = false; $len = strlen($str); for ($i=0; $i<$len; $i++) { $previouschar = $char; $char = $str[$i]; switch ($char) { case $separator: if (!$betweenquotes) { if (!$depth) { if ($buffer !== '') { $stack[] = $buffer; $buffer = ''; } continue 2; } } break; case $quote: if ($ignore_escaped_quotes) { if ($previouschar!="\\") { $betweenquotes = !$betweenquotes; } } else { $betweenquotes = !$betweenquotes; } break; case $leftbracket: if (!$betweenquotes) { $depth++; } break; case $rightbracket: if (!$betweenquotes) { if ($depth) { $depth--; } else { $stack[] = $buffer.$char; $buffer = ''; continue 2; } } break; } $buffer .= $char; } if ($buffer !== '') { $stack[] = $buffer; } return $stack; } 

Вместо preg_split сделайте preg_match_all :

 $str = "AAA, BBB, (CCC,DDD), 'EEE', 'FFF,GGG', ('HHH','III'), (('JJJ','KKK'), LLL, (MMM,NNN)) , OOO"; preg_match_all("/\((?:[^()]|(?R))+\)|'[^']*'|[^(),\s]+/", $str, $matches); print_r($matches); 

будет печатать:

  массив
 (
     [0] => Массив
         (
             [0] => AAA
             [1] => BBB
             [2] => (CCC, DDD)
             [3] => 'EEE'
             [4] => 'FFF, GGG'
             [5] => ('HHH', 'III')
             [6] => (('JJJ', 'KKK'), LLL, (MMM, NNN))
             [7] => ООО
         )

 ) 

Регулярное выражение \((?:[^()]|(?R))+\)|'[^']*'|[^(),\s]+ может быть разделено на три части:

  1. \((?:[^()]|(?R))+\) , которая соответствует сбалансированным парам круглых скобок
  2. '[^']*' соответствие строки с кавычками
  3. [^(),\s]+ которая соответствует любой char-последовательности, не состоящей из символов '(' , ')' , ',' или white-space

Сумасшедшее решение

Спартанское регулярное выражение, которое символизирует и также проверяет все токены, которые он извлекает:

 \G\s*+((\((?:\s*+(?2)\s*+(?(?!\)),)|\s*+[^()',\s]++\s*+(?(?!\)),)|\s*+'[^'\r\n]*+'\s*+(?(?!\)),))++\))|[^()',\s]++|'[^'\r\n]*+')\s*+(?:,|$) 

Regex101

Поместите его в строковый литерал с разделителем:

 '/\G\s*+((\((?:\s*+(?2)\s*+(?(?!\)),)|\s*+[^()\',\s]++\s*+(?(?!\)),)|\s*+\'[^\'\r\n]*+\'\s*+(?(?!\)),))++\))|[^()\',\s]++|\'[^\'\r\n]*+\')\s*+(?:,|$)/' 

ideone

Результат заключается в захвате группы 1. В примере на ideone я указываю флаг PREG_OFFSET_CAPTURE , чтобы вы могли проверить против последнего совпадения в группе 0 (полное совпадение), была ли потреблена вся исходная строка или нет.

Предположения

  • Текст без кавычек может не содержать пробельного символа, как определено в \s . Следовательно, он может не охватывать несколько строк.
  • Текст без кавычек не может содержать ( , ) , ' или.
  • Текст без кавычек должен содержать не менее 1 символа.
  • Одиночный кавычек не может содержать несколько строк.
  • Одиночный кавычек не может содержать цитату. Следовательно, нет способа указать ' .
  • Одиночный кавычек может быть пустым.
  • Тонер-маркер содержит одно или несколько из следующих подтекстов: некабельный текстовый токен, одноточечный токен или другой токен.
  • В маркере скобки 2 смежных подноса разделены ровно одним ,
  • Идентификатор маркера начинается с ( и заканчивается ) .
  • Следовательно, токен скобки должен иметь сбалансированные скобки, а пустая скобка () не допускается.
  • Ввод будет содержать один или несколько символов: текст без кавычек, одиночный кавычек или токен. Токены на входе разделяются запятой. Отдельная конечная запятая считается действительной.
  • Символьный пробел (как определено \s , который включает в себя новый символ строки) произвольно разрешен между токенами, запятыми (-ами) , разделительными маркерами и скобками (-ами) ( , ) токенов-скобок.

Сломать

 \ G \ s * +
 (
   (
     \ (
     (?:
         \ S * +
         (? 2)
         \ S * +
         ((?! \)))
       |
         \ S * +
         [^ ()», \ S] ++
         \ S * +
         ((?! \)))
       |
         \ S * +
         '[^' \ Г \ п] * +»
         \ S * +
         ((?! \)))
     ) ++
     \)
   )
   |
   [^ ()», \ S] ++
   |
   '[^' \ Г \ п] * +»
 )
 \ S * + (?, | $)