JavaScriptで国立国会図書館サーチのAPIを利用する方法を考える

# 国立国会図書館の書誌データはオープンデータ
2019年4月1日から国立国会図書館の書誌データがオープンデータになりました。

[国立国会図書館が作成し提供する書誌データがオープンデータになりました](https://iss.ndl.go.jp/information/2019/04/01_ndl_ccby/ "国立国会図書館が作成し提供する書誌データがオープンデータになりました")
https://iss.ndl.go.jp/information/2019/04/01_ndl_ccby/

# JavaScriptで国立国会図書館サーチのAPIを利用する方法を考える
そこでJavaScriptで国立国会図書館サーチのAPIを利用する方法について考えてみます。
1年くらい前から考えていましたが、これを機に重たい腰を上げて記事にしました。

国立国会図書館サーチのAPIの利用条件は下記をご確認ください。

[APIのご利用について «  国立国会図書館サーチについて](https://iss.ndl.go.jp/information/api/ "APIのご利用について «  国立国会図書館サーチについて")
https://iss.ndl.go.jp/information/api/

今回利用する個々の技術やその仕様等については解説していると長くなるので、そこは省略して逃げます。
コード断片は動作確認していないので動作しないかもしれませんが、これも逃げます。
記事の反響次第でバグを修正したり解説やリンクを加筆する可能性はあります。きっと。たぶん。おそらく。
せめてリンクはちゃんと揃えたいです。あまりに少ないです。ごめんなさい。

# プロトコルを選択する。
国立国会図書館サーチのAPIで提供されいるプロトコルには複数種類があります。

[API仕様の概要 «  国立国会図書館サーチについて](https://iss.ndl.go.jp/information/api/riyou/ "API仕様の概要 «  国立国会図書館サーチについて")
https://iss.ndl.go.jp/information/api/riyou/

JavaScriptで扱うならGETで要求してXMLで応答してもらうのが楽な気がするので、私はSRU（Search/Retrieve Via URL）を選びました。

# SOPの制約を回避する。
JavaScriptでブラウザから国立国会図書館サーチのAPIを叩いてAjaxで利用しようとするとSOP（Same Origin Policy︰同一生成元ポリシー）の制約を受けます。
国立国会図書館サーチのAPIはCORS（Cross​-Origin Resource Sharing:オリジン間リソース共有）に対応してないためです。

この制約を回避する方法を3つ考えてみました。

## ブラウザの外部のプログラムを噛ませる。
当然のことながらブラウザの外部のプログラムはSOPの制約を受けません。
例えば下記のようなphpをかませて`Access-Control-Allow-Origin`ヘッダーを付加することでCORS対応させることができます。
`request`に国立国会図書館サーチのAPIのURLとパラメーターを渡して使います。

~~~php
<?php
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/xml');

readfile($_GET['request']);
~~~

## ブラウザの拡張機能にする。
~~ブラウザの拡張機能であればSOPの制約は受けません。~~
ブラウザの拡張機能にはSOPの制約を受けない権限を付与することができます。
manifest.jsonに下記のように記述します。

~~~javascript:manifest.json
{
  /* 前略 */
  "permissions": [
    "https://iss.ndl.go.jp/api/sru"
  ],
  /* 後略 */
}
~~~

私はこれを選択しました。

## WebViewで開く。
スマホでコーディングしていて気がついたのですが、エディタのプレビュー機能で開くとSOPの制約を受けずにAjaxを使用できました。
なので、おそらくWebViewはSOPの制約を受けないと思われます。

# SRUのパラメーターを考える。
国立国会図書館サーチのSRUの仕様については下記ページにAPI仕様書のpdfが掲載されています。

[API仕様の概要 «  国立国会図書館サーチについて](https://iss.ndl.go.jp/information/api/ "APIのご利用について «  国立国会図書館サーチについて")
https://iss.ndl.go.jp/information/api/

結論から、そして大雑把に言うと、下記のようなパラメーターを投げることにします。

`https://iss.ndl.go.jp/api/sru?operation=searchRetrieve&recordSchema=dcndl&recordPacking=xml&maximumRecords=10&query=title%20%3D%20%22%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%22%20AND%20creator%20%3D%20%22%E4%BD%9C%E6%88%90%E8%80%85%22%20AND%20publisher%20%3D%20%22%E5%87%BA%E7%89%88%E7%A4%BE%22%20AND%20dpid%20%3D%20%22iss-ndl-opac%22%20AND%20mediatype%20%3D%20%221%22&startRecord=1`

取得データのスキーマ（メタデータの形式）はdcndlを選択しています。
国立国会図書館サーチのメタデータの仕様については下記ページにフォーマット仕様書のpdfが掲載されています。

[メタデータ «  国立国会図書館サーチについて](https://iss.ndl.go.jp/information/metadata/ "メタデータ «  国立国会図書館サーチについて")
https://iss.ndl.go.jp/information/metadata/

`recordPacking=xml`を指定しておくと、検索結果のレコード1件1件のデータを返戻データ全体のxmlにネストしてくれるのでパースが楽です。
`recordPacking=string`を指定、もしくは指定を省略した場合、レコードは実体参照を使って1つの要素として格納されますので、1件1件実体参照を展開した上でパースして使うことになります。

最大取得件数は10件としています。取得開始位置は1としていますが、この数値は続きを読み込む時に増やしていくことになります。

検索条件はCQLという仕様で指定します。
パーセントエンコーディングして投げるのですが、エンコーディング前のクエリは下記になります。

`title = "タイトル" AND creator = "作成者" AND publisher = "出版社" AND dpid = "iss-ndl-opac" AND mediatype = "1"`

タイトル、作成者、出版社を指定し、データプロバイダは国立国会図書館オンライン、資料種別は本に絞り込みます。

# formからSRUのパラメーターを生成する。
先ほどのパラメーターをform要素から生成するべく、下記のようなhtmlを用意します。

~~~html
<form action="#" id="cql-parameter">
	<label for="title">タイトル</label>
	<input type="text" id="title" name="title" placeholder="タイトル" />
	<label for="creator">作成者</label>
	<input type="text" id="creator" class="form-control" name="creator" placeholder="作成者" />
	<label for="publisher">出版者</label>
	<input type="text" id="publisher" name="publisher" placeholder="出版者" />
	<input type="hidden" id="dpid" name="dpid" value="iss-ndl-opac" />
	<input type="hidden" id="mediatype" name="mediatype" value="1" />
	<button id="search-btn">検索</button>
</form>
<form action="#" id="search-form">
	<input type="hidden" id="operation" name="operation" value="searchRetrieve" />
	<input type="hidden" id="recordSchema" name="recordSchema" value="dcndl" />
	<input type="hidden" id="recordPacking" name="recordPacking" value="xml"	 />
	<input type="hidden" id="maximumRecords" name="maximumRecords" value="10" />
	<input type="hidden" id="query" name="query" />
</form>
~~~

これを使って下記のようにjQueryでSRUのパラメーターを生成します。

~~~javascript
var sru_parameter;
$('#cql-parameter').submit(function() {
	var query = '';
	$(this).find('input').each(function() {
		if (this.value != '') {
			if (query != '') {
				query += ' AND ';
			}
			query += this.name + ' = "' + this.value + '"';
		}
	});
	$('#query').val(query);
	sru_parameter = $('#search-form').serialize();
	
	return false;
});
~~~

# Ajaxで検索結果を取得する。
fetchでもjQueryでもXHRでも何でも好きな物を使っていいと思います。

## fetchを使う。
~~~javascript:fetchを使った例
var parser = new DOMParser();
fetch('http://iss.ndl.go.jp/api/sru?' + sru_parameter).then(function(response) {
	return response.text();
}).then(function(text) {
	var data = parser.parseFromString(text, 'text/xml');
});
~~~

## jQueryを使う。
~~~javascript:jQueryを使った例
$.ajax('http://iss.ndl.go.jp/api/sru?' + sru_parameter).done(function(data, textStatus, jqXHR) {
});
~~~

## XHRを使う。
XHRのコード例は割愛します。

# xmlからデータを取り出す。
取得したxmlをhtmlに変換するのにはXSLTが便利です。
また、データの取得だけであればXPathかjQueryセレクタかquerySelectorを使うとよさそうです。

## XSLTを使う。
~~~javascript:XSLTを使った例
var xslt = new XSLTProcessor();
fetch('./xml2html.xsl').then(function(response) {
	return response.text();
}).then(async function(text) {
	xslt.importStylesheet(parser.parseFromString(text, 'text/xml'));
	var fragment = $(xslt.transformToFragment(data, document));
	$('#result-list').append(fragment);
});
~~~

~~~xml:xml2html.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:owl="http://www.w3.org/2002/07/owl#"
	xmlns:dcterms="http://purl.org/dc/terms/"
	xmlns:dcndl="http://ndl.go.jp/dcndl/terms/"
	xmlns:foaf="http://xmlns.com/foaf/0.1/"
	xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
	<xsl:output method="html" />
	<xsl:template match="/">
		<xsl:apply-templates select=".//rdf:RDF" />
	</xsl:template>
	<xsl:template match="rdf:RDF">
		<li class="list-group-item">
			<a target="_blank">
				<xsl:attribute name="href">
					<xsl:value-of select=".//dcndl:BibAdminResource/@rdf:about" />
				</xsl:attribute>
				<xsl:value-of select=".//dcterms:title" />
			</a>
		</li>
	</xsl:template>
</xsl:stylesheet>
~~~

## XPathを使う
~~~javascript:XPathを使った例
var result = data.evaluate('.//rdf:RDF', data, function() { return 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var nsr = data.createNSResolver(result.snapshotItem(0));

for (var i = 0; i < result.snapshotLength; i ++) {
	var rdf = result.snapshotItem(i);
	
	var about = data.evaluate('.//@rdf:about', rdf, nsr, XPathResult.STRING_TYPE, null).stringValue;
	var title = data.evaluate('.//dcterms:title', rdf, nsr, XPathResult.STRING_TYPE, null).stringValue;
}
~~~

名前空間リゾルバの作成がミソです。

## jQueryセレクタを使う。
~~~javascript:jQueryセレクタを使った例
$(data).find('recordData').each(function() {
	var dataElement = $(this);
	
	var about = dataElement.find('dcndl\\:BibAdminResource').eq(0).attr('rdf:about');
	var title = dataElement.find('dcterms\\:title').eq(0).html();
});
~~~

`find`に渡すセレクタの`\\:`がミソです。`:`では名前空間接頭辞ではなく擬似クラスになってしまいますし、`\:`では`:`と等価になってしまいます。

## querySelectorを使う。
querySelectorのコードは後日作成予定です。
jQueryセレクタよりスマートなのにごめんなさい。

# 取り出したデータを使って何かする。
ここまで来ればもう何をしようが自由ですし、ここから何をするかが重要なのですが、ここでは一例として書誌データを検索してそのページ数を視覚化するFirefox拡張機能のソースコードをGitHubで公開しました。

https://github.com/310kent0/ndlVpagesExt

AMOからインストールできます。
https://addons.mozilla.org/ja/firefox/addon/ndlvpagesext/

GitHubからソースコードをダウンロードした方はFireFoxで`about:debugging`を開いて一時的にインストールしてください。

[Firefox への一時的なインストール](https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/Temporary_Installation_in_Firefox "Firefox への一時的なインストール")
https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/Temporary_Installation_in_Firefox

# 更新履歴
拡張機能がSOPの制約を受けない、という大嘘を訂正。申し訳ありません。（2019/10/22）
SRUの解説、リンクの追加。その他微修正。（2019/05/02）
コード断片の微修正。（2019/04/13）
国立国会図書館サーチの「API仕様の概要」へのリンクを追加。（2019/04/13）
見出し構成見直し。（2019/04/13）
待望のサンプルコードのAMO公開。初めてなので結構手こずりました。お恥ずかしい限りです。（2019/04/10）
サンプルコードのGitHubのURLを追記。サンプルコードの直書きを削除。（2019/04/07）
初版（2019/04/07）