hektor pascal: REGEX Ical

Hi Forum,
gegeben sei folgender Text (Das hier ist natürlich nur ein Auszug.):

  
BEGIN:VEVENT  
DTSTART;VALUE=DATE:20120621  
DTEND;VALUE=DATE:20120622  
RRULE:FREQ=YEARLY;BYMONTH=6;BYMONTHDAY=21  
DTSTAMP:20120703T080230Z  
UID:DBEA2E86EBA4444DA2F456A266B1C83F00000000000000000000000000000000  
CREATED:20120621T163508Z  
DESCRIPTION:  
LAST-MODIFIED:20120621T163508Z  
SEQUENCE:1  
STATUS:CONFIRMED  
SUMMARY:################  
TRANSP:OPAQUE  
CATEGORIES:http://schemas.google.com/g/2005#event  
BEGIN:VALARM  
ACTION:DISPLAY  
DESCRIPTION:This is an event reminder  
TRIGGER;VALUE=DATE-TIME:20120620T000000Z  
END:VALARM  
END:VEVENT  

Dieser Text kommt jetzt X-Mal vor und ich möchte nur das haben was jeweils zwischen BEGIN:VEVENT und END:VEVENT steht. Dies versuche ich Mit einem REGEX, welcher sich allerdings meiner Ansicht komisch verhält.

Hier der ursprüngliche Versuch, welcher aber komplett scheitert ;).

  
<?  
  
  preg_match_all('/BEGIN:(VEVENT)([^(?:END:\1)]*)END:\1/si',file_get_contents('/test/basic.ics'),$matches);  
  
  print_r($matches);  
  
?>  
  

Um der Sache auf die Spur zu kommen, wollte ich die Sache von vorne aufrollen und erst mal testen was alles kommt wenn ich von BEGIN:VEVENT alles bis zum nächsten BEGIN erfasse:

  
<?  
  
  preg_match_all('/BEGIN:(VEVENT)([^(?:BEGIN)]*)/si',file_get_contents('/root/basic.ics'),$matches);  
  
  print_r($matches);  
  
?>  

Doch jetzt wird es komisch und für mich nicht mehr nachvollziehbar. Hier die Ausgabe:

  
Array  
(  
    [0] => Array  
        (  
            [0] => BEGIN:VEVENT  
DTSTART;VALU  
        )  
  
    [1] => Array  
        (  
            [0] => VEVENT  
        )  
  
    [2] => Array  
        (  
            [0] =>  
DTSTART;VALU  
  
        )  
  
)  
  

Warum gibt es nur den Test DTSTART;VALU aus? Dahinter steht doch gar kein BEGIN.

MFG Hektor

  1. Hi Hektor!

    Das sieht ziemlich wirr aus ;)

    Die Indexe im Ausgabe-Array geben für normal die Reihenfolge der nicht durch ?: maskierten Klammerungen im Suchmuster wieder. Durch zB (?: könnte man einen Teil von der Ausgabe ausschließen. [0] ist immer der Teil, der auf das komplette Suchmuster zutrifft, [1] der dem ersten geklammerten Teil des Suchmusters entspricht usw...

    Ein Doppelpunkt muss durch \ escaped werden, wenn er nicht als Metazeichen interpretiert werden soll.

    Doch jetzt wird es komisch und für mich nicht mehr nachvollziehbar.

    Auch wohl für die regex engine nicht mehr.

    Im Prinzip möchtest du ja nur alles was zwischen
    BEGIN:VEVENT und END:VEVENT zu finden ist.

    Davon möglichst wenig, da die Quantoren standardmäßig gierig sind und so zB alles fressen würden, bis zum bitteren Ende, sollte man den . verwendet.

    Durch ein ? nach dem . oder den modifier U für ungreedy kann man aber der engine mitteilen, dass sie eben möglichst bescheiden sein soll.

    Außerdem möchtest du, dass der Punkt auch auf das Zeilende zutrifft, also wird auch der s modifier benötigt.

    Es wäre also ein ganz einfaches Muster:
    /BEGIN:VEVENT(.*)END:VEVENT/sU

    Die gewünschten Brocken sollten dann im $array[1] zu finden sein, da [0] ja alles enthält, was dem kompletten Suchmuster entspricht.

    Viel Erfolg!
    Jonny 5

    1. Durch ein ? nach dem . oder

      omg, ich meinte natürlich nach dem Quantor, also zB .*?

      biba

    2. Durch ein ? nach dem . oder den modifier U für ungreedy kann man aber der engine mitteilen, dass sie eben möglichst bescheiden sein soll.

      Danke esrtmal. Wegen der Gierigkeit war ja mein Ansatz auch so:

      BEGIN:(VEVENT) ("Nimm alles es sei denn es ist ein END:$VEVENT") END:$VEVENT

      1. Huhu,

        war ja mein Ansatz auch so:

        BEGIN:(VEVENT) ("Nimm alles es sei denn es ist ein END:$VEVENT") END:$VEVENT

        '/BEGIN:(VEVENT)([^(?:END:\1)]*)END:\1/si'

        Es ist innerhalb einer Zeichenklasse definiert.

        Es würde sagen:

        Nimm alles, was keine ( ? : E N D \ 1 ) sind, davon möglichst viel.
        Das wäre wie zB [)(1END:?\\] -> es steht innerhalb eckiger Klammern, wird also als Menge von Zeichen interpretiert.

        Vielleicht hättest einen negativen Lookahead gemeint, wie zB (.(?!\END:\1))+
        Aber viel zu mühsam, finde ich. Es gibt endlose Möglichkeiten, banale Probleme auf komplizierte Weise zu lösen.

        Für dieses Problem gibt es übrigens natürlich auch viele Möglichkeiten ohne regex.

        Viele Grüße, Jonny 5

    3. gudn tach!

      Das sieht ziemlich wirr aus ;)

      allerdings. und zwar ist
        [^(?:END:\1)]
      dasselbe wie
        [^()?:END\1]
      da mit den brackets [] zeichenklassen beschrieben werden und die anordnung der zeichen darin mit wenigen ausnahmen egal ist. innerhalb eckiger klammern gelten andere regeln als ausserhalb, siehe z.b.
      selfhtml-doku.

      Ein Doppelpunkt muss durch \ escaped werden, wenn er nicht als Metazeichen interpretiert werden soll.

      nee, das ist in keiner mir bekannten regexp-engine so. der doppelpunkt wird normalerweise literal verstanden. und der genannte link sgt da auch nix anderes.

      Es wäre also ein ganz einfaches Muster:
      /BEGIN:VEVENT(.*)END:VEVENT/sU

      sogar noch einfacher:

      /BEGIN:VEVENT(.*)END:VEVENT/sU

      oder, falls es BEGIN:VEVENTXY geben sollte:

      /BEGIN:VEVENT\s(.*)END:VEVENT\s/sU

      oder eben in pcre-syntax, die dann auch ausserhalb von php funzt:

      /BEGIN:VEVENT\s(.*?)END:VEVENT\s/s

      das setzt u.a. voraus, dass VEVENT nicht verschachtelt vorkommen kann und dass kein BEGIN:VEVENT im code vergessen wurde.

      prost
      seth

      1. Ein Doppelpunkt muss durch \ escaped werden, wenn er nicht als Metazeichen interpretiert werden soll.

        nee, das ist in keiner mir bekannten regexp-engine so. der doppelpunkt wird normalerweise literal verstanden. und der genannte link sgt da auch nix anderes.

        Oho, tatsächlich |-)

        Danke für die Richtigstellung!

  2. Hi,

    Warum gibt es nur den Test DTSTART;VALU aus? Dahinter steht doch gar kein BEGIN.

    Nein, dahinter steht das E von VALUE, und das ist das erste Zeichen das in deiner negierten Zeichenklasse [^(?:BEGIN)] vorkommt – also ist da Schluss mit dem Treffer.

    MfG ChrisB

    --
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?