Domanda

I want to insert html tags within a text node with TreeWalker, but TreeWalker forces my html brackets into & lt; & gt; no matter what I've tried. Here is the code:

var text;
var tree = document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);
while (tree.nextNode()) {
    text = tree.currentNode.nodeValue;
    text = text.replace(/(\W)(\w+)/g, '$1<element onmouseover="sendWord(\'$2\')">$2</element>');
    text = text.replace(/^(\w+)/, '<element onmouseover="sendWord(\'$1\')">$1</element>');
    tree.currentNode.nodeValue = text;
}

Using \< or " instead of ' won't help. My workaround is to copy all of the DOM tree to a string and to replace the html body with that. It works on very simple webpages and solves my first problem, but is a bad hack and won't work on anything more than a trivial page. I was wondering if I could just work straight with the text node rather than use a workaround. Here is the code for the (currently buggy) workaround:

var text;
var newHTML = "";
var tree = document.createTreeWalker(document.body);
while (tree.nextNode()) {
    text = tree.currentNode.nodeValue;
    if (tree.currentNode.nodeType == 3){
        text = text.replace(/(\W)(\w+)/g, '$1<element onmouseover="sendWord(\'$2\')">$2</element>');
        text = text.replace(/^(\w+)/, '<element onmouseover="sendWord(\'$1\')">$1</element>');
        }
    newHTML += text
}
document.body.innerHTML = newHTML;

Edit: I realize a better workaround would be to custom tag the text nodes ((Customtag_Start_Here) etc.), copy the whole DOM to a string, and use my customs tags to identify text nodes and modify them that way. But if I don't have to, I'd rather not.

È stato utile?

Soluzione

To 'change' a text node into an element, you must replace it with an element. For example:

var text = tree.currentNode;
var el = document.createElement('foo');
el.setAttribute('bar','yes');
text.parentNode.replaceChild( el, text );

If you want to retain part of the text node, and inject an element "in the middle", you need to create another text node and insert it and the element into the tree at the appropriate places in the tree.


Edit: Here's a function that might be super useful to you. :)

Given a text node, it runs a regex on the text values. For each hit that it finds it calls a custom function that you supply. If that function returns a string, then the match is replaced. However, if that function returns an object like:

{ name:"element", attrs{onmouseover:"sendWord('foo')"}, content:"foo" }

then it will split the text node around the match and inject an element in that location. You can also return an array of strings or those objects (and can recursively use arrays, strings, or objects as the content property).

Demo: http://jsfiddle.net/DpqGH/8/

function textNodeReplace(node,regex,handler) {
  var mom=node.parentNode, nxt=node.nextSibling,
      doc=node.ownerDocument, hits;
  if (regex.global) {
    while(node && (hits=regex.exec(node.nodeValue))){
      regex.lastIndex = 0;
      node=handleResult( node, hits, handler.apply(this,hits) );
    }
  } else if (hits=regex.exec(node.nodeValue))
    handleResult( node, hits, handler.apply(this,hits) );

  function handleResult(node,hits,results){
    var orig = node.nodeValue;
    node.nodeValue = orig.slice(0,hits.index);
    [].concat(create(mom,results)).forEach(function(n){
      mom.insertBefore(n,nxt);
    });
    var rest = orig.slice(hits.index+hits[0].length);
    return rest && mom.insertBefore(doc.createTextNode(rest),nxt);
  }

  function create(el,o){
    if (o.map) return o.map(function(v){ return create(el,v) });
    else if (typeof o==='object') {
      var e = doc.createElementNS(o.namespaceURI || el.namespaceURI,o.name);
      if (o.attrs) for (var a in o.attrs) e.setAttribute(a,o.attrs[a]);
      if (o.content) [].concat(create(e,o.content)).forEach(e.appendChild,e);
      return e;
    } else return doc.createTextNode(o+"");
  }
}

It's not quite perfectly generic, as it does not support namespaces on attributes. But hopefully it's enough to get you going. :)


You would use it like so:

findAllTextNodes(document.body).forEach(function(textNode){
  replaceTextNode( textNode, /\b\w+/g, function(match){
    return {
      name:'element',
      attrs:{onmouseover:"sendWord('"+match[0]+"')"},
      content:match[0]
    };
  });
});

function findAllTextNodes(node){
  var walker = node.ownerDocument.createTreeWalker(node,NodeFilter.SHOW_TEXT);
  var textNodes = [];
  while (walker.nextNode())
    if (walker.currentNode.parentNode.tagName!='SCRIPT')
      textNodes.push(walker.currentNode);
  return textNodes;
}

or if you want something closer to your original regex:

  replaceTextNode( textNode, /(^|\W)(\w+)/g, function(match){
    return [
      match[1], // might be an empty string
      {
        name:'element',
        attrs:{onmouseover:"sendWord('"+match[2]+"')"},
        content:match[2]
      }
    ];
  });

Altri suggerimenti

Function that returns the parent element of any text node including partial match of passed string:

function findElByText(text, mainNode) {
    let textEl = null;
    const traverseNodes = function (n) {
        if (textEl) {
            return;
        }
        for (var nodes = n.childNodes, i = nodes.length; i--;) {
            if (textEl) {
                break;
            }
            var n = nodes[i], nodeType = n.nodeType;
            // Its a text node, check if it matches string
            if (nodeType == 3) {
                if (n.textContent.includes(text)) {
                    textEl = n.parentElement;
                    break;
                }
            }
            else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                traverseNodes(n);
            }
        }
    }
    traverseNodes(mainNode);
    return textEl;
}

Usage:

findElByText('Some string in document', document.body);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top