LDR で記事データを動的に加工する -> 全文取得への応用
これまで、イベント・フックを使ってフィード表示を制御する方法について何度か考えてきました:
今回も同様の主旨なんですが、テンプレート・クラス (/js/template.VERSION.js にあります。toString の使い方など、非常に勉強になります) を読んでいて、これまでと違う手法に気付いたのでご紹介したいと思います。
本来、フィード記事は以下の様な構造になっており:
{ "items": [ { "enclosure": null, "link": "http://hatena.g.hatena.ne.jp/hatenarss/20060823/1156305225", "enclosure_type": null, "author": "hatenarss", "body": "\n\t\t<div>\n\t\t\t<p>未読の記事のみを表示する設定の状態で、フィード毎のエントリー一覧のページを閲覧した場合、「次のXX件」のリンク先が正しくなかったので修正いたしました。</p>\n\t\t\t<p><a href=\"http://i.hatena.ne.jp/idea/11690\">idea:11690</a>にて御指摘いただきありがとうございました.</p>\n\t\t</div>\n", "created_on": 1156305225, "modified_on": 1156305225, "id": "6992277", "title": "次のXX件のリンク先の不具合を修正しました", "category": "" } ] }
その各キーの値 (文字列・数値リテラル) が、テンプレートの対応箇所に埋め込まれることで表示が行われます。
例えばリンク先の修正の例では、この "link" の値を表示の直前 (before_printfeed) に修正することで適切な表示を行っていました。
この「値」が、どうやら関数でも良いらしい、というのが今回気付いたことです。
つまり、こんなことが可能になるのです:
item.body = function() { GM_xmlhttpRequest({ method: "GET", url: item.link, onload: function(res) { /* res.responseText を整形 -> item.body に代入 */ // 表示 var body = $("item_body_"+item.id); body.innerHTML = '<div class="body">'+item.body+"</div>"; fix_linktarget(body); } }; return "Now printing..."; }; item.body.isFunction = true; // Firefox 用おまじない
全文を提供しないフィード記事の "body" に対し、事前に上のような関数を割り当てておくことで、表示の際にはまず "Now printing..." が出力されます。そして、バックグラウンドで記事 HTML の取得が行われ、適切に全文が出力される、というわけです。
(注: キーによっては関数にしてしまうとまずいものもあります。その辺りはソースをよく読んで確認してください)
全体としては、以下のような形で実装することになると思います:
function item_modifier(feed) { // 記事データ (body, link, etc.) 書き換え関数を返す (feed.channel.link 等の値に応じて) var iter; // 上の例なら、このような関数を返すことになります iter = function(item) { item.body = function() { // ... return "Now printing..."; }; item.body.isFunction = true; }; return iter; } function modify_items(feed) { if (feed.items_modified) return; var iter = item_modifier(feed); // 記事書き換え関数を取得 // 書き換え実行 iter && feed.items.forEach(iter); feed.items_modified = true; } register_hook("before_printfeed", modify_items);
必要を感じる方は是非試してみてください。
参考:
m4i::diary - livedoor Reader で EntryFullText
追記:
実装の際の注意点を幾つか挙げておきます。
1. HTML 中の相対リンクの絶対化
body.innerHTML への代入前に BASE.href の値を一時的に記事 URL に書き換え、
document.getElementsByTagName("base")[0].href = URL;
その後元に戻す
document.getElementsByTagName("base")[0].removeAttribute("href");
というテクニックが使えると思います。
ただ、現状 LDR では
にあるべき(追追記: ちょっと試してみたんですが、これは良くない方法だったかも知れません。BASE.href が書き換わった瞬間に、ブラウザが LDR 側の、相対指定になっている画像をそちらへ取得しに行ってしまいます。大量の無効なリクエストを投げてしまうことになり、かなり迷惑です。
尤もこれは IE での挙動で、他のブラウザではどうなのか分かりません。)
2. 記事コンテナの取得失敗への対策
var body = $("item_body_"+item.id);
の部分でエラーが発生することがあります。したがって、この関数の内部全体を try..catch 文で囲み、setTimeout で再帰呼び出しする仕組みが必要でしょう。その際、永久ループを避けるため、許容するエラー回数の上限を定めておくべきです。(具体的な方法は id:reinyannyan:20060714:p1 が参考になるかと思います)
3. 記事 URL が "&" 等の特殊文字を含んでいる場合
item.link にはエスケープされた値 (& 等) が入っています。GET する url の値は常に item.link.unescapeHTML() としておきましょう。