2006年6月26日 (月)

ココログ記事検索の仕組み Part 3

さて、最後は検索と検索結果の表示です。
検索は昨日載せたBlogEntryクラスのsearchメソッドで行っているのですが、特に変わったことはしておらず、単に正規表現で マッチしたらヒットした部分付近の文字列を返しているだけです。
そして、その結果表示は以下のような感じで行っています。
var root = document.createElement('div');
				
var te = document.createElement('a');
te.appendChild(document.createTextNode(entry.title));
te.href = 'javascript:showEntry("'+entry.url+'");';
root.appendChild(te);

var ce = document.createElement('div');
ce.innerHTML = hitString;
root.appendChild(ce);

$("result_element").appendChild(root);
エレメントをいくつか生成していますが、これは以下のような構造のHTMLを構築しています。
<div>
<a href="javascript:showEntry(URL);">タイトル</a>
<div>ヒットした部分文字列</div>
</div>
$("result_element") の部分は見慣れないかもしれませんが、これはPrototype.jsで定義されている関数で、 DynamicHTMLで頻繁に登場する document.getElementById の代わりに使用することが出来ます。
これによって、コードが若干短く、見やすくなると思います。
つまり、この場合は
document.getElementById("result_element").appendChild(root);
と同じことを行っています。
result_elementというのは、あらかじめ結果表示用に用意してある緑色の枠のレイヤーです。

これで、検索結果を表示用に用意してあるレイヤーに挿入できましたので、後はレイヤーを表示して完了です。
ここまで見ていただけるとわかるように、コードの大半は記事の抽出のためのものですので、記事が増えると遅くなってしまうと思いますが、 少しずつ改良していく予定です。

| | コメント (2) | トラックバック (0)

2006年6月25日 (日)

ココログ記事検索の仕組み Part 2

昨日は、月別にアーカイブされた記事のURLを取得するところまで書きましたので、今日はそのURLからHTMLを取得して検索を行う部分を書きます。
まず、以下のように月別のURLを格納している配列から順にHTMLを取得しています。
for(var i=0;i<cocosearch_archiveUrls.length;i++){
    var url = archiveUrls[i];
    new Ajax.Request(url, {method:'GET',onComplete:entryPageLoaded});
}
そして、HTMLが読み込まれるときに呼ばれるように指定しているentryPageLoaded内で検索を行っています。
そのentryPageLoaded関数は、以下のような感じです。
var BlogEntry = Class.create();
BlogEntry.prototype = {
    initialize : function(title, content, date, url){
        this.title = title;
        this.content = content;
        this.date = date;
        this.url = url;
    },

    search : function(keyword){
        var re = new RegExp('('+keyword+')', 'ig');
        var r;
        if((r=re.exec(this.content))){
            var i = r.index;
            i = (i > 25) ? (i-20) : i;
            var l = (this.content.length-i	> 100) ? 90 : this.content.length-i;
            return "..." + this.content.substr(i, l).replace(re, '<b>\$1</b>') + "...";
        }
        return "";
    }
};

var entryCache = [];

function entryPageLoaded(r)
{
    var text = r.responseText.replace(/[\r\n]/g, "");
    var pt1 = '<div class="entry-top">.*?<div class="entry-bottom"></div>.*';
    var re1 = new RegExp(pt1, "gi");
    var pt2 = '<div class="entry">.*<h3>(.*?)</h3>';
    pt2 += '.*<div class="entry-body-text">(.*?)<div class="entry-body-bottom"></div>.*';
    pt2 += '<span class="post-footers">(.*?)<a href.*<a class="permalink" href="(.*?)">';
    var re2 = new RegExp(pt2, "gi");
    var results = text.match(re1);
    
    for(var i=0;i<results.length;i++){
        results[i].match(re2);
        var title = RegExp.$1;
        var content = RegExp.$2;
        var date = RegExp.$3;
        var url = RegExp.$4;
        content = content.replace(/<.*?>/gi, "");
        var blogEntry = new BlogEntry(title, content, date, url);
        entryCache.push(blogEntry);
        searchEntry(blogEntry);
    }
}
まず最初のBlogEntry = Class.create();の部分ですが、これはPrototype.jsの機能で、このように書くとクラスを定義することが出来ます。
このBlogEntryというクラスでは、記事のタイトル・内容・日付・URLを持つプロパティと、searchという検索を行うメソッドを定義しています。
切り出した各々の記事からこのBlogEntryクラスを生成し、その後に定義してあるentryCacheという配列に格納することで2回目以降の検索を高速化することが目的です。

月別にアーカイブされたページのHTMLの構成は、
<div class="entry-top">
....<div class="entry">.....<h3>1つ目の記事のタイトル</h3>....
....<div class="entry-body-text">1つ目の記事の内容<div class="entry-body-bottom"></div>
....<span class="post-footers">1つ目の記事の日付<a href="...">.....<a class="permalink" href="1つ目の記事のURL">....
...........
....<div class="entry">.....<h3>2つ目の記事のタイトル</h3>....
....<div class="entry-body-text">2つ目の記事の内容<div class="entry-body-bottom"></div>
....<span class="post-footers">2つ目の記事の日付<a href="...">.....<a class="permalink" href="2つ目の記事のURL">....
<div class="entry-bottom"></div>
のようになってます。
このような構成のHTMLから記事だけを抽出するために、entryPageLoaded関数では、まず<div class="entry-top">から<div class="entry-bottom"></div>までを切り出し、その結果から記事のタイトルや内容を抽出する作業をforループ内で繰り返し行っています。
記事の内容に関しては、HTMLタグが含まれていますのでタグが検索にヒットしないように、
content = content.replace(/<.*?>/gi, "");
の部分でHTMLタグを除去しています。

長い道のりでしたが、これでやっと記事の抽出まで完了しました。
この後は検索を行い結果を表示する部分ですが、長くなってしまいましたので続きはまた明日書きたいと思います。

| | コメント (0) | トラックバック (0)

2006年6月24日 (土)

ココログ記事検索の仕組み

コメントをいただいたので、先日設置した記事検索の仕組みについて書きたいと思います。
まず、ブログの場合、サーバーサイドのプログラムでデータベースなどにアクセスできないので、 ブログシステムが生成するHTMLファイルをJavaScriptを使って力技で強引に検索するしかありません。
つまり、最初に、記事が書かれているHTMLファイルのURLをどうにかして取り出す必要があるわけです。
色々見てみると、ココログの場合はブログのルートディレクトリにあるarchives.htmlというファイルからそのURLをたどれそうです。
左のサイドバーにある「バックナンバー」というリンクがそのファイルを指していて、 そのファイルを開くと月別に記事をまとめたHTMLファイルへのURLが書かれています。
具体的には以下のような形式になっています。
<div id="archive-datebased">
.......
<a href="http://wildcat.cocolog-nifty.com/web/2006/06/index.html">2006年6月</a>
.......
<div class="archive-category">
.......
つまり、<div id="archive-datebased">から<div class="archive-category">の間にあるaタグのhref属性を取り出せばOKということですよね。
そこで、まずXMLHttpRequestを使ってarchives.htmlのHTML文字列を取り出します。
今回は楽をするためにPrototype.jsを利用していますので、コードは以下のようなものになります。
var BLOG_URL = "http://wildcat.cocolog-nifty.com/web/";
........
var url = BLOG_URL + "archives.html";
new Ajax.Request(url, {method:'GET',onComplete:archivePageLoaded});
Prototype.jsについてはあらためて書こうと思っていますが、ご存知ない方は、1つ目の引数は取得したいURL、2つ目の引数は見慣れない形式かもしれませんが、 リクエストのmethodはGETで、読み込みが完了したらarchivePageLoadedという関数が呼ばれるように指定していると解釈してください。
そして、読み込み完了時に呼ばれるarchivePageLoaded関数内で<div id="archive-datebased">から<div class="archive-category">の間にあるaタグのhref属性を 取り出しています。
その関数のコードは以下のようになっています。
var archiveUrls = [];
........
function archivePageLoaded(r)
{
    var text = r.responseText;
    text = text.replace(/[\r\n]/g, "");
    var pattern = '<div id="archive-datebased">(.*?)<div class="archive-category">';
    var re1 = new RegExp(pattern, "i");
    text.match(re1);
    var dataRange = RegExp.$1;
    var re2 = new RegExp(BLOG_URL+"[0-9]{4}/[0-9]{2}/index.html", "gi");
    archiveUrls = dataRange.match(re2);
}
まず、引数のrは、XMLHttpRequestオブジェクトです。
そのresponseTextプロパティによって、取得したHTMLソースを取得します。
次に、2行目で改行を削除しています。 この後も正規表現が度々出てきますが、色々なサイトで詳しく解説されていますので、ご存知ない方は検索してみていただきたいのですが、簡単に言えば 指定したパターンにマッチした文字列を取り出すことが出来る便利なものです。 3行目では正規表現パターンを定義しています。<div id="archive-datebased">と<div class="archive-category">の間の文字列を後で取り出すために( )でグループ化しています。
そして、4行目・5行目でHTML文字列から正規表現パターンのマッチングを行い、6行目のRegExp.$1でマッチした中の1つ目のグループの文字列(3行目の正規表現パターンの( )内の文字列)を取り出しています。
これで、ようやく<div id="archive-datebased">と<div class="archive-category">の間のHTML部分を取り出せました。
この取り出したHTMLに目的のURLが
http://wildcat.cocolog-nifty.com/web/yyyy/mm/index.html
というパターンで記述されていますので、7行目のように
http://wildcat.cocolog-nifty.com/web/数字4文字/数字2文字/index.html
というパターンでマッチングを行い、あらかじめ定義してあったarchiveUrlsという配列に結果を格納しています。

これで、めでたく月別にアーカイブされた記事のURLを取得することが出来ました。
明日は、このURLを使って各月の記事が書かれたHTMLを取得し記事を検索する仕組みを書きたいと思います。

| | コメント (0) | トラックバック (0)

2006年6月21日 (水)

JavaScriptで記事の検索

ブログ始めたばかりでイマイチ勝手がわかってませんが、ココログって記事の検索が出来なさそうですね。
何とかならないものかとココログの管理ページとかソースとか見ていると、ブログのルートにあるarchives.htmlから月別のHTMLファイルのリンクを 取り出せば、どうやら全ての記事をたどれそうでした。
そこで、Ajaxと正規表現で記事を検索するスクリプトを作成してみましたので、試しに設置してみます。
今は記事が少ないので検索も早いですが、記事が増えた時に使い物になるかどうかはわかりません。
が、検索できないよりはマシかな。
あと、半ば勢いで作ったことと、動作確認が不十分なため、動かないブラウザもあると思います。
特にMacでは全然見てないので、ダメかもしれません。(私はMacを持っていないのです・・・。Macの方、スミマセン)
まあ、ベータ版ってことで勘弁してください。少しずつ改良はしていこうと思ってます。
一応、WindowsのIE6とFirefox1.5では動作確認していますが、「俺の環境じゃ動かねぇよ」って方、よろしければコメントいただけると助かります。

| | コメント (2) | トラックバック (0)