使用preg_split来模板化引擎

I am building a templating engine and would like to allow nested logic.

I need to split the following string using "@" for the delimiter but would like to ignore this delimiter - treat is as just another character - if its inside the [square brackets].

Here is the input string:

@if(param1>=7) [ something here @if(param1>9)[ nested statement ] ] @elseif(param2==true) [ 2st condition true ] @else [ default condition ] 

The result should look like:

array(

   " if(param1>=7) [ something here @if(param1>9)[ nested statement ] ] ",

   " elseif(param2==true) [ 2st condition true ] ",

   " else [ default condition ] "

)

I believe preg_split is what Im looking for but could use help with the regex

Regex:

@(?> if | else (?>if)? ) \s*  # Match a control structure
(?> \( [^()]*+ \) \s*)?  # Match statements inside parentheses (if any)
\[  # Match start of block
(?:
    [^][@]*+  # Any character except `[`, `]` and `@`
    (?> \\.[^][@]* )* (@+ (?! (?> if | else (?>if)? ) ) )? # Match escaped or literal `@`s
    |  # Or
    (?R)  # Recurs whole pattern
)*  # As much as possible
\]\K\s*  # Match end of container block

Live demo

PHP:

print_r(preg_split("~@(?>if|else(?>if)?)\s*(?>\([^()]*+\)\s*)?\[(?:[^][@]*+(?>\\.[^][@]*)*(@+(?!(?>if|else(?>if)?)))?|(?R))*\]\K\s*~", $str, -1, PREG_SPLIT_NO_EMPTY));

Output:

Array
(
    [0] => @if(param1>=7) [ something here @if(param1>9)[ nested statement ] ]
    [1] => @elseif(param2==true) [ 2st condition true ]
    [2] => @else [ default condition ]
)

PHP live demo

To match nested brackets, you need to use a recursive pattern.

(?:(\[(?:[^[\]]|(?1))*])|[^@[\]])+

It will match each segment, not including the leading @. It will also capture the last bracket into group 1, which you can ignore.

Use the pattern with preg_match or preg_match_all.

Thank you for you replies! Revo's answer worked!

I was unable to come up with the regex myself and have instead built a parser function that works as well. Perhaps it can be useful to somebody:

function parse_context($subject) { $arr = array(); // array to return

    $n = 0;  // number of nested sets of brackets
    $j = 0;  // current index in the array to return, for convenience

    $n_subj = strlen($subject);
    for($i=0; $i<$n_subj; $i++){
        switch($subject[$i]) {
            case '[':
                $n++;
                $arr[$j] .= '[';
                break;
            case ']':
                $n--;
                $arr[$j] .= ']';
                break;
            case '@':
                if ($n == 0) {
                    $j++;
                    $arr[$j] = '';
                    break;
                }
            default: $arr[$j].=$subject[$i];
        }
    }
    return $arr;

} // parse_context()