2006 年 8 月 9 日 19 時 9 分

HTML とテキスト


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


HTML には、実体参照と呼ばれる仕組みがある。
& や &#0064 等がそれだ。
正確には、前者を文字実体参照、後者を数値文字参照と呼ぶ。
乱暴な言い方をすると、HTML のエスケープ構文である。

今までの設計では、日記やタイトルを取得する際には、
正規表現を使って HTML を直接解析していたが、
実体参照に関しては考慮していなかった。

なので、MixiDiaryEntry#getContent メソッドで、
日記の本文を取得した場合は、< 等の実体参照や、
mixi が自動的に挿入した <br> タグが含まれている。

元の日記の本文は、テキストとして入力した文字列なので、
getContent が HTML に依存した結果を返すのはおかしく、
ただのテキストとして取り出せるようにすべきである。

そのためには、mixi が返却した HTML を解析し、
実体参照や改行などを処理しなければならない。

これらの影響を受けるのは、「文字列」である。
日記で言えば、タイトルや本文などが対象となる。

では、その役割を持つメソッドを作成しよう。
以前作った HttpUtil.js の中に定義する。
今回は、JavaScript ならではの方法を使ってみる。

========== part of HttpUtil.js ==========

/**
* HTML の文字実体参照と数値文字参照を展開する。
* @addon
*/
String.prototype.decodeHTML = function () {

    // 参照を表す正規表現リテラル
    var ref= /&(\w+|#\d+|#x[0-9a-f]+)(?:[;\r]|(?![-.:\w]))/ig,

    // 1 つの参照を展開する関数
    var func = function (match, name, offset, source) {

        if (name.charAt(0) == "#") {
            // 数値文字参照
            var code;
            if (name.charAt(1).toLowerCase() == "x") {
                // #x0000
                code = parseInt(name.substring(2), 16);
            } else {
                // #0000
                code = parseInt(name.substring(1), 10);
            }
            if (isNaN(code)) return match;
            return String.fromCharCode(code);
        }

        // 文字実体参照(手抜き)
        switch (name) {
            case "amp":  return "&";
            case "gt":   return ">";
            case "lt":   return "<";
            case "quot": return '"';
            case "nbsp": return "\u00a0";
            default:     return match;
        }

    };

    return String(this).replace(ref, func);
}

========== end of part of HttpUtil.js ==========

まず、String.prototype.decodeHTML に登録している。
String プロトタイプを書き換えることによって、
あらゆる String オブジェクトから利用できるようになる。

これには、賛否両論あるだろうが、
俺は「Object プロトタイプ」以外なら、
独自にメソッドを追加しても良しと考えている。

デコードするのは文字列である HTML なので、
文字列(型・値)のメソッドとすればしっくりくるのだ。

    var html = "~";
    var text = html.decodeHTML();

そして、ポイントは、String#replace メソッドだ。

この replace メソッドは非常に強力で、
検索対象として正規表現が使える上に、
置換対象として関数を渡すことができる。

ref は、Perl などではお馴染みの正規表現のリテラルだ。

文字実体参照は、& で始まり識別子が続き、
セミコロンか改行で終わる。
セミコロンや改行がない場合は、
識別子として使えない文字が来た時点で終わる。

数値文字参照は、&# で始まり、10 進表記の数値か、
前に x がつく 16 進表記の数値が続く。
終端に関しては文字実体参照と同じである。

replace メソッドに渡す関数は、
第 1 引数に、一致した文字列全体が渡される。
その後に、括弧でキャプチャした部分文字列が続く。
上記の rex は括弧が 1 つだけなので、1 つだけだ。
そしてその後に一致した位置が渡され、
最後に検索されている文字列全体が渡される。
RegExp#exec の戻り値が展開されたような変な引数だ。

func 関数では、括弧で束縛した参照の名前を、
name 引数として受け取って処理をしている。

#x のものは 16 進数、# のものは 10 進数として
数値に変換し、String#fromCharCode で文字に変換して返す。

それ以外は、文字実体参照として適当に処理する。
HTML には物凄い数の文字実体参照があるので、
全て書くと面倒だ。厳密にやりたい人は追加してな。

さて、関数の戻り値は置換後の文字列として使われる。
なので、不明な参照(や怠慢で処理しない参照)が
関数に渡された場合は、一致部分をそのまま返す。
そうすれば、置換は発生しないことになる。

さて、テストしてみよう。

    WScript.Echo( "&quot;&gt&#x0064&#x??".decodeHTML() );

これを実行すると、以下のように表示された。

    ">d&#x??

よし、問題ないようだ。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.