Rolf B: regex Kommentare finden

Beitrag lesen

(Dieser Beitrag wurde editiert, um die groben Klöpse zu korrigieren, die ich mir geleistet habe - Rolf)

Hallo Henry,

wenn Du es mit Regex machst, ja, dann musst du gruppieren. Deine Regex enthält bereits eine.

Ich würde allerdings noch \s* hinzufügen:

/<!-+-\s*([\s\S]*?)\s*-+->/mg

um den Whitespace zwischen Kommentarbegrenzer und Text zu überspringen.

Die Zeichenklasse [\s\S] hatte mich zuerst verwundert, aber: Das ist völlig richtig. Ein Punkt wäre falsch, denn der matcht keine Zeilenumbrüche!

Wenn Du aber alle Treffer auf einmal finden und dann auf Gruppeninhalte zugreifen willst, darfst Du nicht match verwenden, es muss matchAll sein. Diese Funktion liefert einen Iterator, den Du mit for..of durchlaufen kannst. Jeder einzelne Match ist ein Array. Dieses Array enthält am Index 0 den kompletten Match für die Regex (also den ganzen Kommentar inclusive der Begrenzer), und dann für jede Gruppe einen weiteren Eintrag. Die Zuordnung Gruppe zu Arrayposition kannst Du Dir so merken: zur n-ten linken Klammer im Pattern gehört der Eintrag bei Index n. Für die erste Klammer brauchen wir also den Index 1.

const commentPattern = /<!-+-\s*([\s\S]*?)\s*-+->/mg;
const htmlDocument = document.documentElement.outerHTML;
for (let match of htmlDocument.matchAll(commentPattern)) {
   const commentText = match[1];
   console.log(commentText);
}

Das kann man so machen, aber...

Zum einen gibt es eine kleine Unschärfe. Stell Dir vor, jemand würde das hier schreiben:

   <img alt="<!-- foo -->" src="...">

Das würdest Du als Kommentar finden. Es ist aber keiner. Ja okay, das macht eigentlich auch niemand.

Zum anderen ist outerHTML an sich nicht so toll. Es muss über das komplette DOM rennen und einen String generieren. Und den jagst Du dann durch eine g-Regex. Das ist, gerade bei längerem HTML, ziemlich aufwändig.

Eleganter kann es sein, einen rekursiven Generator über das DOM zu schicken und nach Kommentar-Nodes zu suchen. Wenn Du danach auf die textContent-Eigenschaft der gefundenen Kommentare zugreifst, hast Du das Problem von <!-- --> nicht mehr. Dafür ein anderes, dazu gleich mehr.

function* getCommentNodes(node) {
   for (let childNode of node.childNodes) {
      if (childNode.nodeType == Node.COMMENT_NODE)
         yield childNode;
      else
         yield* getCommentNodes(childNode);
   }
}

let textOnly = /^[\s\-]*([\s\S]*?)[\s\-]*$/;
for (let commentNode of getCommentNodes(document)) {
   let commentText = commentNode.textContent.match(textOnly);
   console.log(commentText[1]);
}

Ein Generator ist eine Funktion, die einen Iterator zurückgibt. Das Bereitstellen der einzelnen Werte erfolgt mit yield. Eine schicke Eigenschaft von JavaScript-Generatoren ist, dass ein Generator seinen Job auch an Subgeneratoren delegieren kann, mit yield*. Letztlich ist function* eine dicke Kruste aus Syntaxzucker für ein komplexes Gebilde, das unter der Haube läuft, aber aus Programmierersicht ist diese Form der Programmierung sehr elegant. Es sieht nämlich so aus, als würde man eine Pipeline programmieren. getCommentNodes und die auslesende for..of Schleife scheinen unabhängig voneinander zu laufen, und die for..of Schleife bekommt vom Generator Stück für Stück die Werte angereicht. Wenn Du wissen willst, wie widerlich der Fraß unter der Syntaxzuckerkruste ist, dann schau Dir hier die user-defined iterables an...

Man muss über die childNodes Collection laufen, weil die Kommentare keine HTML Elemente sind und darum in der children Collection nicht enthalten sind.

Restproblem: Ein Kommentar <!----- Hallo ------> würde als
--- Hallo ---- gefunden werden. Und JavaScript hat keine Trim-Funktion, die andere Zeichen als Whitespace trimmen kann. Man muss dafür am Ende also doch wieder eine Regex verwenden, um die zu ignorieren. Dafür habe ich die textOnly Regex gemacht. Aber diese läuft nur über einen einzelnen Kommentar, nicht über das ganze HTML Dokument. Beachte, dass diese Regex keine m Option tragen darf, weil ^ und $ dann Zeilenumbrüche matchen würden. Das wollen wir bei mehrzeiligen Kommentaren gerade nicht.

Rolf

--
sumpsi - posui - obstruxi