molily: reguläre Ausdrücke

Beitrag lesen

Hallo,

Mit regexp verfolgst Du den falschen Ansatz. Man kann den regulären Ausdruck zwar noch verbessern, damit weniger Fehler gemacht werden, aber eine verschachtelte Struktur wie HTML bekommt damit nicht richtig in den Griff.
Besser ist es, das Dokument zu Parsen und nur innerhalb aller Textknoten zu ersetzen. Dankbarer Weise hat der Browser Dir das Parsen schon abgenommen. So braucht man nur noch den DOM-Baum rekursiv zu durchlaufen:

function ersetzen(element, w1, w2) {
  for(var i in element.childNodes) {

Das liefert auch die Methoden (Funktionsobjekte) der NodeList (und im Opera nur diese).

var node = element.childNodes[i];
    if(node.nodeType == 3) { // Text
      node.nodeValue = node.nodeValue.replace(w1, w2);

Er will aber Markup um das Suchwort herumbauen. Markup in einen Textknoten zu schreiben, bringt nichts, es wird nicht als solches verarbeitet. Man müsste den Textknoten in zwei aufsplitten und dazwischen das span-Element einfügen. Das ist keinesfalls so trivial. Es wundert mich, dass folgendes auf Anhieb klappt:

<html>
<head>
<title></title>
<style type="text/css">
.highlight {color:red}
</style>
<script type="text/javascript">
function ersetzen (element, suchwort) {
 for (var i = 0; i < element.childNodes.length; i++) {
  var node = element.childNodes.item(i);
  if (node.nodeType == 3) {
   var pos = node.nodeValue.indexOf(suchwort);
   if (pos > -1) {
    var string_before = node.nodeValue.substring(0, pos);
    var string_after = node.nodeValue.substr(pos + suchwort.length);
    // alert('[' + string_before + '] [' + suchwort + '] [' + string_after + ']');
    if (node.nodeValue == suchwort && node.parentNode.getAttribute('class') == 'highlight')
     continue;
    var textnode_before = document.createTextNode(string_before)
    var suchwort_node = document.createElement('span');
    suchwort_node.setAttribute('class', 'highlight');
    var suchwort_textnode = document.createTextNode(suchwort);
    suchwort_node.appendChild(suchwort_textnode);
    var textnode_after = document.createTextNode(string_after);

node.parentNode.replaceChild(textnode_after, node);
    textnode_after.parentNode.insertBefore(textnode_before, textnode_after);
    textnode_after.parentNode.insertBefore(suchwort_node, textnode_after);
   }
  } else if (node.nodeType == 1) {
   // alert('rekursion');
   ersetzen(node, suchwort);
  }
 }
}
</script>
</head>
<body onload="ersetzen(document.body, 'entchen')">

<p>alle meine entchen schwimmen auf dem <span>entchensee <span>alle entchen meine<span>eee<span>entchen</span>ee</span>eeeeeeeeentchen!</span> meine entchen schwimmen auf dem</span> alle<span>meine entchen</span> schwimmen auf dem see meine entchen schwimmen auf dem entchen, entchen!</p>

</body>
</html>

Ich verstehe nicht, wieso die for-Schleife so oft durchläuft, wie sie soll, denn es werden ständig neue Knoten eingefügt, die dann als nächstes drankommen. Also müssten irgendwann Knoten nicht bearbeitet werden, weil deren Index höher als das vor dem Einfügen abgefragte childNodes.length ist.

Ich habe meine Zweifel, ob diese Methode robuster als RegExp-Fummelei über innerHTML ist. Ich würde sie sowieso nie produktiv einsetzen. Mir wäre allerdings auch keine Möglichkeit bekannt, dies mit Regulären Ausdrücken zu lösen. Anders als etwa in PHP ist es bei replace nicht möglich, einen Ausdruck anzugeben, der bei jedem Match ausgerechnet wird und der Zugriff auf die Subpatterns hat. Daher lassen sich Methoden wie http://www.dclp-faq.de/q/q-regexp-ersetzen.html nicht in ECMAScript übertragen.

Mathias