reguläre Ausdrücke
Carsten Pieper
- javascript
0 Daniel Thoma
Hallo zusammen!
Ich speichere den Inhalt eines HTML-Dokumentes per
Inhalt = Inhalt.replace(/$inklusiva/g,"<span class='verweis' style='font-weight:bold;background-color:#FF0000;color:#FFFFFF'>".Suchwort."</span>");
in der Variablen Inhalt.
Die Ersetzung klappt auch hervorragend. Dummerweise wird so aber auch aus
<a href="capitis.de">capitis</a>
<a href="<span class='verweis' style='font-weight:bold;background-color:#FF0000;color:#FFFFFF'>capitis</span>.de">=<span class='verweis' style='font-weight:bold;background-color:#FF0000;color:#FFFFFF'>capitis</span></a>
Damit funktioniert der Link natürlich nicht mehr.
Deshalb dachte ich mir, einen regulären Ausdruck hinterherzuschicken, der die ersetzung rückgängig macht, falls sie innerhalb von <.+ und .+> gefunden wird. Da das aber irgendwie immer gilt und meine Versuche bisher gescheitert sind, weil sie komischerweise auf den Linktext und nicht den Link selber angewandt wurden, quasi genau umgekehrt wie geplant, hoffe ich, dass mir hier vielleicht jemand den entscheidenden Tipp geben kann.
Dafür danke ich schon jetzt im Voraus und wünsche ein schönes Wochenende,
Carsten
Hallo Carsten!
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) {
var node = element.childNodes[i];
if(node.nodeType == 3) { // Text
node.nodeValue = node.nodeValue.replace(w1, w2);
}
else if(node.nodeType == 1{ // Element
ersetzen(node, w1, w2);
}
}
}
Anwenden sollte man die Funktion besser auf document.body als direkt auf document, da man sonst auch in style-Bereichen u.ä. ersetzt.
Außerdem sollte man evt auch noch script, textarea usw von der ersetzung ausnehmen.
Grüße
Daniel
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
Hallo,
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.
Naja, es gibt da diese Netscape-Erweiterungen (lastMatch, leftContext, rightContext), die nicht Teil von ECMAScript sind. Damit geht es ganz gut, replace braucht man gar nicht:
function ersetzen (str, wort, insertbefore, insertafter) {
var expr = new RegExp('(<[^>]*)|' + wort, 'ig');
while (erg = expr.exec(str)) {
// alert(RegExp.leftContext + '[' + RegExp.lastMatch + ']' + RegExp.rightContext + ' (' + expr.lastIndex + ')');
if (RegExp.lastMatch.charAt(0) != '<' && RegExp.leftContext.substr(RegExp.leftContext.length - insertbefore.length) != insertbefore) {
str = RegExp.leftContext + insertbefore + RegExp.lastMatch + insertafter + RegExp.rightContext;
// alert('string geändert:\n\n' + str);
}
}
return str;
}
window.onload = function () {
document.body.innerHTML = ersetzen(document.body.innerHTML, 'entchen', '<span class="highlight">', '</span>');
}
Diese Erweiterungen kennen wohl nur Mozilla und MSIE ab 5.5 (letzterer ungetestet, im 5.0 könnte man etwas über lastIndex basteln).
Das bestätigt eigentlich nur, dass JavaScript nicht die richtige Technik dafür ist. Serverseitig ließen sich sowohl die DOM-Variante als auch die mit Regulären Ausdrücken zuverlässiger umsetzen.
Mathias
Hallo Molily,
Er will aber Markup um das Suchwort herumbauen. Markup in einen Textknoten zu schreiben, bringt nichts, es wird nicht als solches verarbeitet.
Ja stimmt. Kann ich mich jetzt darauf herausreden, dass ich keine fertige Lösung liefern wollte? ;-)
Die Schleifenbedingung wird bei _jedem_ Schleifendurchlauf ausgewertet. Wenn Du innerhalb der Schleife also neue Knoten einfügst, wird childNodes.length entsprechend erhöht.
Man müsste das also auch so lösen können:
<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);
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);
i += 2;
}
} else if (node.nodeType == 1) {
// alert('rekursion');
ersetzen(node, suchwort);
}
}
}
Ich habe meine Zweifel, ob diese Methode robuster als RegExp-Fummelei über innerHTML ist.
Wenn der Browser das DOM gut genug unterstüzt, sollte das recht robust sein.
Deine RegExp Variante ist wirklich recht trickreich, aber austricksen lässt sie sich dennoch, z.B. durch:
<tag attr1=">" attr2="suchwort">
Außerdem dürfte es recht schwierig sein, Textfelder, Scriptbereiche o.ä. auszuschließen. Daher würde ich die DOM-Variante eindeutig bevorzugen.
Grüße
Daniel
Grüße
Daniel