Я очень новичок в регулярном выражении, и для меня это слишком продвинуто. Поэтому я спрашиваю экспертов здесь.
Проблема Я хотел бы получить константы / значения из php define ()
DEFINE('TEXT', 'VALUE');
В основном я хотел бы, чтобы регулярное выражение могло возвращать имя константы и значение константы из указанной строки. Только ТЕКСТ и ЗНАЧЕНИЕ. Возможно ли это?
Зачем мне это нужно? Я имею дело с языковым файлом, и я хочу получить все пары (имя, значение) и поместить их в массив. Мне удалось сделать это с помощью str_replace () и trim () и т. Д., Но этот путь длинный, и я уверен, что его можно упростить с помощью одной строки регулярного выражения.
Примечание. VALUE может содержать экранированные одинарные кавычки. пример:
DEFINE('TEXT', 'J\'ai');
Надеюсь, я не прошу что-то слишком сложное. 🙂
С уважением
Для любого анализа, основанного на грамматике, регулярные выражения обычно являются ужасным решением. Даже грамматические грамматики (например, арифметические) имеют гнездование, и наложение (в частности) на регулярные выражения просто падает.
К счастью, PHP предоставляет гораздо лучшее решение для вас, предоставляя вам доступ к тому же лексическому анализатору, который используется интерпретатором PHP через функцию token_get_all () . Дайте ему характерный поток PHP-кода, и он проанализирует его в токенах («lexemes»), которые вы можете выполнить немного простого анализа с помощью довольно простой конечной машины .
Запустите эту программу (она запускается как test.php, поэтому она пытается сама по себе). Файл намеренно отформатирован плохо, поэтому вы можете видеть, что он легко справляется с этим.
<? define('CONST1', 'value' ); define (CONST2, 'value2'); define( 'CONST3', time()); define('define', 'define'); define("test", VALUE4); define('const5', // 'weird declaration' ) ; define('CONST7', 3.14); define ( /* comment */ 'foo', 'bar'); $defn = 'blah'; define($defn, 'foo'); define( 'CONST4', define('CONST5', 6)); header('Content-Type: text/plain'); $defines = array(); $state = 0; $key = ''; $value = ''; $file = file_get_contents('test.php'); $tokens = token_get_all($file); $token = reset($tokens); while ($token) { // dump($state, $token); if (is_array($token)) { if ($token[0] == T_WHITESPACE || $token[0] == T_COMMENT || $token[0] == T_DOC_COMMENT) { // do nothing } else if ($token[0] == T_STRING && strtolower($token[1]) == 'define') { $state = 1; } else if ($state == 2 && is_constant($token[0])) { $key = $token[1]; $state = 3; } else if ($state == 4 && is_constant($token[0])) { $value = $token[1]; $state = 5; } } else { $symbol = trim($token); if ($symbol == '(' && $state == 1) { $state = 2; } else if ($symbol == ',' && $state == 3) { $state = 4; } else if ($symbol == ')' && $state == 5) { $defines[strip($key)] = strip($value); $state = 0; } } $token = next($tokens); } foreach ($defines as $k => $v) { echo "'$k' => '$v'\n"; } function is_constant($token) { return $token == T_CONSTANT_ENCAPSED_STRING || $token == T_STRING || $token == T_LNUMBER || $token == T_DNUMBER; } function dump($state, $token) { if (is_array($token)) { echo "$state: " . token_name($token[0]) . " [$token[1]] on line $token[2]\n"; } else { echo "$state: Symbol '$token'\n"; } } function strip($value) { return preg_replace('!^([\'"])(.*)\1$!', '$2', $value); } ?>
Вывод:
'CONST1' => 'value' 'CONST2' => 'value2' 'CONST3' => 'time' 'define' => 'define' 'test' => 'VALUE4' 'const5' => 'weird declaration' 'CONST7' => '3.14' 'foo' => 'bar' 'CONST5' => '6'
Это в основном конечный автомат, который ищет шаблон:
function name ('define') open parenthesis constant comma constant close parenthesis
в лексическом потоке исходного файла PHP и рассматривает две константы как пару (имя, значение). При этом он обрабатывает вложенные операторы define () (в соответствии с результатами) и игнорирует пробелы и комментарии, а также работает через несколько строк.
Примечание. Я намеренно сделал это, игнорируя случай, когда функции и переменные являются постоянными именами или значениями, но вы можете расширить его до этого, как вы пожелаете.
Также стоит отметить, что PHP довольно прощает, когда дело доходит до строк. Они могут быть объявлены одинарными кавычками, двойными кавычками или (при определенных обстоятельствах) без кавычек вообще. Это может быть (как указано Gumbo) быть двусмысленной ссылкой на константу, и у вас нет способа узнать, что это (не гарантированный способ), давая вам chocie:
Лично я бы пошел (1) тогда (3).
Это возможно, но я предпочел бы использовать get_defined_constants () . Но убедитесь, что все ваши переводы имеют что-то общее (например, все переводы, начинающиеся с T), поэтому вы можете отличить их от других констант.
Попробуйте это регулярное выражение, чтобы найти вызовы define
:
/\bdefine\(\s*("(?:[^"\\]+|\\(?:\\\\)*.)*"|'(?:[^'\\]+|\\(?:\\\\)*.)*')\s*,\s*("(?:[^"\\]+|\\(?:\\\\)*.)*"|'(?:[^'\\]+|\\(?:\\\\)*.)*')\s*\);/is
Так:
$pattern = '/\\bdefine\\(\\s*("(?:[^"\\\\]+|\\\\(?:\\\\\\\\)*.)*"|\'(?:[^\'\\\\]+|\\\\(?:\\\\\\\\)*.)*\')\\s*,\\s*("(?:[^"\\\\]+|\\\\(?:\\\\\\\\)*.)*"|\'(?:[^\'\\\\]+|\\\\(?:\\\\\\\\)*.)*\')\\s*\\);/is'; $str = '<?php define(\'foo\', \'bar\'); define("define(\\\'foo\\\', \\\'bar\\\')", "define(\'foo\', \'bar\')"); ?>'; preg_match_all($pattern, $str, $matches, PREG_SET_ORDER); var_dump($matches);
Я знаю, что eval
– это зло. Но это лучший способ оценить строковые выражения:
$constants = array(); foreach ($matches as $match) { eval('$constants['.$match[1].'] = '.$match[1].';'); } var_dump($constants);
Возможно, вам не нужно будет перегружать сложность регулярного выражения – что-то вроде этого, вероятно, будет достаточно
/DEFINE\('(.*?)',\s*'(.*)'\);/
Вот пример PHP, показывающий, как вы можете его использовать.
$lines=file("myconstants.php"); foreach($lines as $line) { $matches=array(); if (preg_match('/DEFINE\(\'(.*?)\',\s*\'(.*)\'\);/i', $line, $matches)) { $name=$matches[1]; $value=$matches[2]; echo "$name = $value\n"; } }
Не каждая проблема с текстом должна быть решена с помощью регулярного выражения, поэтому я предлагаю вам указать, чего вы хотите достичь, а не как.
Итак, вместо использования парсера php, который не очень полезен, или вместо использования полностью undebuggable regexp, почему бы не написать простой парсер?
<?php $str = "define('nam\\'e', 'va\\\\\\'lue');\ndefine('na\\\\me2', 'value\\'2');\nDEFINE('a', 'b');"; function getDefined($str) { $lines = array(); preg_match_all('#^define[(][ ]*(.*?)[ ]*[)];$#mi', $str, $lines); $res = array(); foreach ($lines[1] as $cnt) { $p = 0; $key = parseString($cnt, $p); // Skip comma $p++; // Skip space while ($cnt{$p} == " ") { $p++; } $value = parseString($cnt, $p); $res[$key] = $value; } return $res; } function parseString($s, &$p) { $quotechar = $s[$p]; if (! in_array($quotechar, array("'", '"'))) { throw new Exception("Invalid quote character '" . $quotechar . "', input is " . var_export($s, true) . " @ " . $p); } $len = strlen($s); $quoted = false; $res = ""; for ($p++;$p < $len;$p++) { if ($quoted) { $quoted = false; $res .= $s{$p}; } else { if ($s{$p} == "\\") { $quoted = true; continue; } if ($s{$p} == $quotechar) { $p++; return $res; } $res .= $s{$p}; } } throw new Exception("Premature end of line"); } var_dump(getDefined($str));
Вывод:
array(3) { ["nam'e"]=> string(7) "va\'lue" ["na\me2"]=> string(7) "value'2" ["a"]=> string(1) "b" }