メイン | 2007年01月 »

2006年12月 アーカイブ

2006年12月03日

JavaScriptによるMD5の実装

実力試しにJavaScriptでMD5の実装をしてみた。今後、色々なハッシュを組み込む事を考え、MessageDigestクラスのクラスメソッドとしている。
/*
================================================================================
    Name        :   MessageDigest
    In          :   [none]      
    Out         :   [none]      
    Note        :   メッセージダイジェスト
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2006/11/18  |   新規作成
--------------------------------------------------------------------------------
    License     :   New BSD license
    URL         :   www.kanasansoft.com
================================================================================
*/

/*--------------------------------------------------------------------------------
    コンストラクタ
--------------------------------------------------------------------------------*/
function MessageDigest(){}

/*--------------------------------------------------------------------------------
    定数
--------------------------------------------------------------------------------*/
MessageDigest.BY_STRING     =   "string";
MessageDigest.BY_ARRAY      =   "array";

///*--------------------------------------------------------------------------------
//  バイト配列に変換する(内部関数)
//--------------------------------------------------------------------------------*/
//MessageDigest._changeParameterToByteArray
//= function(param){
//
//  var rtn;
//
//  switch(param.constructor){
//      case Number:
//          rtn =   MessageDigest._changeNumberToByteArray(param);
//          break;
//      case String:
//          rtn =   MessageDigest._changeStringToByteArray(param);
//          break;
//      default:
//          rtn =   null;
//          break;
//  }
//
//  return rtn;
//
//}

/*--------------------------------------------------------------------------------
    数値をバイト配列に変換する(内部関数)
--------------------------------------------------------------------------------*/
MessageDigest._changeNumberToByteArray
=   function(num){

    //参照渡し破棄
    var numbuf          =   Number(num);

    var ary             =   [];
    //下位桁より配列化
    while(numbuf!=0){
        ary             .   push(numbuf & 0xFF);
        numbuf          =   numbuf >>> 8;
    }
    //バイトを反転
    ary                 .   reverse();

    return ary;

}

/*--------------------------------------------------------------------------------
    文字を文字コード配列に変換する(内部関数)
    (2バイト以上の場合は1バイト毎)
--------------------------------------------------------------------------------*/
MessageDigest._changeStringToByteArray
=   function(str){

    var bytearray           =   [];
    var strlen              =   str.length;

    //上位ワードより配列化
    for(var i=0;i<strlen;i++){
        bytearray           =   bytearray.concat(
                                    MessageDigest._changeNumberToByteArray(
                                        str.charCodeAt(i)
                                    )
                                );
    }

    return bytearray;

}

/*--------------------------------------------------------------------------------
    ビットを回転する(内部関数)
--------------------------------------------------------------------------------*/
MessageDigest._rotateBit
=   function(bit,max,num){
    var mask    =   Math.pow(2,max)-1;
    var buf     =   bit & mask;
    if(false){
    }else if(num>0){
        return (( (buf <<  ( num)) | (buf >>> (max-num)) ) & mask);
    }else if(num<0){
        return (( (buf >>> (-num)) | (buf <<  (max+num)) ) & mask);
    }else if(num==0){
        return buf;
    }
}

/*--------------------------------------------------------------------------------
    MD5本体
--------------------------------------------------------------------------------*/
MessageDigest.MD5
=   function(param,by){

    //MessageDigest参照オブジェクトの作成
    var MD          =   MessageDigest;

    //バイト配列変換
    var charArray   =   MD._changeStringToByteArray(param);

    //バイト配列長と拡張バイト長の取得
    var charLen     =   charArray.length;
    var paddingLen  =   512-((charLen*8+64)%512);

    //拡張バイト補填
    charArray       =   charArray.concat(
                                [   0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,
                                    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ].slice(0,paddingLen/8)
                        );

    //バイト長補填
    var lenArray    =   MD._changeNumberToByteArray(charLen*8);
    lenArray        .   reverse();
    lenArray        =   lenArray.concat([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]).slice(0,8);
    charArray       =   charArray.concat(lenArray);

    //レジスタ(32ビット)の初期化
    var wordA       =   0x67452301;
    var wordB       =   0xefcdab89;
    var wordC       =   0x98badcfe;
    var wordD       =   0x10325476;

    //[T]テーブルの作成
    var table       =   MD.MD5._makeTable();

    //バイト配列を64バイト単位で処理
    var charLen64   =   charArray.length;
    for(var i=0;i<charLen64;i+=64){

        //64バイトのデータを4バイト単位で格納
        //(4バイトは下位より格納)
        var numX    =   [];
        for(var j=0;j<16;j++){
            numX[j] =   0;
            for(var k=0;k<4;k++){
                numX[j] +=  charArray[i+j*4+k]*Math.pow(2,k*8);
            }
        }

        //データ退避
        var wordAA      =   wordA;
        var wordBB      =   wordB;
        var wordCC      =   wordC;
        var wordDD      =   wordD;

        //ハッシュ生成(Round 1)
        wordA   =   MD.MD5._rogicRound1(wordA,wordB,wordC,wordD,numX[0] , 7,table[ 1]);
        wordD   =   MD.MD5._rogicRound1(wordD,wordA,wordB,wordC,numX[1] ,12,table[ 2]);
        wordC   =   MD.MD5._rogicRound1(wordC,wordD,wordA,wordB,numX[2] ,17,table[ 3]);
        wordB   =   MD.MD5._rogicRound1(wordB,wordC,wordD,wordA,numX[3] ,22,table[ 4]);
        wordA   =   MD.MD5._rogicRound1(wordA,wordB,wordC,wordD,numX[4] , 7,table[ 5]);
        wordD   =   MD.MD5._rogicRound1(wordD,wordA,wordB,wordC,numX[5] ,12,table[ 6]);
        wordC   =   MD.MD5._rogicRound1(wordC,wordD,wordA,wordB,numX[6] ,17,table[ 7]);
        wordB   =   MD.MD5._rogicRound1(wordB,wordC,wordD,wordA,numX[7] ,22,table[ 8]);
        wordA   =   MD.MD5._rogicRound1(wordA,wordB,wordC,wordD,numX[8] , 7,table[ 9]);
        wordD   =   MD.MD5._rogicRound1(wordD,wordA,wordB,wordC,numX[9] ,12,table[10]);
        wordC   =   MD.MD5._rogicRound1(wordC,wordD,wordA,wordB,numX[10],17,table[11]);
        wordB   =   MD.MD5._rogicRound1(wordB,wordC,wordD,wordA,numX[11],22,table[12]);
        wordA   =   MD.MD5._rogicRound1(wordA,wordB,wordC,wordD,numX[12], 7,table[13]);
        wordD   =   MD.MD5._rogicRound1(wordD,wordA,wordB,wordC,numX[13],12,table[14]);
        wordC   =   MD.MD5._rogicRound1(wordC,wordD,wordA,wordB,numX[14],17,table[15]);
        wordB   =   MD.MD5._rogicRound1(wordB,wordC,wordD,wordA,numX[15],22,table[16]);

        //ハッシュ生成(Round 2)
        wordA   =   MD.MD5._rogicRound2(wordA,wordB,wordC,wordD,numX[1] , 5,table[17]);
        wordD   =   MD.MD5._rogicRound2(wordD,wordA,wordB,wordC,numX[6] , 9,table[18]);
        wordC   =   MD.MD5._rogicRound2(wordC,wordD,wordA,wordB,numX[11],14,table[19]);
        wordB   =   MD.MD5._rogicRound2(wordB,wordC,wordD,wordA,numX[0] ,20,table[20]);
        wordA   =   MD.MD5._rogicRound2(wordA,wordB,wordC,wordD,numX[5] , 5,table[21]);
        wordD   =   MD.MD5._rogicRound2(wordD,wordA,wordB,wordC,numX[10], 9,table[22]);
        wordC   =   MD.MD5._rogicRound2(wordC,wordD,wordA,wordB,numX[15],14,table[23]);
        wordB   =   MD.MD5._rogicRound2(wordB,wordC,wordD,wordA,numX[4] ,20,table[24]);
        wordA   =   MD.MD5._rogicRound2(wordA,wordB,wordC,wordD,numX[9] , 5,table[25]);
        wordD   =   MD.MD5._rogicRound2(wordD,wordA,wordB,wordC,numX[14], 9,table[26]);
        wordC   =   MD.MD5._rogicRound2(wordC,wordD,wordA,wordB,numX[3] ,14,table[27]);
        wordB   =   MD.MD5._rogicRound2(wordB,wordC,wordD,wordA,numX[8] ,20,table[28]);
        wordA   =   MD.MD5._rogicRound2(wordA,wordB,wordC,wordD,numX[13], 5,table[29]);
        wordD   =   MD.MD5._rogicRound2(wordD,wordA,wordB,wordC,numX[2] , 9,table[30]);
        wordC   =   MD.MD5._rogicRound2(wordC,wordD,wordA,wordB,numX[7] ,14,table[31]);
        wordB   =   MD.MD5._rogicRound2(wordB,wordC,wordD,wordA,numX[12],20,table[32]);

        //ハッシュ生成(Round 3)
        wordA   =   MD.MD5._rogicRound3(wordA,wordB,wordC,wordD,numX[5] , 4,table[33]);
        wordD   =   MD.MD5._rogicRound3(wordD,wordA,wordB,wordC,numX[8] ,11,table[34]);
        wordC   =   MD.MD5._rogicRound3(wordC,wordD,wordA,wordB,numX[11],16,table[35]);
        wordB   =   MD.MD5._rogicRound3(wordB,wordC,wordD,wordA,numX[14],23,table[36]);
        wordA   =   MD.MD5._rogicRound3(wordA,wordB,wordC,wordD,numX[1] , 4,table[37]);
        wordD   =   MD.MD5._rogicRound3(wordD,wordA,wordB,wordC,numX[4] ,11,table[38]);
        wordC   =   MD.MD5._rogicRound3(wordC,wordD,wordA,wordB,numX[7] ,16,table[39]);
        wordB   =   MD.MD5._rogicRound3(wordB,wordC,wordD,wordA,numX[10],23,table[40]);
        wordA   =   MD.MD5._rogicRound3(wordA,wordB,wordC,wordD,numX[13], 4,table[41]);
        wordD   =   MD.MD5._rogicRound3(wordD,wordA,wordB,wordC,numX[0] ,11,table[42]);
        wordC   =   MD.MD5._rogicRound3(wordC,wordD,wordA,wordB,numX[3] ,16,table[43]);
        wordB   =   MD.MD5._rogicRound3(wordB,wordC,wordD,wordA,numX[6] ,23,table[44]);
        wordA   =   MD.MD5._rogicRound3(wordA,wordB,wordC,wordD,numX[9] , 4,table[45]);
        wordD   =   MD.MD5._rogicRound3(wordD,wordA,wordB,wordC,numX[12],11,table[46]);
        wordC   =   MD.MD5._rogicRound3(wordC,wordD,wordA,wordB,numX[15],16,table[47]);
        wordB   =   MD.MD5._rogicRound3(wordB,wordC,wordD,wordA,numX[2] ,23,table[48]);

        //ハッシュ生成(Round 4)
        wordA   =   MD.MD5._rogicRound4(wordA,wordB,wordC,wordD,numX[0] , 6,table[49]);
        wordD   =   MD.MD5._rogicRound4(wordD,wordA,wordB,wordC,numX[7] ,10,table[50]);
        wordC   =   MD.MD5._rogicRound4(wordC,wordD,wordA,wordB,numX[14],15,table[51]);
        wordB   =   MD.MD5._rogicRound4(wordB,wordC,wordD,wordA,numX[5] ,21,table[52]);
        wordA   =   MD.MD5._rogicRound4(wordA,wordB,wordC,wordD,numX[12], 6,table[53]);
        wordD   =   MD.MD5._rogicRound4(wordD,wordA,wordB,wordC,numX[3] ,10,table[54]);
        wordC   =   MD.MD5._rogicRound4(wordC,wordD,wordA,wordB,numX[10],15,table[55]);
        wordB   =   MD.MD5._rogicRound4(wordB,wordC,wordD,wordA,numX[1] ,21,table[56]);
        wordA   =   MD.MD5._rogicRound4(wordA,wordB,wordC,wordD,numX[8] , 6,table[57]);
        wordD   =   MD.MD5._rogicRound4(wordD,wordA,wordB,wordC,numX[15],10,table[58]);
        wordC   =   MD.MD5._rogicRound4(wordC,wordD,wordA,wordB,numX[6] ,15,table[59]);
        wordB   =   MD.MD5._rogicRound4(wordB,wordC,wordD,wordA,numX[13],21,table[60]);
        wordA   =   MD.MD5._rogicRound4(wordA,wordB,wordC,wordD,numX[4] , 6,table[61]);
        wordD   =   MD.MD5._rogicRound4(wordD,wordA,wordB,wordC,numX[11],10,table[62]);
        wordC   =   MD.MD5._rogicRound4(wordC,wordD,wordA,wordB,numX[2] ,15,table[63]);
        wordB   =   MD.MD5._rogicRound4(wordB,wordC,wordD,wordA,numX[9] ,21,table[64]);

        //退避していたデータを加算
        wordA   +=  wordAA;
        wordB   +=  wordBB;
        wordC   +=  wordCC;
        wordD   +=  wordDD;

        //32ビットへの丸め込み(レジスタのサイズ)
        wordA   =   wordA & 0xffffffff;
        wordB   =   wordB & 0xffffffff;
        wordC   =   wordC & 0xffffffff;
        wordD   =   wordD & 0xffffffff;

    }

    //各々を下位バイト順にした後(反転した後)、順に連結
    var rtnArray    =   [];
    var rtnA    =   MD._changeNumberToByteArray(wordA);
    var rtnB    =   MD._changeNumberToByteArray(wordB);
    var rtnC    =   MD._changeNumberToByteArray(wordC);
    var rtnD    =   MD._changeNumberToByteArray(wordD);
    rtnA.reverse();
    rtnB.reverse();
    rtnC.reverse();
    rtnD.reverse();
    rtnArray        =   rtnArray.concat(rtnA);
    rtnArray        =   rtnArray.concat(rtnB);
    rtnArray        =   rtnArray.concat(rtnC);
    rtnArray        =   rtnArray.concat(rtnD);

    //取得指定方法毎に戻り値処理
    switch(by){
        case MD.BY_ARRAY:
            return rtnArray;
            break;
        case MD.BY_STRING:
        default:
            var rtnArrayLen =   rtnArray.length;
            var rtnString   =   "";
            for(var i=0;i<rtnArrayLen;i++){
                rtnString   +=  ("00"+rtnArray[i].toString(16)).slice(-2);
            }
            return rtnString;
            break;
    }

}

/*--------------------------------------------------------------------------------
    補助関数(内側)(内部関数)
--------------------------------------------------------------------------------*/
MessageDigest.MD5._rogicF       =   function(x,y,z){return ( ( x) & ( y) ) | ( (~x) & ( z) ) };
MessageDigest.MD5._rogicG       =   function(x,y,z){return ( ( x) & ( z) ) | ( ( y) & (~z) ) };
MessageDigest.MD5._rogicH       =   function(x,y,z){return ( x) ^   ( y) ^ ( z)   };
MessageDigest.MD5._rogicI       =   function(x,y,z){return ( y) ^ ( ( x) | (~z) ) };

/*--------------------------------------------------------------------------------
    補助関数(外側)(内部関数)
--------------------------------------------------------------------------------*/
MessageDigest.MD5._rogicRound1  =   function(a,b,c,d,K,s,I){return b+MessageDigest._rotateBit((a+MessageDigest.MD5._rogicF(b,c,d)+K+I),32,s)};
MessageDigest.MD5._rogicRound2  =   function(a,b,c,d,K,s,I){return b+MessageDigest._rotateBit((a+MessageDigest.MD5._rogicG(b,c,d)+K+I),32,s)};
MessageDigest.MD5._rogicRound3  =   function(a,b,c,d,K,s,I){return b+MessageDigest._rotateBit((a+MessageDigest.MD5._rogicH(b,c,d)+K+I),32,s)};
MessageDigest.MD5._rogicRound4  =   function(a,b,c,d,K,s,I){return b+MessageDigest._rotateBit((a+MessageDigest.MD5._rogicI(b,c,d)+K+I),32,s)};

/*--------------------------------------------------------------------------------
    テーブル作成(内部関数)
--------------------------------------------------------------------------------*/
MessageDigest.MD5._makeTable
=   function(){
    var table                   =   [];
    for(var i=0;i<=64;i++){
        table[i]                =   Math.floor(0xffffffff*Math.abs(Math.sin(i)));
    }
    return table;
}

文字列の操作を行うjavaScript Class

これまで作ってきた文字列操作系の関数で使えそうなものを、StringUtilityとしてひとつにまとめた。(まとめようと考えてから、かれこれ一年以上たってた。)
/*
================================================================================
    Name        :   StringUtility
    In          :   [none]      
    Out         :   [none]      
    Note        :   文字列用ユーティリティ群
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2006/01/16  |   新規作成
--------------------------------------------------------------------------------
    License     :   New BSD license
    URL         :   www.kanasansoft.com
================================================================================
*/

/*--------------------------------------------------------------------------------
    コンストラクタ
--------------------------------------------------------------------------------*/
function StringUtility(){
}

StringUtility.Encode
=   function(){
}

StringUtility.Decode
=   function(){
}

StringUtility.Convert
=   function(){
}

/*--------------------------------------------------------------------------------
    HTMLエンコードを行なう
--------------------------------------------------------------------------------*/
StringUtility.Encode.HTML
=   function(str){
    return          str                                         .
                    replace(    /&/ig   ,   "&amp;"     )       .
                    replace(    /</ig   ,   "&lt;"      )       .
                    replace(    />/ig   ,   "&gt;"      )       .
                    replace(    /"/ig   ,   "&quot;"    )       .
                    replace(    / /ig   ,   "&nbsp;"    )       ;
}

/*--------------------------------------------------------------------------------
    HTMLデコードを行なう
--------------------------------------------------------------------------------*/
StringUtility.Decode.HTML
=   function(str){
    return          str                                         .
                    replace(    /&nbsp;/ig  ,   " "     )       .
                    replace(    /&quot;/ig  ,   "\""    )       .
                    replace(    /&gt;/ig    ,   ">"     )       .
                    replace(    /&lt;/ig    ,   "<"     )       .
                    replace(    /&amp;/ig   ,   "&"     )       ;
}

/*--------------------------------------------------------------------------------
    HTMLエンコードを行なう(Bookmarklet用)
--------------------------------------------------------------------------------*/
StringUtility.Encode.HTMLforBookmarklet
=   function(str){
    return          str                                         .
                    replace(    /&/ig   ,   "&amp;"     )       .
                    replace(    /</ig   ,   "&lt;"      )       .
                    replace(    />/ig   ,   "&gt;"      )       .
                    replace(    /"/ig   ,   "&quot;"    )       ;
}

/*--------------------------------------------------------------------------------
    JavaScriptエンコードを行なう
--------------------------------------------------------------------------------*/
StringUtility.Encode.JavaScript
=   function(str){
    return          str                                         .
                    replace(    /\\/ig  ,   "\\\\"      )       .
/*                  replace(    /\b/ig  ,   "\\b"       )       .*/
                    replace(    /\f/ig  ,   "\\f"       )       .
                    replace(    /\n/ig  ,   "\\n"       )       .
                    replace(    /\r/ig  ,   "\\r"       )       .
                    replace(    /\t/ig  ,   "\\t"       )       .
                    replace(    /'/ig   ,   "\\'"       )       .
                    replace(    /"/ig   ,   "\\\""      )       ;
}

/*--------------------------------------------------------------------------------
    tabをspaceに変換する
--------------------------------------------------------------------------------*/
StringUtility.Convert.TabToSpace
=   function(str,tabNumber){

    var linesRN                         =   str.split("\r\n");
    for(var lineCntRN=0;lineCntRN<linesRN.length;lineCntRN++){
        var linesN                      =   linesRN[lineCntRN].split("\n");
        for(var lineCntN=0;lineCntN<linesN.length;lineCntN++){
            var linesR                  =   linesN[lineCntN].split("\r");
            for(var lineCntR=0;lineCntR<linesR.length;lineCntR++){
                var wordsT              =   linesR[lineCntR].split("\t");
                for(var wordsCntT=0;wordsCntT<wordsT.length-1;wordsCntT++){
                    wordsT[wordsCntT]   +=  StringUtility.getRepeatString(
                                                " "                                                                     ,
                                                tabNumber-(StringUtility.getLengthByBite(wordsT[wordsCntT])%tabNumber)  )
                }
                linesR[lineCntR]        =   wordsT.join("");
            }
            linesN[lineCntN]            =   linesR.join("\r");
        }
        linesRN[lineCntRN]              =   linesN.join("\n");
    }
    var rtn                             =   linesRN.join("\r\n");

    return rtn;

}

/*--------------------------------------------------------------------------------
    改行コードを"<br />"に変換する
--------------------------------------------------------------------------------*/
StringUtility.Convert.NewLineCodeToTag
=   function(str){
    return          str                                             .
                    replace(    /\r\n/ig    ,   "<br />"    )       .
                    replace(    /\r/ig      ,   "<br />"    )       .
                    replace(    /\n/ig      ,   "<br />"    )       ;
}

/*--------------------------------------------------------------------------------
    文字バイト長取得
--------------------------------------------------------------------------------*/
StringUtility.getLengthByBite
=   function(str){
    var count   =   0;
    for(var i=0;i<str.length;i++){
        var code    =   str.charCodeAt(i);
        while(code!=0){
            count++;
            code>>>=8;
        }
    }
    return count;
}

/*--------------------------------------------------------------------------------
    繰り返し文字取得
--------------------------------------------------------------------------------*/
StringUtility.getRepeatString
=   function(str,num){
    var rtn =   "";
    for(var i=0;i<num;i++){
        rtn +=  str;
    }
    return rtn;
}
追記(2007/07/07)
下記リンク先でバージョンアップしたものを公開中。

javascriptでJSON<=>Object変換

2009/11/24 追記
このJSONの変換処理は時代遅れとなっています。
最新の動向を知りたい方は、以下のキーワードで検索する事をお勧めします。
「JSON.stringify」「JSON.parse」「json2.js」
追記終わり
JavaScriptのObjectをJSONに変換するクラス。JSONの規格外のオブジェクト(Functionオブジェクト等)はnullとして処理するので注意。Collection & Copy - JSON入門に詳しい日本語訳があるので興味のある方は参照してほしい。逆の処理をするevalもついでにラッピング。こちらはFunctionも認識してしまう。
/*
================================================================================
    Name        :   JSON
    In          :   [none]      
    Out         :   [none]      
    Note        :   JSONユーティリティ群
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2006/12/04  |   新規作成
--------------------------------------------------------------------------------
    License     :   New BSD license
    URL         :   www.kanasansoft.com
================================================================================
*/

/*--------------------------------------------------------------------------------
    コンストラクタ
--------------------------------------------------------------------------------*/
function JSON(){
}

/*--------------------------------------------------------------------------------
    JSONエンコードを行なう
--------------------------------------------------------------------------------*/
JSON.Encode
=   function(obj){

    var rtn;

    if(obj==null){

        rtn = "null";

    }else{

        switch(obj.constructor){

            case Boolean:
                rtn = obj?"true":"false";
                break;

            case Number:
                rtn = isNaN(obj)||!isFinite(obj)?"null":obj.toString(10);
                break;

            case String:
                rtn = "\""+StringUtility.Encode.JavaScript(obj)+"\"";
                break;

            case Array:
                var buf = [];
                for(var i=0;i<obj.length;i++){
                    //再帰呼出
                    buf.push(arguments.callee(obj[i]));
                }
                rtn = "["+buf.join(",")+"]";
                break;

            case Object:
                var buf = [];
                for(var key in obj){
                    //Object汚染回避判定有
                    if(obj.hasOwnProperty(key)){
                        //再帰呼出
                        buf[buf.length] = arguments.callee(key)+":"+arguments.callee(obj[key]);
                    }
                }
                rtn = "{"+buf.join(",")+"}";
                break;

            default:
                rtn = "null";
                break;

        }

    }

    return rtn;
}

/*--------------------------------------------------------------------------------
    JSONデコードを行なう
--------------------------------------------------------------------------------*/
JSON.Decode
=   function(str){

    var rtn;

    eval("rtn="+str);

    return rtn;
}
追記(2007/07/07)
バージョンアップしたものが下記リンク先で公開済。

2006年12月14日

Kanasansoft BlogEditor公開

開発の動機
blogでプログラムの解説が面倒に思ったことはないか。私はめんどくさいと思う。
例えば文章とコードが混在するような内容だと
JavaScriptのfor分は次のように書く。
<div class="code">
for(var&nbsp;i=0;i&lt;ary.length;i++){<br />
&nbsp;&nbsp;&nbsp;&nbsp;alert(ary[i]);<br />
}
</div>
のようになり、encodeが必要な部分と必要でない部分が入りまじってしまって書くのに集中できない。blogの設定で、htmlをencodeするのかしないのか、改行は自動で行うのかの指定はエントリー毎にできるけど、「ここからここまではencode、ここはencodeなし」というような柔軟性はない。レイアウト上の理由でdivタグの出力は行いたいが、その中身、つまりコンテンツ部分はencodeしたい。色々、実現方法を考えているうちに思いついたのが「文章・コード・bookmarklet等を、div単位で記述するエリア」として、「このエリアをブロックみたいに積み重ねていく」構成である。これを実現したのが今回公開する「Kanasansoft BlogEditor」となる。この文章はまさにBlogEditorを使って書いている。

Kanasansoft BlogEditor設定方法(その1)

BlogEditorはBookmarkletで構成されている。
(詳しい仕組みはそのうち解説するかもしれないが、興味のある人はコードを見てほしい。)
設定が少々面倒ではあるが我慢して頂きたい。まずはダウンロードと解凍を行なって欲しい。
設置場所
まず、設置場所を決める。制約は「ブラウザからアクセスできる場所」、どこかのサイトでもいいし、自分のPCにApacheなんかが入っているならそこでもいい。ただ、あまりパスが長いとIEの「お気に入り」の文字数制限に引っかかるから気をつけて頂きたい。
(もしかするとWindowsなら「c:¥」と書けばローカルにアクセスできるかもしれないが、試していないため何とも言えない。)
ここでは「http://xxx/utility/」に置くことにする。
settingInitial.js
Kanasansoft->Bookmarklet->BlogEditor->settingInitial.js

定数DYNAMIC_LOAD_FILE_PATHのlocalhostとなっているところを自分の環境に書き換えよう。
var DYNAMIC_LOAD_FILE_PATH  =   "http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/DynamicLoad.js";
DynamicLoad.js
Kanasansoft->Bookmarklet->BlogEditor->DynamicLoad.js

定数KANASANSOFT_FOLDER_PATHをさっきと同じように書き換える。
var KANASANSOFT_FOLDER_PATH =   "http://xxx/utility/Kanasansoft/";
BookmarkletInitial.js
Kanasansoft->Bookmarklet->BlogEditor->BookmarkletInitial.js

ここはちょっと多めになる。テンプレート(別エントリーで説明)の数だけ書き換える必要がある。
var TEMPLATE_FILE_PATHS     =   [];
TEMPLATE_FILE_PATHS         .   push(   "http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/Template/Kanasansoft/PlainText.js"       );
TEMPLATE_FILE_PATHS         .   push(   "http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/Template/Kanasansoft/Source.js"          );
TEMPLATE_FILE_PATHS         .   push(   "http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/Template/Kanasansoft/Bookmarklet.js"     );
TEMPLATE_FILE_PATHS         .   push(   "http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/Template/Kanasansoft/LinkForPage.js"     );
TEMPLATE_FILE_PATHS         .   push(   "http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/Template/Kanasansoft/LinkForFile.js"     );
TEMPLATE_FILE_PATHS         .   push(   "http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/Template/Kanasansoft/Image.js"           );
JavaScriptで書かれているテンプレートをBookmarkletで動的に読み込んでいるのだが、「誰か他の人が書いたテンプレートに直接アクセスする」ような需要をみこしてこのようになっている。本来はこのサイト上に置けばいいのだが、レンタルサーバのためにあまりアクセスを集中させたくない。また、このサイトが仮に閉じてしまったことを考えてのことなのでご了承願いたい。

更に、TARGET_TEXTAREA_IDを書き換えなくてはならない。BlogEditorを使用するblogのエントリー入力画面にあるテキストエリアのIDが必要だ。
(少なくとも昨今のblogは入力欄にID位は指定しているだろうという考え。)
例えば、あなたが「KanasanBlog」を使っていてテキストエリアのIDが「entry_body」だったとしたら下のようになる。
(Movable Typeは絶対使わないのであれば、その行は削除してもいい。)
var TARGET_TEXTAREA_ID      =   {};
TARGET_TEXTAREA_ID["Movable Type"]  =   "text";
TARGET_TEXTAREA_ID["KanasanBlog"]   =   "entry_body";

Kanasansoft BlogEditor設定方法(その2)

BlogEditorの設置
「http://xxx/utility/」に設置場所を決めたはずなので、Kanasansoftフォルダをここにアップロードしよう。
(下の階層に同名のフォルダがあるが間違えないで頂きたい。深すぎる為、間違えににくいと思うが念のため。)
Bookmarkletの登録
ここでも「http://xxx/utility/」に設置したと仮定する。「http://xxx/utility/Kanasansoft/Bookmarklet/BlogEditor/setting.htm」にアクセスしてみよう。タイトルが「Bookmarklet Kanasansoft BlogEditor」になっているページが開く。「Kanasansoft BlogEditor」というリンクが表示されているはずだ。表示されていないなら「文字のエンコード」をチェックしよう。BlogEditorは文字コードが「UTF-8」で書かれているが、一部の環境ではブラウザが正常に認識しないため、表示されないことがある。
(誤認識を起こしているのに表示される。)
(誤認識を避ける方法があるなら教えてほしい...。)
表示されているリンクを右クリック(Macでシングルボタンのマウスの場合はcontrol+クリック)してブックマーク(IEではお気に入り)に登録しよう。
blogの設定
htmlのエンコードや改行処理はBlogEditor側で行う。blog側のこれらの設定が有効になっていると重複した処理になるため、無効にする。
(各blogのヘルプを参照のこと。)
BlogEditorの起動テスト
BlogEditorは別ウィンドウで起動する。ウィルス対策ソフト等のポップアップブロックが有効になっていると動かないので無効にする。ポップアップブロックの有効無効はサイト別に指定できるはずなので、自分のblogのあるサイト(可能ならディレクトリ)のみ無効にしよう。blogの新規エントリーを開いてほしい。表示されたら、bookmark(またはお気に入り)から「Kanasansoft BlogEditor」を選択する。
別ウィンドウが表示されるはずだ。
(タブブラウザなら新規タブに表示される。)
新規ウィンドウに何も表示されないなら、また「文字コード」を疑ってみよう。方法は先程と同じ。
(起動時にライブラリ等を読み込むので表示までに数秒かかることがあるので注意。)
左側に何かの一覧が表示されたらBlogEditorの起動テストは完了となる。

Kanasansoft BlogEditorの使い方

BlogEditorの起動
エントリー画面を開いてブックマークから「Kanasansoft BlogEditor」を選択しよう。別ウィンドウが開きBlogEditorが表示される。全て英語表記となっているが比較的容易に利用可能かと思う。
(エントリーには日本語の利用が可能。)
全て日本語で表示させたいと思っているが、文字コードの誤認識によるエラーを避けたかったので全部英語にした。
(逆に全部日本語の方が認識率を上げられるのであれば、今すぐにでも取りかかる。)
次に、画面のそれぞれの項目の説明を行なう。
Template List
テンプレートの一覧である。雛形の一覧と言いかえてもいい。blogで使うテンプレートとは別物である。ここで言うテンプレートは、特に説明がない限りBlogEditorで使用するものだ。BlogEditorを使ってblogのエントリーを登録する時は、このテンプレートが最小単位になる。「Plain Text」や「Link For Page」、「Image」等と表示されてる。「Plain Text」は文章を書くためのテンプレート、「Link For Page」は別ページへのリンクを書くための、「Image」は画像を表示するためのテンプレートだ。それぞれのテンプレートの使い方は、そのうち解説するつもりでいるが、ここでは記述しない。
(使って頂ければすぐに理解して頂けると思う。)
「Template List」からテンプレートを選択して「add」ボタンをクリックするか、「Template List」のテンプレートをダブルクリックしてみよう。「Item List」の最後にテンプレートが追加されるはずだ。
Item List
アイテムの一覧、つまり項目の一覧。blogに実際に追加される項目が表示されている。アイテムを選ぶと「Edit Area」に詳細が表示される。選んだ状態で「up」ボタンと「down」ボタンで順番の変更、「delete」ボタンで削除を行なう。元になったテンプレートの名前しか表示されないため、少々わかりにくい。
(改善が必要か?)
Edit Area
まずは重要なボタンの説明。「initial」ボタンは初期化、テンプレートが持っている初期値に戻す。テンプレートからアイテムに追加した際の初期値となる。
「recollect」ボタン、直訳すると「思い出す」ボタン。次に説明する「memorize」か、後で説明する「import」を行った時の状態に戻すことができる。そして、一番大事な「memorize」ボタン。「Edit Area」で編集した内容は、この「memorize」ボタンを押さないと記憶されない。「memorize」を押す前に、「Item List」の順番を変更等をすると、書いた文章が消えてしまうため注意が必要だ。ボタンの下の部分は、選んだアイテムによって、つまりアイテムの元になったテンプレートによって違うため色々試して頂きたい。
エントリー画面とデータのやりとり
「import」ボタンと「export」ボタン、そしてその横にプルダウンメニューがある。これは、blogのエントリー画面と情報をやりとりするためのものである。プルダウンメニューから使っているblogの名前を選ぶ。
(内部的には、親ウィンドウにあるテキストエリアのIDを選択する。)
「import」ボタンで親ウィンドウから情報を取得できる。取得できるデータはBlogEditorで出力したものだけ。
(データの保存に独自形式を使っているからこればかりはどうしようもない。)
BlogEditorが意図しない形式だった場合はエラーが表示される。「export」ボタンを押すと親ウィンドウに情報を出力できる。ちなみに、上書きする時にはアラートが表示される。

Kanasansoft BlogEditorとCSS

BlogEditorから出力されるhtmlをそのまま表示させると、ハッキリ言って見た目が悪い。これは、「BlogEditorから出力するhtmlは構造のみで見た目に関してはCSSにお任せ」というコンセプトがあるためである。出力したものをみると、(一部例外があるが)ほとんどのダグがdiv(要素)で、要所要所にclass(属性)が指定してある。レイアウトを良くするにはcssを弄らないといけない。
(そもそも、BlogEditorが勝手に見た目を決めてしまうと、blogのcssとの調和が問題になる。)
自分で設置したblogなら好きに触れるし、どこかのblogサービスを使っていてもcssは編集できる場合が多い(はずだ)。僕は次のようにしている。
既存のcssの最後に次の行を追加
@import url(<$MTBlogURL$>kanasansoft-blog-editor.css);
(Movable Type使用)
kanasansoft-blog-editor.css(新しく追加したblogのテンプレート)
.Kanasansoft_BlogEditor div{
    margin:8px 0px;
}

.Kanasansoft_BlogEditor div div{
    margin:0px 0px;
}

.Kanasansoft_BlogEditor h5{
    font-size:120%;
    font-weight:bold;
}

.Kanasansoft_BlogEditor .Com_Kanasansoft_PlainText .PlainText{
    line-height:150%;
}

.Kanasansoft_BlogEditor .Com_Kanasansoft_Source{
    border:2px outset #cccccc;
    color:#000000;
    background-color:#cccccc;
}

.Kanasansoft_BlogEditor .SourceForCopy{
    font-size:80%;
    border:2px outset #cccccc;
    color:#cccccc;
    background-color:#cccccc;
    height:16px;
    width:16px;
    overflow:hidden;
    white-space:pre;
    font-family:monospace;
}

.Kanasansoft_BlogEditor .SourceForDisplay{
    margin:8px 0px;
    padding:8px;
    font-size:80%;
    border:2px inset #cccccc;
    color:#000000;
    background-color:#cccccc;
    width:450px;
    overflow:scroll;
    white-space:pre;
    font-family:monospace;
}
Kanasansoft Web Lab.(2006/12/14時点)
BlogEditorのテンプレートはあるけどcssはまだ定義できていないものもあるため、そのまま使えるわけではないが参考にはなると思う。

2006年12月17日

Kanasansoft BlogEditor(20061217170408)公開

アイテム一覧の項目数が10以上になると正常に動作しなくなる不具合を修正した。
Kanasansoft/Bookmarklet/BlogEditor/Bookmarklet.js
の入れ替えで対応可能。

Kanasansoft BlogEditorのテンプレートの作成方法

クラスの命名規則は、
    『テンプレート作成者識別文字列』+"_(アンダーバー)"+"『テンプレート名(キャメルケース)』"
であり、『テンプレート作成者識別文字列』は
    『uriを"."で分割し、それぞれの先頭を大文字に変換し"_(アンダーバー)"で連結』
となる。
特に、『テンプレート作成者識別文字列』は名前衝突を防ぐためのものであるため、推奨であって必須ではない。例えば、「kanasansoft」が作成したテンプレート「Plain Text」の場合、uriは「kanasansoft.com」であるので
    Com_Kanasansoft_PlainText
となる。また、このルールは出力するHTMLのclass要素や、入力欄のid要素にも使用している。テンプレートはJavaScriptのクラスとして記述されており、次のような構成になっている。
コンストラクタ
[インスタンスメソッド]データ取得
[インスタンスメソッド]データ設定
[インスタンスメソッド]編集領域出力
[インスタンスメソッド]編集領域からデータ取得
[インスタンスメソッド]編集領域にデータ設定
[インスタンスメソッド]HTML作成
[インスタンスメソッド]テンプレート名取得
[インスタンスメソッド]テンプレートID取得
テンプレート追加処理
では、その内部構造を、テンプレート「Plain Text」を例にその構造を見ていこう。
コンストラクタ
function Com_Kanasansoft_PlainText(){
    this._Title                 =   "";
    this._PlainText             =   "";
}
テンプレート内部で使用するインスタンス変数を初期化している。
(内部変数であるため"_"ではじまる。)
「Plain Text」では、タイトルと本文が入力可能なため、「_Title」「_PlainText」を初期化している。
データ取得
Com_Kanasansoft_PlainText.prototype.getData
=   function(){
    return {    "Title"                 :   this._Title                 ,
                "PlainText"             :   this._PlainText             };
}
テンプレート内部で使用している変数で、情報を再現するために必要なデータをオブジェクトで返している。返すデータは必ずObject型とするが、その内部構造はテンプレート作成者の考え方に依存する。ただ、後にJSONに変換を行うため、Functionオブジェクトや独自に作成したオブジェクトは用いることはできない。
データ設定
Com_Kanasansoft_PlainText.prototype.setData
=   function(obj){
    this._Title                 =   obj["Title"];
    this._PlainText             =   obj["PlainText"];
}
インスタンスメソッド「データ取得」で返した値が引数で渡される。引数のオブジェクトから、内部情報の再現を行なっている。
編集領域出力
Com_Kanasansoft_PlainText.getEditAreaHTML
=   function(){
    var html            =   "";
    html                +=  "<div>";
    html                +=      "<div>";
    html                +=          "Title";
    html                +=      "</div>";
    html                +=      "<div>";
    html                +=          "<input type=\"text\" id=\"Com_Kanasansoft_PlainText_Title\" />";
    html                +=      "</div>";
    html                +=  "</div>";
    html                +=  "<div>";
    html                +=      "<div>";
    html                +=          "Plain Text";
    html                +=      "</div>";
    html                +=      "<div>";
    html                +=          "<textarea cols=\"60\" rows=\"20\" id=\"Com_Kanasansoft_PlainText_Text\">";
    html                +=          "</textarea>";
    html                +=      "</div>";
    html                +=  "</div>";
    return html;
}
情報を入力するための入力欄HTMLを文字列で返す。入力されているデータの出力は行なわれない。つまり「Plain Text」ではインスタンス変数の「_Title」「_PlainText」の値を出力や判定に一切用いない。データの反映はインスタンスメソッド「編集領域にデータ設定」で行なう。フォーム部品のidには入力された値を取得するためにid要素を定義している。
編集領域からデータ取得
Com_Kanasansoft_PlainText.prototype.getEditAreaData
=   function(){

    var obj                     =   document.getElementById("Com_Kanasansoft_PlainText_Title");
    this._Title                 =   obj.value;

    var obj                     =   document.getElementById("Com_Kanasansoft_PlainText_Text");
    this._PlainText             =   obj.value;

}
インスタンスメソッド「編集領域出力」で出力した入力欄に、入力されているデータを取得する。
編集領域にデータ設定
Com_Kanasansoft_PlainText.prototype.setEditAreaData
=   function(){

    var obj                     =   document.getElementById("Com_Kanasansoft_PlainText_Title");
    obj.value                   =   this._Title;

    var obj                     =   document.getElementById("Com_Kanasansoft_PlainText_Text");
    obj.value                   =   this._PlainText;

}
インスタンスメソッド「編集領域出力」で出力した入力欄に、インスタンスのデータを反映する。
HTML作成
Com_Kanasansoft_PlainText.prototype.getHTML
=   function(){
    var html            =   "";
    html                +=  "<div class=\"Com_Kanasansoft_PlainText\">";
    if(this._Title!=""){
        html            +=      "<div class=\"Title\">";
        html            +=          "<h5>";
        html            +=              StringUtility.Encode.HTML(this._Title);
        html            +=          "</h5>";
        html            +=      "</div>";
    }
    html                +=      "<div class=\"PlainText\">";
    html                +=          StringUtility.Convert.NewLineCodeToTag(
                                        StringUtility.Encode.HTML(this._PlainText)
                                    );
    html                +=      "</div>";
    html                +=  "</div>";
    return html;
}
BlogEditorが出力するためのHTMLを文字列で返す。「Plain Text」ではタイトルは入力必須ではないため、入力されなかった場合はdiv要素そのものを出力しない。タイトル・本文ともにエンコードされている点に注意してほしい。本文は改行処理も行なわれている。これらの処理には「Kanasansoft/Library」配下のライブラリ群を使用している。
(ライブラリはテンプレート内部のどこからでも使用可能。)
テンプレート名取得
Com_Kanasansoft_PlainText.getName
=   function(){
    return "Plain Text";
}
テンプレートの名称を返す。主にテンプレート一覧およびアイテム一覧で使用するためのもの。一意となるようなものでなくてもよい。ただ、一見して内容のわかる名称が望ましい。
テンプレートID取得
Com_Kanasansoft_PlainText.getId
=   function(){
    return "Com_Kanasansoft_PlainText";
}
テンプレートを識別するための固有のIDを返す。「BlogEditor」はこの戻り値をもとに処理を行なうため、仮にテンプレートのバージョンがあがったとしても、名称を変更したとしても、戻り値を変更してはならない。今後、テンプレートのバージョンアップや上位互換性を意識するのであれば、戻り値に何を返すのかしっかりと考えた方がよい。
(内部で下位バージョンのデータコンバートを行なう場合も含む。)
逆に、バージョンアップにより、テンプレートに互換性が全くなくなったのであれば、戻り値を変更したほうがよい。
テンプレートを追加
addTemplate(Com_Kanasansoft_PlainText);
最後に、テンプレートの読み込みが完了したことを「BlogEditor」に通知する。引数にテンプレートを定義しているクラス(Functionオブジェクト)を指定し、グローバルな関数「addTemplate」を呼び出している。

2006年12月19日

任意のコードを実行するBookmarklet Ver1.1.1

/*
================================================================================
    Name        :   任意のコードを実行するBookmarklet Ver1.1.1
    In          :   [none]      
    Out         :   [none]      
    Note        :   "window.opener"を使用し親ウィンドウ上でスクリプトを実行させる事ができます
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2004/12/10  |   新規作成
                :   Ver1.1.0    |   2005/03/31  |   textareaに wrap="off"を追加
                :   Ver1.1.1    |   2006/12/19  |   Operaでレイアウトが崩れる不具合を修正
                :               |               |   LicenseをNew BSD licenseに変更
--------------------------------------------------------------------------------
    License     :   New BSD license
    URL         :   www.kanasansoft.com
================================================================================
*/

(
    function(){

        var s=window.open();
        with(s.document){
            open();
            write("<html><head><title>run!</title><style><!--body{margin:0px;padding:0px;}textarea{width:100%;height:45%;font-size:12px;font-family:monospace;}--></style></head><body><input type=\"button\" value=\"run\" onclick=\"eval(document.getElementById(\&quot;code\&quot;).value)\"><br><textarea id=\"code\" wrap=\"off\"></textarea><textarea id=\"out\" wrap=\"off\" readonly></textarea></body></html>");
            close()
        }

    }
)
()
自作のBookmarkletで一番の使用頻度、開発やデバッグに重宝している。実行すると別ウィンドウが開き、「run」ボタンとテキストエリア2つが表示される。上のテキストエリアがJavaScriptの入力欄、下のテキストエリアが出力欄となっている。例えば、
document.getElementById("o").value=window.opener.document.getElementsByTagName("div").length;
と入力し、「run」ボタンを押すと、親ウィンドウ内のdivタグ数が表示される。また、親ウィンドウ側に
var stringbuf="abc";
と(Globalで)宣言されていた場合、
document.getElementById("o").value=window.opener.stringbuf;
で、参照できる。更に、
window.opener.stringbuf="def";
で書き変えることさえできる。同様な機能は、FirefoxのDOM Inspectorを利用することで可能である。しかし、このBookmarkletはIE等他のブラウザでも利用できる。そして、JavaScriptで処理ができるためループ等で一括して処理することさえできる。Functionオブジェクトの書き換えも可能だ。Webアプリケーション開発者、特にテキストエディタで開発している人や、デバッグのためにalertを多用している人にはおすすめだと思う。

文字列の操作を行うjavaScript Classを更新

これまで作ってきた文字列操作系の関数で使えそうなものを、StringUtilityとしてひとつにまとめた。
(シングルクォーテーションに対する処理が抜けていたので追加。)
/*
================================================================================
    Name        :   StringUtility
    In          :   [none]      
    Out         :   [none]      
    Note        :   文字列用ユーティリティ群
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2006/01/16  |   新規作成
                :   Ver1.0.1    |   2006/12/19  |   シングルクォーテーションに対する処理を追加
--------------------------------------------------------------------------------
    License     :   New BSD license
    URL         :   www.kanasansoft.com
================================================================================
*/

/*--------------------------------------------------------------------------------
    コンストラクタ
--------------------------------------------------------------------------------*/
function StringUtility(){
}

StringUtility.Encode
=   function(){
}

StringUtility.Decode
=   function(){
}

StringUtility.Convert
=   function(){
}

/*--------------------------------------------------------------------------------
    HTMLエンコードを行なう
--------------------------------------------------------------------------------*/
StringUtility.Encode.HTML
=   function(str){
    return          str                                         .
                    replace(    /&/ig   ,   "&amp;"     )       .
                    replace(    /</ig   ,   "&lt;"      )       .
                    replace(    />/ig   ,   "&gt;"      )       .
                    replace(    /'/ig   ,   "&apos;"    )       .
                    replace(    /"/ig   ,   "&quot;"    )       .
                    replace(    / /ig   ,   "&nbsp;"    )       ;
}

/*--------------------------------------------------------------------------------
    HTMLデコードを行なう
--------------------------------------------------------------------------------*/
StringUtility.Decode.HTML
=   function(str){
    return          str                                         .
                    replace(    /&nbsp;/ig  ,   " "     )       .
                    replace(    /&quot;/ig  ,   "\""    )       .
                    replace(    /&apos;/ig  ,   "'"     )       .
                    replace(    /&gt;/ig    ,   ">"     )       .
                    replace(    /&lt;/ig    ,   "<"     )       .
                    replace(    /&amp;/ig   ,   "&"     )       ;
}

/*--------------------------------------------------------------------------------
    HTMLエンコードを行なう(Bookmarklet用)
--------------------------------------------------------------------------------*/
StringUtility.Encode.HTMLforBookmarklet
=   function(str){
    return          str                                         .
                    replace(    /&/ig   ,   "&amp;"     )       .
                    replace(    /</ig   ,   "&lt;"      )       .
                    replace(    />/ig   ,   "&gt;"      )       .
                    replace(    /'/ig   ,   "&apos;"    )       .
                    replace(    /"/ig   ,   "&quot;"    )       ;
}

/*--------------------------------------------------------------------------------
    JavaScriptエンコードを行なう
--------------------------------------------------------------------------------*/
StringUtility.Encode.JavaScript
=   function(str){
    return          str                                         .
                    replace(    /\\/ig  ,   "\\\\"      )       .
/*                  replace(    /\b/ig  ,   "\\b"       )       .*/
                    replace(    /\f/ig  ,   "\\f"       )       .
                    replace(    /\n/ig  ,   "\\n"       )       .
                    replace(    /\r/ig  ,   "\\r"       )       .
                    replace(    /\t/ig  ,   "\\t"       )       .
                    replace(    /'/ig   ,   "\\'"       )       .
                    replace(    /"/ig   ,   "\\\""      )       ;
}

/*--------------------------------------------------------------------------------
    tabをspaceに変換する
--------------------------------------------------------------------------------*/
StringUtility.Convert.TabToSpace
=   function(str,tabNumber){

    var linesRN                         =   str.split("\r\n");
    for(var lineCntRN=0;lineCntRN<linesRN.length;lineCntRN++){
        var linesN                      =   linesRN[lineCntRN].split("\n");
        for(var lineCntN=0;lineCntN<linesN.length;lineCntN++){
            var linesR                  =   linesN[lineCntN].split("\r");
            for(var lineCntR=0;lineCntR<linesR.length;lineCntR++){
                var wordsT              =   linesR[lineCntR].split("\t");
                for(var wordsCntT=0;wordsCntT<wordsT.length-1;wordsCntT++){
                    wordsT[wordsCntT]   +=  StringUtility.getRepeatString(
                                                " "                                                                     ,
                                                tabNumber-(StringUtility.getLengthByBite(wordsT[wordsCntT])%tabNumber)  )
                }
                linesR[lineCntR]        =   wordsT.join("");
            }
            linesN[lineCntN]            =   linesR.join("\r");
        }
        linesRN[lineCntRN]              =   linesN.join("\n");
    }
    var rtn                             =   linesRN.join("\r\n");

    return rtn;

}

/*--------------------------------------------------------------------------------
    改行コードを"<br />"に変換する
--------------------------------------------------------------------------------*/
StringUtility.Convert.NewLineCodeToTag
=   function(str){
    return          str                                             .
                    replace(    /\r\n/ig    ,   "<br />"    )       .
                    replace(    /\r/ig      ,   "<br />"    )       .
                    replace(    /\n/ig      ,   "<br />"    )       ;
}

/*--------------------------------------------------------------------------------
    文字バイト長取得
--------------------------------------------------------------------------------*/
StringUtility.getLengthByBite
=   function(str){
    var count   =   0;
    for(var i=0;i<str.length;i++){
        var code    =   str.charCodeAt(i);
        while(code!=0){
            count++;
            code>>>=8;
        }
    }
    return count;
}

/*--------------------------------------------------------------------------------
    繰り返し文字取得
--------------------------------------------------------------------------------*/
StringUtility.getRepeatString
=   function(str,num){
    var rtn =   "";
    for(var i=0;i<num;i++){
        rtn +=  str;
    }
    return rtn;
}
追記(2007/07/07)
下記リンク先でバージョンアップしたものを公開中。

Kanasansoft BlogEditor(20061219235244)公開

ライブラリの修正に伴う再公開。
Kanasansoft/Library/StringUtility.js

2006年12月25日

MD5の脆弱性

先日、JavaScriptでMD5の実装をおこなった。プログラマなら当然、既知のことだとは思うが念のため。MD5はここ数年の間に弱点が指摘されている。実際に、プログラムにはより安全性・衝突耐性の高いSHAを用いるべきである。SHAに弱点がないわけではないらしいが、MD5よりは信頼性が高い。適当なRFCの邦訳が見つかり次第、MessageDigestクラスにもSHA系の実装を行ないたいと考えている。

2006年12月28日

検索デスクのテキストボックスでインクリメンタルサーチを行なうBookmarklet Ver1.0.0

横断検索サイトである『検索デスク』をリンク集として使用している場合、目的の検索サイトへのリンクが何処に表示されているのか忘れてしまうことがある。
(私は忘れる。)
このBookmarkletは検索デスクのテキストボックスに検索デスク内検索機能を追加する。まず、検索デスクのテキストボックスが表示されているページを開き、このBookmarkletを実行する。テキストボックスに文字を入力すると、入力文字を含む検索サイトへのリンクが強調表示されるようになる。検索デスクのHTML構造は非常にしっかりと構成されているために、今回のような処理が非常に行ないやすい。コーディングに使用可能な文字数が限られているBookmarkletの場合、非常にありがたい。強調対象が検索サイトのみで、サイト内リンク等を簡単に除くことが可能だったのはこのおかげである。
(ただ、この判定にclass属性の値を用いている。頂けなくも致し方ない部分であるため、あまりお勧めできない。)
HTMLをしっかりと書けば(つまりDOM構造を熟考していれば)、JavaScriptもかなり簡潔になるということである。
/*
================================================================================
    Name        :   検索デスクのテキストボックスでインクリメンタルサーチを行なうBookmarklet Ver1.0.0
    In          :   [none]      
    Out         :   [none]      
    Note        :   検索デスクの検索用テキストボックスでインクリメンタルサーチを行ないリンク先を強調表示します。
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2006/12/28  |   新規作成
--------------------------------------------------------------------------------
    License     :   New BSD license
    URL         :   www.kanasansoft.com
================================================================================
*/

(
    function(){
        if((/^http:\/\/www.searchdesk.com\//).test(location.href)){
            try{
                document.forms[0].qkey.onkeyup  =
                function(){
                    var
                         o=document.getElementsByTagName("div")
                        ,t=this.value
                        ,r=new RegExp(t,"i")
                        ,a
                        ,i
                        ,j
                    ;
                    for(i=0;i<o.length;i++){
                        if(o[i].className=="part-r"){
                            a=o[i].getElementsByTagName("a");
                            for(j=0;j<a.length;j++){
                                a[j].style.backgroundColor=t!=""&&r.test(a[j].innerText||a[j].textContent)?"#ff0000":""
                            }
                        }
                    }
                }
            }catch(e){
                alert("not find textbox")
            }
        }else{
            alert("don¥'t match location¥n[SearchDesk]")
        }
    }
)
()

2006年12月29日

検索デスクの一覧を開閉式にするBookmarklet Ver1.0.0

横断検索サイト『検索デスク』のメニューに開閉機能を追加する。実用性があるかどうかは不明である。今回ちょっと困ったのは、検索デスクのHTMLが以下のようになっていた点である。
『検索デスク』のHTMLの構造
<h2>[サブタイトル1]</h2>
<div class="part-r">
    <a>[検索サイトA]</a><input type="button"><br />
    <a>[検索サイトB]</a><input type="button"><br />
</div>
<h2>[サブタイトル2]</h2>
<div class="part-r">
    <a>[検索サイトC]</a><input type="button"><br />
    <a>[検索サイトD]</a><input type="button"><br />
</div>
このため、h2要素から、div要素(class属性にpart-rを持つdiv)を取得するのに「nextSibling」を使わざるをえなかった。今回のような用途では、nextSiblingは本来使うべきではないと思う。次のようになっていれば、
<div>
    <h2>[サブタイトル1]</h2>
    <div class="part-r">
        <a>[検索サイトA]</a><input type="button"><br />
        <a>[検索サイトB]</a><input type="button"><br />
    </div>
</div>
<div>
    <h2>[サブタイトル2]</h2>
    <div class="part-r">
        <a>[検索サイトC]</a><input type="button"><br />
        <a>[検索サイトD]</a><input type="button"><br />
    </div>
</div>
データの意味上でも、レイアウト上でも問題なかったのではと思う。
/*
================================================================================
    Name        :   検索デスクの一覧を開閉式にするBookmarklet Ver1.0.0
    In          :   [none]      
    Out         :   [none]      
    Note        :   検索デスクの検索サイト一覧をカテゴリ毎に開閉可能にします。
--------------------------------------------------------------------------------
    Version     :   Ver1.0.0    |   2006/12/29  |   新規作成
--------------------------------------------------------------------------------
    License     :   New BSD license
    URL         :   www.kanasansoft.com
================================================================================
*/

(
    function(){
        if((/^http:\/\/www.searchdesk.com\//).test(location.href)){
            var
                 o=document.getElementsByTagName("h2")
                ,p
                ,f=function(a){
                    var n=a;
                    return function(){
                        n.style.display=n.style.display=="none"?"":"none";
                    }
                }
            ;
            for(var i=0;i<o.length;i++){
                p=o[i].nextSibling;
                while(p.nodeType!=1){
                    p=p.nextSibling
                }
                o[i].onclick=f(p);
                o[i].style.padding="4px";
                p.style.display="none";
            }
        }else{
            alert("don\'t match location\n[SearchDesk]")
        }
    }
)
()
Google

タグ クラウド