Regex для синтаксического анализа содержимого define (), возможно? - PullRequest
5 голосов
/ 14 марта 2009

Я очень плохо знаком с регулярным выражением, и это слишком продвинуто для меня. Поэтому я спрашиваю экспертов здесь.

Проблема Я хотел бы получить константы / значения из php define ()

DEFINE('TEXT', 'VALUE');

В основном я хотел бы, чтобы регулярное выражение могло возвращать имя константы и значение константы из приведенной выше строки. Просто ТЕКСТ и ЦЕННОСТЬ. Это вообще возможно?

Зачем мне это нужно? Я имею дело с языковым файлом, и я хочу получить все пары (имя, значение) и поместить их в массив. Мне удалось сделать это с помощью str_replace (), trim () и т. Д., Но этот путь длинный, и я уверен, что это можно сделать проще с помощью одной строки регулярного выражения.

Примечание: VALUE также может содержать экранированные одинарные кавычки. пример:

DEFINE('TEXT', 'J\'ai');

Надеюсь, я не прошу чего-то слишком сложного. :)

Привет

Ответы [ 5 ]

17 голосов
/ 14 марта 2009

Для любого вида синтаксического анализа на основе грамматики регулярные выражения обычно являются ужасным решением. Даже некоторые грамматики (например, арифметические) имеют вложенность, и именно на вложенности (в частности) регулярные выражения просто падают.

К счастью, PHP предоставляет вам гораздо лучшее решение, предоставляя вам доступ к тому же лексическому анализатору, который используется интерпретатором PHP, через функцию token_get_all () . Дайте ему символьный поток кода PHP, и он будет разбирать его на токены («лексемы»), которые вы можете выполнить с помощью довольно простого конечного автомата .

.

Запустите эту программу (она запускается как 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 довольно прост, когда дело касается строк. Они могут быть объявлены одинарными кавычками, двойными кавычками или (в определенных обстоятельствах) без кавычек вообще. Это может быть (как указал Гамбо) двусмысленная ссылка на константу, и у вас нет возможности узнать, что это такое (в любом случае это не гарантированный путь), что дает вам выбор:

  1. Игнорирование этого стиля строк (T_STRING);
  2. Проверка, была ли объявлена ​​константа с этим именем, и замена ее значения. Нет никакого способа узнать, какие другие файлы были вызваны, и при этом вы не можете обработать какие-либо определения, которые создаются условно, поэтому вы не можете с уверенностью сказать, является ли что-то определенным константой или нет, и какое значение это имеет; или
  3. Вы можете просто жить с возможностью того, что это могут быть константы (что маловероятно), и просто рассматривать их как строки.

Лично я бы пошел на (1), затем (3).

2 голосов
/ 14 марта 2009

Это возможно, но я бы скорее использовал get_defined_constants () . Но убедитесь, что все ваши переводы имеют что-то общее (например, все переводы, начинающиеся с T), чтобы вы могли отличить их от других констант.

2 голосов
/ 14 марта 2009

Попробуйте это регулярное выражение, чтобы найти 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);
1 голос
/ 14 марта 2009

Вам, возможно, не нужно идти за борт со сложностью регулярных выражений - что-то вроде этого, вероятно, будет достаточно

 /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";
    }

}
0 голосов
/ 14 марта 2009

Не все проблемы с текстом должны решаться с помощью регулярных выражений, поэтому я бы посоветовал вам указать, чего вы хотите достичь, а не как.

Итак, вместо того, чтобы использовать парсер php, который не очень полезен, или вместо использования полностью отлаживаемого регулярного выражения, почему бы не написать простой парсер?

<?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"
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...