Ankündigung

Einklappen
Keine Ankündigung bisher.

Parsen von Delimitern

Einklappen
Dieses Thema ist geschlossen.
X
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Parsen von Delimitern

    Tutorial von nikosch77:

    Willkommen.
    Dieses Tutorial beschäftigt sich mit dem Parsen von Zeichenketten.

    Zielstellung:
    Gegeben ist ein String, der Befehle, Tags o.ä. enthält, die wiederum Parameter definieren, die in Zeichenketten eingeschlossen sind. Als Beispiel nehmen wir eine Menge von SQL Anfragen:

    $sql = "EXPLAIN Table; SELECT * FROM Table a WHERE a.name = 'Peter\'s Blog; A plain story';
    DELETE FROM Table WHERE name = 'Foo';DELETE FROM Table WHERE name = 'Bar'";
    Wir wollen nun jeden einzelnen Befehl in ein Array Element überführen. Dazu sollen z.B. Semikoleon und Zeilenumbruch als Trennzeichen (TZ) dienen.

    Was zunächst simpel erscheint offenbart zwei Probleme:

    1. Wir können die Zeichenkette nicht einfach am TZ auftrennen, da z.B. das Semikoleon auch innerhalb eines Eintrags des Befehls existiert. Wir müssen also diese Bereiche separat behandeln.

    2. Auch diese als separat bearbeiteten Bereiche sind nicht unproblematisch, da sie beliebig viele sog. escapete Zeichen beinhalten können, die ohne \ die Zeichenkette beenden würden. Deshalb muß die Erkennung des Bereiches nicht nur 'Peter\' sondern 'Peter\'s Blog; A plain story' zurückgeben.

    Herleitung:
    Nach einigem Hin- und Herüberlegen stellte sich die Bruteforce Methode als die einfachste heraus: Wir gehen also den gesamten Text Fundstelle für Fundstelle durch, schneiden Teile ab und fügen diese wieder zusammen.

    Wir basteln uns dazu zwei Hilfsfunktionen.

    1/ explode_by_set()

    entspricht weitestgehend der Funktion explode(), teilt also einen gegebenen String unter Zuhilfenahme eines Trennzeichens auf. explode_by_set() erweitert die Funktion auf die Nutzung mehrerer verschiedener TZ.

    PHP-Code:

      
    function explode_by_set ($separatorArray $string)
        {
        
    $separatorArray = (array) $separatorArray;

        
    $string str_replace($separatorArray $separatorArray[0], $string);

        return (
    explode($separatorArray[0] , $string));
        } 
    $separatorArray ist ein Array mit Trennzeichen, da der String $string ohnehin gleich aufgeteilt wird, werden alle TZ im String in eines aus der Menge ersetzt und dann der String per explode() aufgeteilt.
    Damit sind wir in der Lage unsere obigen SQL Elemente in Befehle aufzutrennen. Bleibt das Problem mit '' begrenzten Bereichen.

    2/ cut_left

    Die Funktion arbeitet folgenderweise: ermittle mit strpos() das erste Auftauchen eines Suchstrings (später "'") im Text. Wenn gefunden gib den Teil von 0 bis zur Fundstelle zurück. Die Fundposition wird gespeichert. Beim nächsten Aufruf wird ab dieser Position gesucht.
    Bei jedem Fund des Suchsstrings wird das Zeichen davor mit einem Escapezeichen (typischerweise "\") verglichen. Stimmen sie überein, wird an dieser Stelle nichts abgeschnitten, sondern nach dem nächsten Begrenzer gesucht. Das passiert bis zum ersten nicht escapeten Begrenzer. Bis dort wird der linke Teil der Zeichenkette zurückgegeben usw.
    Findet strpos() bis zum Ende des Reststrings kein Begrenzerzeichen mehr, wird der Rest des Textes zurückgegeben, weitere Aufrufe geben anschließend nur noch false zurück.
    Hier die gesamte Funktion:

    PHP-Code:

      
    function cut_left $string$searchString $escapeChar '' $reset false)
        {
        static 
    $position;
        if (
    $reset !== false$position is_numeric($reset)
                                          ? 
    max ($reset 0) -1
                                          
    : -1;
        if (!isset(
    $position)) $position = -1;

        
    $start $position 1;

        
    # ---- Suchstring suchen
      
        
    $position strpos ($string $searchString $start);

        
    # ---- Suchstring nicht gefunden, Rest ausgeben

        
    if (false===$position
          {
          if (
    $position == strlen ($string) - 1) return (false);

          
    $position strlen ($string) - 1;
          return (
    substr($string $start));
          }

        
    # --- keine Escapezeichen verwendet, Teilzeichenkette (TZK) zurückgeben

        
    if (!$escapeChar)
          return (
    substr ($string $start $position-$start));

        
    $length $position $start;

        
    # --- Escapezeichen verwendet, letztes Zeichen der TZK prüfen

        # --- kein escapter Suchstring, TZK zurückgeben
        
    if ($position==|| $string{$position-1} != $escapeChar
          return (
    substr ($string $start $length));

        
    # --- es ist ein escapter Suchstring, rekursiv TZK um nächsten Teil erweitern
        #     dabei escapten Suchstring mitnehmen (+1)
        
    else 
          return (
    substr ($string $start $length 1) .
                  
    cut_left ($string $searchString $escapeChar)
                 );
        } 
    Der $reset Parameter dient nur dem Fall, daß mehrere solcher Trennaktionen erfolgen sollen,
    dann kann mit einem boolschen true die Startposition auf 0 bzw. mit einem int Wert auf eine beliebige andere Position gesetzt werden.

    Da diese zweite Funktion irgendwann false zurückgibt, kann sie optimal in einer Schleife eingesetzt werden.
    Wichtig ist dabei ein === Vergleich, um die Schleife nicht schon bei einem leeren Stringrückgabewert zu beenden.

    PHP-Code:

    $sql 
    "EXPLAIN Table; SELECT * FROM Table a WHERE a.name = 'Peter\'s Blog; A plain story';
    DELETE FROM Table WHERE name = 'Foo';DELETE FROM Table WHERE name = 'Bar'"
    ;

    for (
    $i=0;;$i++)
      {
      
    $return cut_left $sql "'" "\\");
      if (
    false===$return) break;
      echo 
    "\n",$return;
      } 
    Schauen wir uns das resultierende Array an, haben wir schon fast die Lösung für unser Ursprungsproblem:

    EXPLAIN Table; SELECT * FROM Table a WHERE a.name =
    Peter\'s Blog; A plain story
    ;
    DELETE FROM Table WHERE name =
    Foo
    ;DELETE FROM Table WHERE name =
    Bar
    Hier stehen nämlich die Bestandteile innerhalb und außerhalb eines '' Bereichs schon jeweils in einem extra Eintrag. Und noch besser: Jeder zweite Eintrag ist stets ein ' separierter Sting, die anderen immmer Resttext. Das ist auch so, wenn unser Ausgangstext bereits mit Hochkommata beginnt, dann ist allerdings das Element 0 des Arrays leer. Anhand des Arraykeys können wir nun mit dem Modulooperator herausfinden, ob wir gerade einen geschützten Bereich bearbeiten.

    Wir nutzen nun die erste Funktion und trennen Eintrag 0, 2, 4 usw. mit den angegebenen TZ auf, also Semikoleon und Zeilenumbruch:

    Array
    (
    [0] => EXPLAIN Table
    [1] => SELECT * FROM Table a WHERE a.name =
    )
    Peter\'s Blog; A plain story
    Array
    (
    [0] =>
    [1] =>
    [2] => DELETE FROM Table WHERE name =
    )
    Foo
    Array
    (
    [0] =>
    [1] => DELETE FROM Table WHERE name =
    )
    Bar
    Die letzte Aktion schließlich besteht darin, die ursprünglich in Hochkommata eingeschlossenen Parameter wieder in '' einzuschließen, und das ganze sinnvoll zu einem Array zu verknüpfen. Dazu füllen wir ein neues Array per Wert für Wert auf. Aus den Arrays mit aufgetrennten Befehlen fügen wir den ersten stets an den letzte Datensatz des Zielarrays an (denn die erste Trennung ist genaugenommen erst zwischen Element 0 und 1 gewünscht) - ebenso die Bereiche in '' - und fügen das restliche Array an unser Zielarray an. Das bei diesen Aktionen öfter leere Werte verarbeitet werden soll uns nicht weiter stören.
    Wie wir sehen kann das Element 0 mit dem Inhalt 'EXPLAIN Table' nirgends angefügt werden, deshalb muß unser Zielarray mit einem leeren Element initialisiert werden. Dann erhalten wir:

    Array
    (
    [0] => EXPLAIN Table
    [1] => SELECT * FROM Table a WHERE a.name = 'Peter\'s Blog; A plain story'
    [2] =>
    [3] => DELETE FROM Table WHERE name = 'Foo'
    [1] => DELETE FROM Table WHERE name = 'Bar'
    )
    Nahezu perfekt! Die eine Lücke entsteht, da zwischen "plain story';" und "DELETE " genaugenommen zwei Trennzeichen, nämlich ; und Zeilenumbruch stehen. Um dies zu beheben wird unsere Funktion 1 um einen Parameter erweitert, der per preg_replace mehrere Trennzeichen zu einem reduziert.

    Die Lösung:
    Hier nun das fertige Script, mit erweiterten Funktionen:

    PHP-Code:

      
    /*
      int = explode_by_set (mixed separators , string string [, int Flag ] )

      --------------------------------------------------------------------------- */
      
    function explode_by_set ($separatorArray $string $reduce )
        {
        
    # ---- explizite Typumwandlung
        
    $separatorArray = (array) $separatorArray;

        
    # ---- alle Trennzeichen in das erste umwandeln
        
    $string str_replace($separatorArray $separatorArray[0], $string);

        
    # ---- wenn gewünscht, direkt aufeinanderfolgende zu einem zusammenfassen
        
    if ($reduce == 1
          
    $string preg_replace('/['preg_quote($separatorArray[0]) .']+/'  
                                 
    $separatorArray[0]                           , 
                                 
    $string);

        return (
    explode($separatorArray[0] , $string));
        }


      
    /*
      string = cut_left ( string string , string searchString [, char escapeCharacter [, bool reset]] )

      --------------------------------------------------------------------------- */
      
    function cut_left $string$searchString $escapeChar '' $reset false)
        {
        static 
    $position;
        if (
    $reset !== false$position is_numeric($reset)
                                          ? 
    max ($reset 0) -1
                                          
    : -1;
        if (!isset(
    $position)) $position = -1;

        
    $start $position 1;

        
    # ---- Suchstring suchen
      
        
    $position strpos ($string $searchString $start);

        
    # ---- Suchstring nicht gefunden, Rest ausgeben

        
    if (false===$position
          {
          if (
    $position == strlen ($string) - 1) return (false);

          
    $position strlen ($string) - 1;
          return (
    substr($string $start));
          }

        
    # --- keine Escapezeichen verwendet, Teilzeichenkette (TZK) zurückgeben

        
    if (!$escapeChar)
          return (
    substr ($string $start $position-$start));

        
    $length $position $start;

        
    # --- Escapezeichen verwendet, letztes Zeichen der TZK prüfen

        # --- kein escapter Suchstring, TZK zurückgeben
        
    if ($position==|| $string{$position-1} != $escapeChar
          return (
    substr ($string $start $length));

        
    # --- es ist ein escapter Suchstring, rekursiv TZK um nächsten Teil erweitern
        #     dabei escapten Suchstring mitnehmen (+1)
        
    else 
          return (
    substr ($string $start $length 1) .
                  
    cut_left ($string $searchString $escapeChar)
                 );
        }



    $sql "EXPLAIN Table; SELECT * FROM Table a WHERE a.name = 'Peter\'s Blog; A plain story';
    DELETE FROM Table WHERE name = 'Foo';DELETE FROM Table WHERE name = 'Bar'"
    ;

    $delimiter  "'";
    $escape        "\\";
    $cutters    = array( "\r\n" ";" );


    $result = array (=> '');
    for (
    $i=;; $i++)
      {
      
    $return cut_left ($sql $delimiter $escape);
      if (
    $return===false) break;

      
    # ---- jeder 2. Stringpart ist eine Zeichenkette in Hochkommata

      
    if (($i 2) == 1)
        
    $result[count($result)-1] .= "'$return'";

      
    # ---- der Rest wird nach Trennzeichen aufgeteilt 
      
    else 
        {
        
    $items explode_by_set ($cutters $return 1);
        
    $result[count($result) -1] .= array_shift($items);
        
    $result array_merge ($result $items);
        }  
      }

    print_r($result); 

Lädt...
X