How to find a string based on a group of regular expressions

Asked

Viewed 89 times

3

I’m trying to create a Template Engine in PHP for study purposes.

Suppose I have the following array:

$regexList = [
  'varPattern' => '/{{\s*\$(.*?)\s*}}/',
  'loopPattern' => '/@for\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}/',
  'statementPattern' => '/@if\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}/'
]

and followed them function:

getVar($nomeDaVariavel);
loop($nomeDoArray);
getStatementResult($expressãoBooleana);

and the following string:

$string = '

<span>{{ $nomeCompleto }}</span>

@for($nomes as $nome)
{{
  @if($nome == 'Eleandro)
  {{
    <p>{{ $nome }}</p>
  }}
}} ';

The idea is to read the string from top to bottom and based on the list of regular expressions find the result and deliver it the correct function.

For example: The first thing to be found must be {{ $string }}, then we pass the variable name to function getVar($nomeDaVariávelEncontrada).

Next will be the loop, then we call the loop($comoNomeDoArrayEncontrado); and within the loop will be found the if, then we get the contents of if and hand it over to function getStatementResult($expressãoBooleanaEncontrada) with the value found.

How can I do it in the right order (from top to bottom)?

  • In this case, it would be interesting to use the interpreter pattern, because it is evident that your problem generates a Composite structure. At the moment, I’m running out of time to formulate a solution.

  • I am not understanding your question, the Regexlist has 3 Regex’s inside it, why not call only one of them when there is the need and assign in the variable, using it at the time you need? There is no method that makes the code "read" from top to bottom, it already does it naturally, just you structure correctly.

  • @I’m using preg_replace_callback to replace the results of Patterns who are in the $regexList. As you can see here, that one function also receives a array with Patterns_ (in case the $regexList. The problem is that function takes the occurrence of the first Pattern in $regexList, instead of picking up the first occurrence in $string that has a Pattern in the $regexList, understands?

  • I don’t know what I can find first on $string, can be a @if, one @for or a {{$var}}, but I know that I must replace the occurrences that appear first on $string, in the case, the first occurrence would be **{$var}}, then I would replace {{$var}} with something, the second would be @for, then I’d run the loop, and so on... This is what I’m not getting.

1 answer

1


I’m using the preg_replace_callback to replace the results of the tests in the $regexList.
[...] The problem is that this function picks up the occurrence of the first Pattern in $regexList, instead of taking the first occurrence in $string that has a Pattern on $regexList.


Use only one regex.

In this case, do not use an array. Instead:

$regexList = [
                 '/regex1/',
                 '/regex2/',
                 '/regex3/',
             ];


Use this:

$regex = '/regex1|regex2|regex3/';

In function:

$resultado = preg_replace_callback(
                 $regex,
                 function ($matches) {
                     // ...
                     return 'substituído';
                 },
                 $texto
             );


But how do you know which regex was found?

You can use groups to identify them.

$regex = '/(regex1)|(regex2)|(regex3)/';

So:

$resultado = preg_replace_callback(
                 $regex,
                 function ($matches) {
                     if ($matches[1]) {
                         return 'regex1 substituído';
                     } else if ($matches[2]) {
                         return 'regex2 substituído';
                     } else if ($matches[3]) {
                         return 'regex3 substituído';
                     }
                 },
                 $texto
             );



But you have other groups in the patterns, and counting the right group number can be tricky. We can use appointed groups to make things easier.

$regex = '/(?P<padrao1>regex1)|(?P<padrao2>regex2)|(?P<padrao3>regex3)/';


$resultado = preg_replace_callback(
                 $regex,
                 function ($matches) {
                     if ($matches['padrao1']) {
                         return 'regex1 substituído';

                     } else if ($matches['padrao2']) {
                         return 'regex2 substituído';

                     } else if ($matches['padrao3']) {
                         return 'regex3 substituído';
                     }
                 },
                 $texto
             );


Code

To answer your question, with a little recursion:

function analisar( $string ) {
    $regex = '/
                    (?P<var>       {{\s*\$(.*?)\s*}}                      )
                |
                    (?P<loop>      @for\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}  )
                |
                    (?P<statement> @if\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}   )
              /x';

    $resultado = preg_replace_callback(
                     $regex,
                     function ($matches) {
                         /*
                             //debug
                             echo "\n\nSubst: $matches[0]\n\$matches = ";
                             var_export($matches);
                         */
                         if ($matches['var']) {
                             return getVar($matches[2]);

                         } else if ($matches['loop']) {
                             return loop($matches[4], $matches[5]);

                         } else if ($matches['statement']) {
                             return getStatementResult($matches[7], $matches[8]);
                         }
                     },
                     $string
                 );

    return $resultado;
}

function getVar($nomeDaVariavel){
    return 'VAR('
        . analisar($nomeDaVariavel)
        . ')';
}
function loop($nomeDoArray, $codigo){
    return "LOOP\nLOOP-COND("
        . analisar($nomeDoArray)
        . ")\nLOOP-CODIGO("
        . analisar($codigo)
        . ')';
}
function getStatementResult($expressãoBooleana, $codigo){
    return "IF\nIF-COND("
        . analisar($expressãoBooleana)
        . ")\nIF-CODIGO("
        . analisar($codigo)
        . ')';
}

Testing:

$string = '
<span>{{ $nomeCompleto }}</span>

@for($nomes as $nome)
{{
  @if($nome == \'Eleandro\')
  {{
    <p>{{ $nome }}</p>
  }}
}} 
';

echo analisar($string);

Upshot:

<span>VAR(nomeCompleto)</span>

LOOP
LOOP-COND($nomes as $nome)
LOOP-CODIGO(
  IF
IF-COND($nome == 'Eleandro')
IF-CODIGO(
    <p>VAR(nome)</p>
  )
) 

Demo: https://ideone.com/pBfQBP

  • 1

    Thank you, bro. Better late than never.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.