« XMLHttpRequestでファイルをDataSchemeで取得する実験 | メイン | XMLHttpRequestでファイルをDataSchemeで取得する実験 その3 »

XMLHttpRequestでファイルをDataSchemeで取得する実験 その2

前回のエントリーの後日談をライブ間たっぷりに書いていくぜ! 誤字脱字の修正を除いて、書きっぱなしの推敲なしで最後まで。
前回からこれまでの流れ
なんだか次のURLを参照すれば良いのではとWassrで聞いた。
function load_binary_resource(url) {
  var req = new XMLHttpRequest();
  req.open('GET', url, false);
  //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
  req.overrideMimeType('text/plain; charset=x-user-defined');
  req.send(null);
  if (req.status != 200) return '';
  return req.responseText;
}
前回のエントリーの最後の方で書いた通り、Content-TypeやAccept-Charsetの指定で解決するのではと考えていたので、次のようにすればいいと思い込んでしまった。
GM_xmlhttpRequest(
    {
        "method":"GET",
        "url":url,
        "onload":callbackForSuccess,
        "onerror":callbackForError,
        "headers":{
            "Content-Type":"text/plain",
            "Accept-Charset":"x-user-defined"
            "If-Unmodified-Since":lastModified,
            "Range":"bytes="+from+"-"+To
        }
    }
);
結果はやっぱり文字化けした。でも、そのあとでblogへのコメントとはてなブックマークのコメントに同じような指摘があった。「あれ?うまくいかなかったのになぁ...」と思いつつもう一回MDCを確認した。あああ、もしかして「overrideMimeType」ってメソッドが重要ってこと?
じゃあ調べてみよう
MDCのサンプルを見ると、「overrideMimeType」はXMLHttpRequestのメソッドとして準備されている。前回はGreasemonkeyで実装しようとしたので、GM_xmlhttpRequestにI/Fが準備されていないか調べてみる。
あった。そのまんま。こんな感じに修正してみた。
GM_xmlhttpRequest(
    {
        "method":"GET",
        "url":data.url,
        "onload":callback,
        "onerror":callback,
        "overrideMimeType":"text/plain; charset=x-user-defined",
        "headers":{
            "If-Unmodified-Since":data.lastModified,
            "Range":"bytes="+range+"-"+range
        }
    }
);
GM_xmlhttpRequest(
    {
        "method":"HEAD",
        "url":data.url,
        "onload":loadHeaderMethod,
        "onerror":loadHeaderMethod,
        "overrideMimeType":"text/plain; charset=x-user-defined"
    }
);
動くかな。どきどき。
ダメだった。何でだろう。調査中。GM_xmlhttpRequestの書き方がまずいのかな。
ここ見ても間違えてなさそうなんだけどなぁ。
22:07
しまったなぁ。ハードル上げ過ぎた。戦略練り直す。ここから更新時間を入れることにする。
22:54
う〜〜〜ん。無理。中断。再開は今日かもしれないし明日かもしれない。ちょっと頭冷やす。
23:43
再開。単純なcodeから原因を突き止めて行くことにする。
この画像で確認
sample
00:01
こうゆうグリモンを書いて確認。
// ==UserScript==
// @name        getFileByDataScheme
// @namespace   http://www.kanasansoft.com/
// @include     *
// ==/UserScript==

(
    function(){

        var base64=function(original){
            var table=(
                function(s){
                    var len=s.length;
                    var hash={};
                    for(var i=0;i<len;i++){
                        var bit=i.toString(2);
                        var bit="000000".slice(0,-bit.length%6)+bit;
                        hash[bit]=s.charAt(i);
                    }
                    return hash;
                }
            )("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
            var len=original.length;
            var bits8=[];
            if(original instanceof Array){
                for(var i=0;i<len;i++){
                    var bit=original[i].charCodeAt(0).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }else{
                for(var i=0;i<len;i++){
                    var bit=original.charCodeAt(i).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }
            var len=bits8.length;
            var bits6=[];
            for(var i=0;i<len;i+=3){
                var bit=bits8.slice(i,i+3).join("");
                bit+="000000".slice(0,-bit.length%6);
                Array.prototype.push.apply(bits6,bit.match(/.{6}/g));
            }
            var len=bits6.length;
            var base64=[];
            for(var i=0;i<len;i++){
                base64.push(table[bits6[i]]);
            }
            Array.prototype.push.apply(base64,["=","=","=","="].slice(0,-base64.length%4));
            return base64.join("");
        }

        var parseHTTPHeader=function(responseHeader){
            var headers=responseHeader.split("\n");
            var len=headers.length;
            var parsing=[];
            for(var i=0;i<len;i++){
                if(/^$/.test(headers[i])){
                }else if(/^[\x09\x20]/.test(headers[i])){
                    if(parsing.length==0){
                        throw "SyntaxError:HTTPHeader (first line) "+headers[i];
                    }
                    parsing[parsing.length-1]+="\n"+headers[i];
                }else{
                    parsing.push(headers[i]);
                }
            }
            var len=parsing.length;
            var parsed={};
            for(var i=0;i<len;i++){
                var pair=parsing[i].split(": ",2);
                if(pair.length!=2){
                    throw "SyntaxError:HTTPHeader (format) "+parsing[i];
                }
                if(pair[0] in parsed){
                    throw "SyntaxError:HTTPHeader (repetition) "+pair[0];
                }
                parsed[pair[0]]=pair[1];
            }
            return parsed;
        }

        var loadDataMethod=function(res){
            var headers=parseHTTPHeader(res.responseHeaders);
            if(res.status!=200){
                throw "RequestError"+
                    ":status="+res.status+
                    ":statusText="+res.statusText+
                    ":responseHeaders="+res.responseHeaders;
            }
            var contentType=("Content-Type" in headers)?
                headers["Content-Type"]:
                "application/octet−stream";
            var dataScheme="data:"+contentType+
                ";base64,"+base64(res.responseText);
            data.callback(dataScheme);
        }

        var getData=function(url){
            data.url=url;
            GM_xmlhttpRequest(
                {
                    "method":"GET",
                    "url":data.url,
                    "onload":loadDataMethod,
                    "onerror":loadDataMethod,
                    "overrideMimeType":"text/plain; charset=x-user-defined"
                }
            );
        }

        var data={};

        var getFileByDataScheme=function(url,callback){
            data.callback=callback;
            getData(url);
        }

        if(window.self==window.top){
            var url=prompt("image url","");
            if(url==null||url==""){
                return;
            }
            var handler=function(url,callback){
                return function(){
                    getFileByDataScheme(url,callback);
                }
            }
            var callback=function(dataScheme){
                var elem=document.createElement("img");
                elem.src=dataScheme;
                document.body.appendChild(elem);
            }
            window.addEventListener(
                "load",
                handler(url,callback),
                false
            );
        }

    }
)();
結果
//グリモンで出力されたDataScheme
data:image/png;base64,94lQTkcNChoKAAAADUlIRFIAAAAQAAAAEAIDAAAAYvedF/fyAAAAAXNSR0IA9673zhz36QAAAAxQTFRFAC1n98z3zPf/AAAA95n3mff/99VVX/f6AAAAAXRSTlMAQPfm99hmAAAAAWJLR0QA94gFHUgAAAAJcEhZcwAACxMAAAsTAQD3mvecGAAAAAd0SU1FB/fYAwwPCTX3s/fi98n33QAAAENJREFUCPfXY2BgamBgYPfgTAAS96oP94D3xHQg95dpHveQ9+B+CRT3434aAffkXg0DSkwNBQr3qveGAgU5QQQT94j3yzQNKPfB9/0CRPeA9/QC940CAFYZDvfC99z3r3b31QAAAABJRU5E965CYPeC

//[http://software.hixie.ch/utilities/cgi/data/data]で変換
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAAAXNSR0IArs4c6QAAAAxQTFRFAC1nzMz%2FAAAAmZn%2F1VVf%2BgAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH2AMMDwk1s%2BLJ3QAAAENJREFUCNdjYGBqYGBg4EwAEqoPgMR0IJdpHpDgfgkU434aAeReDQNKTA0FCqqGAgU5QQQTiMs0DSjB%2FQJEgPQCjQIAVhkOwtyvdtUAAAAASUVORK5CYII%3D
似ても似つかない悲惨な結果に...。昨日のグリモンでは似たような結果だったのになんでだろう。画像を変えたからかな。また、変えてみるかなぁ。
00:22
上の画像で「console.log(res.responseText);」してみたら「�」が大量に出力されてた。やっぱりRangeで分割する必要があるのかな。
04/14 04:54
寝オチしてた。続きは明日。
23:23
Twitterで色々教えてもらった。取得した文字列の上位bitを削らなければいけない、Base64変換の変わりにbtoa関数を使えばよい、GlitchMonkeyというグリモンが参照になるかも、とのこと。JavaScirptは文字列をUTF-16で扱うんだった。「text/plain; charset=x-user-defined」で取得した文字列は1byteだけども、client側で認識される時にはUTF-16になるなって2bytesとなってしまうってことだよね。0x00-0xFFの文字列が0x0000-0x00FFとなってしまうので上位bitを削除しないとbinaryとしてはおかしなことになると...。あと、btoa関数、動作がよくわからなかったのでBase64関数を新規に作ったんだけど...。btoaでいいのか...。native関数の方が速いのはもちろんわかっているんだけど、このままでやってみる。もし、正常に動作すれば古いブラウザでも動くはずなので二兎を追える。最後のGlitchMonkeyは画像を操作するuser scriptみたい。必要に応じて追っかけてみる。
04/15 00:01
認識間違ってた。GlitchMonkeyのsourceが無茶苦茶重要。
特にこの部分。
function base64encode(data) {
  return btoa(data.replace(/[\u0100-\uffff]/g, function(c) {
    return String.fromCharCode(c.charCodeAt(0) & 0xff);
  }));
}
短いのにやりたいことに本質がぎっしり詰まってる。
00:12
次のように修正。
変更前
var dataScheme="data:"+contentType+
    ";base64,"+base64(res.responseText);
変更後
var resText=res.responseText.replace(
    /[\u0100-\uffff]/g,
    function(c){
        return String.fromCharCode(c.charCodeAt(0)&0xff);
    }
)
var dataScheme="data:"+contentType+
    ";base64,"+base64(resText);
さて動くかな...。
00:16
うをぉおおおおお〜〜〜〜〜!!! 動いた!!! すげぇ!!! とりあえず動いたグリモンをはる。

// ==UserScript==
// @name        getFileByDataScheme
// @namespace   http://www.kanasansoft.com/
// @include     *
// ==/UserScript==

(
    function(){

        var base64=function(original){
            var table=(
                function(s){
                    var len=s.length;
                    var hash={};
                    for(var i=0;i<len;i++){
                        var bit=i.toString(2);
                        var bit="000000".slice(0,-bit.length%6)+bit;
                        hash[bit]=s.charAt(i);
                    }
                    return hash;
                }
            )("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
            var len=original.length;
            var bits8=[];
            if(original instanceof Array){
                for(var i=0;i<len;i++){
                    var bit=original[i].charCodeAt(0).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }else{
                for(var i=0;i<len;i++){
                    var bit=original.charCodeAt(i).toString(2);
                    bit="00000000".slice(0,-bit.length%8)+bit;
                    Array.prototype.push.apply(bits8,bit.match(/.{8}/g));
                }
            }
            var len=bits8.length;
            var bits6=[];
            for(var i=0;i<len;i+=3){
                var bit=bits8.slice(i,i+3).join("");
                bit+="000000".slice(0,-bit.length%6);
                Array.prototype.push.apply(bits6,bit.match(/.{6}/g));
            }
            var len=bits6.length;
            var base64=[];
            for(var i=0;i<len;i++){
                base64.push(table[bits6[i]]);
            }
            Array.prototype.push.apply(base64,["=","=","=","="].slice(0,-base64.length%4));
            return base64.join("");
        }

        var parseHTTPHeader=function(responseHeader){
            var headers=responseHeader.split("\n");
            var len=headers.length;
            var parsing=[];
            for(var i=0;i<len;i++){
                if(/^$/.test(headers[i])){
                }else if(/^[\x09\x20]/.test(headers[i])){
                    if(parsing.length==0){
                        throw "SyntaxError:HTTPHeader (first line) "+headers[i];
                    }
                    parsing[parsing.length-1]+="\n"+headers[i];
                }else{
                    parsing.push(headers[i]);
                }
            }
            var len=parsing.length;
            var parsed={};
            for(var i=0;i<len;i++){
                var pair=parsing[i].split(": ",2);
                if(pair.length!=2){
                    throw "SyntaxError:HTTPHeader (format) "+parsing[i];
                }
                if(pair[0] in parsed){
                    throw "SyntaxError:HTTPHeader (repetition) "+pair[0];
                }
                parsed[pair[0]]=pair[1];
            }
            return parsed;
        }

        var loadDataMethod=function(res){
            var headers=parseHTTPHeader(res.responseHeaders);
            if(res.status!=200){
                throw "RequestError"+
                    ":status="+res.status+
                    ":statusText="+res.statusText+
                    ":responseHeaders="+res.responseHeaders;
            }
            var contentType=("Content-Type" in headers)?
                headers["Content-Type"]:
                "application/octet−stream";
            var resText=res.responseText.replace(
                    /[\u0100-\uffff]/g,
                function(c){
                    return String.fromCharCode(c.charCodeAt(0)&0xff);
                }
            )
            var dataScheme="data:"+contentType+
                ";base64,"+base64(resText);
            data.callback(dataScheme);
        }

        var getData=function(url){
            data.url=url;
            GM_xmlhttpRequest(
                {
                    "method":"GET",
                    "url":data.url,
                    "onload":loadDataMethod,
                    "onerror":loadDataMethod,
                    "overrideMimeType":"text/plain; charset=x-user-defined"
                }
            );
        }

        var data={};

        var getFileByDataScheme=function(url,callback){
            data.callback=callback;
            getData(url);
        }

        if(window.self==window.top){
            var url=prompt("image url","");
            if(url==null||url==""){
                return;
            }
            var handler=function(url,callback){
                return function(){
                    getFileByDataScheme(url,callback);
                }
            }
            var callback=function(dataScheme){
                var elem=document.createElement("img");
                elem.src=dataScheme;
                document.body.appendChild(elem);
            }
            window.addEventListener(
                "load",
                handler(url,callback),
                false
            );
        }

    }
)();
続きます。

トラックバック

このエントリーのトラックバックURL:
http://www.kanasansoft.com/cgi/mt/mt-tb.cgi/216

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

Google