te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>javascript - What's a good alternative to HTML rewriting? - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - What's a good alternative to HTML rewriting? - Stack Overflow

programmeradmin4浏览0评论

Consider this document fragment:

<div id="test">
    <h1>An article about John</h1>
    <p>The frist paragraph is about John.</p>
    <p>The second paragraph contains a <a href="#">link to John's CV</a>.</p>
    <div class="ments">
        <h2>Comments to John's article</h2>
        <ul>
            <li>Some user asks John a question.</li>
            <li>John responds.</li>
        </ul>
    </div>
</div>

I would like to replace every occurrence of the string "John" with the string "Peter". This could be done via HTML rewriting:

$('#test').html(function(i, v) {
    return v.replace(/John/g, 'Peter');    
});

Working demo: /

The above jQuery code looks simple and straight-forward, but this is deceiving because it is a lousy solution. HTML rewriting recreates all the DOM nodes inside the #test DIV. Subsequently, changes made on that DOM subtree programmatically (for instance "onevent" handlers), or by the user (entered form fields) are not preserved.

So what would be an appropriate way to perform this task?

Consider this document fragment:

<div id="test">
    <h1>An article about John</h1>
    <p>The frist paragraph is about John.</p>
    <p>The second paragraph contains a <a href="#">link to John's CV</a>.</p>
    <div class="ments">
        <h2>Comments to John's article</h2>
        <ul>
            <li>Some user asks John a question.</li>
            <li>John responds.</li>
        </ul>
    </div>
</div>

I would like to replace every occurrence of the string "John" with the string "Peter". This could be done via HTML rewriting:

$('#test').html(function(i, v) {
    return v.replace(/John/g, 'Peter');    
});

Working demo: http://jsfiddle/v2yp5/

The above jQuery code looks simple and straight-forward, but this is deceiving because it is a lousy solution. HTML rewriting recreates all the DOM nodes inside the #test DIV. Subsequently, changes made on that DOM subtree programmatically (for instance "onevent" handlers), or by the user (entered form fields) are not preserved.

So what would be an appropriate way to perform this task?

Share Improve this question asked May 16, 2011 at 0:12 Šime VidasŠime Vidas 186k65 gold badges287 silver badges391 bronze badges 6
  • How about using .text instead of .html. – James Black Commented May 16, 2011 at 0:18
  • What about something like this ? – alex Commented May 16, 2011 at 0:18
  • @alex - I haven't looked, but .text should use document.createTextNode for changes, so any event handlers, for example, should be untouched. – James Black Commented May 16, 2011 at 0:33
  • @James text() rewriting destroys all child elements. See here. – Šime Vidas Commented May 16, 2011 at 0:37
  • @James I assumed text() used innerText or textContent. – alex Commented May 16, 2011 at 0:38
 |  Show 1 more ment

8 Answers 8

Reset to default 4

How about a jQuery plugin version for a little code reduction?

http://jsfiddle/v2yp5/4/

jQuery.fn.textWalk = function( fn ) {
    this.contents().each( jwalk );
    function jwalk() {
        var nn = this.nodeName.toLowerCase();
        if( nn === '#text' ) {
            fn.call( this );
        } else if( this.nodeType === 1 && this.childNodes && this.childNodes[0] && nn !== 'script' && nn !== 'textarea' ) {
            $(this).contents().each( jwalk );
        }
    }
    return this;
};

$('#test').textWalk(function() {
    this.data = this.data.replace('John','Peter');
});

Or do a little duck typing, and have an option to pass a couple strings for the replace:

http://jsfiddle/v2yp5/5/

jQuery.fn.textWalk = function( fn, str ) {
    var func = jQuery.isFunction( fn );
    this.contents().each( jwalk );

    function jwalk() {
        var nn = this.nodeName.toLowerCase();
        if( nn === '#text' ) {
            if( func ) {
                fn.call( this );
            } else {
                this.data = this.data.replace( fn, str );
            }
        } else if( this.nodeType === 1 && this.childNodes && this.childNodes[0] && nn !== 'script' && nn !== 'textarea' ) {
            $(this).contents().each( jwalk );
        }
    }
    return this;
};

$('#test').textWalk(function() {
    this.data = this.data.replace('John','Peter');
});

$('#test').textWalk( 'Peter', 'Bob' );

You want to loop through all child nodes and only replace the text nodes. Otherwise, you may match HTML, attributes or anything else that is serialised. When replacing text, you want to work with the text nodes only, not the entire HTML serialised.

I think you already know that though :)

Bobince has a great piece of JavaScript for doing that.

I needed to do something similar, but I needed to insert HTML markup. I started from the answer by @user113716 and made a couple modifications:

$.fn.textWalk = function (fn, str) {
    var func = jQuery.isFunction(fn);
    var remove = [];

    this.contents().each(jwalk);

    // remove the replaced elements
    remove.length && $(remove).remove();

    function jwalk() {
        var nn = this.nodeName.toLowerCase();
        if (nn === '#text') {
            var newValue;

            if (func) {
                newValue = fn.call(this);
            } else {
                newValue = this.data.replace(fn, str);
            }

            $(this).before(newValue);
            remove.push(this)
        } else if (this.nodeType === 1 && this.childNodes && this.childNodes[0] && nn !== 'script' && nn !== 'textarea') {
            $(this).contents().each(jwalk);
        }
    }
    return this;
};

There are a few implicit assumptions:

  • you are always inserting HTML. If not, you'd want to add a check to avoid manipulating the DOM when not necessary.
  • removing the original text elements isn't going to cause any side effects.

Slightly less intrusive, but not necessarily any more performant, is to select elements which you know only contain text nodes, and use .text(). In this case (not a general-purpose solution, obviously):

$('#test').find('h1, p, li').text(function(i, v) {
    return v.replace(/John/g, 'Peter');
});

Demo: http://jsfiddle/mattball/jdc87/ (type something in the <input> before clicking the button)

This is how I would do it:

var textNodes = [], stack = [elementWhoseNodesToReplace], c;
while(c = stack.pop()) {
    for(var i = 0; i < c.childNodes.length; i++) {
        var n = c.childNodes[i];
        if(n.nodeType === 1) {
            stack.push(n);
        } else if(n.nodeType === 3) {
            textNodes.push(n);
        }
    }
}

for(var i = 0; i < textNodes.length; i++) textNodes[i].parentNode.replaceChild(document.createTextNode(textNodes[i].nodeValue.replace(/John/g, 'Peter')), textNodes[i]);

Pure JavaScript and no recursion.

You could wrap every textual instance that is variable (e.g. "John") in a span with a certain CSS class, and then do a .text('..') update on all those spans. Seems less intrusive to me, as the DOM isn't really manipulated.

<div id="test">
    <h1>An article about <span class="name">John</span></h1>
    <p>The frist paragraph is about <span class="name">John</span>.</p>
    <p>The second paragraph contains a <a href="#">link to <span class="name">John</span>'s CV</a>.</p>
    <div class="ments">
        <h2>Comments to <span class="name">John</span>'s article</h2>
        <ul>
            <li>Some user asks <span class="name">John</span> a question.</li>
            <li><span class="name">John</span> responds.</li>
        </ul>
    </div>
</div>


$('#test .name').text(function(i, v) {
    return v.replace(/John/g, 'Peter');    
});

Another idea is to use jQuery Templates. It's definitely intrusive, as it has its way with the DOM and makes no apologies for it. But I see nothing wrong with that... I mean you're basically doing client-side data binding. So that's what the templates plugin is for.

This seems to work (demo):

$('#test :not(:has(*))').text(function(i, v) {
  return v.replace(/John/g, 'Peter');    
});

The POJS solution offered is ok, but I can't see why recursion is avoided. DOM nodes are usually not nested too deeply so it's fine I think. I also think it's much better to build a single regular expression than use a literal and build the expression on every call to replace.

// Repalce all instances of t0 in text descendents of
// root with t1
// 
function replaceText(t0, t1, root) {

  root = root || document;
  var node, nodes = root.childNodes;

  if (typeof t0 == 'string') {
    t0 = new RegExp(t0, 'g');
  }

  for (var i=0, iLen=nodes.length; i<iLen; i++) {
    node = nodes[i];

    if (node.nodeType == 1) {
      arguments.callee(t0, t1, node);

    } else if (node.nodeType == 3) {
      node.data = node.data.replace(t0, t1);
    }
  }
}
发布评论

评论列表(0)

  1. 暂无评论