それ、うまいのか?

... 記憶の残滓

URLをリンク化するブックマークレット

http://q.hatena.ne.jp/1356135347#a1182565 にあった「URLをリンク化するjQueryプラグインとブックマークレット | TipsZone」に、とてもインスパイアされた。

とても良い感じなのだけれど、

  • ちょっと重たい
  • 人力検索 @はてなハイク でよく使われる http:// 無しのテキストも対象にしたい
  • 複数回実行すると、アンカータグがどんどん入れ子になっていく

というわけで、自分用に書きなおしてみる。

jquery.url2link.js が重たい理由

jQuery ですっきりしたコードにはなっているのは良いのだけれど、find('*') を使ってるので、ページ中の全Nodeを指す jQuery オブジェクトの配列ができてしまう。
カレンダー(jQuery UI の datePicker)のように見た目は小さいけど、Element がいっぱいあるようなページだと、ちょっときつい。
フレームの中もたどるので、広告にももぐりこんでいくし。

DOM の firstChild と nextSibling でたどっていけば、不要な中間オブジェクトができないので軽いはず。

たどってゆくときに、Element と TextNode の区別がつくので、アンカータグの子要素を変換対象から外すことで、アンカータグが入れ子になっていくことも防げる。

http:// 無しの表現

ホストの表現で、[a-z]+\.hatena\.ne\.jp を追加。
とりあえず、q.hatena.ne.jp だけ変換できれば、当座の用は成す。

URL の表現でも、スペースなどの区切りを入れずに文章を続ける人もいるので、漢字は対象外とすることにした。
フレームの中に再帰的に潜っていく機能は無くても良いかな、という気がするけれど、とりあえず重さが気にならないので、残しておく。



というわけで、出来上がったのはこんな感じ。

(function(){
    // URL 表現を拾うための正規表現
    var RE_URL = /(?:h?t)?(tps?:\/\/[^/\s]+|[a-z]+\.hatena\.ne\.jp)((\/[A-Za-z0-9!-/:;=?@_~]*)?)/i;

    // プロトコルを補完するときに、どちらのタイプに引っかかったかを判定するための正規表現
    var RE_TPS = /^tps?:\/\//i;

    // TextNode を url を指すアンカータグで置き換える
    function txt2anchor(txt, url) {
        var anchor = txt.ownerDocument.createElement("a");
        anchor.href = url;
        anchor.target = "_blank";
        anchor.innerHTML = txt.nodeValue;
        txt.parentNode.replaceChild(anchor, txt);
    }

    // TextNode 中に URL を指す表現があるかどうかを探し、アンカータグに置き換えてゆく
    function url2link(txt) {
        var result = RE_URL.exec(txt.nodeValue);
        if (result) {
            var middlebit = txt.splitText(result.index);
            var endbit = middlebit.splitText(result[0].length);
            var url;
            if (RE_TPS.test(result[1])) {
                url = 'ht' + result[1] + result[2];
            } else {
                url = 'http://' + result[1] + result[2];
            }
            txt2anchor(middlebit, url);
            return url2link(endbit);
        }
        return txt.nextSibling;
    }

    // DOM をたどって、URL を指す表現をアンカータグに置き換えてゆく
    function url2link_walk_element(ele) {
        var e = ele.firstChild;
        while (e) {
            if (e.nodeType == 3) {
                e = url2link(e);
            } else {
                if (e.nodeType == 1 && ! (e.tagName == "A" && e.href != "")) {	// walk in <A name="xxx">
                    url2link_walk_element(e);
                }
                e = e.nextSibling;
            }
        }
    }

    // 自window と、含まれる frame, iframe に対して、再帰的に url2link_walk_element を実行する
    function url2link_walk_window(w) {
        url2link_walk_element(w.document.body);
        var ff = w.frames;
        for (var i = 0 ; i < ff.length ; ++i) {
            url2link_walk_window(ff[i]);
        }
    }

    url2link_walk_window(window);

})();

Firefox でしか確認してないけど、DOM の Core API しか使ってないから、モダンブラウザなら何でも動くはず(多分)。


ついでに、Hatena::Let にもブックマークレットを置いてみた。
http://let.hatelabo.jp/a-kuma3/let/gYC-yv69yveYaQ



@2015-2-18
name 属性を指定した A タグの中をたどってなかったので、ちょっと修正。