2017-05-24 97 views
1

我試圖創建一個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> 
    }} 
}} '; 

這個想法是從上到下閱讀字符串,並依靠正則表達式的列表找到結果並將其賦給正確的函數

例如:首先要找到必須爲{{$string}},所以我們通過變量名稱爲getVar($matchedVarName)功能

下一步必須是@loop(){{ }},所以,我們叫loop($matchedArrayName);

和內部循環必須要瑤池的@if(){{ }},所以,我們得到的結果,分給getStatementResult($matchedBooleanExpression)與相匹配的值。

我該如何以正確的順序(從上到下)進行操作?謝謝。

回答

1

如果您使用PREG_OFFSET_CAPTURE標誌爲preg_match_all,這是可能的。該標誌將做什麼,是更改捕獲的匹配,以包括模式字符串中的偏移量。現在,我們唯一需要做的就是按照偏移指令的順序遍歷所有比賽。

這有點棘手,但絕對有可能。我的方法是以下情況:

  1. 創建每正則表達式匹配項的陣列。既然你有三個正則表達式,這將是一個有三個值的數組。每個值都是簡單的$matches,該正則表達式匹配preg_match_all
    重要注意事項:匹配按每個數組內的偏移排序。
  2. 爲每個正則表達式創建一個索引數組。我們將使用它來跟蹤每個正則表達式的位置。這初始化爲[0, 0, 0]。爲了使它更通用,並讓它處理任意數量的正則表達式,我使用array_fill來完成此操作。
  3. 雖然不是所有的比賽被處理......

    1. 確定下一場比賽來處理。這將是下一場比賽具有最小偏移量的正則表達式。我爲此寫了一個小的minIndex函數。
    2. 用匹配組的值調用適當的函數(在下面的$functions中硬編碼)。此時,我們知道要調用哪個函數,因爲我們知道哪個正則表達式創建了此特定匹配項。

      如果您對所有匹配調用相同的函數都可以,我們可以將所有匹配合併到一個數組中,並按偏移量對它們進行排序。

    3. 增加匹配的正則表達式的索引。

在這一點上,我應該此話,在您的示例中的最後兩個正則表達式不匹配的。這是因爲循環和語句中有{}的實例。爲了演示,我只是簡單地將這些正則表達式中的一部分截掉,所以它們只匹配循環/語句的條件。

下面的腳本將打印此。

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]++; 
} 
+0

就是這樣,理解那裏發生的事情有點複雜,但是。我現在使用preg_replace_callback,所以,我會嘗試使用帶PREG_OFFSET_CAPTURE標誌的preg_match_all來完成這項工作,而不是像您已經完成的那樣。謝啦! – e200

+0

有一個問題,如果我改變功能的順序爲 $ functions = ['loop','getVar','getStatementResult'];它不會首先獲取var,它需要首先獨立於訂單獲取var。否則,我將無法執行出現在var之前的語句,它將是靜態的,所以,我將不得不始終執行相同的順序:var,loop,statement,如果有人創建了一個以語句開​​頭的模板將失敗! – e200

+0

函數的順序必須與'$ regexList'中正則表達式的順序相匹配。這段代碼會按照它們出現在字符串中的順序報告變量,語句和循環,我認爲這是你需要的嗎? –