我出于学习原因尝试创建PHP模板引擎。
假设我们有以下数组:
$regexList = [
'varPattern' => '/{{\s*\$(.*?)\s*}}/',
'loopPattern' => '/@for\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}/',
'statementPattern' => '/@if\((.*?)\)\s*{{((?:[^{}]|(?R))*)}}/'
]
和以下功能:
getVar($varName);
loop($arrayName);
getStatementResult($booleanExpression);
和字符串如下:
$string = '
<span>{{ $fullName }}</span>
@for($names as $name)
{{
@if($name == 'Eleandro)
{{
<p>{{ $name }}</p>
}}
}} ';
我的想法是从上到下阅读字符串并依赖正则表达式列表找到结果并将其提供给正确的函数< / em>的
例如:要查找的第一件事必须是{{$string}}
,因此我们将变量名称传递给getVar($matchedVarName)
函数。< / p>
下一步必须是@loop(){{ }}
,因此,我们致电loop($matchedArrayName);
并且循环内部必须找到@if(){{ }}
,因此,我们得到结果并将getStatementResult($matchedBooleanExpression)
提供给匹配的值。
我如何以正确的顺序(从上到下)执行此操作?谢谢。
答案 0 :(得分:1)
如果您对preg_match_all
使用 PREG_OFFSET_CAPTURE
标记,则可以执行此操作。该标志将执行的操作是更改捕获的匹配项以包括模式字符串中的偏移量。现在,我们唯一需要做的就是以与偏移量相同的顺序遍历所有匹配。
这有点棘手,但绝对有可能。我的方法如下:
$matches
匹配的preg_match_all
重要提示:匹配按每个数组中的偏移量排序。 [0, 0, 0]
。为了使它更通用并让它处理任意数量的正则表达式,我使用array_fill
。虽然并非所有比赛都得到处理......
minIndex
函数。使用匹配组的值调用相应的函数(在$functions
下硬编码)。此时,我们知道要调用哪个函数,因为我们知道哪个正则表达式创建了这个特定的匹配。
如果你可以为所有匹配调用相同的函数,我们可以在一个数组中合并所有匹配并按偏移量排序。
增加匹配的正则表达式的索引。
此时,我应该注意您示例中的最后两个正则表达式根本不匹配。这是因为循环和语句中存在{
和}
的实例。为了演示,我简单地删除了那些正则表达式的一部分,因此它们只匹配循环/语句的条件。
以下脚本将打印出来。
getVar('fullName')
loop('$names as $name')
getStatementResult('$name === "Eleandro"')
getVar('name')
我相信,这是你期望的输出。
// functions
function getVar($varName) {
echo "getVar('$varName')\n";
}
function loop($arrayName) {
echo "loop('$arrayName')\n";
}
function getStatementResult($booleanExpression) {
echo "getStatementResult('$booleanExpression')\n";
}
// input
$string = '
<span>{{ $fullName }}</span>
@for($names as $name)
{{
@if($name === "Eleandro")
{{
<p>{{ $name }}</p>
}}
}} ';
// helper functions
function minIndex($arr) {
$i = 0; $l = count($arr);
$min = false; $minI = -1;
for ($i = 0; $i < $l; ++$i) {
if ($arr[$i] === false) continue; // skip non numbers
if ($min === false || $arr[$i] < $min) {
$min = $arr[$i];
$minI = $i;
}
}
return $minI;
}
// regular expressions
$regexList = [
'varPattern' => '/{{\s*\$(.*?)\s*}}/',
'loopPattern' => '/@for\((.*?)\)\s*{{/',
'statementPattern' => '/@if\((.*?)\)\s*{{/'
];
// functions to map above regexes to
$functions = ['getVar', 'loop', 'getStatementResult'];
// matches per regex
$matchesAll = [];
// combine the above regexes into a single one, run that
foreach ($regexList as $name => $regex) {
unset($matches);
preg_match_all($regex, $string, $matches, PREG_OFFSET_CAPTURE);
$matchesAll[] = $matches;
}
// walk over the matches in order of offset in string
// current match per regex
$indexes = array_fill(0, count($regexList), 0);
// number of matches per regex
$counts = array_map(function($m) { return count($m[0]); }, $matchesAll);
while ($indexes !== $counts) {
$offsets = array_map(function($m, $i) {
return (count($m[0]) > $i ? $m[0][$i][1] : false);
}, $matchesAll, $indexes);
$next = minIndex($offsets);
call_user_func($functions[$next],
$matchesAll[$next][1][$indexes[$next]][0]);
$indexes[$next]++;
}