テキストの任意の文字列を検索してマークアップして置換する

HTMLのTextObject(ブラウザで人が目にするテキスト)の中から、指定の文字列を検索し、その部分だけをマークアップして置換する。

HOGE<h1>FUGA</h1>に置換する。

<div id="demo-container">
  FUGA HOGE HOGE FUGA
  <div id="demo-box">Demonstration Box</div>
  <ul>
    <li>list item 1</li>
    <li>list item 2</li>
  </ul>
</div>

上のHTMLが下の様になってほしい。

<div id="demo-container">
  FUGA <h1>FUGA</h1> <h1>FUGA</h1> FUGA
  <div id="demo-box">Demonstration Box</div>
  <ul>
    <li>list item 1</li>
    <li>list item 2</li>
  </ul>
</div>

良くない方法

innerHTMLでreplaceする

var x = document.getElementById('demo-container')
x.innerHTML = x.innerHTML.replace('HOGE', '<h1>FUGA</h1>', 'g')

これはうまく行くけど、次のような場合だとどうだろう。

var x = document.getElementById('demo-container')
x.innerHTML = x.innerHTML.replace('li', '<h1>FUGA</h1>', 'g')

タグに反応して残念な結果に。

<div id="demo-container">
  FUGA HOGE HOGE FUGA
  <div id="demo-box">Demonstration Box</div>
  <ul>
    <<h1>FUGA</h1>><h1>FUGA</h1>st item 1</<h1>FUGA</h1>>
    <<h1>FUGA</h1>><h1>FUGA</h1>st <strong>item</strong> 2</<h1>FUGA</h1>>
  </ul>
</div>

解決方法

DOMで考えると、やりたい操作はtextノードだったものを、textノード(パターンにマッチしない部分)とそうじゃないノード(パターンにマッチする)に置き換える操作になる。

  • textノードを列挙。
  • パターンにマッチするなら新しいノードを作り、変更したHTMLを書き込む。
  • textノードと新しく作ったノードとを入れ替える。
// 参考:[javascript - Find all text nodes in HTML page - Stack Overflow](http://stackoverflow.com/questions/10730309/find-all-text-nodes-in-html-page)
var allTextNode = function(node){
  var res = []
  if (node){
    node = node.firstChild
    while (node != null){
      if (node.nodeType === 3) res[res.length] = node
      else res = res.concat(allTextNode(node))
      node = node.nextSibling
    }
  }
  return res
}
var replaceText = function(node, pat, rep) {
  var tnodes = allTextNode(node)
  // console.log('tnodes.length=' + tnodes.length)
  for (var i = 0; i < tnodes.length; i++) {
    var tnode = tnodes[i]
    var par = tnode.parentNode
    var new_tag = document.createElement('markwords')
    new_tag.innerHTML = tnode.data.replace(pat, rep)
    if (tnode.data.length != new_tag.innerHTML.length) par.replaceChild(new_tag, tnode)
  }
}
replaceText(document.body, 'li', '<h1>FUGA</h1>')

結果、次のように、タグは置き換えられず、ブラウザ上で表示される文字列のみが置き換える事が出来る。

<div id="demo-container">
  FUGA HOGE HOGE FUGA
  <div id="demo-box">Demonstration Box</div>
  <ul>
    <li><markwords><h1>FUGA</h1>st item 1</markwords></li>
    <li><markwords><h1>FUGA</h1>st item 2</markwords></li>
  </ul>
</div>