全ての形式に対応させる

 RSS 1.0, RSS 2.0, Atomに対応した簡易RSSリーダーを作成してみます。まず、読み込まれたデータがRSS 1.0なのかRSS 2.0なのかAtomを調べる必要があります。以下のスクリプトで種類を調べます。RSS 1.0の場合はRDF, RSS2.0の場合はRSS, Atomの場合はAtomの文字を返します。厳密には必ず、いずれかの形式であることが前提なので適当なXMLデータの場合には正しく判別できません。また、文法的に合ってない物やUTF-8の文字コードでない場合なども正しく判別できません。

<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>RSSの種類を調べる</title>
<link rel="stylesheet" href="main.css" type="text/css" media="all">
<script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript"><!--
function rssCheck()
{
var rssURL = $("siteURL").value;
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","getrss.rb?query="+rssURL+"&cache="+(new Date()).getTime(),true);
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
checkRSSver(httpObj.responseXML);
}else{
$("result").innerHTML = "読み込み中です...";
}
}
// 種類を調べる
function checkRSSver(sXML)
{
var resultText = "";
try { tName = sXML.childNodes[1].tagName; }
catch(e){ var tName = sXML.childNodes[0].tagName; }
if (tName == "feed") resultText = "Atom";
if (tName == "rss") resultText = "RSS";
if (tName == "rdf:RDF") resultText = "RDF";
$("result").innerHTML = tName + "<br><b>" + resultText + "</b>";
}
// --></script>
</head>
<body>
<h1>RSSの種類を調べる</h1>
<form method="get" name="ajaxForm" onsubmit="rssCheck();return false;">
<select id="siteURL">
<option value="http://hotwired.goo.ne.jp/news/index.rdf">Wired (RSS 1.0/RDF)</option>
<option value="http://japan.cnet.com/rss/index.rdf">CNET (RSS 1.0/RDF)</option>
<option value="http://rss.fujitv.co.jp/whatsnew.xml">フジテレビ (RSS 2.0)</option>
<option value="http://www.openspc2.org/blog/atom.xml">OpenSpace (Atom)</option>
</select>
<input type="button" value="チェックする" onClick="rssCheck()">
</form>
<div id="result"></div>
</div>
</body>
</html>

 RSS 1.0とRSS 2.0は同じスクリプトで動作しますが、Atomは同じスクリプトが動作しません。そこでAtomの場合だけ別の処理を行います。RSSでは記事のタイトルやリンク、要約記事はitemタグ内に、Atomではentryタグ内に記述されています。これは以下のように種類を判別して読み出すタグを設定しておきます。

if (ver == "Atom")
{
itemTag = "entry";
catTag = "summary";
}

 後は以下のようにするとカテゴリの情報が配列itemListに格納されます。

itemList = sXML.getElementsByTagName(itemTag)

 あとはカテゴリ情報からタイトルとリンク、要約記事を読み出します。まず、リンクですがRSSではlinkタグの最初のノードの内容を読み出しますが、Atomの場合にはlinkタグのhref属性の内容を読み出します。
 要約記事もRSSとAtomでタグが異なるため、種類を判別してRSSならdescription、Atomならsummaryタグの内容を読み出します。ただし、要約記事のタグは必須ではないため、省略された場合も考えておく必要があります。省略された場合に読み出そうとするとはエラーが発生するのでtry..catchを使って処理します。(サンプルを実行する

<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>RSS, Atomの見出しと記事内容を表示する</title>
<link rel="stylesheet" href="main.css" type="text/css" media="all">
<script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript"><!--
function rssSearch()
{
var rssURL = $("siteURL").value;
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","getrss.rb?query="+rssURL+"&cache="+(new Date()).getTime(),true);
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
parseRSS(httpObj.responseXML);
}else{
$("result").innerHTML = "読み込み中です...";
}
}
// RSS, Atomを解析
function parseRSS(sXML)
{
var ver = checkRSSversion(sXML);
var itemTag = "item";
var catTag = "description";
if (ver == "Atom")
{
itemTag = "entry";
catTag = "summary";
}
var resultText = rssTitle = rssLink = rssText = "";
var itemList = sXML.getElementsByTagName(itemTag);
for (var i=0; i<itemList.length; i++)
{
rssTitle = itemList[i].getElementsByTagName("title")[0].childNodes[0].nodeValue;
if (ver == "Atom")
{
rssLink = itemList[i].getElementsByTagName("link")[0].getAttribute("href");
}else{
rssLink = itemList[i].getElementsByTagName("link")[0].childNodes[0].nodeValue;
}
try { rssText = itemList[i].getElementsByTagName(catTag)[0].childNodes[0].nodeValue; }catch(e){ rssText = "" }
resultText += '<a href="'+rssLink+'">'+rssTitle + '</a><div class="desc">'+rssText+'</div>';
}
$("result").innerHTML = resultText;
}
// 種類を調べる
function checkRSSversion(sXML)
{
var resultText = "";
try { tName = sXML.childNodes[1].tagName; }
catch(e){ var tName = sXML.childNodes[0].tagName; }
if (tName == "feed") resultText = "Atom";
if (tName == "rss") resultText = "RSS";
if (tName == "rdf:RDF") resultText = "RDF";
return resultText;
}
// --></script>
</head>
<body>
<h1>RSS, Atomの見出しと記事内容を表示する</h1>
<p>Safariでは動作しない</p>
<form method="get" name="ajaxForm" onsubmit="rssSearch();return false;">
<select id="siteURL">
<option value="http://www.openspc2.org/blog/atom.xml">OpenSpace (Atom)</option>
<option value="http://blog.hada.org/naoki/atom.xml">羽田製茶 (Atom)</option>
<option value="http://hotwired.goo.ne.jp/news/index.rdf">Wired (RSS 1.0/RDF)</option>
<option value="http://japan.cnet.com/rss/index.rdf">CNET (RSS 1.0/RDF)</option>
<option value="http://www3.asahi.com/rss/index.rdf">Asahi.com (RSS 1.0/RDF)</option>
<option value="http://nikkeibp.jp/jp/flash/index.rdf">日経BP (RSS 1.0/RDF)</option>
<option value="http://japan.zdnet.com/rss/news/index.rdf">ZDNET (RSS 1.0/RDF)</option>
<option value="http://rss.news.yahoo.com/rss/nasashuttle">Yahoo.com - NASA - (RSS 2.0)</option>
<option value="http://rss.fujitv.co.jp/whatsnew.xml">フジテレビ (RSS 2.0)</option>
</select>
<input type="button" value="検索する" onClick="rssSearch()">
</form>
<div id="result"></div>
</div>
</body>
</html>

 これで、完成といきたいところですが、困ったことにSafari 2では動作しません。Safari 2ではgetElementsByTagName()で、うまくタグリストが取得できなかったり、getAttributes()が期待通り動作しない場合があるためです。そこで、getElementsByTagName()を使った方法をやめて、ノードを調べて該当するノード名(タグ名)があるかどうかをfor()命令で繰り返し調べます。この方法は効率の良い方法ではありませんが、Safari 2でも他のブラウザでも同じスクリプトで動作させることができます。(サンプルを実行する

<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>RSS, Atomの見出しと記事内容を表示する</title>
<link rel="stylesheet" href="main.css" type="text/css" media="all">
<script type="text/javascript" src="xmlhttp.js"></script>
<script type="text/javascript"><!--
ver = "";
function rssSearch()
{
var rssURL = $("siteURL").value;
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","getrss.rb?query="+rssURL+"&cache="+(new Date()).getTime(),true);
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
parseRSS(httpObj.responseXML);
}else{
$("result").innerHTML = "読み込み中です...";
}
}
// RSS, Atomを解析
function parseRSS(sXML)
{
var resultText = rssTitle = rssLink = rssText = "";
ver = checkRSSversion(sXML);
var itemTag = "item";
var catTag = "description";
if (ver == "Atom") itemTag = "entry";
var itemList = sXML.getElementsByTagName(itemTag);
for (var i=0; i<itemList.length; i++)
{
rssTitle = getTitle(itemList[i]);
rssLink = getLink(itemList[i]);
rssText = getDescription(itemList[i]);
resultText += '<a href="'+rssLink+'">'+rssTitle + '</a><div class="desc">'+rssText+'</div>';
}
$("result").innerHTML = resultText;
}
// item, entryタグ内のtitleタグの内容を返す
function getTitle(sTag)
{
for (var i=0; i<sTag.childNodes.length; i++)
{
if (sTag.childNodes[i].tagName == "title") return sTag.childNodes[i].childNodes[0].nodeValue;
}
return "";
}
// item, entryタグ内のlinkタグの内容を返す
function getLink(sTag)
{
for (var i=0; i<sTag.childNodes.length; i++)
{
if (sTag.childNodes[i].tagName == "link")
{
if (ver == "Atom")
{
for (var j=0; j<sTag.childNodes[i].attributes.length; j++)
{
if (sTag.childNodes[i].attributes[j].name == "href") return sTag.childNodes[i].attributes[j].value;
}
}else{
return sTag.childNodes[i].childNodes[0].nodeValue;
}
}
}
return "";
}
// description, summaryタグ内の内容を返す
function getDescription(sTag)
{
var catTag = "description";
if (ver == "Atom") catTag = "summary";
for (var i=0; i<sTag.childNodes.length; i++)
{
if (sTag.childNodes[i].tagName == catTag)
{
if (sTag.childNodes[i].hasChildNodes()) return sTag.childNodes[i].childNodes[0].nodeValue;
}
}
return "";
}
// 種類を調べる
function checkRSSversion(sXML)
{
var resultText = "";
try { tName = sXML.childNodes[1].tagName; }
catch(e){ var tName = sXML.childNodes[0].tagName; }
if (tName == "feed") resultText = "Atom";
if (tName == "rss") resultText = "RSS";
if (tName == "rdf:RDF") resultText = "RDF";
return resultText;
}
// --></script>
</head>
<body>
<h1>RSS, Atomの見出しと記事内容を表示する</h1>
<p>Safari対応</p>
<form method="get" name="ajaxForm" onsubmit="rssSearch();return false;">
<select id="siteURL">
<option value="http://www.openspc2.org/blog/atom.xml">OpenSpace (Atom)</option>
<option value="http://blog.hada.org/naoki/atom.xml">羽田製茶 (Atom)</option>
<option value="http://hotwired.goo.ne.jp/news/index.rdf">Wired (RSS 1.0/RDF)</option>
<option value="http://japan.cnet.com/rss/index.rdf">CNET (RSS 1.0/RDF)</option>
<option value="http://www3.asahi.com/rss/index.rdf">Asahi.com (RSS 1.0/RDF)</option>
<option value="http://nikkeibp.jp/jp/flash/index.rdf">日経BP (RSS 1.0/RDF)</option>
<option value="http://japan.zdnet.com/rss/news/index.rdf">ZDNET (RSS 1.0/RDF)</option>
<option value="http://rss.fujitv.co.jp/whatsnew.xml">フジテレビ (RSS 2.0)</option>
</select>
<input type="button" value="検索する" onClick="rssSearch()">
</form>
<div id="result"></div>
</div>
</body>
</html>

 これで一応できあがりですが、タグが不当なものだったり、タグが存在しない場合にはエラーが発生してしまいます。また、RSS 2.0でdescription内にHTMLタグが含まれている場合にもInternet Explorerなどでは正しく表示できません。他にも、正しく表示できないRSS, Atomがいくつか存在しますが、それらにも対応するのであれば、try...catchでエラーとして処理し無視してしまう方法もあります。
 また、最初の段階で動作対象とするブラウザで動作する命令を確認して調べておかないと、今回のように最後にスクリプトを作り直す事になってしまいます。動くと思っていた命令が特定条件やブラウザでは動かないこともあります。

[目次へ]

(2006.1.30)