Entwickler-Ecke
Programmiersprachen (Server) - Ausdruck mit RegEx sinnvoll zerlegen
Heiko - Sa 07.03.09 21:18
Titel: Ausdruck mit RegEx sinnvoll zerlegen
Hallo,
ich will in meinem Templatesytem momentan Variablen verarbeiten. Diese sind folgendermaßen aufgebaut:
Quelltext
1:
| $abc.def[$xyz.abc[$...[]]].blubb |
Sprich die Variablen fangen mit $, oder auch nicht ;), an und werden durch die Punkte genauer spezifisziert. In eckihgen Klammern stehen "Untervariablen". Zur Erklärung am besten mal, wie oberes später aussehen soll:
Quelltext
1:
| $this->var["abc"]["def"][$this->var["xyz"]["abc"][...]]["blubb"] |
Mein Problem ist jetzt: Wie überführe ich das ganze? Ich wollte es mit einem RegEx splitten, aber woran erkennt der, dass zu einem [ es die richtige ] ist, da das ganze ja rekursiv definiert ist?
Grüße
Heiko
Heiko - So 08.03.09 21:28
Inzwischen habe ich einen RegEx gebastelt, der meinen Ausdruck erkennt. Allerdings habe ich immer noch keine Idee, wie ich ihn am besten parse :(:
C#-Quelltext
1:
| (\$?\w+)(\.\w+|\[(?R)\])* |
Kha - So 08.03.09 21:40
Das, was ein Regex nicht kann, nämlich Rekursion, musst du selbst übernehmen: Mal ein starker Pseudocode:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| method Parse(s: String; var currentPos: Integer) : String; begin var match := Regex.Match('\$\w+(\.\w+)*\[', s.Copy(currentPos)); currentPos += match.Length; Result := ... + Parse(s, currentPos); if s[currentPos] <> "]" then Whoops!(); Inc(currentPos); end; |
BenBE - So 08.03.09 23:32
Stimmt so auch nicht ganz: Wenn man sich mit (?R) schon eine Rekursion schaft, muss man diese nur zusätzlich mätchen, sprich noch einmal zusätzliche Klammern drum rum. Teile in matchenden Klammern, die nicht zum Ergebnis beitragen, ergeben Leerstrings.
Deinen Ausdruck könntest Du also besser so hier schreiben:
C#-Quelltext
1:
| (\$?\w+)((?:\.\w+|\[(?R)\])*) |
In deiner Match-Gruppe 1 hast Du den Haupt-Variablen-Namen, in Gruppe 2 die gesamte Unter-Variablen-Struktur. Wichtig ist hieran anzumerken, dass man bei Wiederholungen einer Matching-Group immer nur deren letzten Match bekommt.
Das korrekte Vorgehen für deinen Fall wäre daher:
1. Mit
(\$?\w+)((?:\.\w+|\[(?R)\])*) die gesamte Variable matchen
2. Aus Matching-Group 1 den Haupt-Namen auslesen.
3. Matching-Group 2 mit einem Match_all von
\.\w+|\[(?R)\] in die optionalen, wiederholenden Teile aufteilen (ein preg_replace mit diesem Muster bei ersetzung durch einen Leerstring MUSS einen Leerstring liefern ;-)
4. Für jeden Teilstring prüfen, ob er mit einem . anfängt (Teil-Bezeichner), oder mit [ startet und auf ] endet. Diese weglassen (oder Match-Group auf den Content der eckigen Klammern) und diesen Teil ab Schritt 1 geschildert erneut parsen.
5. Das Ergebnis in eine brauchbare Struktur klemmen ;-)
Heiko - So 08.03.09 23:53
Meinst du nicht, dass ein preg_split da besser ist, wenn man eh alle Teile per Hand durchgehen muss?
Am Split scheitere ich aber gerade *g*:
C#-Quelltext
1:
| $match = preg_split('/((\[\$?\w*((?R)\.?\w*)*\])|\.)/', '$abc.def.geh[$test][$test[df][test]][sad]', -1, PREG_SPLIT_DELIM_CAPTURE); |
ergibt:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
| array(15) { [0]=> string(4) "$abc" [1]=> string(1) "." [2]=> string(3) "def" [3]=> string(1) "." [4]=> string(3) "geh" [5]=> string(7) "[$test]" [6]=> string(7) "[$test]" [7]=> string(0) "" [8]=> string(17) "[$test[df][test]]" [9]=> string(17) "[$test[df][test]]" [10]=> string(6) "[test]" [11]=> string(0) "" [12]=> string(5) "[sad]" [13]=> string(5) "[sad]" [14]=> string(0) "" } |
Mich wundern das 5 und 6 gleich sind, also ein Teil 2x erscheint :gruebel:
Heiko - Mo 06.04.09 12:48
So, ich habs jetzt erfolgreich hinbekommen.
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| <?php
function parseVarPart($match){ if ($match[0][0]=='.') return "['".substr($match[0], 1)."']"; return "[".parseVar(substr($match[0], 1, -1))."]"; }
function parseVar($varStr){ preg_match('/(\$?\w+)((?:\.\w+|\[(?R)\])*)/', $varStr, $match); if ($match[1][0]=="$") { $result = '$this->vars[\''.substr($match[1], 1).'\']'; }else{ $result = '$this->vars[\'tplVars\'][\''.$match[1].'\']'; }
$result .= preg_replace_callback('/\.\w+|\[\$?\w+(?:(?R)*)\]/', "parseVarPart", $match[2]);
return $result; }
$str = '$abc.def.geh[$test][$test[df][$test].ijk.lmn][sad]'; if (preg_replace('/\s*\$?\w+(?:\s*(?:\.\s*\w+|\[(?R)\]))*\s*/', '', $str, 1) == '') { $str = strtr($str, array("\t" =>"", "\n" =>"", "\r" =>"", " " =>"")); echo parseVar($str).';'; }else echo "Error";
?> |
Irgendwelche (Performance-)Verbesserungsvorschläge?
besten dank @all
Heiko
BenBE - Mo 06.04.09 13:31
Würd die Syntax-Prüfung noch in ne Funktion auslagern, die dann auch gleich die Struktur der Variable liefert.
Heiko hat folgendes geschrieben : |
Irgendwelche (Performance-)Verbesserungsvorschläge? |
Trace das Script doch einfach mal mit XDebug. Ein Testscript, wie man damit die Performance misst, liegt im
GeSHi SVN Repo [
http://geshi.svn.sourceforge.net/svnroot/geshi/branches/MISC_STUFF/profiling/]. (Damit haben Milian Wolf und ich GeSHi um das 10fache beschleunigt ;-)) ... Zur Verwendung ggf. einfach fragen.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!