5 Stimmen

Regex zum Parsen von define()-Inhalten, möglich?

Ich bin sehr neu in Regex, und dies ist viel zu fortgeschritten für mich. Also frage ich die Experten hier.

Problem Ich möchte die Konstanten / Werte aus einer php define() abrufen

DEFINE('TEXT', 'VALUE');

Grundsätzlich möchte ich eine Regex in der Lage sein, den Namen der Konstante und den Wert der Konstante aus der obigen Zeile zurückzugeben. Nur TEXT und VALUE . Ist dies überhaupt möglich?

Warum ich es brauche? Ich habe mit einer Sprachdatei zu tun und möchte alle Paare (Name, Wert) erhalten und in ein Array einfügen. Ich schaffte es, es mit str_replace() und trim() etc. zu tun, aber dieser Weg ist lang und ich bin sicher, es könnte einfacher mit einer einzigen Zeile von Regex gemacht werden.

Hinweis: Der VALUE kann auch einfache Anführungszeichen enthalten. Beispiel:

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

Ich hoffe, ich verlange nicht nach etwas zu Kompliziertem :)

Mit freundlichen Grüßen

0 Stimmen

Wie sieht es mit nicht-literalen Werten aus, z. B. define('NOW', time()) ?

0 Stimmen

Ich habe es mit einer Sprachdatei zu tun, die nur Text in einfachen Anführungszeichen auf beiden Seiten enthält. Konstanter Name und Wert.

0 Stimmen

Die Namen und Werte sind also immer feste Zeichenfolgen?

19voto

cletus Punkte 596503

Für jede Art von grammatikbasiertem Parsing sind reguläre Ausdrücke in der Regel eine schreckliche Lösung. Selbst einfache Grammatiken (wie Arithmetik) haben Verschachtelungen, und gerade bei Verschachtelungen fallen reguläre Ausdrücke einfach um.

Glücklicherweise bietet PHP eine viel, viel bessere Lösung für Sie, indem es Ihnen Zugriff auf denselben lexikalischen Analysator gibt, der vom PHP-Interpreter über die Option Funktion token_get_all() . Geben Sie ihm einen Zeichenstrom von PHP-Code, und es wird ihn in Token ("Lexeme") zerlegen, die Sie mit einem ziemlich einfachen Parsing-Programm endlicher Automat .

Führen Sie dieses Programm aus (es wird als test.php ausgeführt, damit es sich selbst ausprobiert). Die Datei ist absichtlich schlecht formatiert, damit Sie sehen können, dass es das mit Leichtigkeit handhabt.

<?
    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);
}
?>

Ausgabe:

'CONST1' => 'value'
'CONST2' => 'value2'
'CONST3' => 'time'
'define' => 'define'
'test' => 'VALUE4'
'const5' => 'weird declaration'
'CONST7' => '3.14'
'foo' => 'bar'
'CONST5' => '6'

Dabei handelt es sich im Grunde um einen endlichen Zustandsautomaten, der nach dem Muster sucht:

function name ('define')
open parenthesis
constant
comma
constant
close parenthesis

im lexikalischen Strom einer PHP-Quelldatei und behandelt die beiden Konstanten als ein (Name,Wert)-Paar. Dabei behandelt es verschachtelte define()-Anweisungen (gemäß den Ergebnissen) und ignoriert Leerzeichen und Kommentare sowie die Arbeit über mehrere Zeilen hinweg.

Anmerkung: Ich habe es absichtlich so gemacht, dass der Fall ignoriert wird, wenn Funktionen und Variablen konstante Namen oder Werte sind, aber Sie können es nach Belieben erweitern.

Es ist auch erwähnenswert, dass PHP ziemlich nachsichtig ist, wenn es um Strings geht. Sie können mit einfachen Anführungszeichen, doppelten Anführungszeichen oder (unter bestimmten Umständen) ganz ohne Anführungszeichen deklariert werden. Dies kann (wie von Gumbo hervorgehoben) ein mehrdeutiger Verweis auf eine Konstante sein und Sie haben keine Möglichkeit zu wissen, um welche es sich handelt (jedenfalls keine garantierte Möglichkeit), was Ihnen die Möglichkeit gibt:

  1. Ignorieren dieser Art von Strings (T_STRING);
  2. Prüfen, ob eine Konstante mit diesem Namen bereits deklariert wurde, und Ersetzen ihres Wertes. Es gibt keine Möglichkeit zu wissen, welche anderen Dateien aufgerufen wurden, noch können Sie Defines verarbeiten, die bedingt erstellt wurden, so dass Sie nicht mit Sicherheit sagen können, ob etwas definitiv eine Konstante ist oder nicht oder welchen Wert es hat; oder
  3. Sie können einfach mit der Möglichkeit leben, dass es sich um Konstanten handelt (was unwahrscheinlich ist) und sie einfach als Zeichenketten behandeln.

Ich persönlich würde mich für (1) und dann für (3) entscheiden.

0 Stimmen

Was ist, wenn CONST2 bereits eine Konstante ist? define('foo', 'bar'); define(foo, 'baz'); => foo='bar', bar='baz'

0 Stimmen

CONST2 ist eine T_STRING-Konstante. Mit einer zusätzlichen Überprüfung könnten Sie prüfen, ob Sie eine T_STRING-Konstante erhalten und dann is_defined() darauf anwenden, um den Wert zu erhalten oder, wenn er nicht definiert ist, als String zu behandeln (wie PHP es tut).

0 Stimmen

"CONST2 ist eine T_STRING-Konstante". - Oh, ich vergaß: es ist PHP ;)

2voto

soulmerge Punkte 70900

Dies ist möglich, aber ich würde lieber die get_defined_constants() . Achten Sie aber darauf, dass alle Ihre Übersetzungen etwas gemeinsam haben (z. B. alle Übersetzungen, die mit T beginnen), damit Sie sie von anderen Konstanten unterscheiden können.

0 Stimmen

Aber dabei muss ich vielleicht zu viele Zeilen bearbeiten? Sie haben nichts gemeinsam also dachte ich, ich könnte eine Regex verwenden, deshalb

2voto

Gumbo Punkte 617646

Probieren Sie diesen regulären Ausdruck aus, um die define Anrufe:

 /\bdefine\(\s*("(?:[^"\\]+|\\(?:\\\\)*.)*"|'(?:[^'\\]+|\\(?:\\\\)*.)*')\s*,\s*("(?:[^"\\]+|\\(?:\\\\)*.)*"|'(?:[^'\\]+|\\(?:\\\\)*.)*')\s*\);/is

Also:

$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);

Ich weiß, dass eval ist böse. Aber das ist der beste Weg, um die String-Ausdrücke auszuwerten:

$constants = array();
foreach ($matches as $match) {
    eval('$constants['.$match[1].'] = '.$match[1].';');
}
var_dump($constants);

1voto

Paul Dixon Punkte 286600

Möglicherweise müssen Sie es mit der Komplexität der Regex nicht übertreiben - etwas wie das Folgende wird wahrscheinlich ausreichen

 /DEFINE\('(.*?)',\s*'(.*)'\);/

Hier ist ein PHP-Beispiel, das zeigt, wie Sie es verwenden können

$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 Stimmen

Danke Paul. Dies prüft nur auf das Muster define('text', 'value') richtig? -- Ich meine, wenn ich als nächstes Text und Wert erfassen wollte, wie würde ich das tun?

0 Stimmen

Hat wunderbar funktioniert! ~ saved me a lot!

0 Stimmen

Err... dies schlägt bei jeder trivialen Variante fehl, z.B. define("blah", "foo"). Auch bei anderen Abständen als denen, die Sie haben, bei Defines, die sich über mehrere Zeilen erstrecken, bei Heredocs und so weiter.

0voto

phihag Punkte 261131

Nicht jedes Problem mit Text sollte mit einer Regexp gelöst werden, daher schlage ich vor, dass Sie angeben, was Sie erreichen wollen und nicht wie.

Also, anstatt den Parser von php zu verwenden, der nicht wirklich nützlich ist, oder anstatt einen völlig undebuggable regexp zu verwenden, warum nicht einen einfachen Parser schreiben?

<?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));

Ausgabe:

array(3) {
  ["nam'e"]=>
  string(7) "va\'lue"
  ["na\me2"]=>
  string(7) "value'2"
  ["a"]=>
  string(1) "b"
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X