Nico R.: Verständnisfrage zu Regex in JS und HTML

Hallo allerseits,

um Regex habe ich mich immer gedrückt, jetzt brauche ich aber doch mal einen. Ich habe diesen hier gefunden, der in meinem Formular dafür sorgt, dass nur Beträge mit bis zu drei Ziffern, Punkt oder Komma und drei Nachkommastellen eingegeben werden dürfen:

<input type="text" pattern="[0-9]{0,3}[.|,]{0,}[0-9]{1,2}" title="Bitte nur Ziffern sowie , oder . angeben" >

Das funktioniert genau wie es soll. Wenn ich den gleichen Ausdruck allerdings in JS anwende, weicht das Verhalten ab...

<!DOCTYPE html>
<html>
<body>

<p id="demo"></p>

<script>
let text = "b1";
let pattern = /([0-9]{0,3}[.|,]{0,}[0-9]{1,2})/;
let result = pattern.test(text);

document.getElementById("demo").textContent = result;
</script>

</body>
</html>  

Hier führt eine Eingabe von "b1" richtigerweise zu einem FALSE, die Eingabe von "1b" allerdings zu einem TRUE. Anscheinend wird hier, sobald einmal ein TRUE gesetzt wurde, entweder nicht weiter geprüft oder das TRUE nicht von FALSE überschrieben, das habe ich noch nicht heraus bekommen. Ich bräuchte im Prinzip eine Suche, die nur dann TRUE ergibt, wenn der komplette String plausibel ist, und nicht nur ein Teil.

Wieso funktioniert das als input-Attribut, aber nicht als JS-Funktion? Vielleicht kann mir ja jemand auf die Sprünge helfen.

Schöne Grüße

Nico

  1. Hallo Nico R.,

    die HTML Spec besagt, dass die Regex im pattern-Attribut nicht 1:1 nach JavaScript übertragen wird. Statt dessen wird sie mit

    "^(?:" + pattern + ")$"
    

    umhüllt, bevor sie der JavaScript-Engine zur Auswertung anvertraut wird.

    ^ und $ verankern die Regex am Anfang und Ende des geprüften Texts, und die Klammerung mit (: ) erzeugt eine non-capturing group - d.h. der Inhalt wird gegen den Text gematcht, es wird aber nichts gespeichert, was sich später als "gib mir den Inhalt der x-ten gematchten Klammergruppe" abfragen ließe. Ich nehme an, das hat Performancegründe und soll mögliche Speicherüberlaufattacken erschweren.

    Ohne die Verankerung mit ^ und $ matcht deine Regexp erfolgreich die 1 und hört vor dem b auf. Mit der Verankerung merkt sie, dass hinter der 1 der String nicht zu Ende ist und schlägt fehl.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      danke für die schnelle Rückmeldung. Ich habs zu etwa 80 Prozent verstanden :-)

      Mit

      let pattern = /^([0-9]{0,3}[.|,]{0,}[0-9]{1,2})$/ 
      

      funktioniert jetzt so wie in HTML. Ich habe aber noch gemerkt, dass eine Eingabe von '12345' TRUE ergibt. Das soll sie natürlich nur, wenn zwischen '123' und '45' ein '.' oder ',' steht. Ich werde dazu nochmal etwas rumprobieren. Ich vermute aber mal, dass ich noch einen Thread erstelle ;-)

      Schöne Grüße Nico

      1. Hallo Nico,

        Ich habs zu etwa 80 Prozent verstanden :-)

        das ist ja schon eine ganze Menge! 😀

        Ich vermute aber mal, dass ich noch einen Thread erstelle ;-)

        Wenn das Thema nicht komplett in eine andere Richtung rutscht, bleib doch bitte mit Folgefragen eher hier. Dann ist alle notwendige Information an einer Stelle versammelt.

        Einen schönen Tag noch
         Martin

        --
        Möchtegern-Dichter zum Verleger: "Sie meinen also, ich sollte etwas mehr Feuer in meine Verse legen?" - "Umgekehrt, mein Lieber, umgekehrt. Mehr Verse ins Feuer."
      2. @@Nico R.

        let pattern = /^([0-9]{0,3}[.|,]{0,}[0-9]{1,2})$/ 
        

        funktioniert jetzt so wie in HTML. Ich habe aber noch gemerkt, dass eine Eingabe von '12345' TRUE ergibt. Das soll sie natürlich nur, wenn zwischen '123' und '45' ein '.' oder ',' steht. Ich werde dazu nochmal etwas rumprobieren. Ich vermute aber mal, dass ich noch einen Thread erstelle ;-)

        Vorher solltest du dir klarmachen, was [.|,]{0,} bedeutet. Wink: dasselbe wie [.|,]*.

        Und es ist nicht das, was du willst. Innerhalb von eckigen Klammern verlieren Zeichen ihre Sonderbedeutung: | steht nicht für „oder“, sondern für das Zeicehen |, d.h. „123|45“ würde auch matchen.

        🖖 Живіть довго і процвітайте

        --
        „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
        — @Grantscheam auf Twitter
        1. Ohje, bitte nicht noch ein neues Problem 😟 Trotzdem danke natürlich für den Hinweis. Für mich scheint dann wohl [.|,]{1} die bessere Lösung zu sein, da eine Eingabe von '123,,45' natürlich nicht TRUE sein soll.

          Für das Problem mit dem '|' habe ich gerade keine Idee. Wenn ich [.|,] durch (.|,) ersetze, kann ich weiterhin ein '|' eingeben. Ich schiebe das erstmal nach hinten...

          Um zu erreichen, dass vorne maximal drei Ziffern stehen dürfen und - wenn ein Punkt oder Komma gesetzt wurde - maximal zwei Ziffern folgen dürfen, hab ich folgendes probiert, was natüüüürlich nicht funktioniert hat:

          ^([0-9]{0,3}(?:(?=[.|,]{1})[0-9]{1,2}|(?![.|,])))$
          
          

          Meine Gedanken dazu, wild zusammengesucht und gelesen...

          Die große Klammer ums "if-Konstrukt":

          (?:

          Wenn ein Punkt oder Komma vorkommt:

           (?=[.|,]{1})
          

          ...können min ein und max zwei Ziffern folgen:

              [0-9]{1,2}
          

          ODER

          |
          

          Es kommt kein Punkt oder Komma vor:

          (?![.|,])
          

          )

          Bin ich auf der richtigen Spur oder ist das kompletter Quatsch?

          1. Hallo,

            Für das Problem mit dem '|' habe ich gerade keine Idee.

            weil du ein Problem suchst, wo keins ist.

            Ich schiebe das erstmal nach hinten...

            Schieb es wieder ganz nach vorn. Die eckigen Klammern in einem RegEx bedeuten: Irgendeins der Zeichen in den Klammern. Nimm daher das Pipe-Symbol raus, es hat hier keinen Sinn. [.,] bedeutet: Entweder ein Punkt oder ein Komma.

            Einen schönen Tag noch
             Martin

            --
            Möchtegern-Dichter zum Verleger: "Sie meinen also, ich sollte etwas mehr Feuer in meine Verse legen?" - "Umgekehrt, mein Lieber, umgekehrt. Mehr Verse ins Feuer."
          2. @@Nico R.

            Um zu erreichen, dass vorne maximal drei Ziffern stehen dürfen und - wenn ein Punkt oder Komma gesetzt wurde - maximal zwei Ziffern folgen dürfen

            „Wenn“ ist das wichtige Wort hier.

            Übrigens schriebst du im OP was von bis zu drei Ziffern nach dem Komma. Was denn nun, zwei oder drei?

            hab ich folgendes probiert, was natüüüürlich nicht funktioniert hat:

            ^([0-9]{0,3}(?:(?=[.|,]{1})[0-9]{1,2}|(?![.|,])))$ 
            

            Klammern ist schon mal richtig – wegen dem „Wenn“.

            Lookahead assertions brauchst du aber nicht. Dein Problem lässt sich mit einem regulären Ausdruck lösen (einer echten Teilmenge von RegExp).[1]

            Du willst 0–3 Ziffern (oder doch 1–3?) [0-9]{0,3} gefolgt von einer optionalen (?) Gruppe (Klammer!) bestehend aus einem Dezimaltrennzeichen gefolgt von einer oder zwei (drei?) Ziffern (?:[,.][0-9]{1,2})?.

            Das heißt, entweder die ganze Gruppe (Dezimaltrennzeichen und Ziffern danach) tritt auf oder nichts davon. Das haste das „Wenn“.


             (?=[.|,]{1})
            

            {1} ist überflüssig; A{1} ist dasselbe wie A.

            Bin ich auf der richtigen Spur oder ist das kompletter Quatsch?

            Es ist viel zu kompliziert. Für einfach s.o.

            🖖 Живіть довго і процвітайте

            --
            „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
            — @Grantscheam auf Twitter

            1. RegExp ≠ regular expression ↩︎

            1. Ach, das ist ja verrückt, das funktioniert! Jetzt ergibt das alles langsam einen Sinn. Durch ()? sag ich, der Komma-/Nachkommateil in der Klammer kann einmal vorkommen oder gar nicht... sehr gut, aber auch sehr eigenartig.

              Ist, zumindest aus der normalen (prozeduralen) Programmierlogik heraus, für mich eine Riesenumstellung im Kopf. Vielleicht bin ich aber euch einfach nicht der Hellste 😀 Der Ausdruck macht jetzt auf jeden Fall exakt das, was er soll:

              ^([0-9]{0,3}([,.][0-9]{1,2})?)$
              

              Es hat übrigens ( statt (?: genügt. Oder hab ich etwas übersehen? Der Text mit Regex Coach lief jedenfalls tadellos.

              Vielen Dank für die Hilfe! :-)

              Schöne Grüße Nico

              1. @@Nico R.

                ^([0-9]{0,3}([,.][0-9]{1,2})?)$
                

                Es hat übrigens ( statt (?: genügt.

                Durch () wird ein Teil gemerkt. Wenn man das nicht will, verwendet man öffnendes (?:.

                Der gesamte Match steht sowieso zur Verfügung; die äußere Klammer ist sinnfrei.

                🖖 Живіть довго і процвітайте

                --
                „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
                — @Grantscheam auf Twitter
                1. Alles klar, dann (?:

                  Danke an euch beide

                  1. @@Nico R.

                    Alles klar, dann (?:

                    Du meinst außen und (?: innen‽

                    🖖 Живіть довго і процвітайте

                    --
                    „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
                    — @Grantscheam auf Twitter
              2. Hi,

                Es hat übrigens ( statt (?: genügt. Oder hab ich etwas übersehen? Der Text mit Regex Coach lief jedenfalls tadellos.

                Mit () wird eine Capturing Group definiert, mit (?:) eine Non-Capturing-Group. Wenn Du den Inhalt der Group nicht nochmal brauchst, ist eine Non-Capturing-Group immer besser, weil sie weniger Aufwand für die Regex-Engine verursacht.

                Du beziehst Dich nirgendwo mehr auf die Gruppe (Du machst keine Ersetzung, und Du brauchst auch in Deiner Suche die Gruppe nicht nochmal), also Non-Capturing-Group, also (?:)

                cu,
                Andreas a/k/a MudGuard

  2. @@Nico R.

    um Regex habe ich mich immer gedrückt, jetzt brauche ich aber doch mal einen. Ich habe diesen hier gefunden, der in meinem Formular dafür sorgt, dass nur Beträge mit bis zu drei Ziffern, Punkt oder Komma und drei Nachkommastellen eingegeben werden dürfen:

    <input type="text" pattern="[0-9]{0,3}[.|,]{0,}[0-9]{1,2}" title="Bitte nur Ziffern sowie , oder . angeben" >
    

    Das funktioniert genau wie es soll.

    Nein. Einen Fehler hast du selbst schon gefunden: 1234 geht durch, obwohl es nicht sollte.

    Ein anderer Fehler ist, dass 0,123 nicht passt, obwohl es sollte.


    Der Hinweis „Bitte nur Ziffern sowie , oder . angeben“ gehört angezeigt, nicht in ein Tooltip versteckt und damit nur einigen Nutzern zugänglich:

    <label for="input">Eingabe</label>
    <p id="hint">Bitte nur Ziffern sowie , oder . angeben</p>
    <input id="input" aria-describedby="hint" pattern=""/>
    

    Aber wenn das Eingabefeld nur für Zahlen ist, sollte es auch den entsprechenden Typ haben, womit Nutzer von virtuellen Keyboards auch nur Ziffern und Dezimaltrennzeichen (evtl. auch Minus) vorgesetzt bekommen:

    <label for="input">Eingabe</label>
    <p id="hint">Bitte nur Ziffern sowie , oder . angeben</p>
    <input id="input" aria-describedby="hint" type="number" pattern=""/>
    

    let text = "b1";
    let pattern = /([0-9]{0,3}[.|,]{0,}[0-9]{1,2})/;
    let result = pattern.test(text);
    

    Statt let sollte hier überall const stehen.


    <p id="demo"></p>
    
    document.getElementById("demo").textContent = result;
    

    Für Ausgaben ist das output-Element da.

    🖖 Живіть довго і процвітайте

    --
    „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
    — @Grantscheam auf Twitter
    1. Die Antwort sehe ich erst jetzt. Schon wieder einiges, das ich nicht wusste...

      Die Erklärung zu aria-describedby="hint" auf https://developer.mozilla.org hab ich beim Überfliegen jetzt nicht verstanden, ich werd mir das nochmal ansehen. Ist das essentiell oder eher optional?

      Aber wenn das Eingabefeld nur für Zahlen ist, sollte es auch den entsprechenden Typ haben, womit Nutzer von virtuellen Keyboards auch nur Ziffern und Dezimaltrennzeichen (evtl. auch Minus) vorgesetzt bekommen:

      <label for="input">Eingabe</label>
      <p id="hint">Bitte nur Ziffern sowie , oder . angeben</p>
      <input id="input" aria-describedby="hint" type="number" pattern=""/>
      

      Mit input type="number" kann je nach Browser kein Komma eingegeben werden. Das ist für deutsche Nutzer verwirrend, daher hab ich mich für type="text" und inputmode="numeric" entschieden.

      Statt let sollte hier überall const stehen.

      Jetzt mal doof gefragt. Ist das wichtig?

      Für Ausgaben ist das output-Element da.

      😀 Okay

      Besten Dank und schöne Grüße

      1. @@Nico R.

        Die Erklärung zu aria-describedby="hint" auf https://developer.mozilla.org hab ich beim Überfliegen jetzt nicht verstanden, ich werd mir das nochmal ansehen. Ist das essentiell oder eher optional?

        Das ist essentiell.

        Screenreader-Nutzer navigieren per Tab-Taste von einem interaktiven Element (Formularfeld) zum nächsten. Bei einem Eingabefeld wird dann die zugehörige Beschriftung angesagt (das label, dessen for-Attribut mit der id des input-Elements übereinstimmt oder das ein Vorfahrenelement des input-Elements ist).

        Zusätzlich wird die Beschreibung angesagt, die in dem Element steht, dessen id mit dem aria-describedby übereinstimmt. In dem Fall also: „Eingabe – Bitte nur Ziffern sowie Komma oder Punkt angeben“

        Dasselbe lässt sich auch erreichen durch

        <label for="input">
          Eingabe
          <span class="hint">Bitte nur Ziffern sowie , oder . angeben</span>
        </label>
        <input id="input" />
        

        Das span.hint kann dann anderes gestylt werden als der andere Text im label.

        Siehe Form Design Patterns S. 23–25. Und wer das Buch noch nicht hat, sollte es sich zulegen.


        Aber wenn das Eingabefeld nur für Zahlen ist, sollte es auch den entsprechenden Typ haben, womit Nutzer von virtuellen Keyboards auch nur Ziffern und Dezimaltrennzeichen (evtl. auch Minus) vorgesetzt bekommen:

        <input id="input" aria-describedby="hint" type="number" pattern=""/>
        

        Das war Quatsch von mir; pattern kann nicht mit type="number" kombiniert werden.

        Mit input type="number" kann je nach Browser kein Komma eingegeben werden.

        Es sollte das Dezimaltrennzeichen erscheinen, das der Sprache des Systems entspricht. Also für den jeweiligen Nutzer das richtige.

        daher hab ich mich für type="text" und inputmode="numeric" entschieden.

        Falsche Entscheidung. Dadurch kann auf Systemen mit virtueller Tastatur kein Komma eingegeben werden. "decimal" wäre die richtige Entscheidung. [MDN]


        Statt let sollte hier überall const stehen.

        Jetzt mal doof gefragt. Ist das wichtig?

        Ja, sonst kannst du ja gleich beim alten var bleiben. Wenn sich Werte später nicht mehr ändern sollen, dann const.

        🖖 Живіть довго і процвітайте

        --
        „Im Vergleich mit Elon Musk bei Twitter ist ein Elefant im Porzellanladen eine Ballerina.“
        — @Grantscheam auf Twitter