PHP разбить строку по символу с исключением

Есть к примеру такая строка 123|@rand(тест {@rand(номер 1234|номер 4321)}|строка||. Это строка параметров функции в тексте. (функция выглядит, как {@funcname(param1|param2)}). Каждый параметр отделяется знаком |

И мне нужно получить из этой строки массив с параметрами. Т.е. разбить строку по символу |.

Главная проблема в том, что в строке может содержаться вызов функции с такой же строкой параметров, в которой тоже содержится знак |. Отдельный вызов функции НЕЛЬЗЯ ДЕЛИТЬ!

Итого нужно разбить строку выше на такой массив

array(
  123,
  '@rand(тест {@rand(номер 1234|номер 4321)}',
  строка,
  '', // || считать, как пустая строка
  ''
);

Помогите составить регулярное выражение!

Я накидал что-то такое, но это работает неправильно - "/[^({@\w+\()]\|[^(\)})]/ui


Ответы (2 шт):

Автор решения: phpBear

В свете более подробного разъяснения автором задачи, я бы предложил новое решение.

//Во-первых в приведенном примере автор немного ошибся и забыл у первого `@rand` в конце закрывающую скобку я ее добавил, если это не так - поправьте строку ну и регулярку
$text = "123|@rand(тест {@rand(номер 1234|номер 4321)})|строка||";

//Разбитие по `|` приведет к тому, что Вы в выходном массиве получите еще и разбитые параметры функции `@rand`. Что как Вы сами пишите и является проблемой которую Вам в том числе нужно решить
//Поэтому я бы сделал так. В строке которая содержит параметры `@rand(тест {@rand(номер 1234|номер 4321)})` вначале заменил `|` на какой-то другой символ.
if ( preg_match_all("/(@(\S+)\((.*)\))/", $text, $matches) && is_array( $matches ) && count($matches) > 0 && is_array($matches[0]) && count($matches[0]) > 0) {
    //Меняем `|` на `&`
    $rtext = preg_replace("/\|/", "&", $matches[0][0]);
    //preg_quotes экранирует спец.символы использующиеся в регулярных выражениях - а их у нас в полученной строке полно
    $text = preg_replace("/".preg_quote($matches[0][0])."/", $rtext, $text);

    //После чего спокойно разбил всю исходную строку `$text`, по символу `|`
    $params = explode("|", $text);

    if ( is_array( $params ) && count( $params ) > 0 ) {
        //Далее идем по массиву и ищем элемент-строку начинающийся на '@'
        foreach( $params as $p ) {
            $p = trim( $p );
            if ( mb_substr( $p, 0, 1) == '@' ) {
                //@rand(тест {@rand(номер 1234&номер 4321)})
                //А теперь парсим эту строку на содержание параметров
                //Если кол-во параметров другое или формат строки отличается - подправьте регулярку
                if ( preg_match_all("/^\@\S+\(\W+\{\@\S+\((.+?)\&(.+?)\)\}\)/", $p, $matches) && is_array( $matches ) && count($matches) > 0 && is_array($matches[0]) && count($matches[0]) > 0) {
                    $param1 = $matches[1][0]."\n"; //номер 1234
                    $param2 = $matches[2][0]."\n"; //номер 4321
                }
            }
        }
    }

} 
→ Ссылка
Автор решения: Wiktor Stribiżew

Используйте

preg_split('~{@\w+\(.*?\)}(?![^|])(*SKIP)(*F)|\|~u', $text)
  • {@ - текст {@
  • \w+ - одна и более букв, цифр или знак _
  • \( - символ (
  • .*? - ноль или более символов, отличных от символа перевода строки, как можно меньше
  • \)} - текст )}
  • (?![^|]) - сразу после текущей позиции должен быть символ | или конец строки
  • (*SKIP)(*F) - в текущей позиции сигнализирует конец совпадения, которое отменяется, а поиск нового совпадения начинается именно с текущей позиции
  • | - или
  • \| - символ |.

Пример кода на PHP:

$text = "123|@rand(тест {@rand(номер 1234|номер 4321)}|строка||";
print_r(preg_split('~{@\w+\(.*?\)}(?![^|])(*SKIP)(*F)|\|~u', $text));

Результат:

Array
(
    [0] => 123
    [1] => @rand(тест {@rand(номер 1234|номер 4321)}
    [2] => строка
    [3] => 
    [4] => 
)
→ Ссылка