xml2json.cgi - ドメインを超えてXMLを読みこむ
Ajax の弱点は別ドメインのページを取得できないこと。そんな制限を取っ払って、別ドメインの XML を取得できる CGI を作ってみました。
使い方
XMLファイルの例:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<items>
<item>
<jcity>千代田区</jcity>
<jlocal>千代田</jlocal>
<jpref>東京都</jpref>
<pref_cd>13</pref_cd>
<zip_cd>1000001</zip_cd>
</item>
</items>
XMLファイルへのアクセス方法1(同期)
XML の URL は http://www.example.com/sample.xml だと仮定します。XML を読み込むには次のようなタグを HTML に記述します。
<script charset="utf-8" type="text/javascript" src="xml2json.cgi?url=http%3A%2F%2Fwww.example.com%2Fsample.xml"></script>
xml2json.cgi は自分のサーバーに設置します。
上記の JavaScript は次のように展開されてインポートされます。
if (typeof(xml) == 'undefined') xml = {};
xml.data = {
items: {
item: {
jcity: "千代田区",
jlocal: "千代田",
jpref: "東京都",
pref_cd: "13",
zip_cd: "1000001"
}
}
}
if (typeof(xml.onload) == 'function') xml.onload(xml.data);
たとえば、HTML からは次のようにして XML の情報を利用します。
<script charset="utf-8" type="text/javascript" src="xml2json.cgi?url=http%3A%2F%2Fwww.example.com%2Fsample.xml"></script>
<script>
alert(xml.data["items"]["item"]["jcity"];
</script>
XMLファイルへのアクセス方法2(非同期)
script タグでインポートしてしまうと、スクリプトのダウンロードが完了するまで、script タグ以降の HTML の表示がとまってしまいます。
そこで、非同期に xml2json を呼び出す方法を示します。ここでは、window.onload
イベントで読み込みを行っています。
window.onload = function(){
var s = document.getElementsByTagName("head")[0].appendChild(document.createElement("script"));
s.type = "text/javascript";
s.charset = "utf-8";
s.src = "xml2json.cgi?url=http%3A%2F%2Fwww.example.com%2Fsample.xml";
}
var xml = {};
xml.onload = function(data){
// 読み込み後の処理
}
XML2JSON のソースコード
分かりやすくするために、複雑なエラー処理がない単純なソースコードを示します。サーバー側で CGI として動作します。
LWP::UserAgent
でダウンロードして XML::Simple
で XML 解析して、Data::Dumper
でダンプしています。Perl の Dump と JavaScript の JSON 形式が酷似しているため、処理はこれだけです(ダンプデータの =>
を :
に置換するだけ・・・)。
#!/usr/local/bin/perl
use CGI;
use LWP::UserAgent;
use XML::Simple;
use Data::Dumper;
my $q = new CGI;
# param
my $var = $q->param('var');
$var = "xml" if !defined $var or $var!~/^[a-zA-Z_]+$/;
my $url = $q->param('url');
die if !defined $url or $url!~m!^http://!;
# request
my $ua = LWP::UserAgent->new;
$ua->agent("XML2JSON/0.1 ");
my $req = HTTP::Request->new(GET => $url);
my $res = $ua->request($req);
die if !$res->is_success;
die if $res->headers->header("Content-Type")!~m!/xml!;
# XML Dump
$XML::Simple::PREFERRED_PARSER = 'XML::Parser';
my $xmlobj = XMLin($res->content);
$Data::Dumper::Indent = 1;
$Data::Dumper::Useqq = "utf8";
$json = Dumper($xmlobj);
$json=~s/^\$VAR1/$var.data/;
$json=~s/([^\\])" => ("|{|\[)/$1" : $2/g;
# output
print $q->header(-type => "text/plain", -charset => "utf-8");
print "if (typeof($var) == 'undefined') $var = {};\n";
print $json."\n";
print "if (typeof($var.onload) == 'function') $var.onload($var.data);";
毎回サーバーに取りに行くのは負荷が高いので、こんな風にキャッシュ機能をつけるとよいかもしれません。
#!/usr/local/bin/perl
use CGI;
use Jcode;
use LWP::UserAgent;
use XML::Simple;
use Digest::MD5 qw(md5_hex);
use Data::Dumper;
my $cache_dir = "../cache/";
my $cache_lifetime = 600; # 10min
my $q = new CGI;
# param
my $var = $q->param('var');
$var = "xml" if !defined $var or $var!~/^[a-zA-Z_]+$/;
my $url = $q->param('url');
&error("url is not set") if !defined $url or $url!~m!^http://!;
my $json;
my $cache = $cache_dir.md5_hex($url);
if(-f $cache && (stat($cache))[9] + $cache_lifetime > time){
open(CACHE, $cache);
$json = join("", <CACHE>);
close(CACHE);
}
else{
# request
my $ua = LWP::UserAgent->new;
$ua->agent("XML2JSON/0.1 ");
my $req = HTTP::Request->new(GET => $url);
my $res = $ua->request($req);
&error("cannot get url") if !$res->is_success;
&error("cotnent is not xml (".$res->headers->header("Content-Type").")") if $res->headers->header("Content-Type")!~m!/xml!;
# XML Dump
$XML::Simple::PREFERRED_PARSER = 'XML::Parser';
my $xmlobj = XMLin($res->content);
$Data::Dumper::Indent = 1;
$Data::Dumper::Useqq = "utf8";
$json = Dumper($xmlobj);
# Write cache
my $cache = $cache_dir.md5_hex($url);
if(open(CACHE, ">$cache")){
print CACHE $json;
close(CACHE);
}
}
$json=~s/^\$VAR1/$var.data/;
$json=~s/([^\\])" => ("|{|\[)/$1" : $2/g;
# output
print $q->header(-type => "text/plain", -charset => "utf-8");
print "if (typeof($var) == 'undefined') $var = {};\n";
print $json."\n";
print "if (typeof($var.onload) == 'function') $var.onload($var.data);";
sub error{
print $q->header(-type => "text/plain", -charset => "utf-8");
print "var $var = \"$_[0]\";";
exit;
}
思うこと
開発者的には RSS を XML で吐き出すのと同時に JSON で吐き出してくれるページが増えてくれたらうれしいなぁ。
ただ、悪意のある JavaScript をインポートしてしまうと、訪問者の Cookie が漏れてしまうので、Cookie で個人情報を管理している場合は注意が必要です。信頼できる会社に XML2JSON なサービスをやってほしいところです。
(追記) Yahoo! Pipes を使えば、外部サイトの内容を JSONP で取得できるようになりました。詳しくは Yahoo! Pipes の Page Fetch モジュールでスクレイピングし放題 を見てください。