document.evaluateはIEで動かないんですが、RSSのようにネームスペース付きXMLファイルにIEでもそれ以外のブラウザでもquerySelectorを使った同一ソースでアクセスする方法をまとめました。jQueryを使わなくても大した長さにはなりません。
前のページ「Google Feed APIを使わずXMLHttpRequestでクロスドメインのRSSを取得する方法 まとめ 」でJavaScriptのXMLHttpRequestを使ってRSSを取得するまで書きました。取得したRSSはネームスペース付きのXMLなので、どうやってデータにアクセスすればいいのかわかりませんでしたが、このページではそれを解決する方法をまとめています。jQueryは使っていません。
document.evaluateはIEで動かない
最近はIEで見ても、その他のブラウザで見ても同じに見えるので油断していました。
ググってみると「JavaScriptでデータを走査する時、namespaceを解決するにはdocument.evaluateを使う」というような感じだったので、それを参考にスクリプトを書いてGoogle Chromeで試したらちゃんと動いたんですけど、InternetExplorerでは動きません。document.evaluateはIEで実装していないらしいです。
という事はIEだけ別のソースを書かなくてはいけないのか?と思ったんですけど、querySelectorだけで両方対応出来ました。
responseTextではなくresponseXMLを使う
XMLHttpRequestで取得したデータをresponseTextで取って、それを次のようにHTMLにしてquerySelectorをかけてもうまくいきません。正確に言うと、これで取れるデータもあるんですが取れないデータがあります。
HTMLの受け皿に、下ではdivを使っていますが、ちゃんとそれ用のobjectがあったような気もします。もし、それをご存知ならそちらを使われた方がよろしいかと。。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
/* Bad example */
var div = document.createElement('div');
div.innerHTML = xhr.responseText ;
var items = div.querySelectorAll('item');
}
};
xhr.open('GET', rss_url, true);
xhr.send('');
そうではなく、responseXMLにquerySelectorをかければ、namespaceがあってもデータが取得できます。
var items = xhr.responseXML .querySelectorAll('item');
namespaceがついたタグの指定方法
namespaceのついたタグを抽出する時は、querySelectorの引数に、namespaceを除いたタグ名を指定すればいいみたいです。
次のRSSから<dc :date >の中身を抜き出す場合を例にします。
<rdf:RDF xmlns :rdf="" xmlns :dc ="" xmlns :admin="" xmlns :content="" xmlns ="">
<channel rdf:about="">
<title></title>
<link></link>
<description></description>
<dc :language></dc :language>
<items>
<rdf:Seq>
<rdf:li rdf:resource=""/>
<rdf:li rdf:resource=""/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="">
<link></link>
<title></title>
<description>
...
</description>
<dc :subject></dc :subject>
<dc :creator></dc :creator>
<dc :date >yyyy-mm-ddT00:00:00+09:00</dc :date>
<content:encoded>
<![CDATA[ ... ]]>
</content:encoded>
</item >
<item rdf:about="">
<link></link>
<title></title>
<description>
...
</description>
<dc :subject></dc :subject>
<dc :creator></dc :creator>
<dc :date >yyyy-mm-ddT00:00:00+09:00</dc :date>
<content:encoded>
<![CDATA[ ... ]]>
</content:encoded>
</item >
</rdf:RDF>
先ほどの
var items = xhr.responseXML .querySelectorAll('item ');
これでitem の一覧は配列化されているので、それをループして各itemの中から日付を抽出したい場合はnamespaceのdc とコロンを取り除いたタグ名「date」でエレメントが取得できます。
var items = xhr.responseXML .querySelectorAll('item ');
for(var i = 0; i < items.length; i++){
var date = items[i].querySelector('date ');
alert(date.textContent); // yyyy-mm-ddT00:00:00+09:00
}
属性の取得はgetAttribute
例えば、itemタグのrdf:aboutを取得するには、items[i].about としてもダメでした。この場合はgetAttribute()に取得したい属性名を指定するんですが、こちらの場合はnamespaceも必要 みたいです。
// <item rdf :about ="http://example.com"></item> からrdf:aboutの値を抽出
var items = xhr.responseXML.querySelectorAll('item ');
for(var i = 0; i < items.length; i++){
alert(items[i].about ); // undefined
alert(items[i].getAttribute('about ')); // null
alert(items[i].getAttribute('rdf :about ') ); // http://example.com
}
<![CDATA[]]>の中のテキスト抽出にはtextContentを使う
<![CDATA[]]>セクションのテキストを取り出すには、innerHTMLではなくtextContentを使います。
正確に言うと、innerHTMLでも取れなくはないですが、<![CDATA[ と ]]> のタグも含まれたテキストになります。
textContentだと、<![CDATA[ と ]]> のタグがついていません。
参考ページ「Node.textContent – Web API インターフェイス | MDN 」
CDATAの中は例えHTMLのソースだとしても、あくまでテキストです。そのHTML文の中にあるエレメントを抽出するにはそのソースを元にHTML化します。
例えば、<content:encoded>タグの中にあるブログの各ページの本文から<img />タグの個数を調べるには、次のような方法があります。
var items = xhr.responseXML.querySelectorAll('item');
for(var i = 0; i < items.length; i++){
var src = items[i].querySelector('encoded').textContent;
var div = document.createElement('div');
div.innerHTML = src;
var img = div.querySelectorAll('img');
alert(img.length);
}
HTMLの受け皿に、上ではdivを使っていますが、ちゃんとそれ用のobjectがあったような気もします。もし、それをご存知ならそちらを使われた方がよろしいかと。。
XMLHttpRequestで取得したデータを一覧表示するサンプル
以下は取得したいRSSのURLを入力すると、その結果を記事内の最初の画像付きで5個表示させるサンプルです。前のページ「Google Feed APIを使わずXMLHttpRequestでクロスドメインのRSSを取得する方法 まとめ 」の方法でクロスドメインに対応しています。
ただしこのソースでは簡略化のためAtomには未対応です。
Atomにも対応させるには下のソースのcallback関数を変えるだけです。このページの一番下に「RSSとAtom両対応版のcallback関数」を置いておきます。
<!-- HTML部分 -->
<p id="sample_form">
<input type="text" name="sample" value="" placeholder="RSSのURLを入力して送信して下さい" />
<input type="button" value="送信" />
</p>
<div id="result"></div>
<!-- JavaScript部分 -->
<script>
// 絵文字を除外する
// imgタグが20ピクセル以下に指定されていたらfalse
// heightもwidthも未指定または20ピクセル以上ならtrue
function image_size_check(img){
if(img.height && 20 >= img.height){
return false;
}
if(img.width && 20 >= img.width){
return false;
}
return true;
}
// 本文中から画像を抽出
function get_image(content, link){
// contentの中のimgタグを全部取得
var div = document.createElement('div');
div.innerHTML = content;
var images = div.querySelectorAll('img');
for(var n = 0; n < images.length; n++){
// imagesの中で最初に見つかった20ピクセル以上の画像を抽出
// 20ピクセルは絵文字を除くため
if(image_size_check(images[n])){
// ブラウザによってはスラッシュ一つで始まる画像のURLが
// 開いているページのものになってしまうのでその対策
var pattern = /^(\S+?:\/)?\/([^\/\s]+).*$/;
var location_domain = window.location.href.replace(pattern, '$2');
var origin_domain = link.replace(pattern, '$2');
var src = images[n].src.replace(location_domain, origin_domain);
return '<p><img src="' + src + '" alt="画像" /></p>';
}
}
return '<p></p>';
}
// callback本体
function callback(rss){
var max_count = 5; // 5個まで表示
var items = rss.querySelectorAll('item');
var html = '';
for(var i = 0; i < items.length && i < max_count; i++){
var link = items[i].querySelector('link').textContent;
var title = items[i].querySelector('title').textContent;
var date = items[i].querySelector('date').textContent;
var content = items[i].querySelector('encoded').textContent;
var img = get_image(content, link);
date = date.replace(/^(\d{4})\-(\d{2})\-(\d{2})T.+$/,'$1年$2月$3日');
html += '<li>' + img
+ '<p><a href="' + link + '">' + title + '</a></p>'
+ '<p>' + date + '</p>'
+ '</li>';
}
document.querySelector('#result').innerHTML = '<ul>' + html + '</ul>';
}
function get_rss(url){
var rss_server = 'ここに自作RSS中継サーバーのURL http://example.com/ ';
var pattern = /^(\S+?:\/)?\/([^\/\s]+).*$/;
var server_domain = rss_server.replace(pattern, '$2');
var location = window.location.href.replace(pattern, '$2');
/*
指定されたRSSのドメインが閲覧中のページのドメインと
同じならそのままRSSが取得できるので
違う場合のみ中継サーバーを使う。
*/
if(server_domain != location){
url = rss_server + '?rss=' + encodeURIComponent(url);
}
if(window.XDomainRequest){
var xhr = new XDomainRequest();
xhr.onload = function(){callback(this.responseXML);};
xhr.onerror = function(){}
xhr.open('GET', url, true);
xhr.send('');
}else if(window.XMLHttpRequest){
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(this.readyState == 4 && this.status == 200){
callback(this.responseXML);
}
};
xhr.open('GET', url, true);
xhr.send('');
}
}
var form = document.querySelectorAll('#sample_form input');
form[1].onclick = function(){
get_rss(form[0].value);
}
</script>
下のフォームにRSSかAtomのURLを入力して送信ボタンを押すと最新記事一覧を(画像があるページではページ内の画像付きで)5個表示します。クロスドメイン対応です。
注) エラーの時はエラーメッセージは表示せず、何もしません。 bloggerのURLはリダイレクトされるので、リダイレクト先のURLを入力しない場合は取得しません。
ちなみにCSSは次の通りです。
CSSの下にRSSとAtom両対応版のcallback関数を置いておきます。callback以外は上と同じです。
<!-- CSS部分 -->
<style>
#result {
margin:0;
padding:0;
border:none;
width:400px;
background:transparent;
}
#result ul,
#result li {
list-style-type:none !important;
background:transparent;
}
#result li {
padding-bottom:0.5em;
margin-bottom:0.5em;
border-bottom: 2px solid #ddd;
}
#result li:last-child {
border-bottom:none;
}
#result li:after {
content: ".";
display: block;
height: 0;
font-size:0;
clear: both;
visibility:hidden;
}
#result img {
width:100%;
height:auto;
}
#result a {
text-decoration:none;
line-height:1.1em;
background:transparent;
}
#result p {
margin:0;
padding:0;
line-height:1em;
background:transparent;
}
#result p:nth-child(1) {
width:80px;
float:left;
margin-right:5px;
}
#result p:nth-child(2) {
display:block;
overflow:hidden;
}
#result p:nth-child(3) {
line-height:1.4em;
text-align:right;
font-size:90%;
}
#sample_form input:nth-child(1) {
width:400px;
}
</style>
<!-- RSSとAtom両対応版のcallback関数 -->
<script>
function callback(xml, content_type){
// HTMLの生成関数(callbackの外に書いてもOK)
function make_html(link, title, date, img){
return '<li>' + img
+ '<p><a href="' + link + '">' + title + '</a></p>'
+ '<p>' + date + '</p>'
+ '</li>';
}
var count_max = 5;
var i = 0;
var html = '';
var items = xml.querySelectorAll('item');
if(items.length){
if(items[0].querySelector('date')){
// RSS
// Seesaa FC2 livedoor(livedoorはRSSとAtomがある)
for(var i = 0; i < items.length && i < count_max; i++){
var link = items[i].querySelector('link').textContent;
var title = items[i].querySelector('title').textContent;
var date = items[i].querySelector('date').textContent;
var content = items[i].querySelector('encoded').textContent;
var img = get_image(content, link);
date = date.replace(/^(\d{4})\-(\d{2})\-(\d{2})T.+$/,'$1年$2月$3日')
html += make_html(link, title, date, img);
}
}else{
// Atom
// WordPress blogger
// ただしbloggerのURLはリダイレクトされるので
// リダイレクト先のURLを入力しない場合は取得しない
for(var i = 0; i < items.length && i < count_max; i++){
var link = items[i].querySelector('link').textContent;
var title = items[i].querySelector('title').textContent;
var date = items[i].querySelector('pubDate').textContent;
var thumb = items[i].querySelector('thumbnail');
var content = items[i].querySelector('encoded');
var img = '<p></p>';
date = date.replace(/^\S+?,\s+([0-9]+)\s+(\S+)\s+([0-9]+)\s.+$/,function(all, g1, g2, g3){
var m = {
'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
};
return g3 + '年' + m[g2] + '月' + g1 + '日';
});
if(thumb){
var src = thumb.getAttribute('url');
img = '<p><img src="' + src + '" alt="画像" /></p>';
}else if(content){
img = get_image(content.textContent, link);
}
html += make_html(link, title, date, img);
}
}
}else{
// livedoorのAtom
items = xml.querySelectorAll('entry');
for(var i = 0; i < items.length && i < count_max; i++){
var link = items[i].querySelector('link').getAttribute('href');
var title = items[i].querySelector('title').textContent;
var date = items[i].querySelector('issued').textContent;
var content = items[i].querySelector('content').textContent;
var img = get_image(content, link);
date = date.replace(/^(\d{4})\-(\d{2})\-(\d{2})T.+$/,'$1年$2月$3日')
html += make_html(link, title, date, img);
}
}
document.querySelector('#result').innerHTML = '<ul>' + html + '</ul>';
}
</script>