<?xml version="1.0" encoding="utf-8"?>

<rdf:RDF 
  xmlns="http://purl.org/rss/1.0/"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:admin="http://webns.net/mvcb/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
> 

  <channel rdf:about="http://blog.bulknews.net/cookbook/blosxom">
    <title>Blog Developer's Cookbook</title>
    <link>http://blog.bulknews.net/cookbook/blosxom</link>
    <description>Cookbook About Programming Weblog Technologies</description>
    <language>ja</language>
    <dc:creator>Tatsuhiko Miyagawa (miyagawa@bulknews.net)</dc:creator>
    <dc:rights>Copyright Tatsuhiko Miyagawa</dc:rights>
    <admin:generatorAgent rdf:resource="http://www.raelity.org/apps/blosxom/?v=2.0rc5" />
    <admin:errorReportsTo rdf:resource="mailto:miyagawa@bulknews.net"/>

    <items>
      <rdf:Seq>
        <rdf:li rdf:resource="http://blog.bulknews.net/cookbook/blosxom/trackback/tbtrace.html" />

      </rdf:Seq>
    </items>


  </channel>
  <item rdf:about="http://blog.bulknews.net/cookbook/blosxom/trackback/tbtrace.html">
    <title>Trackback Tracer</title>
    <link>http://blog.bulknews.net/cookbook/blosxom/trackback/tbtrace.html</link>
    <description>
[[Trackback]] は Blog のエントリ間のつながりを表現することができるプロトコルです.</description>
    <dc:subject>Trackback Pings</dc:subject>
    <dc:creator>Tatsuhiko Miyagawa</dc:creator>
    <dc:date>2003-12-05T03:54+09:00</dc:date>
    <trackback:ping rdf:resource="http://blog.bulknews.net/cookbook/trackback/trackback_tbtrace" />
    <content:encoded><![CDATA[
<p />
<a href="http://blog.bulknews.net/cookbook/kwiki/kwiki.cgi?Trackback">Trackback</a> は Blog のエントリ間のつながりを表現することができるプロトコルです。ある記事を起点にして Trackback Ping をたどれば、E-mail やニュースグループのスレッド表示のように、関連する記事を一覧でブラウズすることができます。今回は、起点となる URL から Trackback をたどっていく Web アプリケーション Trackback Tracer をつくってみます。

<hr class="seemore">


<h2>Trackback Tracer</h2>

Trackback をたどるアプリケーションは、<a href="http://blog.bulknews.net/cookbook/blosxom/trackback/tb_discovery.rss10">Trackback Auto-Discovery</a> と <a href="http://blog.bulknews.net/cookbook/blosxom/trackback/tb_rss.rss10">Trackback の RSS 出力</a> を組み合わせれば容易に実装できます。つまり、
<ol>
<li>起点となる URL から Trackback Ping URL を抽出</li>
<li>Ping URL に <code>__mode=rss</code> を付加して RSS を取得</li>
</ol>

という流れで、起点の URL に対して Trackback Ping を送信したエントリ一覧が取得できます。

<h2>サンプルコード</h2>

CGI スクリプトとして動作する Trackback Tracer のコードは <a href="#listing-1">List 1</a> のようになります。

<p />
<pre class="scriptsSnippet">use CGI;
use Net::TrackBack;
use LWP::Simple;
use URI;
use XML::RSS;
</pre>

使用するモジュールを use します。もうおなじみのモノばかりですね。

<p />
<pre class="scriptsSnippet">my $q   = CGI-&gt;new();
my $url = $q-&gt;param('url');
my $rss = fetch_tb_rss($url) if $url;

print_form($q);

if ($rss &amp;&amp; ref($rss)) {
    # if $rss is object, it's XML::RSS
    show_rss($q, $rss);
} elsif ($rss) {
    # otherwise, it's error string
    print &quot;Error: $rss&quot;;
}
</pre>

ここがメインの処理になります。CGI パラメータの <code>url</code> を取得し、入力されていれば <code>fetch_tb_rss</code> で Trackback Ping URL から RSS を取得します。<code>print_form</code> では入力フォームを表示、レスポンスの <code>$rss</code> がオブジェクトであれば <code>show_rss</code> で Ping の中身を表示、そうでなければエラーを表示します。

<p />
<pre class="scriptsSnippet">sub print_form {
    my $q = shift;
    print $q-&gt;header('text/html; charset=utf-8'),
        $q-&gt;h1(&quot;Trackback Tracer&quot;),
        $q-&gt;start_form(-method =&gt; &quot;GET&quot;),
        &quot;URL: &quot;, $q-&gt;textfield(-name =&gt; 'url', -size =&gt; 50),
        $q-&gt;submit(-value =&gt; &quot;Trace&quot;), $q-&gt;end_form;
}
</pre>

CGI.pm のフォーム生成機能で、HTML とフォーム部品を出力します。テンプレートを使用するのでもいいのですが、フォームの中身を Sticky<a href="#note-1">(*1)</a> にするには、CGI.pm のメソッドを使用するのが簡単です。

<p />
<pre class="scriptsSnippet">sub fetch_tb_rss {
    my $url = shift;
    my $p = Net::TrackBack-&gt;new();
    my($ping_url) = $p-&gt;discover($url);
    $ping_url or return &quot;Trackback Ping URL Not Found&quot;;

    # strips weird &lt;response&gt; things
    my $xml = get(&quot;$ping_url?__mode=rss&quot;);
    $xml =~ s!&lt;response&gt;.*&lt;error&gt;.*&lt;/error&gt;!!s;
    $xml =~ s!&lt;/response&gt;!!s;

    # parse it with XML::RSS
    my $rss = XML::RSS-&gt;new();
    eval { $rss-&gt;parse($xml) };

    return $@ ? &quot;$@&quot; : $rss;
}
</pre>

<a href="http://search.cpan.org/search?m=module&q=Net%3a%3aTrackBack">Net::TrackBack</a> で Ping URL を Auto-Discovery します。見つからなかった場合には、文字列でエラーを返します。

<p />
次はちょっと汚いのですが、Trackback Ping の RSS モードについている、<code>response</code> や <code>error</code> というエレメントが、XML パースの邪魔になるため、除去しておき、その文字列 <code>$xml</code> を <a href="http://search.cpan.org/search?m=module&q=XML%3a%3aRSS">XML::RSS</a> に食わせます<a href="#note-2">(*2)</a>。エラーの場合はエラー文字、成功すれば XML::RSS オブジェクトを return します。

<p />
<pre class="scriptsSnippet">sub show_rss {
    my($q, $rss) = @_;
    if (@{$rss-&gt;items}) {
        for my $item (@{$rss-&gt;items}) {
            my $trace = URI-&gt;new($q-&gt;url);
            $trace-&gt;query_form(url =&gt; $item-&gt;{link});
            print $q-&gt;a({ -href =&gt; $item-&gt;{link} },
                        $item-&gt;{title}), &quot;\n&quot;,
                $q-&gt;a({ -href =&gt; $trace-&gt;as_string }, &quot;[trace]&quot;),
                $q-&gt;blockquote($item-&gt;{description}), $q-&gt;br,
        }
    } else {
        print &quot;No Trackback to this entry.&quot;;
    }
}
</pre>

RSS に item がある場合は、title, link, description を表示します。<code>$trace</code> には、この CGI の URL を入れた後、クエリパラメータ <code>url</code> に item の link をセットしています。この <code>$trace</code> にリンクを張れば、今度はこのアイテムを起点にして Ping をたどることができるようになります。


<h2>Running the Hack</h2>

このスクリプトを CGI 実行可能な環境に配置して、ブラウザからアクセスします。

<img src="/cookbook/images/tbtrace/tbtrace1.gif" alt="tbtrace フォーム" class="inline-image" />

このようなフォームが表示されるので、適当に Blog エントリの URL を入れましょう<a href="#note-3">(*3)</a>。ここでは、<a href="http://naoya.dyndns.org/~naoya/mt/archives/000718.html">Monday Module ひとり</a> というエントリの URL を入れてみました。実行結果は <a href="http://bulknews.net/tbtrace.cgi?url=http%3A%2F%2Fnaoya.dyndns.org%2F%7Enaoya%2Fmt%2Farchives%2F000718.html&amp;.submit=Trace">このように</a>なります。

<img src="/cookbook/images/tbtrace/tbtrace2.gif" alt="tbtrace 実行結果" class="inline-image" />

このエントリに Trackback Ping を送信したエントリのタイトルと概要 (excerpt) が表示されます。それぞれのタイトルの右には [trace] というリンクがあり、これをクリックすると、さらにそのエントリを起点にしてたどることができます。

<h2>See Also</h2>
<ul>
<li><a href="http://holic.org/b2uvoyager.php">Trackback Voyager</a></li>
<li><a href="http://hail2u.net/blog/blog/tbtrace.html">Trackback Tracer</a></li>
</ul>

いずれも今回紹介したスクリプトと同等の処理ができます。

<h2>Listings</h2>

<div class="scriptsListings"><a name="listing-1"><span class="scripsListingHeader">List 1: tbtrace.cgi</span></a>
<pre class="scriptsListing">#!/usr/local/bin/perl -w
# tbtrace.cgi - Trackback Tracer

use strict;
use CGI;
use Net::TrackBack;
use LWP::Simple;
use URI;
use XML::RSS;

my $q   = CGI-&gt;new();
my $url = $q-&gt;param('url');
my $rss = fetch_tb_rss($url) if $url;

print_form($q);

if ($rss &amp;&amp; ref($rss)) {
    # if $rss is object, it's XML::RSS
    show_rss($q, $rss);
} elsif ($rss) {
    # otherwise, it's error string
    print &quot;Error: $rss&quot;;
}

sub print_form {
    my $q = shift;
    print $q-&gt;header('text/html; charset=utf-8'),
        $q-&gt;h1(&quot;Trackback Tracer&quot;),
        $q-&gt;start_form(-method =&gt; &quot;GET&quot;),
        &quot;URL: &quot;, $q-&gt;textfield(-name =&gt; 'url', -size =&gt; 50),
        $q-&gt;submit(-value =&gt; &quot;Trace&quot;), $q-&gt;end_form;
}

sub fetch_tb_rss {
    my $url = shift;
    my $p = Net::TrackBack-&gt;new();
    my($ping_url) = $p-&gt;discover($url);
    $ping_url or return &quot;Trackback Ping URL Not Found&quot;;

    # strips weird &lt;response&gt; things
    my $xml = get(&quot;$ping_url?__mode=rss&quot;);
    $xml =~ s!&lt;response&gt;.*&lt;error&gt;.*&lt;/error&gt;!!s;
    $xml =~ s!&lt;/response&gt;!!s;

    # parse it with XML::RSS
    my $rss = XML::RSS-&gt;new();
    eval { $rss-&gt;parse($xml) };

    return $@ ? &quot;$@&quot; : $rss;
}

sub show_rss {
    my($q, $rss) = @_;
    if (@{$rss-&gt;items}) {
        for my $item (@{$rss-&gt;items}) {
            my $trace = URI-&gt;new($q-&gt;url);
            $trace-&gt;query_form(url =&gt; $item-&gt;{link});
            print $q-&gt;a({ -href =&gt; $item-&gt;{link} },
                        $item-&gt;{title}), &quot;\n&quot;,
                $q-&gt;a({ -href =&gt; $trace-&gt;as_string }, &quot;[trace]&quot;),
                $q-&gt;blockquote($item-&gt;{description}), $q-&gt;br,
        }
    } else {
        print &quot;No Trackback to this entry.&quot;;
    }
}
</pre>
</div>
<div class="footnote"><a href="#note-1" name="note-1">*1)</a> フォームに入力された値を保持して再表示すること。エラー表示などに便利です。<br />
<a href="#note-2" name="note-2">*2)</a> Hack 的なのでこの処理を CPAN モジュール化した方がよいかもしれませんね。<br />
<a href="#note-3" name="note-3">*3)</a> Blog のホームではなく、エントリを入力してください。<br />
</div>
]]></content:encoded>
  </item>


</rdf:RDF>
