まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:xml

無料のグループウェアを SaaS で提供していたサイボウズ Live が 2019 年4月15日をもってサービスを終了する、というアナウンスがありました:
サイボウズLiveサービス終了のお知らせ

個人的にも仲間内サークル活動の中で使っていたりしていたもので、このニュース自体はとても残念なものでした。が、終了まで1年半も猶予を持ったアナウンスであり、また(上記リンクによると)今後データのエクスポート(CSVファイル?機能?)などを提供する予定もあるらしいです。データを他のサービスに移行するための準備期間としては充分にあるようにも感じました。

データ移行に関しては時間的な猶予もあるので、しばらくは謹製のデータエクスポートが出されるのを待ってもいいとは思っています。が、個人的/技術的な興味もあって自分でも PHP で作ってみました。詳しくは後述しますが、もしご利用になる場合、現時点ではまだ不完全なものだと理解の上で使ってください。なおこのツールではデータを XML ファイルでエクスポートします(掲示板データなど、改行情報を含むデータは CSV に向かないと判断したので):
https://github.com/dotnsf/cbl_export/blob/master/cbl_export.php


実際に使うには、まず PHP 実行環境が必要です(自分自身がテストした範囲では PHP 5.3.3 でも動きました、かなり古いバージョンでも大丈夫だと思います):
(例 CentOS の場合)
# yum install php php-mbstring php-xml php-pear

加えてプログラムの中で OAuth などの外部ライブラリモジュールを使うため、これらのモジュールをあらかじめインストールしておいてください:
(例 pear を使う場合)
# pear install Net_URL2
# pear install HTTP_Request2
# pear install HTTP_OAuth

またこのツールを使う準備としてサイボウズ Developer Center のアカウントが必要です。同アカウントをお持ちでない場合はリンク先の「デベロッパー登録」ボタンから申請してアカウントを作成してください:
2017102901


アカウント作成後にログインし、「Myアプリケーション」の一覧で「アプリケーションを登録する」を選択し、今から動かすアプリケーションの登録を行います:
2017102902


アプリケーションを登録する際のアプリケーション名などは任意に設定いただいていいのですが、「アプリケーションの種類」は「クライアント」、そして「アクセスレベル」は「レベルZ」を選択する点に注意してください(ここを間違えると正しく動かなくなります。なおアプリケーション種類をクライアントにしないとレベルZのアクセスレベルは選択できないはずです):
2017102903


こうしてアプリケーションを登録し、登録後の画面に表示される Consumer Key と、Consumer Secret をメモしておきます(他人に教えてはいけません)。またアプリケーションの種類とアクセスレベルが正しく登録されていることを確認してください:
20171026



ここまでの準備ができたところで、改めて今回作成したエクスポートアプリを使います。こちらのサイトから git clone するなどして cbl_export.php をコピーします:
https://github.com/dotnsf/cbl_export/blob/master/cbl_export.php


cbl_export.php をテキストエディタで開き、一部を編集します:
<?php
//. cbl_export.php
//. Referer: https://developer.cybozulive.com/doc/current/#id1
//. For XAuth(Ones of Level Z)
$consumer_key           = '(CybozuLive Developer Center Consumer Key)';
$consumer_secret        = '(CybozuLive Developer Center Consumer Secret)';
$xauth_access_token_url = 'https://api.cybozulive.com/oauth/token';
 
$params = array(
    'x_auth_username' =--> '(CybozuLive Email)',
    'x_auth_password' => '(CybozuLive Password)',
    'x_auth_mode'     => 'client_auth',
);

:
:

上記の青字部分を編集します。$consumer_key には上記サイボウズ Developer Center でのアプリケーション登録時に確認した Consumer Key を、$consumer_secret には同 Consumer Secret を代入します。また $params 'x_auth_username' には自分のサイボウズ Live ログイン時のメールアドレスを、そして 'x_auth_password' には同パスワードを指定します。


ここまでの準備ができたら php コマンドで cbl_export.php を実行します:
# php -f cbl_export.php

実行が成功するとカレントディレクトリに自分のメールアドレスと同じ名前のフォルダと、自分がサイボウズ Live で所属しているグループごとの ID(X:XXXXX みたいなフォーマットです)のフォルダが作成され、各データがそれぞれのフォルダ内にエクスポートされます。例えば1つのグループにしか所属していない場合であれば、自分のメールアドレスのフォルダが1つと、そのグループのIDのフォルダが1つ生成されます:
2017102904
(↑自分が実行した場合の結果サンプル)


そして個人のデータはメールアドレスのフォルダ内に、要素毎に XML ファイルでエクスポートされています(現在の仕様ではスケジュール、チャット、ToDo、コネクションのデータがエクスポートされます):
2017102905


またグループ ID のフォルダには各グループ毎のメンバー、掲示板、イベント、ToDo、ファイル共有の情報が XML ファイルでエクスポートされています:
2017102906


なお、このツールを開発するにあたり、サイボウズ Live API を使っています。その仕様はこちらです:
サイボウズLive データ API ドキュメント


(↓2017/Oct/29 時点での制限事項)
上記のように一応動くものが作れたつもりなのですが、2017/Oct/29 時点ではファイルのダウンロード機能が正しく動いていません。例えばサイボウズ Live のファイル共有機能や掲示板の中でファイルを添付しているケースは珍しくないと思っています。その「どんなファイルが添付されているか?」といった情報は取得できて、その情報は現在取得できるファイルの中にも正しくエクスポートできているのですが、肝心のファイルそのもののダウンロードができていません。具体的にはこの API の実行時にだけ想定外のエラーになってしまい、まだその理由や解決策が分からずにいます:
https://developer.cybozulive.com/doc/current/pub/fileDownload.html


この部分については私のミスなのか、API 側の問題なのかの判別ができず、現在は問い合わせ中です。分かり次第に対応するつもりです。
(↑2017/Oct/29 時点での制限事項)


(↓2017/Nov/06 追記)
サイボウズより返答あり。サイボウズ側では問題なく動いているとのこと。むむむ・・ (--;
(↑2017/Nov/06 追記)


添付ファイルのダウンロード以外についてはある程度動くようになっているつもりです。早めのデータ移行を検討していたり、移行先候補へのテストを早めに実行したい場合に活用ください。

なおサイボウズから謹製のエクスポートデータやエクスポートツールが提供された場合は、(きっと面倒なアカウント登録なども不要になると思うので)そちらの利用をオススメします。 (^^;


久しぶりに PHP を使っていて気付いたことをブログネタにしました。

最近の Web API/REST API は JSON フォーマットで結果を取得できるものが多いですが、中には XML のみサポートされているものもあります(RSS とかね)。そんな XML を PHP で扱う方法について調べました。
2017102700


まず、PHP には simplexml_load_file および simplexml_load_string という便利な関数が用意されています。これらは XML ファイル(のURL)や XML 文字列を指定して、PHP のオブジェクト化するというものです。例えば以下のような ATOM フィードフォーマットの XML ファイル(atom.xml)があったとします:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
  <id>tag:phpxml/</id>
  <title>サンプルタイトル</title>
  <link rel="alternate" type="text/html" href="http://sample.xml.jp/feed/"/>
  <link rel="self" type="application/atom+xml" href="http://sample.xml.jp/feed/atom10.xml"/>
  <entry>
    <id>http://sample.xml.jp/test1.html</id>
    <title>紹介タイトルその1</title>
    <link rel="alternate" type="text/html" href="http://sample.xml.jp/test1.html"/>
    <summary>紹介するエントリの1つです。</summary>
    <dc:updated>Thu, 26 Oct 2017 07:00:00 +0900</dc:updated>
  </entry>
  <entry>
    <id>http://sample.xml.jp/test2.html</id>
    <title>紹介タイトルその2</title>
    <link rel="alternate" type="text/html" href="http://sample.xml.jp/test2.html"/>
    <summary>紹介するエントリの2つめです。</summary>
    <dc:updated>Fri, 27 Oct 2017 07:00:00 +0900</dc:updated>
  </entry>
</feed>

この atom.xml ファイルに対して、例えば以下のようなコード(test1.php)を用意して実行します:
<?php
$feed = simplexml_load_file( "atom.xml" ); echo "id: " . ( $feed->id ) . "\n"; echo "title: " . ( $feed->title ) . "\n";
foreach( $feed->entry as $entry ){ echo "entry_title: " . ( $entry->title ) . "\n"; echo "link_url: " . ( $entry->link->attributes()->href ) . "\n"; }
?>

その実行結果(赤字部分)はこちら:
$ php -f test1.php

id: tag:phpxml/
title: サンプルタイトル
entry_title: 紹介タイトルその1
link_url: http://sample.xml.jp/test1.html
entry_title: 紹介タイトルその2
link_url: http://sample.xml.jp/test2.html

これだけ。simplexml_load_file 関数や simplexml_load_string 関数は XML ファイルや XML 文字列を簡単に PHP のオブジェクト化して、XML 内の各データや属性値にアクセスできるようになるものです。

※上記のように属性値は $entry->link->attributes()->属性名 で取得できます。


さてここまでは普通にできるのですが、実はこの方法では取得が難しいデータがあります。各 entry 内にある <dc:updated> という特殊な名前のデータです。 
      :
      :
    <title>紹介タイトルその1</title>
    <link rel="alternate" type="text/html" href="http://sample.xml.jp/test1.html"/>
    <summary>紹介するエントリの1つです。</summary>
    <dc:updated>Thu, 26 Oct 2017 07:00:00 +0900</dc:updated>
      :
      :

この部分が少し厄介なのです。test1.php と全く同じ要領でこんな test2.php ファイルを作ってみます:
<?php
$feed = simplexml_load_file( "atom.xml" ); echo "id: " . ( $feed->id ) . "\n"; echo "title: " . ( $feed->title ) . "\n";
foreach( $feed->entry as $entry ){ echo "entry_title: " . ( $entry->title ) . "\n"; echo "link_url: " . ( $entry->link->attributes()->href ) . "\n"; echo "dc:updated: " . ( $entry->dc:updated ) . "\n"; }
?>

この test2.php を実行すると・・・ Parse error となってしまいます。PHP の文法ではここに ":" を記述してはいけないんですね。。
$ php -f test2.php

Parse error: syntax error, unexpected ':' in test2.php on line 8

なるほど。これでは文法的にNGということはわかった。が、問題はどうやって回避すればいいのか、です。simplexml_load_file 関数を使わずに別の方法で XML をパースする方法もありますが、それはそれで面倒だったりします。なんとか simplexml_load_file の便利な機能をそのままに回避できないか・・

そこでググって見つけたがこの方法でした。簡単にいうと
 (1) タグ(<から>まで)内の ":" を "_" とかに一括変換する
 (2) ただしプロトコル( http:// とか https:// とか)で使われている部分だけは元に戻す
という方法です。この方法で作り直したのがこちら(test3.php)です:
<?php
//. XML ファイルを文字列変数へ読み込む
$txt = file_get_contents( "atom.xml" );
//. <***:****> -> <***_****>
$txt  = preg_replace( "/<([^>]+?):(.+?)>/", "<$1_$2>", $txt );
//. プロトコルは元に戻す
$txt  = preg_replace( "/_\/\//", "://", $txt );

$feed = simplexml_load_string( $txt );
echo "id: " . ( $feed->id ) . "\n";
echo "title: " . ( $feed->title ) . "\n";
foreach( $feed->entry as $entry ){
  echo "entry_title: " . ( $entry->title ) . "\n";
  echo "link_url: " . ( $entry->link->attributes()->href ) . "\n";
  echo "dc:updated: " . ( $entry->dc_updated ) . "\n";
}
?>

青字部分ではまず普通に atom.xml ファイルを文字列として読み込み、上記 (1) および (2) の処理を行います。そしてその結果を simplexml_load_string 関数でオブジェクト化しました。このようにすると、各 entry 内の <dc:updated> 要素は <dc_updated> と変わっているので、そのまま扱えるようになります。

この test3.php を実行すると、期待通りの結果を得ることができました:
$ php -f test3.php

id: tag:phpxml/
title: サンプルタイトル
entry_title: 紹介タイトルその1
link_url: http://sample.xml.jp/test1.html
dc:updated: Thu, 26 Oct 2017 07:00:00 +0900
entry_title: 紹介タイトルその2
link_url: http://sample.xml.jp/test2.html
dc:updated: Fri, 27 Oct 2017 07:00:00 +0900

とりあえず動くことは動きました。でももうちょっと上手い方法がないもんかな。。

このエントリの続きです。取得した XML の内容を詳しくみてみます。


改めて、まず API に入力したテキストは以下の様なものでした:
John Smith lives in New York, and he has been living there since 2001.


そして、API で取得した解析結果の XML はこのようなものでした:
<?xml version='1.0' encoding='utf-8'?>
<rep sts="OK">
<doc id="">
  <text>John Smith lives in New York, and he has been living there since 2001.
</text>
  <sents>
    <sent sid="0" begin="0" end="15">
      <text>John Smith lives in New York, and he has been living there since 2001.</text>
      <parse score="-2.73025">[S [S [NP John_NNP Smith_NNP NP] [VP lives_VBZ [PP in_IN [NP New_NNP York_NNP NP] PP] VP] S] ,_, and_CC [S [NP he_PRP NP] [VP has_VBZ [VP been_VBN [VP living_VBG [ADVP there_RB ADVP] [PP since_IN [NP 2001_CD NP] PP] VP] VP] VP] S] ._. S]</parse>
      <dependency_parse>John NNP 1 -I Smith NNP 2 NP lives VBZ -1 VP in IN 2 PP New NNP 5 -I York NNP 3 NP , , 2 -E and CC 2 -E he PRP 9 NP has VBZ 2 VP been VBN 9 VP living VBG 10 VP there RB 11 ADVP since IN 11 PP 2001 CD 13 NP . . 2 -E </dependency_parse>
      <usd_dependency_parse>John NNP 1 dep Smith NNP 2 npmod lives VBZ -1 root in IN 5 case New NNP 5 dep York NNP 2 nmod , , 2 dep and CC 2 dep he PRP 9 npmod has VBZ 2 VP been VBN 9 VP living VBG 10 VP there RB 11 ADVP since IN 14 case 2001 CD 11 nmod . . 2 dep</usd_dependency_parse>
      <tokens>
        <token tid="0" begin="0" end="3" type="0">John</token>
        <token tid="1" begin="5" end="9" type="0">Smith</token>
        <token tid="2" begin="11" end="15" type="0">lives</token>
        <token tid="3" begin="17" end="18" type="0">in</token>
        <token tid="4" begin="20" end="22" type="0">New</token>
        <token tid="5" begin="24" end="27" type="0">York</token>
        <token tid="6" begin="28" end="28" type="2048">,</token>
        <token tid="7" begin="30" end="32" type="0">and</token>
        <token tid="8" begin="34" end="35" type="0">he</token>
        <token tid="9" begin="37" end="39" type="0">has</token>
        <token tid="10" begin="41" end="44" type="0">been</token>
        <token tid="11" begin="46" end="51" type="0">living</token>
        <token tid="12" begin="53" end="57" type="0">there</token>
        <token tid="13" begin="59" end="63" type="0">since</token>
        <token tid="14" begin="65" end="68" type="0">2001</token>
        <token tid="15" begin="69" end="69" type="6144">.</token>
      </tokens>
    </sent>
  </sents>
  <mentions>
    <mention mid="-M0" mtype="NAM" begin="0" end="9" head-begin="0" head-end="9" eid="-E0" etype="PERSON" role="PERSON" metonymy="0" class="SPC" score="0.995606" corefScore="1">John Smith</mention>
    <mention mid="-M1" mtype="NAM" begin="20" end="27" head-begin="20" head-end="27" eid="-E1" etype="GPE" role="LOCATION" metonymy="0" class="SPC" score="0.993534" corefScore="1">New York</mention>
    <mention mid="-M2" mtype="PRO" begin="34" end="35" head-begin="34" head-end="35" eid="-E0" etype="PERSON" role="PERSON" metonymy="0" class="SPC" score="0.955652" corefScore="0.417012">he</mention>
    <mention mid="-M3" mtype="NONE" begin="65" end="68" head-begin="65" head-end="68" eid="-E2" etype="DATE" role="DATE" metonymy="0" class="SPC" score="0.820601" corefScore="1">2001</mention>
  </mentions>
  <entities>
    <entity eid="-E0" type="PERSON" generic="0" class="SPC" level="NAM" subtype="OTHER" score="0.645765">
      <mentref mid="-M0">John Smith</mentref>
      <mentref mid="-M2">he</mentref>
    </entity>
    <entity eid="-E1" type="GPE" generic="0" class="SPC" level="NAM" subtype="OTHER" score="1">
      <mentref mid="-M1">New York</mentref>
    </entity>
    <entity eid="-E2" type="DATE" generic="0" class="SPC" level="NONE" subtype="OTHER" score="1">
      <mentref mid="-M3">2001</mentref>
    </entity>
  </entities>
  <relations version="KLUE2_cascaded:2011_10_25">
    <relation rid="-R1" type="residesIn" subtype="OTHER">
      <rel_entity_arg eid="-E0" argnum="1"/>
      <rel_entity_arg eid="-E1" argnum="2"/>
      <relmentions>
        <relmention rmid="-R1-1" score="0.525653" class="SPECIFIC" modality="ASSERTED" tense="UNSPECIFIED">
          <rel_mention_arg mid="-M0" argnum="1">John Smith</rel_mention_arg>
          <rel_mention_arg mid="-M1" argnum="2">New York</rel_mention_arg>
        </relmention>
      </relmentions>
    </relation>
  </relations>
  <sgml_sent_info/>
  <sgml_char_info/>
</doc>
</rep>

この XML を上から眺めていくと、入力データである "John Smith lives in New York, and he has been living there since 2001." が解析されて、トークンに分類され、それぞれどういった品詞なのかの分析がされ、・・・ という経緯が分かると思います。

その中に以下の <mentions> 要素が見て取れます:
  <mentions>
    <mention mid="-M0" mtype="NAM" begin="0" end="9" head-begin="0" head-end="9" eid="-E0" etype="PERSON" role="PERSON" metonymy="0" class="SPC" score="0.995606" corefScore="1">John Smith</mention>
    <mention mid="-M1" mtype="NAM" begin="20" end="27" head-begin="20" head-end="27" eid="-E1" etype="GPE" role="LOCATION" metonymy="0" class="SPC" score="0.993534" corefScore="1">New York</mention>
    <mention mid="-M2" mtype="PRO" begin="34" end="35" head-begin="34" head-end="35" eid="-E0" etype="PERSON" role="PERSON" metonymy="0" class="SPC" score="0.955652" corefScore="0.417012">he</mention>
    <mention mid="-M3" mtype="NONE" begin="65" end="68" head-begin="65" head-end="68" eid="-E2" etype="DATE" role="DATE" metonymy="0" class="SPC" score="0.820601" corefScore="1">2001</mention>
  </mentions>

Watson が解析した結果、0文字目から9文字目までの "John Smith" は 99.5606% の確率で一人の人間(PERSON)の名前(NAM)であり、同様に "New York" は 99.3534% の確率で1つの場所(LOCATION)の名前(NAM)だと解析しています。

また "he" は人間(PERSON)の代名詞(PRO)であり、かつ "John Smith" と同じ eid の値("-E0")になっていて、その関連確率は 41.7012% になっています。つまりこの "he" は約40%の確率で "John Smith" のことだ、と判断していることになります。この文脈だとその通りなので、ちゃんとその辺りを正しく判断できていることになります。

そして文章の最後にある "2001" という数字はただの数字ではなく、この文脈の中では 82.0601% の確率で日付(DATE)の一部、つまり「年」であると判断しています。これもおそらく前後関係からの判断だと思いますが、実際正しいですよね。


そして、これら名詞の解析結果が以下の <entities> に記載されている内容で出力されています:
 
  <entities>
    <entity eid="-E0" type="PERSON" generic="0" class="SPC" level="NAM" subtype="OTHER" score="0.645765">
      <mentref mid="-M0">John Smith</mentref>
      <mentref mid="-M2">he</mentref>
    </entity>
    <entity eid="-E1" type="GPE" generic="0" class="SPC" level="NAM" subtype="OTHER" score="1">
      <mentref mid="-M1">New York</mentref>
    </entity>
    <entity eid="-E2" type="DATE" generic="0" class="SPC" level="NONE" subtype="OTHER" score="1">
      <mentref mid="-M3">2001</mentref>
    </entity>
  </entities>

更に、これらの各名詞同士の関係として <relations> に記載される関連性が出力されています:
 
  <relations version="KLUE2_cascaded:2011_10_25">
    <relation rid="-R1" type="residesIn" subtype="OTHER">
      <rel_entity_arg eid="-E0" argnum="1"/>
      <rel_entity_arg eid="-E1" argnum="2"/>
      <relmentions>
        <relmention rmid="-R1-1" score="0.525653" class="SPECIFIC" modality="ASSERTED" tense="UNSPECIFIED">
          <rel_mention_arg mid="-M0" argnum="1">John Smith</rel_mention_arg>
          <rel_mention_arg mid="-M1" argnum="2">New York</rel_mention_arg>
        </relmention>
      </relmentions>
    </relation>
  </relations>

eid の "-E0" と "-E1"、つまり "John Smith" と "New York" の間は 52.5653% の確率で "residesIn"、つまり「常設する/住んでいる」の関係がある、と判断しています。これは "living" あたりから判断しているのだと思いますが、文脈としても正しい判断ができていますね。。


今回は簡単なサンプルを使ったので、結果も比較的説明しやすいものでした。ただこの結果を見る限りでは、入力した文章は正しく解析され、各単語の関係性もかなり正しく分析してくれているようです。 Watson はアメリカのクイズ王に勝った時に「クイズ問題文章を解析して、何が問われているのかを分析した上でその答を導き出し、その確信度がある程度の確率を上回った時に回答する」という解説を見たのですが、その仕組みの一端を垣間見たような気がしますね。。


これだけのサービスが言語限定とはいえ、IBM Bluemix を通じて無料で提供されているのはなかなか凄いことだと思います。これだけの機能を自前で実装するにはかなり困難が伴うでしょうが、今は無料サービスとして、誰でも使える形で提供されていることになります。

こうなると日本語サービスの提供が待ち遠しいです。もちろん言語としての難易度は全然違うでしょうけど、






 

今年(2014年)の10月31日より、オープンデータ高度化の一環で総務省統計局より政府統計データの多くが Web API にて公開されました:
API機能で利用できる統計データの拡充 -統計におけるオープンデータの高度化- (PDF)

この公開された政府統計データ(eStat)を使ったサンプルを紹介します。
top_header_logo



まず、この政府統計データを使う上での利用者登録と、アプリケーション登録が必要です。利用者登録は以下のページから行います:
利用登録・ログイン

メールアドレスやパスワードを登録し、最終的にアクティベートまで行い、利用登録が完了すると上記ページからログインできるようになります。

ログイン後にアプリケーション登録を行います。「アプリケーションIDの取得」をクリックして、アプリケーションの名称、URL 、概要を入力し、「発行」ボタンをクリックすると appId と書かれた箇所にアプリケーション ID が表示されます。これは後から使う情報になるのでメモするなどしておきます(以下、appId を XXXXXXXXXX として説明を続けます):
2014121501


なお、1つの利用IDで最大3つまでのアプリケーションが登録できます。ウェブ上に公開されているアプリケーションから利用する場合はその URL を指定する必要があります。公開されていないアプリや、テスト目的で利用したい場合はURLに http://localhost/ を指定すればいいようです。


アプリケーションの登録が完了し、appId が取得できたら早速 API を利用してみましょう。まずはどのような統計データが公開されているのか、その一覧を取り出してみます。

データリストを取得するには、以下の URL に GET アクセスします:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsList?appId=XXXXXXXXXX&statsCode=ZZZZ

getStatsList では URL パラメータが2つ指定されています。appId には上記で取得したアプリケーション ID を、そして statsCode には政府統計コードを指定します。例えば平成22年に実施・更新された国勢調査のデータを取得するのであれば、政府統計コードは 00200521 になるので、こんな感じです:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsList?appId=XXXXXXXXXX&statsCode=00200521
(XXXXXXXXXX = 取得したアプリケーションID)

結果は以下の様な XML フォーマットで取得できます:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GET_STATS_LIST xsi:noNamespaceSchemaLocation="http://statdb.nstac.go.jp/api/1.0b/schema/GetStatsList.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <RESULT>
  <STATUS>0</STATUS>
  <ERROR_MSG>正常に終了しました。</ERROR_MSG>
  <DATE>2014-12-15T15:15:36.690+09:00</DATE>
 </RESULT>
 <PARAMETER>
  <LANG>J</LANG>
  <STATS_CODE>00200521</STATS_CODE>
 </PARAMETER>
 <DATALIST_INF>
  <NUMBER>4180</NUMBER>
  <LIST_INF id="0000030001">
   <STAT_NAME code="00200521">国勢調査</STAT_NAME>
   <GOV_ORG code="00200">総務省</GOV_ORG>
   <STATISTICS_NAME>昭和55年国勢調査 第1次基本集計 全国編</STATISTICS_NAME>
   <TITLE no="00101">男女の別(性別)(3),年齢5歳階級(23),人口 全国・市部・郡部・都道府県(47),全域・人口集中地区の別</TITLE>
   <CYCLE>-</CYCLE>
   <SURVEY_DATE>198010</SURVEY_DATE>
   <OPEN_DATE>2007-10-05</OPEN_DATE>
   <SMALL_AREA>0</SMALL_AREA>
  </LIST_INF>
  <LIST_INF id="0000030002">
   <STAT_NAME code="00200521">国勢調査</STAT_NAME>
   <GOV_ORG code="00200">総務省</GOV_ORG>
   <STATISTICS_NAME>昭和55年国勢調査 第1次基本集計 全国編</STATISTICS_NAME>
   <TITLE no="00102">男女の別(性別)(3),年齢各歳階級(103),人口 全国・市部・郡部・都道府県(47),全域・人口集中地区の別</TITLE>
   <CYCLE>-</CYCLE>
   <SURVEY_DATE>198010</SURVEY_DATE>
   <OPEN_DATE>2007-10-05</OPEN_DATE>
   <SMALL_AREA>0</SMALL_AREA>
  </LIST_INF>
  <LIST_INF id="0000030003">
    :
  </LIST_INF>
    :
    :
 </DATALIST_INF>
</GET_STATS_LIST>

<DATALIST_INF> の中に複数の <LIST_INF> 要素があり、それら1つ1つが異なる統計データを示しています。また個々の統計データにはデータの説明文なども含まれており、 <LIST_INF id="YYYYYYYYYY"> の YYYYYYYYYY 部分が ID として区別されています。これでどのような統計データがあるか、の一覧が管理されています。


また、統計データの種類を1つ指定して、実際の統計データを取得するには getStatsData を使います:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsData?appId=XXXXXXXXXX&statsDataId=YYYYYYYYYY

パラメータの appId には getStatsList の時と同様にアプリケーション ID を、また statsDataId には getStatsList で取得したデータID(上記の赤字で示されている部分)を指定します。 上記例の一番上にある「昭和55年国勢調査の年齢5歳階級別の人口データ」であれば 0000030001 なので、以下の様な URL になります:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsData?appId=XXXXXXXXXX&statsDataId=0000030001

そして、この結果は以下の様な XML フォーマットで取得できます:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GET_STATS_DATA xsi:noNamespaceSchemaLocation="http://statdb.nstac.go.jp/api/1.0b/schema/GetStatsData.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <RESULT>
  <STATUS>0</STATUS>
  <ERROR_MSG>正常に終了しました。</ERROR_MSG>
  <DATE>2014-12-15T15:15:38.380+09:00</DATE>
 </RESULT>
 <PARAMETER>
  <LANG>J</LANG>
  <STATS_DATA_ID>0000030001</STATS_DATA_ID>
  <DATA_FORMAT>X</DATA_FORMAT>
  <START_POSITION>1</START_POSITION>
  <METAGET_FLG>Y</METAGET_FLG>
 </PARAMETER>
 <STATISTICAL_DATA>
  <TABLE_INF id="0000030001">
   <STAT_NAME code="00200521">国勢調査</STAT_NAME>
   <GOV_ORG code="00200">総務省</GOV_ORG>
   <STATISTICS_NAME>昭和55年国勢調査 第1次基本集計 全国編</STATISTICS_NAME>
   <TITLE no="00101">男女の別(性別)(3),年齢5歳階級(23),人口 全国・市部・郡部・都道府県(47),全域・人口集中地区の別</TITLE>
   <SURVEY_DATE>198010</SURVEY_DATE>
   <TOTAL_NUMBER>3651</TOTAL_NUMBER>
   <FROM_NUMBER>1</FROM_NUMBER>
   <TO_NUMBER>3651</TO_NUMBER>
  </TABLE_INF>
  <CLASS_INF>
   <CLASS_OBJ id="cat01" name="全域・集中の別030002">
   <CLASS code="00700" name="全域" level="1"/>
   <CLASS code="00701" name="人口集中地区" level="1"/>
  </CLASS_OBJ>
  <CLASS_OBJ id="cat02" name="男女A030001">
   <CLASS code="000" name="男女総数" level="1"/>
   <CLASS code="001" name="男" level="1"/>
   <CLASS code="002" name="女" level="1"/>
  </CLASS_OBJ>
  <CLASS_OBJ id="cat03" name="年齢5歳階級A030002">
   <CLASS code="000" name="総数" level="1" unit="人"/>
   <CLASS code="001" name="0-4歳" level="1" unit="人"/>
   <CLASS code="002" name="5-9歳" level="1" unit="人"/>
   <CLASS code="003" name="10-14歳" level="1" unit="人"/>
     :
  </CLASS_OBJ>
  <CLASS_OBJ id="area" name="全国都道府県030001">
   <CLASS code="00000" name="全国" level="1"/>
   <CLASS code="00001" name="全国市部" level="1"/>
   <CLASS code="00002" name="全国郡部" level="1"/>
   <CLASS code="01000" name="北海道" level="1"/>
   <CLASS code="02000" name="青森県" level="1"/>
   <CLASS code="03000" name="岩手県" level="1"/>
     :
  </CLASS_OBJ>

  <DATA_INF>
   <NOTE char="***">当該数値がないもの</NOTE>
   <NOTE char="-">当該数値がないもの</NOTE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="00000" time="1980000000" unit="人">117060396</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="00001" time="1980000000" unit="人">89187409</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="00002" time="1980000000" unit="人">27872987</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="01000" time="1980000000" unit="人">5575989</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="02000" time="1980000000" unit="人">1523907</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="03000" time="1980000000" unit="人">1421927</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="04000" time="1980000000" unit="人">2082320</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="05000" time="1980000000" unit="人">1256745</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="06000" time="1980000000" unit="人">1251917</VALUE>
     :
  </DATA_INF>
 </STATISTICAL_DATA>
</GET_STATS_DATA>

この結果(<STATISTICAL_DATA>)は以下のように読み解けます。

まず、<CLASS_INF> を見ることで、データは4つのカテゴリ分類がされています。まず cat01 が「全域(00700)か、人口集中地区(00701)か」を示しています。

次に cat02 は男女別分類をしています。男女総数のデータ(000)なのか、男性のみのデータ(001)なのか、それとも女性のみのデータ(002)なのか、という分類のようです。

そして cat03 は年齢を5歳区切りにした分類です。全年代(000)、0~4歳(001)、5~9歳(002)、・・といった具合で分類されていることが分かります。

最後に area が都道府県別分類をしていることが分かります。全国(00000)、全国市部(00001)、全国郡部(00002)、北海道(01000)、青森(02000)、・・・といった具合のようです。


続いて <DATA_INF> 内の <VALUE> 部分が実際の数値データになっています。例えば先頭にあるこのデータ:
<VALUE cat01="00700" cat02="000" cat03="000" area="00000" time="1980000000" unit="人">117060396</VALUE>
これは cat01=00700, cat02=000, cat03=000, area=0000 という属性が付与されているので、エリアは全域、男女総数、全年代の全国での人口総数を意味していて、その数値が 117060396 (人)である、ということが分かります。

したがって、例えば年代毎の全国の人口分布を調べたいのであれば、cat01=00700, cat02=000, area=00000 で固定して、cat03 が 001 以上のものをフィルタリングして調べればよい、ということになりますね。 


なお、Web API の app と getStats***** の間に json を入れると、出力結果が XML ではなく JSON フォーマットになります。JSON で必要な場合はこちらを使ってください:
http://statdb.nstac.go.jp/api/1.0b/app/json/getStatsList?appId=XXXXXXXXXX&statsCode=00200521
http://statdb.nstac.go.jp/api/1.0b/app/json/getStatsData?appId=XXXXXXXXXX&statsDataId=0000030001






 

IBM Bluemix チャレンジでは、用意された多くのミドルウェアサービスを使ってアプリケーションを構築・開発することになると思いますが、入賞を狙おうとするとやはりなるべく IBM 製品を使ってアピールすべきではないか(苦笑)、とも思うわけです。

そんなわけで今回のエントリでは IBM DB2 が持つ特徴的な機能を紹介します。こういうことをやろうとすると DB2 の特徴を活かしてプログラミングや構築ができる、というサンプルになれば。


最近の話ではないのですが、DB2 に V9 から搭載された XML ストア機能というものがあります。

リレーショナルデータベースである DB2 に XML という型の列を定義することができます。そしてこの列には XML データをそのまま格納することができます。テーブル作成時にはこんな感じで定義します:
create table test(id int not null, data xml, updated timestamp)


他のリレーショナルデータベースでもちょっと無理をすればテキスト型の列を定義して、そこに XML を(文字列として)格納する、ということもできます、格納するだけであれば。

でも決定的な違いもあります。それは XML データを(文字列ではなく) ネイティブな XML データとして格納している、という点です。これによって以下のようなことができるようになります。

例えば上記の内容で定義した test テーブルに以下のようなレコードが格納されていると仮定します(最後のupdated列は無視します):
IDDATA
1<Response><Say language="ja" gender="woman">いい天気ですね</Say></Response>
2<Response><Say language="en" gender="man">Hello. What are you going do today?</Say></Response>
3<Response><Say language="ja" gender="man">encode と decode</Say></Response>

この状態のテーブルの DATA 列から色々な条件でレコードを検索したい時、普通の text 型データだと一般的には like 節を使った SQL の全文検索を実行することになると思います。例えば「天気」という文字を含むレコードを検索するにはこんな感じで:
> select * from test where data like '%天気%'

上記例であれば ID = 1 のレコードが検索されるはずです。text 型のデータでもこのくらいはできます。

では「<Say>要素の language 属性が "en" のものだけを検索したい」場合はどうでしょう?ケースとしては「英語で格納されているデータだけを取り出したい」ような場合です。先程の応用で SQL はこんな感じでしょうか?
> select * from test where data like '%en%'

でもこれだと ID = 2 だけでなく、(本文の中に "en" が含まれる)ID = 3 のレコードも検索されてしまいます。求めたかった結果とは違ってしまいますね。とはいえ text 型のデータに対する SQL はこの辺りはシンプルにはいかないのです。

一方、XML 型で格納した場合はどうでしょう? DB2 の XML 列に対しては XPath を使ったクエリーを実行できます。今回の例であれば、こんな具合で SQL(?) を実行します:
> select * from test where xmlexists( '$t/Response/Say[@language="en"]' passing test.data as "t" )

XPath を使って test テーブルの data 列の中身で <Reponse> 要素の下の <Say> 要素の language 属性が "en" のものを指定して検索しています。つまり単なる全文検索ではなく、XML のデータの属性値を対象に絞り込みを行っています。XML データとして扱うことができるので、もちろん「テキスト値だけを対象とした検索」や「他の属性値による絞り込み」を行うことも可能です。

この方法によって、非構造的なデータを XML の形式で保存するだけでなく、XML データとしてのテキスト検索や属性検索、といった非構造データの特性に合わせたクエリーの実行もできるようになるのでした。


・・・と、こんな具合に XML フォーマットのデータや、XML フォーマットに変換できる非構造データに関しては DB2 の XML ストア機能を使って保存することで、単なる全文検索だけでなく、そのデータの特性に合わせた検索も可能になるのでした。



 

このページのトップヘ