2012年3月9日金曜日

[JavaScript][PHP] クエリストリングからオブジェクトを作る

クエリストリングやURLパラメータ、GETパラメータと様々な呼び方があるが、
要するにURLの?以降にあるtest_key=test_valueの形式の文字列のことである。

PHPはわざわざ自前でパースしなくても、$_GETの中に配列として展開された形式で入ってるし、
parse_str関数を使えば好きなタイミングでパースすることが出るが、
JavaScriptにはそのような仕組みはなく、自力でパースしないといけない。

クエリストリングには配列として変換する仕組みがある。
test_key[]=test_value1&test_key[]=test_value2&test_key[test]=test_value3
というクエリストリングだと

array(1) {
  ["test_key"]=>
  array(3) {
    [0]=>
    string(11) "test_value1"
    [1]=>
    string(11) "test_value2"
    ["test"]=>
    string(11) "test_value3"
  }
}

という配列が生成される。

何サイトかJavaScriptでクエリストリングをオブジェクトに変える処理を見てみたが、
ほとんどのサイトでこの配列形式をサポートしてなかった。

なのでちょっと作ってみた。

//link・・・http://mio-koduki.blogspot.com/2012/03/javascriptphp.html
//第一引数・・・パースしたいクエリストリング。省略した場合は現在のURLのクエリストリング
//返り値・・・クエリストリングからパースされたオブジェクト
function parse_query(query)
{
    if(query==null)
    {
        query=(location.search+'').replace(/^[?&]/,'');
    }
    var separator1='=';
    var separator2='&';
    var data={};
    query=(query+'').replace(/^( |%20)*/g,'').split(separator2);
    for(var i=0;i<query.length;i++)
    {
        var tmp=query[i].split(separator1);
        if(tmp.length<2)
        {
            tmp=[tmp,''];
        }
        var key=url_decode(tmp.shift());
        if(key.length<=0)
        {
            continue;
        }
        data=merge(data,make_array(key,url_decode(tmp.join(separator1))));
    }
    return data;

    function url_decode(string)
    {
        return decodeURIComponent((string+'').replace(/\+/g,'%20'));
    }

    function make_array(key,value)
    {
        var tmp_key=[];
        var bracket_key='';
        var bracket=false;
        key=key.replace(/^ */g,'');
        if(key.length>0&&key.charAt(0)=='[')
        {
            key='';
        }
        for(var i=0;i<key.length;i++)
        {
            var char=key.charAt(i);
            if(!bracket&&char=='[')
            {
                if(bracket_key!='')
                {
                    tmp_key.push(bracket_key);
                    bracket_key='';
                }
                bracket=true;
                bracket_key+=char;
            }
            else if(char==']')
            {
                if(bracket)
                {
                    if(tmp_key==1)
                    {
                        tmp_key.push(bracket_key);
                    }
                    else
                    {
                        tmp_key.push(bracket_key.substr(1));
                    }
                    bracket_key='';
                    bracket=false;
                    if(key.charAt(i+1)!='[')
                    {
                        break;
                    }
                }
                else
                {
                    bracket_key+=char;
                }
            }
            else if(char=='\0')
            {
                break;
            }
            else
            {
                bracket_key+=char;
            }
        }

        if(bracket)
        {
            if(tmp_key.length==1)
            {
                tmp_key[tmp_key.length-1]+=bracket_key;
            }
        }
        else if(bracket_key!='')
        {
            tmp_key.push(bracket_key);
        }
        var count=0;
        if(tmp_key.length>0)
        {
            for(var i=0;i<tmp_key[0].length;i++)
            {
                var char=tmp_key[0].charAt(i);
                if(char==' '||char=='.'||char=='[')
                {
                    tmp_key[0]=tmp_key[0].substr(0,i)+'_'+tmp_key[0].substr(i+1);
                }
                //下の1文を変えれば$_GET準拠、現在はparse_str準拠
                //if(char=='['&&++count>0)
                if(char=='['&&count++>0)
                {
                    break;
                }
            }
        }
        for(var i=tmp_key.length-1;i>=0;i--)
        {
            var tmp_data={};
            tmp_data[tmp_key[i]]=value;
            value=tmp_data;
        }
        return value;
    }

    function merge(base,extend)
    {
        if(!is_object(base)||!is_object(extend))
        {
            return {};
        }
        extend=numbering(base,extend);
        for(var i in extend)
        {
            if(is_object(base[i])&&is_object(extend[i]))
            {
                base[i]=merge(base[i],extend[i]);
            }
            else
            {
                base[i]=extend[i];
            }
        }
        return base;
    }

    function numbering(base,extend)
    {
        var tmp={};
        for(var i in extend)
        {
            var index=(i.match(/^\s?$/)?max_index(base):i);
            if(is_object(extend[i]))
            {
                tmp[index]=numbering(is_object(base)?base[i]:null,extend[i]);
            }
            else
            {
                tmp[index]=extend[i];
            }
        }
        return tmp;
    }

    function max_index(data)
    {
        var max=0;
        if(!is_object(data))
        {
            return max;
        }
        for(var i in data)
        {
            if((i+'').match(/^(0|[1-9][0-9]*)$/)&&i>=max)
            {
                max=i-0+1;
            }
        }
        return max;
    }

    function is_object(data)
    {
        return data!=null&&typeof data=='object'&&Object.prototype.toString.call(data)!='[object Array]';
    }
}

とこんな感じ。
配列形式をサポートすると非常に面倒くさくなってくる。
第一引数にクエリストリングを渡す形式だが、省略すると現在のURLのクエリストリングをパースする。

余談だが、PHPのマニュアルには$_GETや$_POSTと同じ仕組でparse_strを動かしてあると書いているが、「a[b[c=d」というクエリの時に結果が違ってる。
地味に気になるw
この関数もどっちに準拠すべきか迷ったので、一応どっちにも切り替えられるようにしている。

この関数の逆の挙動をする関数も作ってみた。

0 件のコメント:

コメントを投稿