how to implement template with conditions with pre

2019-07-21 16:26发布

问题:

I'm trying to implement an admin interface where manager could create advanced rules of site meta tags formation.

I have a function which takes a template and replaces placeholders in it with values from $registry and applies modifiers if needed.

$registy = array( 'profession_name' => 'actor', 'total_found' => 100, );

$result = preg_replace_callback('/\{([^{}:]+)(:[^{}:]+)?\}/', function($matches) use ($registry) {

    $result = $registry[$matches[1]] ?: $matches[0];

    if (in_array($matches[2], array(':ucfirst', ':strtolower', ':strtoupper', ':lcfirst')))
    {
        $result = call_user_func(substr($matches[2], 1), $result);
    }

    return $result;
},  
$template);

Example:

Profession {name:ucfirtst} is {profession_name:strtoupper}

is parced to

Profession Name is ACTOR

I'm trying to implement conditions, so it would be possible to make rules like this:

text with {placeholdes} before condition. [{total_found}, TRUE text with {placeholders}, FALSE text with {placeholders}], trailing text with {placeholders} after condition.

So my function would get {total_found} value from registry and depending if it's true or false would return string with corresponding false or true-pattern. I'm very bad at regular expressions, and can't figure out how to write this condition in regexp terms. Help me please.

UPDATE: concrete example looks like whis:

$template = 'Profession is {profession_name}. [{total_found}, {total_found} vacancies found, No vacancies found]. {profession_name} in your town';

in my callback function i would place a condition like this

if ($matches[condition]) // i don't no how exactly 
  $result = $matches[true-condition];
else
  $result = $matches[false-condition]

So in case $registry['total_found'] = 100; final result would be «Profession is actor. 100 vacancies found. Actor in your town». In case $registry['total_found'] = 0; final result would be «Profession is actor. No vacancies found. Actor in your town».

回答1:

$available_functions = array('ucfirst', 'strtolower', 'strtoupper', 'lcfirst');
$registry = array( 'profession_name' => 'actor', 'total_found' => 100, );
$template = 'Profession {"is":strtoupper} {profession_name:strtoupper:lcfirst}. [{total_found}, {total_found} vacancies found, No vacancies found]. {profession_name} in your town';

$pattern = <<<'EOD'
~
(?=[[{])  # speed up the pattern by quickly skipping all characters that are not
          # a "[" or a "{" without to test the two branches of the alternation 
(?:
    # conditional
    \[
      { (?<if> [^}]+ ) } ,   
        (?<then> [^][,]* )
        (?:, (?<else> [^][]* ) )?
     ]
  |
     # basic replacement
     (?<!\[) # allow nested conditionals           
     {
        (?<var_name> [^{}:]+ )
        (?: : (?<func_name> [^{}:]+ ) )? # first function to apply
        (?: (?<other_funcs> : [^}]+ ) )? # allow to chain functions
     }
)  
~x
EOD;

$replacement = function ($m) use ($registry, $available_functions) {
    if (!empty($m['if'])) {
        $cond = $m['if'];

        # when the condition doesn't exist, the string is evaluated as it
        if (isset($registry[$cond]))
            $cond = $registry[$cond];

        return $cond ? $m['then'] : $m['else'];

    } else {
        $value = isset($registry[$m['var_name']]) ? $registry[$m['var_name']] : trim($m['var_name'], '"\'');  

        if (isset($m['func_name'])) {
            $func = trim($m['func_name']);

            if (in_array($func, $available_functions))
                $value = call_user_func($func, $value);
        }

        if (isset($m['other_funcs']))
            return '{\'' . $value . '\'' . $m['other_funcs'] . '}';

        return $value;
    }
};

$result = $template;

do {
    $result = preg_replace_callback($pattern, $replacement, $result, -1, $count);
} while ($count);

echo $result;

When a condition is met, the approach consists to replace with the good branch first and to proceed to the replacement inside the branch in a second time. This is the reason why preg_replace_callback is in a do...while loop. The other reason is to allow chained functions.

Note that except small improvements for speed and readability, the pattern doesn't need to use special features.

I have added several improvements like default behaviors (when something is not found in the registry), the ability to use chained functions and literal strings. You can obviously change them to feet to your needs.