Andreas-Lindig: REGULÄRE AUSDRÜCKE - Ich werd' noch zu Profi ;-)

hallo Forum,
meine Beschäftigung mit regulären Ausdrücken geht in die zweite Woche
und wieder tut sich seltsames auf. Ich habe in Javascript eine 'Quelltextmaschine' geschrieben, die Quelltexte HTML-gerecht maskieren soll, damit man sie in einem <pre>-Tag abbilden kann. Unter Anderem sollen dort einzeilige C-Kommentare, denen ein Whitespace vorangeht in ein entsprechend formatiertes <span> eingepackt werden.

der reguläre Ausdruck, der auch funktioniert sieht so aus:

var reg = new RegExp("([ \t\v\n\r\f]//.*)","g")

NICHT funktionieren tut dagegen dies:

var reg = new RegExp("(\s//.*)","g")

obwohl doch \s = [ \t\v\n\r\f] ist oder?
weiß jemand, was das wieder für ein Trick ist?
Dieses Wunderwerk der elektronischen Dateverarbeitung ;-) ist übrigens einschließlich Quelltext zu sehen unter:
http://dhtml.andeas-lindig.de ->Javascript ->reg. Ausdrücke ->Nr.20

meine zweite Frage wäre:
kann man eigentlich auch nach einer Zeichekette suchen, die etwas Bestimmtes nicht enthalten soll?
Wenn ich z.B. alle Wörter suche, die nicht 'ei' enthalten:

/\b[^ei]*\b/

geht nicht, weil 'nicht e' und 'nicht i' einzeln gesucht werden; aber eine Verneinung vor einer runden Klammer ist mir nicht bekannt.

hmmm... für überraschende Antworten immer dankbar, Andreas

  1. Sup!

    meine zweite Frage wäre:
    kann man eigentlich auch nach einer Zeichekette suchen, die etwas Bestimmtes nicht enthalten soll?
    Wenn ich z.B. alle Wörter suche, die nicht 'ei' enthalten:

    /\b[^ei]*\b/

    Manche Leute suchen dann einfach nach "ei", und sehen die Nicht-Treffer als Treffer... so per Programm-Logik.

    Gruesse,

    Bio

    1. Manche Leute suchen dann einfach nach "ei", und sehen die Nicht-Treffer als Treffer... so per Programm-Logik.

      ja, so etwas habe ich auch schon gedacht, aber dann brauche ich zwei Suchvorgänge richtig? Ich muß ja das Andere (z.B. ganze Wörter) auch suchen und dann von diesen gefundenen diejenigen aussondern, die
      'ei' enthalten...
      scheint mir etwas umständlich. Weiß jemand, ob das 'Standard' ist?

      gruß, Andreas

      1. Aloha!

        Manche Leute suchen dann einfach nach "ei", und sehen die Nicht-Treffer als Treffer... so per Programm-Logik.
        ja, so etwas habe ich auch schon gedacht, aber dann brauche ich zwei Suchvorgänge richtig? Ich muß ja das Andere (z.B. ganze Wörter) auch suchen und dann von diesen gefundenen diejenigen aussondern, die
        'ei' enthalten...
        scheint mir etwas umständlich. Weiß jemand, ob das 'Standard' ist?

        Stell dir vor, es ist Ostern und in einem großen Garten (der nicht überschwemmt ist) sind angeblich Eier versteckt. Du sollst nun sagen, ob das stimmt, oder nicht.

        Naja, offenbar sind dann keine Eier im Garten, wenn du kein "ei" findest. Dazu mußt du aber erstmal nach einem "ei" suchen. Wenn du eines findest, sind wohl Eier versteckt, wenn nicht - dann sind wohl keine Eier versteckt.

        Auf den regulären Ausdruck bezogen:
        [abc] definiert eine Zeichenklasse - die steht für _ein_ einzelnes Zeichen, welches in der Klasse angegeben ist.
        [^abc] steht für ein Zeichen, welches dort nicht angegeben ist.
        [e][i] steht für zwei Zeichen, die in dieser Reihenfolge kommen müssen.
        [^e][^i] steht für zwei Zeichen, die in dieser Reihenfolge nicht kommen dürfen.

        Naja, wenn man das letzte Beispiel mal umsetzt:
        [^e][^i] - Das kann nur bei Strings passen, die genau zwei Zeichen lang sind. Wenn die nicht "ei" heißen, stimmt der reguläre Ausdruck. Das ist natürlich doof, denn das ist schneller mit
        if ($string !="ei")
        geprüft.

        Vor und hinter dem "ei" dürfen also noch beliebige Zeichen kommen:
        .*[^e][^i].*
        Wenn wir jetzt mal den String "Brei" nehmen: Da ist "ei" drin, aber der reguläre Ausdruck wird behaupten, daß kein "ei" drin sei. Die Zuordnung des Strings wird so laufen:

        .*   wird das B erkennen
        [^e] wird das r erkennen
        [^i] wird das e erkennen
        .*   wird das i erkennen

        Und selbst im String "eieieieieieieiei" wird der Ausdruck behaupten, daß kein "ei" drinsei:

        .*   wird "eieieie" (oder so) erkennen
        [^e] wird "i" erkennen
        [^i] wird "e" erkennen
        .*   erkennt den Rest vom String.

        Mit anderen Worten: Finde "ei" - wenn du ei finden wolltest, tue das eine, wenn du kein "ei" finden wolltest, tue das andere.

        - Sven Rautenberg

  2. Moin!

    der reguläre Ausdruck, der auch funktioniert sieht so aus:
       var reg = new RegExp("([ \t\v\n\r\f]//.*)","g")
    NICHT funktionieren tut dagegen dies:
       var reg = new RegExp("(\s//.*)","g")
    obwohl doch \s = [ \t\v\n\r\f] ist oder?

    Versuch mal, ob der zweite Ausdruck eine Zeile findet, die mit "s//" beginnt. Falls ja, dann gilt folgende Erklaerung:

    Wenn Du in JS einen String "abc" hinschreibst, dann werden darin enthaltene sog. Backslash-Escape-Sequenzen in deren Bedeutung umgewandelt. D.h., Wenn Du "a\tb" schreibst, dann wirst Du nicht einen 4 Zeichen langen String mit den Zeichen a, , t, b erhalten, sondern einen mit den 3 Zeichen a, <TAB character (ASCII 9)>, b. Schreibst Du einen \ vor ein Zeichen, welches *keine* solche Sonderbedeutung wie z.B. t,v,n,r,f hat, dann ist das Ergebnis der -Esc-Sequenz einfach das Zeichen selbst, ohne den Backslash. "a\sb" ist daher dasselbe wie "asb". Und oben schreibst Du ja gerade \s in einem String. Erst spaeter wird dann dieser String zu einem RegExp weiterverarbeitet, aber dann ist der \ schon laengst Geschichte. Der RegExp constructor ist aber darauf angewiesen, alle diese Zeichen zu bekommen, denn erst *er* wird dem \s seine Sonderbedeutung in regulaeren Ausdruecken geben.

    Du hast im wesentlichen zwei Moeglichkeiten.
    Entweder Du schreibst gleich ein RegExp-Literal in den Source Code:
      var reg = /(\s//.*)/g
    Das ist praktisch gleichbedeutend mit dem von oben, nur dass das \s jetzt *nicht* im Voraus zu einem einfachen s kollabiert wird, denn dies hier ist ein RegExp-Literal, kein String-Literal, da sind die Regeln anders. Dafuer hast Du jetzt das "Leaning Toothpick Syndrome" (LTS - gemeint sind die /). Du musst die / maskieren, da ein einfacher, unmaskierter / das Literal vorzeitig beenden wuerde. Das ist so aehnlich wie wenn Du in einem String-Literal ein " aufnehmen willst und deswegn "ab"cd" schreibst.

    Oder Du maskierst halt den den \ selber auch noch:
      var reg = new RegExp("(\s//.*)", "g")
    sodass zunaechst die Sequenz \ zu einem \ kollabiert wird, und dann vom RegExp constructor das verbliebene \s entsprechend interpretiert wird. Damit umgehst Du das LTS von oben, aber musst dafuer in mehreren Ebenen mitdenken, denn wann bedeuted welche -Sequenz was. Aber Du dafuer Deine RegExpe einfach dynamisch zur Laufzeit zusammensetzen, waehrend ja ein Literal zur Entwurfszeit festgelegt wird (dafuer sind Literale ja da). [1]

    Dir ist jetzt natuerlich klar, dass der String "([ \t\v\n\r\f]//.*)" diese Whitespace-Zeichen TAB, LF usw. direkt enthaelt, wenn er zum RegExp constructor gegeben wird. Das st eigentlich nicht so schoen. Man schreibt ja auch nicht
      / <TAB>
    .../
    direkt in den Quelltext (darf man gar nicht).

    [1] So ein RegExp-Literal hat noch die Eigenschaft, dass wenn man es z.B. in einer Schleife verwendet, immer dieselbe Instanz des daraus erzeugten RegExp-Objektes verwendet wird, waehrend new RegExp jedesmal ein neues Objekt erzeugt. Das kann gewisse Implikationen fuer das Programm haben, die ich aber an dieser Stelle auch nicht ueberblicke.

    HTH && So long

    --
    When a man and a woman marry they become one.
    The trouble starts when they try to decide which one.

    [calokey: js javascript RegExp string literal backslash escape sequence]