skip to Main Content

I’m writing a template compiler in PHP for my own personal projects and ran into an issue where my @if directive doesn’t properly grab the contents of the directive if the condition ends with a ).

private function handleIf(string $page): string
{
    return preg_replace('/@if ?( ?(.*?) ?)(.*?)@endif/is', '<?php if ($1) { ?> $2 <?php } ?>', $page);
}
// the directive...
// @if(!empty($title))
// should result in
<?php !empty($title) { ?>
// but is instead rendered as
<?php !empty($title { ?>
// which obviously no worky

How would I adjust my regex to match this correctly? It has to be global and multiline since it’s part of a template. Is there another method I could use to extract directives?

2

Answers


  1. Can see this code

    /@if[ ]{0,}((.*))(.*)@endif/gsU
    

    input text

    @if(!empty($title))
         <div>hello</div>
    @endif
    

    output

    <?php if ( !empty($title) ) { ?> 
         <div>hello</div>
     <?php } ?>
    

    fully code:

    <?php //php 7.2.24
    
    function handleIf(string $page): string
    {
        return preg_replace('/@if[ ]{0,}((.*))(.*)@endif/isU', '<?php if ( $1 ) { ?> $2 <?php } ?>', $page);
    }
    
    $title = "";
    
    $code = '
    @if(!empty($title))
         <div>hello</div>
    @endif
    ';
    
    echo handleIf($code);
    ?>
    

    can run via this link
    https://rextester.com/DSN63422

    Login or Signup to reply.
  2. For the given example, you could use a recursive pattern to match balanced parenthesis right after the @if.

    Note that you are matching php code with a regex that may give unexpected side effects.

    @ifh*((((?:[^()]++|(?1))*)))s*(.*?)s*@endifb
    
    • @ifh* Match @if followed by optional spaces
    • ( Capture group 1
      • ( Match (
      • ( Capture group 2
        • (?:[^()]++|(?1))* Repeat matching any char except ( or ) or repeat the first sub pattern
      • ) Close group 2
      • ) Match )
    • ) Close group 1
    • s*(.*?)s* Capture group 3, match any character as few as possible between optional whitespace chars
    • @endifb Match @endif followed by a word boundary

    Regex demo | Php demo

    $pattern = '/@ifh*((((?:[^()]++|(?1))*)))s*(.*?)s*@endifb/is';
    $s = '@if(!empty($title)). test
    and testing   @endif';
    $subst = "<?php if($2) { ?>$3<?php } ?>";
    
    $result = preg_replace($pattern, $subst, $s);
    
    echo $result;
    

    Output

    <?php if(!empty($title)) { ?>. test
    and testing<?php } ?>
    

    If you don’t want to cross another @if( in between:

    @ifh*((((?:[^()]++|(?1))*)))s*((?:(?!@ifh*().)*)s*@endifb
    

    Regex demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search