RSSを利用して記事を検索する

 ここではRSSを利用して記事を検索してみます。RSSを利用する→RSSリーダーのようなものを作るのか、といえばそうではありません。ここでは単純に特定のサイトで提供されているRSSデータを表示して記事を検索してみます。(RSSに関してはWebサイトに多くの解説があります。また、書籍では詳解RSSという本が出ています)
 まず、複数のサイトで提供されているRSSのURLを配列で用意しておきます。この配列の要素の数だけ(指定されたサイトの数だけ)読み込みを繰り返すようにします。複数のサイトを読み込む場合に、ここでは1つのサイトのデータを読み込んだら次のサイトのデータを読み込むようにします。データを読み込んだら記事内に文字列があるかどうか調べます。ここでは正規表現ではなくindexOf()を使って調べています。文字列が見つかった場合には見つかった文字列の位置を返し、見つからなかった場合には-1を返します。-1の場合には見つからなかったというメッセージを表示し、見つかった場合にはRSSの内容をそのまま表示します(サンプルを実行する

<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"><!--
srchURL = [ "http://hotwired.goo.ne.jp/news/index.rdf",
"http://japan.cnet.com/rss/index.rdf"];
count = 0;
srchStr = ""; // 検索文字列
function startSearch()
{
count = 0; // 検索サイトカウンタ
srchStr = document.getElementById("query").value;
if (srchStr == "") return;
siteSearch(srchURL[count]);
}
function siteSearch(sURL)
{
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","curl.rb?query="+sURL+"&cache="+(new Date()).getTime(),true);
$("progress").style.visibility = "visible";
$("query").disabled = true;
$("sendButton").disabled = true;
$("findURL").innerHTML = srchURL[count];
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
newWindow();
var str = httpObj.responseText;
if (str.indexOf(srchStr) > -1)
{
$("result"+ winNum).innerHTML = str;
}else{
$("result"+ winNum).innerHTML = srchURL[count]+"<br>該当文字列はありませんでした";
}
winNum++;
count++;
if (count >= srchURL.length)
{
$("query").disabled = false;
$("sendButton").disabled = false;
$("progress").style.visibility = "hidden";
}else{
siteSearch(srchURL[count]);
}
}
}
// 通信を中断する
function abortHttp()
{
if (httpObj)
{
httpObj.abort();
$("progress").style.visibility = "hidden";
$("query").disabled = false;
$("sendButton").disabled = false;
}
}
function newWindow()
{
$("progress").style.visibility = "hidden";
var dObj = $("window0").cloneNode(true);
var winObj = $("contents").appendChild(dObj);
winObj.id = "window"+winNum;
winObj.childNodes[0].id = "result"+winNum;
addEvent("window"+winNum,"click",closeWindow,false);
addEvent("window"+winNum,"mousedown",activeWindowDrag,false);
addEvent("window"+winNum,"mousedown",dragObj.dragStart,false);
winObj.style.left = 10+Math.floor(Math.random()*400)+"px";
winObj.style.top = 150+Math.floor(Math.random()*200)+"px";
winObj.style.visibility ="visible";
winObj.style.zIndex = dragObj.zIndex + 1;
winObj.childNodes[0].style.visibility = "visible";
dragObj.window.push("window"+winNum);
dragObj.maxLayer = dragObj.window.length;
inactiveWindow();
activeWindow(winNum);
}
// ウィンドウをアクティブにする処理
function activeWindow(n)
{
$("window"+n).style.backgroundImage = "url(titlebar.gif)";
}
function activeWindowDrag(evt)
{
inactiveWindow();
var obj = getEventTarget(evt);
if (obj.id.indexOf("window") == -1) return;
obj.style.backgroundImage = "url(titlebar.gif)";
}
// ウィンドウを全てインアクティブにする処理
function inactiveWindow()
{
for (var i=0; i<winNum; i++)
{
try {
$("window"+i).style.backgroundImage = "url(titlebar2.gif)";
}catch(e){}
}
}
// ウィンドウを閉じる処理
function closeWindow(e)
{
// クローズボックス上でマウスボタンが押されたか?
if ((dragObj.offsetX < 2) || (dragObj.offsetX >14) || (dragObj.offsetY < 4) || (dragObj.offsetY > 16)) return;
wObj = getEventTarget(e);
contObj = $("contents");
// ウィンドウをノードから削除
for (var i=0; i<contObj.childNodes.length; i++)
{
if(contObj.childNodes[i].id == wObj.id) contObj.removeChild(contObj.childNodes[i]);
}
// ウィンドウリストから削除
var temp = new Array();
for (i=0; i<dragObj.window.length; i++)
{
if (dragObj.window[i] != wObj.id) temp.push(dragObj.window[i]);
}
dragObj.window = temp;
}
function initObj()
{
window.document.onmousemove = dragObj.dragProc;
window.document.onmouseup = dragObj.dragEnd;
winNum = 1;
dragObj.window = [];
dragObj.maxLayer = 1; // 最大レイヤー枚数
}
// --></script>
</head>
<body onload="initObj()" oncontextmenu="return false">
<h1>RSSで記事を検索する</h1>
<form method="get" name="ajaxForm" onsubmit="startSearch();return false;">
<input type="text" value="" id="query" size="80"><br>
<input type="button" value="検索する" onClick="startSearch()" id="sendButton">
<input type="button" value="読み込みを中断" onClick="abortHttp()" id="abortButton">
</form>
<div id="contents"></div>
<div id="window0" class="windowBorder"><div id="result0" class="windowContents"></div>
</div>
<div id="progress"><img src="ring.gif"> <span id="findURL"></span><br>データを読み込み中です...</div>
</body>
</html>

 記事は表示されるようになりましたが、検索した文字がどこなのかが分かりません。まともにRSSを解析していないせいもありますが(別章で説明します)、文字列の色やサイズなどが全く同じため見にくくなっています。そこで、一致した文字列を赤色にして目立つようにしてみます。このような場合には正規表現を使って置換処理を行うと簡単です。置換処理を行うのはreplace()メソッドです。最初の引数(パラメータ)が正規表現文字列、次が一致した場合に置換する文字列*1になります。例えばstr.replace(/abc/g,"def")とした場合、変数str内の文字列にabcがある場合defに置換します。しかし、今回のような場合には、直接変数名を指定してもうまく動作しません。このような場合にはnew RegExp(変数名)として正規表現文字列(オブジェクト)を生成し、それをreplace()の最初の引数とします。2番目の引数には置換文字列を指定しますが、色を変えるのにspanタグを使い、その中で直接スタイルを指定します。通常、スタイルシートで指定する場合、クラスやIDを使いますが、このような局所的な処理で、他のスタイルに依存したくない場合には、このように指定してしまう方がよいことがあります。
 これで検索文字列が赤色で表示することができるようになりました。(サンプルを実行する

*1 文字列でなく関数を指定し、その戻り値を置換する文字列とすることができます。ただし、一部のブラウザでは関数を指定することはできません。

<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"><!--
srchURL = [ "http://hotwired.goo.ne.jp/news/index.rdf",
"http://japan.cnet.com/rss/index.rdf"];
count = 0;
srchStr = ""; // 検索文字列
function startSearch()
{
count = 0; // 検索サイトカウンタ
srchStr = document.getElementById("query").value;
if (srchStr == "") return;
siteSearch(srchURL[count]);
}
function siteSearch(sURL)
{
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","curl.rb?query="+sURL+"&cache="+(new Date()).getTime(),true);
$("progress").style.visibility = "visible";
$("query").disabled = true;
$("sendButton").disabled = true;
$("findURL").innerHTML = srchURL[count];
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
newWindow();
var str = httpObj.responseText;
if (str.indexOf(srchStr) > -1)
{
var regObj = new RegExp(srchStr,"g");
var repStr = str.replace(regObj, "<span style='color:red'>"+srchStr+"</span>");
$("result"+ winNum).innerHTML = repStr;
}else{
$("result"+ winNum).innerHTML = srchURL[count]+"<br>該当文字列はありませんでした";
}
winNum++;
count++;
if (count >= srchURL.length)
{
$("query").disabled = false;
$("sendButton").disabled = false;
$("progress").style.visibility = "hidden";
}else{
siteSearch(srchURL[count]);
}
}
}
// 通信を中断する
function abortHttp()
{
if (httpObj)
{
httpObj.abort();
$("progress").style.visibility = "hidden";
$("query").disabled = false;
$("sendButton").disabled = false;
}
}
function newWindow()
{
$("progress").style.visibility = "hidden";
var dObj = $("window0").cloneNode(true);
var winObj = $("contents").appendChild(dObj);
winObj.id = "window"+winNum;
winObj.childNodes[0].id = "result"+winNum;
addEvent("window"+winNum,"click",closeWindow,false);
addEvent("window"+winNum,"mousedown",activeWindowDrag,false);
addEvent("window"+winNum,"mousedown",dragObj.dragStart,false);
winObj.style.left = 10+Math.floor(Math.random()*400)+"px";
winObj.style.top = 150+Math.floor(Math.random()*200)+"px";
winObj.style.visibility ="visible";
winObj.style.zIndex = dragObj.zIndex + 1;
winObj.childNodes[0].style.visibility = "visible";
dragObj.window.push("window"+winNum);
dragObj.maxLayer = dragObj.window.length;
inactiveWindow();
activeWindow(winNum);
}
// ウィンドウをアクティブにする処理
function activeWindow(n)
{
$("window"+n).style.backgroundImage = "url(titlebar.gif)";
}
function activeWindowDrag(evt)
{
inactiveWindow();
var obj = getEventTarget(evt);
if (obj.id.indexOf("window") == -1) return;
obj.style.backgroundImage = "url(titlebar.gif)";
}
// ウィンドウを全てインアクティブにする処理
function inactiveWindow()
{
for (var i=0; i<winNum; i++)
{
try {
$("window"+i).style.backgroundImage = "url(titlebar2.gif)";
}catch(e){}
}
}
// ウィンドウを閉じる処理
function closeWindow(e)
{
// クローズボックス上でマウスボタンが押されたか?
if ((dragObj.offsetX < 2) || (dragObj.offsetX >14) || (dragObj.offsetY < 4) || (dragObj.offsetY > 16)) return;
wObj = getEventTarget(e);
contObj = $("contents");
// ウィンドウをノードから削除
for (var i=0; i<contObj.childNodes.length; i++)
{
if(contObj.childNodes[i].id == wObj.id) contObj.removeChild(contObj.childNodes[i]);
}
// ウィンドウリストから削除
var temp = new Array();
for (i=0; i<dragObj.window.length; i++)
{
if (dragObj.window[i] != wObj.id) temp.push(dragObj.window[i]);
}
dragObj.window = temp;
}
function initObj()
{
window.document.onmousemove = dragObj.dragProc;
window.document.onmouseup = dragObj.dragEnd;
winNum = 1;
dragObj.window = [];
dragObj.maxLayer = 1; // 最大レイヤー枚数
}
// --></script>
</head>
<body onload="initObj()" oncontextmenu="return false">
<h1>RSSで記事を検索する</h1>
<form method="get" name="ajaxForm" onsubmit="startSearch();return false;">
<input type="text" value="" id="query" size="80"><br>
<input type="button" value="検索する" onClick="startSearch()" id="sendButton">
<input type="button" value="読み込みを中断" onClick="abortHttp()" id="abortButton">
</form>
<div id="contents"></div>
<div id="window0" class="windowBorder"><div id="result0" class="windowContents"></div>
</div>
<div id="progress"><img src="ring.gif"> <span id="findURL"></span><br>データを読み込み中です...</div>
</body>
</html>

 記事が見つからない場合にも次々とウィンドウが表示されてしまいますので、記事が見つからない場合にはウィンドウを表示しないようにすることもできます。これはフォームにチェックボックスを作成し、その値に応じて処理を行います。(実際のサンプルを実行する

<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"><!--
srchURL = [ "http://hotwired.goo.ne.jp/news/index.rdf",
"http://japan.cnet.com/rss/index.rdf"];
count = 0;
srchStr = ""; // 検索文字列
function startSearch()
{
count = 0; // 検索サイトカウンタ
srchStr = document.getElementById("query").value;
if (srchStr == "") return;
siteSearch(srchURL[count]);
}
function siteSearch(sURL)
{
httpObj = createXMLHttpRequest(displayData);
if (httpObj)
{
httpObj.open("GET","curl.rb?query="+sURL+"&cache="+(new Date()).getTime(),true);
$("progress").style.visibility = "visible";
$("query").disabled = true;
$("sendButton").disabled = true;
$("findURL").innerHTML = srchURL[count];
httpObj.send(null);
}
}
function displayData()
{
if ((httpObj.readyState == 4) && (httpObj.status == 200))
{
var str = httpObj.responseText;
if (str.indexOf(srchStr) > -1)
{
newWindow();
var regObj = new RegExp(srchStr,"g");
var repStr = str.replace(regObj, "<span style='color:red'>"+srchStr+"</span>");
$("result"+ winNum).innerHTML = repStr;
}else{
if ($("notFoundFlag").checked == false)
{
$("result"+ winNum).innerHTML = srchURL[count]+"<br>該当文字列はありませんでした";
}
}
winNum++;
count++;
if (count >= srchURL.length)
{
$("query").disabled = false;
$("sendButton").disabled = false;
$("progress").style.visibility = "hidden";
}else{
siteSearch(srchURL[count]);
}
}
}
// 通信を中断する
function abortHttp()
{
if (httpObj)
{
httpObj.abort();
$("progress").style.visibility = "hidden";
$("query").disabled = false;
$("sendButton").disabled = false;
}
}
function newWindow()
{
$("progress").style.visibility = "hidden";
var dObj = $("window0").cloneNode(true);
var winObj = $("contents").appendChild(dObj);
winObj.id = "window"+winNum;
winObj.childNodes[0].id = "result"+winNum;
addEvent("window"+winNum,"click",closeWindow,false);
addEvent("window"+winNum,"mousedown",activeWindowDrag,false);
addEvent("window"+winNum,"mousedown",dragObj.dragStart,false);
winObj.style.left = 10+Math.floor(Math.random()*400)+"px";
winObj.style.top = 150+Math.floor(Math.random()*200)+"px";
winObj.style.visibility ="visible";
winObj.style.zIndex = dragObj.zIndex + 1;
winObj.childNodes[0].style.visibility = "visible";
dragObj.window.push("window"+winNum);
dragObj.maxLayer = dragObj.window.length;
inactiveWindow();
activeWindow(winNum);
}
// ウィンドウをアクティブにする処理
function activeWindow(n)
{
$("window"+n).style.backgroundImage = "url(titlebar.gif)";
}
function activeWindowDrag(evt)
{
inactiveWindow();
var obj = getEventTarget(evt);
if (obj.id.indexOf("window") == -1) return;
obj.style.backgroundImage = "url(titlebar.gif)";
}
// ウィンドウを全てインアクティブにする処理
function inactiveWindow()
{
for (var i=0; i<winNum; i++)
{
try {
$("window"+i).style.backgroundImage = "url(titlebar2.gif)";
}catch(e){}
}
}
// ウィンドウを閉じる処理
function closeWindow(e)
{
// クローズボックス上でマウスボタンが押されたか?
if ((dragObj.offsetX < 2) || (dragObj.offsetX >14) || (dragObj.offsetY < 4) || (dragObj.offsetY > 16)) return;
wObj = getEventTarget(e);
contObj = $("contents");
// ウィンドウをノードから削除
for (var i=0; i<contObj.childNodes.length; i++)
{
if(contObj.childNodes[i].id == wObj.id) contObj.removeChild(contObj.childNodes[i]);
}
// ウィンドウリストから削除
var temp = new Array();
for (i=0; i<dragObj.window.length; i++)
{
if (dragObj.window[i] != wObj.id) temp.push(dragObj.window[i]);
}
dragObj.window = temp;
}
function initObj()
{
window.document.onmousemove = dragObj.dragProc;
window.document.onmouseup = dragObj.dragEnd;
winNum = 1;
dragObj.window = [];
dragObj.maxLayer = 1; // 最大レイヤー枚数
}
// --></script>
</head>
<body onload="initObj()" oncontextmenu="return false">
<h1>RSSで記事を検索する</h1>
<form method="get" name="ajaxForm" onsubmit="startSearch();return false;">
<input type="text" value="" id="query" size="80"><br>
<input type="button" value="検索する" onClick="startSearch()" id="sendButton">
<input type="button" value="読み込みを中断" onClick="abortHttp()" id="abortButton">
<input type="checkbox" id="notFoundFlag" checked>記事が見つからない場合にはウィンドウを表示しない
</form>
<div id="contents"></div>
<div id="window0" class="windowBorder"><div id="result0" class="windowContents"></div>
</div>
<div id="progress"><img src="ring.gif"> <span id="findURL"></span><br>データを読み込み中です...</div>
</body>
</html>

 まとも(?)なRSSリーダーは別章でやりたいと思います。次の章ではGoogle Mapsを扱います。

[第七章 1:Google Mapsを使うへ]
[目次へ]

(2006.1.22)