script.aculuo.us#Sortable
ダイナミックに項目を追加削除するとうまくいかないなー
続きを読む%uNNNN なエスケープ
一時期はてブのタイトルが「%uNNNN」の羅列になってるのがあったりしたんですけど,あれはあくまで JavaScript(1.3以降,and ECMA-262あたり?)の escape() の仕様であって JavaScript を使わなければ縁がないのでしょうか?
何を心配しているのかというと,普通の x-www-form-urlencoded / form-data なフォームで %uNNNN 形式のクエリを投げるようなブラウザ実装とかないよね?ってことなんですけど。
JavaScript の圧縮
2007/11/14 追記:より包括的な「JavaScript ファイルの圧縮・再訪 - daily dayflower」も書きました。
亜細亜ノ蛾さんの報告にもある通り,gzip 圧縮した JavaScript ファイルをおいとけばブラウザがきちんと読み込んでくれる。odz さんのところの議論によると Safari でも Content-Type を適切に定義すればオッケーぽい。
…………あたりのことを知らなかったゆえ JavaScript 圧縮機について調べてました。
- Huffman JavaScript Compression
- 方式: 文字単位のハフマン符号化(推定)
- 符号表の格納とバイナリデータの文字列化(4/3倍)があるため,たいていの JavaScript が元サイズより大きくなってしまう(笑)
- ということで非実用/実験向き?
- Extended ACSII Javascript Packer
- 方式: 静的辞書+ISO-8859-1 特殊文字符号化
- Crunching (コメントや余白を省くこと)はしてくれない
- 辞書生成等動作が目に見えるので教育向き
ほとんどが静的辞書法なので,圧縮率はいかに最適な辞書を生成するかにかかっています。簡易トークン化によって軽さをかせぐ packer とがんばって探索することで圧縮率をかせぐ memtronic など,性格の違いがあります。
「ISO-8859-1 特殊文字符号化(勝手に名付けた)」というのは,端的に言うと西欧圏でほとんど使われない chr(128) 以降(厳密には違うのでつっこまないで下さい)を,辞書へのポインティングに使ったものです。ですから単純な展開ルーチンだとマルチバイトな環境でうまく展開できない可能性があります。memtronic の場合,一応配慮したコーディングをしてあるそうです。実際 UTF-8 なスクリプトではうまく動きました(辞書ポインティングの部分が結局複数バイトになってしまうのであまり圧縮のうまみがありませんが)。
なお,packer のたぐいは JavaScript 難読化の用途には使えません。デコーダの「eval」あるいは「document.write」の部分を alert なり,console.log(FireBug を使っている場合)に変えるとデコード結果が簡単に取得できてしまいます。たいてい 1 linerですが JavaScript tidy のたぐいで整形してやればわかりやすくなります。
packer でお勉強
さて静的辞書法について目で見てみるために,一番メジャーと思われる packer で遊んでみましょう。packer のサイトに飛び,「Fast Decode」というチェックボックスと「Special Characters」というチェックボックスをはずします。また,Encoding は Normal とします。
ここで,下の「Paste」欄に
hello hello hello world
と入力して「Pack」ボタンを押します。
結果が「Copy」欄に
eval(function(p,a,c,k,e,d){while(c--){ if(k[c]){p=p.replace(new RegExp('\\b'+c+'\\b','g'),k[c])} }return p} ('0 0 0 1',2,2,'hello|world'.split('|')))
のように出力されます(適宜改行を挿入しています)。
この「hello|world」が静的な辞書部分,「0 0 0 1」が圧縮データです。一目瞭然。蛇足ながらデコーダをもう少しわかりやすくインデントしてみます。
function (p, a, c, k) { // not used: 'e', 'd' while (c --) { if (k[c]) p = p.replace(new RegExp("\\b" + c + "\\b", "g"), k[c]); } return p; } ("0 0 0 1", 2, 2, "hello|world".split("|"));
単純な仕組みであることがおわかりいただけると思います。
おっと,では「0」や「1」というトークンを表現したい場合はどうなるでしょうか。
hello 0 hello 1 hello 2 world
を変換すると
eval(function(p,a,c,k,e,d){while(c--){ if(k[c]){p=p.replace(new RegExp('\\b'+c+'\\b','g'),k[c])} }return p} ('3 0 3 1 3 2 4',5,5,'|||hello|world'.split('|')))
のようになりました。使われている数字リテラルに該当する辞書語の部分が空になっています。うまく考えたものです。
packer がよくできているのはこれだけではありません。辞書のサイズが
- 10個以下
- 36個以下
- 62個以下
- 63個以上
の場合で,デコードルーチンが変わるのです。実際に確かめてみてください。
checkpad っぽい In-Place-Edit を作る
クライアントサイドなので答えを見ようとおもえば見れるんですが,挙動からコードを推測する遊びとして。もちろん script.aculo.us の Ajac.InPlaceEditor ベースです。
var CheckPadIPE = Class.create(); Object.extend(CheckPadIPE.prototype, Ajax.InPlaceEditor.prototype); Object.extend(CheckPadIPE.prototype, { initialize: function(element, url, options) { var mydefault = Object.extend({ okText: '更新', cancelText: '[キャンセル]', savingText: '', clickToEditText: '' }, options || {}); Ajax.InPlaceEditor.prototype.initialize.call(this, element, url, mydefault); if (this.options.externalControl) Event.stopObserving(this.element, 'click', this.onclickListener); }, onclickCancel: function() { this.leaveEditMode(); return false; }, enterHover: function() {}, leaveHover: function() {} });
いまいちスーパークラスをどう扱えばいいかわかりませんでした。
結局 Event.stopObserving を使うのなら enterHover, leaveHover を再定義せずにそっちも stopObserving しろという感じですね。ほかいろいろ稚拙ですが許してください。