2010年7月9日金曜日

ActionScript3(Flex)でプレインテキストに含まれるURL文字列にリンクをつけるには?

よくメーラーなどで行われているプレインテキストに含まれているURL文字列にリンクをつけて飛ぶべるようにする処理は、ActionScript3ではどうやればいいだろう?

まずはURL文字列の最後をどう判断するかが問題。これはスペースか改行で判断することにしよう。しかし、これは逆にURL文字列の最後にスペースか改行がなければ正しく処理できないことを意味する。とはいえ、これ以外の判断基準を考えられないし、まぁ大体大丈夫だろうからこれでヨシとする。

もう一つの問題はURL文字列によく含まれている「?」をどうするかだ。ActionScriptでの文字列の置換は基本的に正規表現で行うようだけど、正規表現では「?」はメタ文字だ。正しく処理するには、ナンとか正規表現のメタ文字にひっかからないような別の文字にしておかなければならない。とりあえずこれは「?」のURLエンコード文字列「%3f」で置換しておくことにしよう。

最後に、問題でもないけど改行の処理。これは改行を<br/ >に変換すればいい。

こういった方針で、例えばこのようにするとできる。

/**
 * 改行を<br/ >に変換してURL文字列にリンクタグをつける
 * 
 * @param src プレインテキスト文字列
 * @return HTML文字列
 * 
 */
private function toHTML(src:String):String {

    //とりあえずソースをコピー
    var out:String = new String(src);

    //URL文字列があるなら
    if (out.indexOf("http://") != -1) {

        //「?」は正規表現のメタ文字なので
        //URLエンコードした文字列「%3f」に置き換えておく
        out = out.replace(/\?/g, "%3f");

        //URL文字列の配列
        var links:ArrayCollection = new ArrayCollection();

        //現在のインデックス
        var currIdx:int;
        while (true) {

            //切り出し始めのインデックス
            var beginIdx:int = out.indexOf("http://", currIdx);

            //現在のインデックス以降にURL文字列がなければ終了
            if (beginIdx == -1) {
                break;
            }

            //改行かスペースでURLの最後を判断する
            else {

                //切り出し終りのインデックス
                var endIdx:int;

                //直近で改行があるインデックス
                var endIdxBR:int = out.indexOf("\n", beginIdx);

                //直近でスペースがあるインデックス
                var endIdxSpace:int = out.indexOf(" ", beginIdx);

                //改行もスペースも無いなら終了(準備)
                if (endIdxSpace == -1 && endIdxBR == -1) {
                    endIdx = out.length;
                }

                //改行かスペースのどちらか早い方のインデックスを採用する
                //(ただし、-1でない場合)
                else {
                    if (endIdxSpace == -1) {
                        endIdx = endIdxBR;
                    } else if (endIdxBR == -1) {
                        endIdx = endIdxSpace;
                    } else {
                        if (endIdxSpace < endIdxBR) {
                            endIdx = endIdxSpace;
                        } else {
                            endIdx = endIdxBR;
                        }
                    }
                }

                //URL文字列を配列に追加
                links.addItem(out.substring(beginIdx, endIdx));

                //現在のインデックス更新
                currIdx = endIdx + 1;

            } //END else

        } //END while

        //URL文字列をHTMLに変換
        for each (var str:String in links) {
                var pattern:RegExp = new RegExp(str, "g");
                out = out.replace(pattern,
                                  "<a href=\""
                                  + str
                                  + "\" target=\"_blank\">"
                                  + str
                                  + "</a>");
        }

        //「%3f」を「?」に戻す
        out = out.replace(/%3f/g, "?");

    } //END if

    //改行をHTMLに変換
    out = out.replace(/\n/g, "<br/ >");

    trace("toHTML() out=\n" + out);

    return out;

}

ある程度テストした感じでは大丈夫そうだけど、どうだろう。とりあえず、これでしばらく使ってみよう。