OSDN Git Service

- update tag execution and optimize.
[feedblog/feedblog.git] / js / lunardial / feedblog.js
index 2412114..3239673 100644 (file)
-/**\r
- * FeedBlog CoreScript\r
- *\r
- * @copyright 2013 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)\r
- * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)\r
- * @since 2009/02/27\r
- * @version 4.2.0.0\r
- */\r
-\r
-// ブログ本体のHTMLファイルのURL\r
-var mainPageUrl;\r
-\r
-// 検索用ページURL\r
-var searchPageUrl;\r
-\r
-// 最新の記事を示すパスへの文字列\r
-var latestXml;\r
-\r
-// ログのリストが書かれたXMLのファイルパス\r
-var logXmlUrl;\r
-\r
-// 一画面あたりの表示記事数\r
-var showLength;\r
-\r
-/**\r
- * XMLファイルから読み込んだファイルのバリデートモード\r
- * 0 = 改行コード部分に<br/>を挿入\r
- * 1 = 改行コード部分に<br/>を挿入しない\r
- */\r
-var validateMode;\r
-\r
-// 検索結果をメモリ上に保持する変数\r
-var loadedEntries;\r
-\r
-// fetchEntries 用のセマフォ\r
-var fetchEntriesSemaphore = new Semaphore();\r
-\r
-// URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)\r
-var urlSuffix;\r
-\r
-/**\r
- * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。\r
- * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト\r
- * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid\r
- * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid\r
- * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か\r
- */\r
-function generatePanel(entry, drawitem, renderto, closed) {\r
-       // プラグインを実行\r
-       if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {\r
-               $("#" + renderto).feedblog_contents_plugin({\r
-                       entry : entry\r
-               });\r
-       }\r
-\r
-       // HTML用の配列を用意する\r
-       var htmlBuffer = [];\r
-\r
-       // 内部的に描画先IDを生成\r
-       var feedblogContentId = "" + renderto + "_content_div";\r
-\r
-       // 各要素をオブジェクトに描画\r
-       $("#" + drawitem).html(entry.content);\r
-\r
-       // ヘッダパネルを生成 class= .feedblog_header\r
-       htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +\r
-\r
-       // 本体記事を作成 class= .feedblog_content\r
-       "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");\r
-\r
-       // 最終描画実施\r
-       $("#" + renderto).html(htmlBuffer.join(""));\r
-}\r
-\r
-/**\r
- * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。\r
- * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト\r
- * @param {String} drawitem パネルの本文を格納したDIV要素のid\r
- * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid\r
- * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か\r
- */\r
-function generateSystemPanel(entry, drawitem, renderto, closed) {\r
-       // HTMLを生成する\r
-       var htmlBuffer = [];\r
-\r
-       // 描画先IDを生成\r
-       var feedblogContentId = "" + renderto + "_content_div";\r
-\r
-       // 各要素をオブジェクトに描画\r
-       $("#" + drawitem).html(entry.content);\r
-\r
-       // ヘッダパネルを生成 class= .feedblog_header\r
-       htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +\r
-\r
-       // 本体記事を作成 class= .feedblog_content\r
-       "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");\r
-\r
-       $("#" + renderto).html(htmlBuffer.join(""));\r
-}\r
-\r
-/**\r
- * 全ての定数を取得・セットします\r
- */\r
-function initialize() {\r
-       // 初期値をhiddenパラメータより読み込みます\r
-       mainPageUrl = $("#feedblog_mainpageurl").val();\r
-       searchPageUrl = $("#feedblog_searchpageurl").val();\r
-       latestXml = $("#feedblog_latestxml").val();\r
-       logXmlUrl = $("#feedblog_loglistxmlurl").val();\r
-       showLength = parseInt($("#feedblog_showlength").val());\r
-       if (isNaN(showLength)) {\r
-               showLength = 1;\r
-       }\r
-       validateMode = $("#feedblog_validatemode").val();\r
-       \r
-       // 初期値を設定します\r
-       urlSuffix = +new Date();\r
-\r
-       // 必要な環境を確認します\r
-       var errorBuf = [];\r
-       // 変数確認\r
-       if (mainPageUrl === undefined) {\r
-               errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");\r
-       }\r
-       if (searchPageUrl === undefined) {\r
-               errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");\r
-       }\r
-       if (latestXml === undefined) {\r
-               errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");\r
-       }\r
-       if (logXmlUrl === undefined) {\r
-               errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");\r
-       }\r
-       if (showLength === undefined) {\r
-               errorBuf.push("設定値「feedblog_showlength」が欠落しています。");\r
-       }\r
-       if (validateMode === undefined) {\r
-               errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");\r
-       }\r
-       // SHA-1関数確認\r
-       try {\r
-               if ( typeof (CryptoJS.SHA1) != "function") {\r
-                       errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");\r
-               }\r
-       } catch (ex) {\r
-               errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");\r
-       }\r
-\r
-       // 描画エリアチェック\r
-       if ($("#feedblog_writearea").length == 0) {\r
-               errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");\r
-       }\r
-       if ($("#feedblog_logselecter").length == 0) {\r
-               errorBuf.push("描画エリア「feedblog_logselecter」が存在しません。");\r
-       }\r
-\r
-       // エラーがある場合は以降の処理を継続しない\r
-       if (errorBuf.length > 0) {\r
-               alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));\r
-               return false;\r
-       }\r
-\r
-       return true;\r
-}\r
-\r
-/**\r
- * jQueryへのイベント登録です。すべてのDOMが利用可能になった時点で実行されます。\r
- */\r
-$(document).ready(function() {\r
-       // 初期処理を実施\r
-       if (!initialize()) {\r
-               return false;\r
-       }\r
-\r
-       // 制御に必要な各種パラメタを取得する\r
-       var tag = getParamFromUrl("tag");\r
-       var id = getParamFromUrl("id");\r
-       var urlhash = getHashFromUrl();\r
-\r
-       // ハッシュが空か、ハッシュ形式の正規表現に一致しないようなら通常モードで実行\r
-       if (urlhash.length == 0 && tag.length == 0 && id.length == 0) {\r
-               fullWriteMode(latestXml);\r
-               logXMLLoader();\r
-       } else if (urlhash.length == 0 && id.length == 0) {\r
-               // タグが指定されているのでタグ探索モード\r
-               searchTagMode(tag);\r
-               logXMLLoader();\r
-       } else if (urlhash.length == 0) {\r
-               // IDが指定されているのでID探索モード\r
-               searchIdMode(id);\r
-               logXMLLoader();\r
-       } else {\r
-               // ハッシュ形式の正規表現に一致したら探索モード\r
-               searchHashMode(urlhash);\r
-               logXMLLoader();\r
-       }\r
-});\r
-\r
-/**\r
- * jQueryでのパネル開閉を制御します\r
- */\r
-function closePanel(id) {\r
-       $("#" + id).slideToggle();\r
-}\r
-\r
-/**\r
- * 記事クラス\r
- * @param {Object} obj entry 要素の DOM オブジェクト\r
- */\r
-function Entry(obj) {\r
-       this.title = $("title:first", obj).text();\r
-       if (this.title == "")\r
-               requiredElementError(obj, "title");\r
-       this.title = validateText(this.title);\r
-       this.content = $("content:first", obj).text();\r
-       this.content = validateText(this.content);\r
-       this.id = $("id:first", obj).text();\r
-       if (this.id == "")\r
-               requiredElementError(obj, "id");\r
-       this.date = $("updated:first", obj).text();\r
-       if (this.date == "")\r
-               requiredElementError(obj, "updated");\r
-       this.date = validateData(this.date);\r
-       this.category = $("category", obj);\r
-}\r
-\r
-/**\r
- * システム用記事クラス\r
- * @param {Object} obj entry 要素の DOM オブジェクト\r
- */\r
-function SystemEntry(obj) {\r
-       this.title = $("title:first", obj).text();\r
-       this.title = validateText(this.title);\r
-       this.content = $("content:first", obj).text();\r
-       this.content = validateText(this.content);\r
-       this.id = $("id:first", obj).text();\r
-       this.date = $("updated:first", obj).text();\r
-       this.date = validateData(this.date);\r
-       this.category = $("category", obj);\r
-}\r
-\r
-/**\r
- * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します\r
- */\r
-function loadingEffect() {\r
-       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');\r
-\r
-       // ロード表示用のパネルを生成\r
-       var systemEntry = new SystemEntry();\r
-       systemEntry.title = "Now Loading .....";\r
-       systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';\r
-       generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);\r
-}\r
-\r
-/**\r
- * 記事データのエラー時の処理を行います\r
- */\r
-function showError() {\r
-       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');\r
-\r
-       // エラー内容をパネルに描画\r
-       var systemEntry = new SystemEntry();\r
-       systemEntry.title = "エラー";\r
-       var errorContent = [];\r
-       errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');\r
-       errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');\r
-       errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');\r
-       errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');\r
-       errorContent.push('<br/>');\r
-       systemEntry.content = errorContent.join("\n");\r
-       generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);\r
-\r
-       // 結果表示エリアをリセット\r
-       $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");\r
-}\r
-\r
-/**\r
- * 記事データのエラー時の処理を行います\r
- */\r
-function notFoundError() {\r
-       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');\r
-\r
-       // エラー内容をパネルに描画\r
-       var systemEntry = new SystemEntry();\r
-       systemEntry.title = "検索失敗";\r
-       systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';\r
-       generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);\r
-}\r
-\r
-/**\r
- * 記事データのエラー時の処理を行います\r
- */\r
-function requiredElementError(parent, name) {\r
-       alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");\r
-}\r
-\r
-function xmlAttrContentEscape(str) {\r
-       return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");\r
-}\r
-\r
-/**\r
- * 日付のHTML表示用バリデーション処理を行います\r
- * @param {String} data RFC3339形式のdate-time文字列\r
- */\r
-function validateData(data) {\r
-       var regT = new RegExp("T", "gm");\r
-       data = data.replace(regT, " ");\r
-\r
-       // 秒数の小数点以下の部分はカットする\r
-       data = data.substring(0, 19);\r
-\r
-       return data;\r
-}\r
-\r
-/**\r
- * 記事本文のバリデーション処理を行います\r
- * @param {String} contents 記事の本文が格納されている文字列\r
- */\r
-function validateText(contents) {\r
-       // <br/>タグを挿入する\r
-       if (validateMode == 0) {\r
-               contents = contents.replace(/[\n\r]|\r\n/g, "<br />");\r
-       }\r
-\r
-       return contents;\r
-}\r
-\r
-/**\r
- * URLからパラメタを取得するための関数\r
- */\r
-function getParamFromUrl(paramName) {\r
-       // GETパラメタよりタグを取得する\r
-       var tag = "";\r
-       if (location.search.length > 1) {\r
-               var queries = location.search.substring(1).split('&');\r
-               for (var i = 0; i < queries.length; i++) {\r
-                       if (("" + queries[i].split('=')[0]) == paramName) {\r
-                               tag = "" + queries[i].split('=')[1];\r
-                       }\r
-               }\r
-       }\r
-\r
-       return tag;\r
-}\r
-\r
-/**\r
- * URLからハッシュを取得するための関数\r
- */\r
-function getHashFromUrl() {\r
-       return "" + location.hash.substring(1);\r
-}\r
-\r
-/**\r
- * 長い順に並べるための比較関数です\r
- * @param {String} a 比較対象(1)\r
- * @param {String} b 比較対象(2)\r
- */\r
-function compareLengthDecrease(a, b) {\r
-       a = a.length;\r
-       b = b.length;\r
-       return a > b ? -1 : a < b ? 1 : 0;\r
-}\r
-\r
-/**\r
- * セマフォ制御用のオブジェクトです\r
- */\r
-function Semaphore() {\r
-       this.id = null;\r
-       this.count = 0;\r
-       this.buf = [];\r
-       this.xhrs = [];\r
-}\r
-\r
-/**\r
- * セマフォ初期化用の関数です\r
- */\r
-Semaphore.prototype.init = function() {\r
-       while (this.xhrs.length > 0) {\r
-               this.xhrs.shift().abort();\r
-       }\r
-       this.id = Math.random();\r
-       this.count = 0;\r
-       this.buf = [];\r
-};\r
-\r
-/**\r
- * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します\r
- * class - .feedblog_logform, .feedblog_logselecter\r
- */\r
-function logXMLLoader() {\r
-       // ログ用のXMLを読み込みます\r
-       jQuery.ajax({\r
-               url : logXmlUrl + '?time=' + urlSuffix,\r
-               method : "GET",\r
-               error : showError,\r
-               success : function(xmlData) {\r
-                       var separateTag = xmlData.getElementsByTagName("file");\r
-\r
-                       // 読み込んだ要素をStoreに格納して表示\r
-                       var boxBuffer = [];\r
-                       boxBuffer.push("<form class='feedblog_logselecter' name='feedblog_logform'><select class='feedblog_logselecter' id='feedblog_logbox' onchange='fullWriteMode(this.options[this.selectedIndex].value)'>");\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");\r
-                       }\r
-                       boxBuffer.push("</select></form>");\r
-\r
-                       // コンボボックス要素を生成\r
-                       $("#feedblog_logselecter").html(boxBuffer.join(""));\r
-               }\r
-       });\r
-}\r
-\r
-/**\r
- * 記事のデータが記述されたXMLデータを読み込むロジックを生成します\r
- * @param {String} fileName 読み込み記事のデータが記述されているXMLファイルのパス\r
- */\r
-function fullWriteMode(fileName) {\r
-       // ロードエフェクトに切り替え\r
-       loadingEffect();\r
-\r
-       var url = fileName;\r
-\r
-       // 記事をロードします\r
-       var loader = new jQuery.ajax({\r
-               url : url + '?time=' + urlSuffix,\r
-               method : "GET",\r
-               success : function(xmlData) {\r
-                       var separateTag = xmlData.getElementsByTagName("entry");\r
-                       var stringBuffer = [];\r
-                       // メモリ上での保持変数を初期化します\r
-                       loadedEntries = [];\r
-\r
-                       // メモリ上の変数に全ての記事要素を格納します\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               loadedEntries.push(new Entry(separateTag[i]));\r
-                       }\r
-\r
-                       // 表示ロジック呼び出し\r
-                       showEntriesRange(showLength, 0);\r
-               },\r
-               error : showError\r
-       });\r
-}\r
-\r
-/**\r
- * 渡された文字列と一致するfeed1.0:updated要素を持った記事を検索し、表示します\r
- * @param {String} urlhash feed1.0:updated要素と一致する文字列\r
- */\r
-function searchHashMode(urlhash) {\r
-       // ロードエフェクト表示\r
-       loadingEffect();\r
-\r
-       // ログXMLファイルを読み込む\r
-       var loader = new jQuery.ajax({\r
-               url : logXmlUrl + '?time=' + urlSuffix,\r
-               method : "GET",\r
-               error : showError,\r
-               success : function(xmlData) {\r
-                       // ファイルパスの要素のみを抽出する\r
-                       var separateTag = xmlData.getElementsByTagName("file");\r
-                       var urls = new Array(separateTag.length);\r
-\r
-                       // すべてのファイルパスを配列に格納する\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               // "path"ノードの値を格納\r
-                               urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;\r
-                       }\r
-\r
-                       // セマフォを初期化\r
-                       fetchEntriesSemaphore.init();\r
-                       fetchEntriesSemaphore.urls = urls;\r
-                       fetchEntriesSemaphore.count = urls.length;\r
-\r
-                       // ファイルパス配列に格納されているすべての記事に対し、探索を開始する\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               // ファイルパス配列の要素からリクエストを生成し、対象データをロードする\r
-                               var xhr = new jQuery.ajax({\r
-                                       url : urls[i],\r
-                                       method : "GET",\r
-                                       success : fetchHashEntries\r
-                               });\r
-                               fetchEntriesSemaphore.xhrs.push(xhr);\r
-                       }\r
-               }\r
-       });\r
-}\r
-\r
-/**\r
- * URLハッシュ検索用のjQueryコールバック関数\r
- */\r
-function fetchHashEntries(xmlData) {\r
-       // 既に検索結果が算出されていた場合は、何もしない\r
-       if (fetchEntriesSemaphore.buf > 0) {\r
-               return true;\r
-       }\r
-\r
-       // ハッシュを取得\r
-       var urlhash = getHashFromUrl();\r
-\r
-       // entry要素のみを切り出す\r
-       var entries = xmlData.getElementsByTagName("entry");\r
-\r
-       for (var i = 0; i < entries.length; i++) {\r
-               // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う\r
-               var entry = new Entry(entries[i]);\r
-\r
-               // idの値と比較を行う\r
-               if (urlhash == entry.id) {\r
-                       // 一致した場合は該当記事を表示する\r
-                       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');\r
-                       generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);\r
-\r
-                       fetchEntriesSemaphore.buf.push(entry);\r
-                       return true;\r
-               }\r
-\r
-               // セマフォのカウンタを減少させます (Ajaxとの同期のため)\r
-               fetchEntriesSemaphore.count--;\r
-\r
-               // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。\r
-               if (fetchEntriesSemaphore.count == 0) {\r
-                       var entries = fetchEntriesSemaphore.buf;\r
-\r
-                       if (entries.length == 0) {\r
-                               notFoundError();\r
-                               return false;\r
-                       }\r
-               }\r
-       }\r
-}\r
-\r
-/**\r
- * 渡された文字列と一致するfeed1.0:id(sha-1)要素を持った記事を検索し、表示します\r
- * @param {String} urlhash feed1.0:id(sha-1)要素と一致する文字列\r
- */\r
-function searchIdMode(urlhash) {\r
-       // ロードエフェクト表示\r
-       loadingEffect();\r
-\r
-       // ログXMLファイルを読み込む\r
-       var loader = new jQuery.ajax({\r
-               url : logXmlUrl + '?time=' + urlSuffix,\r
-               method : "GET",\r
-               error : showError,\r
-               success : function(xmlData) {\r
-                       // ファイルパスの要素のみを抽出する\r
-                       var separateTag = xmlData.getElementsByTagName("file");\r
-                       var urls = new Array(separateTag.length);\r
-\r
-                       // すべてのファイルパスを配列に格納する\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               // "path"ノードの値を格納\r
-                               urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;\r
-                       }\r
-\r
-                       // セマフォを初期化\r
-                       fetchEntriesSemaphore.init();\r
-                       fetchEntriesSemaphore.urls = urls;\r
-                       fetchEntriesSemaphore.count = urls.length;\r
-\r
-                       // ファイルパス配列に格納されているすべての記事に対し、探索を開始する\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               // ファイルパス配列の要素からリクエストを生成し、対象データをロードする\r
-                               var xhr = new jQuery.ajax({\r
-                                       url : urls[i],\r
-                                       method : "GET",\r
-                                       success : fetchIdEntries\r
-                               });\r
-                               fetchEntriesSemaphore.xhrs.push(xhr);\r
-                       }\r
-               }\r
-       });\r
-}\r
-\r
-/**\r
- * ID検索用のjQueryコールバック関数\r
- */\r
-function fetchIdEntries(xmlData) {\r
-       // 既に検索結果が算出されていた場合は、何もしない\r
-       if (fetchEntriesSemaphore.buf > 0) {\r
-               return true;\r
-       }\r
-\r
-       // IDを取得\r
-       var id = getParamFromUrl("id");\r
-\r
-       // entry要素のみを切り出す\r
-       var entries = xmlData.getElementsByTagName("entry");\r
-\r
-       for (var i = 0; i < entries.length; i++) {\r
-               // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う\r
-               var entry = new Entry(entries[i]);\r
-\r
-               // idの値と比較を行う\r
-               if (id == CryptoJS.SHA1(entry.id).toString()) {\r
-                       // 一致した場合は該当記事を表示する\r
-                       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');\r
-                       generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);\r
-\r
-                       fetchEntriesSemaphore.buf.push(entry);\r
-                       return true;\r
-               }\r
-\r
-               // セマフォのカウンタを減少させます (Ajaxとの同期のため)\r
-               fetchEntriesSemaphore.count--;\r
-\r
-               // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。\r
-               if (fetchEntriesSemaphore.count == 0) {\r
-                       var entries = fetchEntriesSemaphore.buf;\r
-\r
-                       if (entries.length == 0) {\r
-                               notFoundError();\r
-                               return false;\r
-                       }\r
-               }\r
-       }\r
-}\r
-\r
-/**\r
- * 渡された文字列と一致するfeed1.0:category:termタグ要素を持った記事を検索し、表示します\r
- * @param {String} urlhash feed1.0:category:term要素と一致する文字列\r
- */\r
-function searchTagMode(tag) {\r
-       // ロードエフェクト表示\r
-       loadingEffect();\r
-\r
-       // ログXMLファイルを読み込む\r
-       var loader = new jQuery.ajax({\r
-               url : logXmlUrl + '?time=' + urlSuffix,\r
-               method : "GET",\r
-               error : showError,\r
-               success : function(xmlData) {\r
-                       // ファイルパスの要素のみを抽出する\r
-                       var separateTag = xmlData.getElementsByTagName("file");\r
-                       var urls = new Array(separateTag.length);\r
-\r
-                       // すべてのファイルパスを配列に格納する\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               // "path"ノードの値を格納\r
-                               urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;\r
-                       }\r
-\r
-                       // セマフォを初期化\r
-                       fetchEntriesSemaphore.init();\r
-                       fetchEntriesSemaphore.urls = urls;\r
-                       fetchEntriesSemaphore.count = urls.length;\r
-\r
-                       // ファイルパス配列に格納されているすべての記事に対し、探索を開始する\r
-                       for (var i = 0; i < separateTag.length; i++) {\r
-                               // ファイルパス配列の要素からリクエストを生成し、対象データをロードする\r
-                               var xhr = new jQuery.ajax({\r
-                                       url : urls[i],\r
-                                       method : "GET",\r
-                                       success : fetchTagEntries\r
-                               });\r
-                               fetchEntriesSemaphore.xhrs.push(xhr);\r
-                       }\r
-               }\r
-       });\r
-}\r
-\r
-/**\r
- * タグ検索用のjQueryコールバック関数\r
- */\r
-function fetchTagEntries(xmlData) {\r
-       // 既に記事の表示が行われている場合、何も実施しない\r
-       if (fetchEntriesSemaphore.buf.length > 0) {\r
-               return true;\r
-       }\r
-\r
-       // タグを取得する\r
-       var tag = getParamFromUrl("tag");\r
-\r
-       // entry要素のみを切り出す\r
-       var entries = xmlData.getElementsByTagName("entry");\r
-\r
-       for (var j = 0; j < entries.length; j++) {\r
-               var entry = new Entry(entries[j]);\r
-\r
-               for (var k = 0; k < entry.category.length; k++) {\r
-                       // タグのIDが一致したら格納\r
-                       if (tag == entry.category.eq(k).attr("term")) {\r
-                               // entryを格納する\r
-                               fetchEntriesSemaphore.buf.push(entry);\r
-                       }\r
-               }\r
-       }\r
-\r
-       // セマフォのカウンタを減少させます (Ajaxとの同期のため)\r
-       fetchEntriesSemaphore.count--;\r
-\r
-       if (fetchEntriesSemaphore.count == 0) {\r
-               var entries = fetchEntriesSemaphore.buf;\r
-\r
-               if (entries.length == 0) {\r
-                       notFoundError();\r
-                       return false;\r
-               }\r
-\r
-               // entryをidでソート\r
-               entries = entries.sort(function(a, b) {\r
-                       a = a.id;\r
-                       b = b.id;\r
-                       return a > b ? -1 : a < b ? 1 : 0;\r
-               });\r
-\r
-               loadedEntries = entries;\r
-\r
-               // 表示ロジック呼び出し\r
-               showEntriesRange(showLength, 0);\r
-       }\r
-}\r
-\r
-/**\r
- * 検索結果を分割して表示します\r
- * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど\r
- * @param {int} showLength 一回の画面に表示する記事数\r
- * @param {int} startIndex 表示を開始する記事のインデックス\r
- */\r
-function showEntriesRange(showLength, startIndex) {\r
-       // メモリ上から記事データをロード\r
-       var entries = loadedEntries;\r
-\r
-       // 表示インデックスが範囲外の場合はエラーパネルを表示して終了\r
-       if (startIndex < 0 || (entries.length <= startIndex && entries.length != 0)) {\r
-               showError();\r
-               return;\r
-       }\r
-\r
-       var stringBuffer = [];\r
-\r
-       // リミッターを設定する\r
-       var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;\r
-       var indexShowEntries = loopLimit + 1;\r
-\r
-       // 情報メニュー表示用バッファです\r
-       var menuInfoBuffer = [];\r
-       menuInfoBuffer.push("<div class='feedblog_pager_shownumber'>");\r
-       var viewStartIndex = 0;\r
-       entries.length == 0 ? viewStartIndex = 0 : viewStartIndex = startIndex + 1;\r
-       menuInfoBuffer.push(viewStartIndex + "件~" + loopLimit + "件(全" + entries.length + "件)目の記事を表示中<br/>");\r
-       menuInfoBuffer.push("</div>");\r
-\r
-       // ページ移動メニュー表示用バッファです\r
-       var menuMoveBuffer = [];\r
-       menuMoveBuffer.push("<ul class='feedblog_pager'>");\r
-\r
-       // ブランクエリアを挟む\r
-       menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");\r
-\r
-       // 左パネルの表示制御\r
-       if (startIndex - showLength >= 0) {\r
-               menuMoveBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");\r
-       } else {\r
-               menuMoveBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");\r
-       }\r
-\r
-       // 中央のパネルの表示制御\r
-       menuMoveBuffer.push("<li class='feedblog_pager_center'>[ ");\r
-       var menuNumbers = Math.ceil(entries.length / showLength);\r
-       for ( i = 0; i < menuNumbers; i++) {\r
-               if (startIndex / showLength == i) {\r
-                       menuMoveBuffer.push(i + " ");\r
-               } else {\r
-                       menuMoveBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");\r
-                       menuMoveBuffer.push(i);\r
-                       menuMoveBuffer.push("</span> ");\r
-               }\r
-       }\r
-       menuMoveBuffer.push("]</li>");\r
-\r
-       // 右パネルの表示制御\r
-       if (entries.length > startIndex + showLength) {\r
-               menuMoveBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>\次の" + showLength + "件を表示 \></span\></li>");\r
-       } else {\r
-               menuMoveBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");\r
-       }\r
-\r
-       // ブランクエリアを挟む\r
-       menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");\r
-\r
-       menuMoveBuffer.push("</ul>");\r
-\r
-       // メニューを結合してパネル(前方)に組み込みます\r
-       stringBuffer.push("<div class='feedblog_pager_wrapper'>");\r
-       stringBuffer.push(menuInfoBuffer.join(""));\r
-       stringBuffer.push(menuMoveBuffer.join(""));\r
-       stringBuffer.push("</div>");\r
-\r
-       // 記事描画部分のパネルを生成します\r
-       for (var i = startIndex; i < loopLimit; i++) {\r
-               stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');\r
-               stringBuffer.push(i);\r
-               stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');\r
-               stringBuffer.push(i);\r
-               stringBuffer.push('"><\/div><\/div>');\r
-       }\r
-\r
-       // メニューを結合してパネル(後方)に組み込みます\r
-       stringBuffer.push("<div class='feedblog_pager_wrapper'>");\r
-       stringBuffer.push(menuMoveBuffer.join(""));\r
-       stringBuffer.push(menuInfoBuffer.join(""));\r
-       stringBuffer.push("</div>");\r
-\r
-       $("#feedblog_writearea").html(stringBuffer.join(""));\r
-\r
-       for (var i = startIndex; i < loopLimit; i++) {\r
-               // 各要素をオブジェクトに格納します\r
-               var entry = entries[i];\r
-\r
-               // すべてのパネルをオープン状態で生成します\r
-               generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);\r
-       }\r
-}\r
-\r
+/**
+ * FeedBlog CoreScript
+ *
+ * @copyright 2013 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
+ * @author Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
+ * @since 2009/02/27
+ * @version 4.2.1.0
+ */
+
+// ブログ本体のHTMLファイルのURL
+var mainPageUrl;
+
+// 検索用ページURL
+var searchPageUrl;
+
+// 最新の記事を示すパスへの文字列
+var latestXml;
+
+// ログのリストが書かれたXMLのファイルパス
+var logXmlUrl;
+
+// 一画面あたりの表示記事数
+var showLength;
+
+/**
+ * XMLファイルから読み込んだファイルのバリデートモード
+ * 0 = 改行コード部分に<br/>を挿入
+ * 1 = 改行コード部分に<br/>を挿入しない
+ */
+var validateMode;
+
+// 検索結果をメモリ上に保持する変数
+var loadedEntries;
+
+// fetchEntries 用のセマフォ
+var fetchEntriesSemaphore = new Semaphore();
+
+// URL末尾用文字列(スクリプトを開いた瞬間のミリ秒を記録)
+var urlSuffix;
+
+/**
+ * 記事を実際に生成します。この部分を編集することでデザインを変更可能です。
+ * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
+ * @param {String} drawitem 「本文」を描画すべきパネルのDIV要素のid
+ * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを最終的に描画すべきパネルのDIV要素のid
+ * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
+ */
+function generatePanel(entry, drawitem, renderto, closed) {
+       // プラグインを実行
+       if ( typeof ($("#" + renderto).feedblog_contents_plugin) == "function") {
+               $("#" + renderto).feedblog_contents_plugin({
+                       entry : entry
+               });
+       }
+
+       // HTML用の配列を用意する
+       var htmlBuffer = [];
+
+       // 内部的に描画先IDを生成
+       var feedblogContentId = "" + renderto + "_content_div";
+
+       // 各要素をオブジェクトに描画
+       $("#" + drawitem).html(entry.content);
+
+       // ヘッダパネルを生成 class= .feedblog_header
+       htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
+
+       // 本体記事を作成 class= .feedblog_content
+       "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
+
+       // 最終描画実施
+       $("#" + renderto).html(htmlBuffer.join(""));
+}
+
+/**
+ * システム表示画面を実際に生成します。この部分を編集することでデザインを変更可能です。
+ * @param {Entry} entry 記事の情報が代入されたEntryオブジェクト
+ * @param {String} drawitem パネルの本文を格納したDIV要素のid
+ * @param {String} renderto 「タイトル・更新日時・本文」の1日分の記事データを焼き付けるDIV要素のid
+ * @param {String} closed (Ext jsパネルオプション)記事をクローズ状態で生成するか否か
+ */
+function generateSystemPanel(entry, drawitem, renderto, closed) {
+       // HTMLを生成する
+       var htmlBuffer = [];
+
+       // 描画先IDを生成
+       var feedblogContentId = "" + renderto + "_content_div";
+
+       // 各要素をオブジェクトに描画
+       $("#" + drawitem).html(entry.content);
+
+       // ヘッダパネルを生成 class= .feedblog_header
+       htmlBuffer.push("<div class='feedblog_header' onclick='closePanel(\"" + feedblogContentId + "\")'><span>" + entry.title + "</span></div>" +
+
+       // 本体記事を作成 class= .feedblog_content
+       "<div class='feedblog_content' id='" + feedblogContentId + "'><span>" + document.getElementById(renderto).innerHTML + "</span></div>");
+
+       $("#" + renderto).html(htmlBuffer.join(""));
+}
+
+/**
+ * 全ての定数を取得・セットします
+ */
+function initialize() {
+       // 初期値をhiddenパラメータより読み込みます
+       mainPageUrl = $("#feedblog_mainpageurl").val();
+       searchPageUrl = $("#feedblog_searchpageurl").val();
+       latestXml = $("#feedblog_latestxml").val();
+       logXmlUrl = $("#feedblog_loglistxmlurl").val();
+       showLength = parseInt($("#feedblog_showlength").val());
+       if (isNaN(showLength)) {
+               showLength = 1;
+       }
+       validateMode = $("#feedblog_validatemode").val();
+
+       // 初期値を設定します
+       urlSuffix = +new Date();
+
+       // 必要な環境を確認します
+       var errorBuf = [];
+       // 変数確認
+       if (mainPageUrl === undefined) {
+               errorBuf.push("設定値「feedblog_mainpageurl」が欠落しています。");
+       }
+       if (searchPageUrl === undefined) {
+               errorBuf.push("設定値「feedblog_searchpageurl」が欠落しています。");
+       }
+       if (latestXml === undefined) {
+               errorBuf.push("設定値「feedblog_latestxml」が欠落しています。");
+       }
+       if (logXmlUrl === undefined) {
+               errorBuf.push("設定値「feedblog_loglistxmlurl」が欠落しています。");
+       }
+       if (showLength === undefined) {
+               errorBuf.push("設定値「feedblog_showlength」が欠落しています。");
+       }
+       if (validateMode === undefined) {
+               errorBuf.push("設定値「feedblog_validatemode」が欠落しています。");
+       }
+       // SHA-1関数確認
+       try {
+               if ( typeof (CryptoJS.SHA1) != "function") {
+                       errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
+               }
+       } catch (ex) {
+               errorBuf.push("crypt-jsモジュール(hmac-sha1.js)が読み込まれていません。");
+       }
+
+       // 描画エリアチェック
+       if ($("#feedblog_writearea").length == 0) {
+               errorBuf.push("描画エリア「feedblog_writearea」が存在しません。");
+       }
+       if ($("#feedblog_logselecter").length == 0) {
+               errorBuf.push("描画エリア「feedblog_logselecter」が存在しません。");
+       }
+
+       // エラーがある場合は以降の処理を継続しない
+       if (errorBuf.length > 0) {
+               alert("初期設定値に誤りがあります。\n詳細:\n" + errorBuf.join("\n"));
+               return false;
+       }
+
+       return true;
+}
+
+/**
+ * jQueryへのイベント登録です。すべてのDOMが利用可能になった時点で実行されます。
+ */
+$(document).ready(function() {
+       // 初期処理を実施
+       if (!initialize()) {
+               return false;
+       }
+
+       // 制御に必要な各種パラメタを取得する
+       var tag = getParamFromUrl("tag");
+       var id = getParamFromUrl("id");
+       var urlhash = getHashFromUrl();
+
+       // ハッシュが空か、ハッシュ形式の正規表現に一致しないようなら通常モードで実行
+       if (urlhash.length == 0 && tag.length == 0 && id.length == 0) {
+               fullWriteMode(latestXml);
+               logXMLLoader();
+       } else if (urlhash.length == 0 && id.length == 0) {
+               // タグが指定されているのでタグ探索モード
+               searchTagMode(tag, true);
+               logXMLLoader();
+       } else if (urlhash.length == 0) {
+               // IDが指定されているのでID探索モード
+               searchIdMode(id);
+               logXMLLoader();
+       } else {
+               // ハッシュ形式の正規表現に一致したら探索モード
+               searchHashMode(urlhash);
+               logXMLLoader();
+       }
+});
+
+/**
+ * jQueryでのパネル開閉を制御します
+ */
+function closePanel(id) {
+       $("#" + id).slideToggle();
+}
+
+/**
+ * 記事クラス
+ * @param {Object} obj entry 要素の DOM オブジェクト
+ */
+function Entry(obj) {
+       this.title = $("title:first", obj).text();
+       if (this.title == "")
+               requiredElementError(obj, "title");
+       this.title = validateText(this.title);
+       this.content = $("content:first", obj).text();
+       this.content = validateText(this.content);
+       this.id = $("id:first", obj).text();
+       if (this.id == "")
+               requiredElementError(obj, "id");
+       this.date = $("updated:first", obj).text();
+       if (this.date == "")
+               requiredElementError(obj, "updated");
+       this.date = validateData(this.date);
+       this.category = $("category", obj);
+}
+
+/**
+ * システム用記事クラス
+ * @param {Object} obj entry 要素の DOM オブジェクト
+ */
+function SystemEntry(obj) {
+       this.title = $("title:first", obj).text();
+       this.title = validateText(this.title);
+       this.content = $("content:first", obj).text();
+       this.content = validateText(this.content);
+       this.id = $("id:first", obj).text();
+       this.date = $("updated:first", obj).text();
+       this.date = validateData(this.date);
+       this.category = $("category", obj);
+}
+
+/**
+ * 呼び出すとDIV:id名:feedblog_writearea上のHTMLを削除し、ロードエフェクトを表示します
+ */
+function loadingEffect() {
+       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+
+       // ロード表示用のパネルを生成
+       var systemEntry = new SystemEntry();
+       systemEntry.title = "Now Loading .....";
+       systemEntry.content = '<br/>長時間画面が切り替わらない場合はページをリロードしてください。<br/><br/>';
+       generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
+}
+
+/**
+ * 呼び出すとDIV:id名:feedblog_writearea上の先頭に、処理中の記事を表示します
+ */
+function executingEffect() {
+       $("#feedblog_writearea").html('<div id="feedblog_drawpanel_info" class="feedblog_drawpanel"><div id="feedblog_drawitem_info" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+
+       // ロード表示用のパネルを生成
+       var systemEntry = new SystemEntry();
+       systemEntry.title = "検索処理中";
+       systemEntry.content = '<br/>該当するタグに属する記事を検索中です。<br/><br/>';
+       generateSystemPanel(systemEntry, "feedblog_drawitem_info", "feedblog_drawpanel_info", false);
+}
+
+/**
+ * 呼び出すとDIV:id名:feedblog_drawpanel_info上に、処理完了の記事を表示します
+ */
+function executingEffectComplete() {
+       $("#feedblog_drawpanel_info").html('<div id="feedblog_drawitem_info" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+
+       // 検索処理完了用のパネルを生成
+       var systemEntry = new SystemEntry();
+       systemEntry.title = "検索完了";
+       systemEntry.content = '<br/>検索処理が完了しました。(該当記事:' + fetchEntriesSemaphore.entryCount + '件)<br/><br/>';
+       generateSystemPanel(systemEntry, "feedblog_drawitem_info", "feedblog_drawpanel_info", false);
+
+       // 状況に応じて「全件表示」パネルを生成
+       if (fetchEntriesSemaphore.showLimit == true && fetchEntriesSemaphore.entryIndex != fetchEntriesSemaphore.entryCount) {
+               $("#feedblog_writearea").html($("#feedblog_writearea").html() + '<div id="feedblog_drawpanel_abort" class="feedblog_drawpanel"><div id="feedblog_drawitem_abort" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+               systemEntry = new SystemEntry();
+               systemEntry.title = "以降、" + (fetchEntriesSemaphore.entryCount - fetchEntriesSemaphore.entryIndex) + "件の記事が省略されています。";
+               systemEntry.content = '<br/><a style="cursor: pointer;" onclick="javascript: var tag = getParamFromUrl(\'tag\'); searchTagMode(tag, false);">全ての記事を表示する。</a><br/><br/>';
+               generateSystemPanel(systemEntry, "feedblog_drawitem_abort", "feedblog_drawpanel_abort", false);
+       }
+}
+
+/**
+ * 記事データのエラー時の処理を行います
+ */
+function showError() {
+       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+
+       // エラー内容をパネルに描画
+       var systemEntry = new SystemEntry();
+       systemEntry.title = "エラー";
+       var errorContent = [];
+       errorContent.push('<br/>記事ファイル(XML)の取得に失敗しました。以下のような原因が考えられます。<br/><br/>');
+       errorContent.push('・設定値「feedblog_latestxml」に正しいパスが設定されていない。<br/>');
+       errorContent.push('・設定値「feedblog_loglistxmlurl」に正しいパスが設定されていない。<br/>');
+       errorContent.push('・ローカル環境で起動している(必ずサーバにアップロードして実行してください)。<br/>');
+       errorContent.push('<br/>');
+       systemEntry.content = errorContent.join("\n");
+       generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
+
+       // 結果表示エリアをリセット
+       $("#feedblog_resultwritearea").html("<div class='feedblog_result_status'></div>");
+}
+
+/**
+ * 記事データのエラー時の処理を行います
+ */
+function notFoundError() {
+       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+
+       // エラー内容をパネルに描画
+       var systemEntry = new SystemEntry();
+       systemEntry.title = "検索失敗";
+       systemEntry.content = '<br/>検索条件に一致する記事は見つかりませんでした。<br/><br/>';
+       generateSystemPanel(systemEntry, "feedblog_drawitem", "feedblog_drawpanel", false);
+}
+
+/**
+ * 記事データのエラー時の処理を行います
+ */
+function requiredElementError(parent, name) {
+       alert(parent.ownerDocument.URL + ": 必須な要素 " + name + " が存在しないか空な " + parent.tagName + " 要素が存在します");
+}
+
+function xmlAttrContentEscape(str) {
+       return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/^[ ]+/mg, "&nbsp;").replace(/^[\t]+/mg, "");
+}
+
+/**
+ * 日付のHTML表示用バリデーション処理を行います
+ * @param {String} data RFC3339形式のdate-time文字列
+ */
+function validateData(data) {
+       var regT = new RegExp("T", "gm");
+       data = data.replace(regT, " ");
+
+       // 秒数の小数点以下の部分はカットする
+       data = data.substring(0, 19);
+
+       return data;
+}
+
+/**
+ * 記事本文のバリデーション処理を行います
+ * @param {String} contents 記事の本文が格納されている文字列
+ */
+function validateText(contents) {
+       // <br/>タグを挿入する
+       if (validateMode == 0) {
+               contents = contents.replace(/[\n\r]|\r\n/g, "<br />");
+       }
+
+       return contents;
+}
+
+/**
+ * URLからパラメタを取得するための関数
+ */
+function getParamFromUrl(paramName) {
+       // GETパラメタよりタグを取得する
+       var tag = "";
+       if (location.search.length > 1) {
+               var queries = location.search.substring(1).split('&');
+               for (var i = 0; i < queries.length; i++) {
+                       if (("" + queries[i].split('=')[0]) == paramName) {
+                               tag = "" + queries[i].split('=')[1];
+                       }
+               }
+       }
+
+       return tag;
+}
+
+/**
+ * URLからハッシュを取得するための関数
+ */
+function getHashFromUrl() {
+       return "" + location.hash.substring(1);
+}
+
+/**
+ * 長い順に並べるための比較関数です
+ * @param {String} a 比較対象(1)
+ * @param {String} b 比較対象(2)
+ */
+function compareLengthDecrease(a, b) {
+       a = a.length;
+       b = b.length;
+       return a > b ? -1 : a < b ? 1 : 0;
+}
+
+/**
+ * セマフォ制御用のオブジェクトです
+ */
+function Semaphore() {
+       this.id = null;
+       this.count = 0;
+       this.buf = [];
+       this.xhrs = [];
+}
+
+/**
+ * セマフォ初期化用の関数です
+ */
+Semaphore.prototype.init = function() {
+       while (this.xhrs.length > 0) {
+               this.xhrs.shift().abort();
+       }
+       this.id = Math.random();
+       this.count = 0;
+       this.buf = [];
+};
+
+/**
+ * ログファイル選択用のコンボボックスをid名:feedblog_logselecterに生成します
+ * class - .feedblog_logform, .feedblog_logselecter
+ */
+function logXMLLoader() {
+       // ログ用のXMLを読み込みます
+       jQuery.ajax({
+               url : logXmlUrl + '?time=' + urlSuffix,
+               method : "GET",
+               error : showError,
+               success : function(xmlData) {
+                       var separateTag = xmlData.getElementsByTagName("file");
+
+                       // 読み込んだ要素をStoreに格納して表示
+                       var boxBuffer = [];
+                       boxBuffer.push("<form class='feedblog_logselecter' name='feedblog_logform'><select class='feedblog_logselecter' id='feedblog_logbox' onchange='fullWriteMode(this.options[this.selectedIndex].value)'>");
+                       for (var i = 0; i < separateTag.length; i++) {
+                               boxBuffer.push("<option value='" + separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue + "'>" + separateTag[i].getElementsByTagName("display")[0].firstChild.nodeValue + "</option>");
+                       }
+                       boxBuffer.push("</select></form>");
+
+                       // コンボボックス要素を生成
+                       $("#feedblog_logselecter").html(boxBuffer.join(""));
+               }
+       });
+}
+
+/**
+ * 記事のデータが記述されたXMLデータを読み込むロジックを生成します
+ * @param {String} fileName 読み込み記事のデータが記述されているXMLファイルのパス
+ */
+function fullWriteMode(fileName) {
+       // ロードエフェクトに切り替え
+       loadingEffect();
+
+       var url = fileName;
+
+       // 記事をロードします
+       var loader = new jQuery.ajax({
+               url : url + '?time=' + urlSuffix,
+               method : "GET",
+               success : function(xmlData) {
+                       var separateTag = xmlData.getElementsByTagName("entry");
+                       var stringBuffer = [];
+                       // メモリ上での保持変数を初期化します
+                       loadedEntries = [];
+
+                       // メモリ上の変数に全ての記事要素を格納します
+                       for (var i = 0; i < separateTag.length; i++) {
+                               loadedEntries.push(new Entry(separateTag[i]));
+                       }
+
+                       // 表示ロジック呼び出し
+                       showEntriesRange(showLength, 0);
+               },
+               error : showError
+       });
+}
+
+/**
+ * 渡された文字列と一致するfeed1.0:updated要素を持った記事を検索し、表示します
+ * @param {String} urlhash feed1.0:updated要素と一致する文字列
+ */
+function searchHashMode(urlhash) {
+       // ロードエフェクト表示
+       loadingEffect();
+
+       // ログXMLファイルを読み込む
+       var loader = new jQuery.ajax({
+               url : logXmlUrl + '?time=' + urlSuffix,
+               method : "GET",
+               error : showError,
+               success : function(xmlData) {
+                       // ファイルパスの要素のみを抽出する
+                       var separateTag = xmlData.getElementsByTagName("file");
+                       var urls = new Array(separateTag.length);
+
+                       // すべてのファイルパスを配列に格納する
+                       for (var i = 0; i < separateTag.length; i++) {
+                               // "path"ノードの値を格納
+                               urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
+                       }
+
+                       // セマフォを初期化
+                       fetchEntriesSemaphore.init();
+                       fetchEntriesSemaphore.urls = urls;
+                       fetchEntriesSemaphore.count = urls.length;
+
+                       // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
+                       for (var i = 0; i < separateTag.length; i++) {
+                               // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
+                               var xhr = new jQuery.ajax({
+                                       url : urls[i],
+                                       method : "GET",
+                                       success : fetchHashEntries
+                               });
+                               fetchEntriesSemaphore.xhrs.push(xhr);
+                       }
+               }
+       });
+}
+
+/**
+ * URLハッシュ検索用のjQueryコールバック関数
+ */
+function fetchHashEntries(xmlData) {
+       // 既に検索結果が算出されていた場合は、何もしない
+       if (fetchEntriesSemaphore.buf > 0) {
+               return true;
+       }
+
+       // ハッシュを取得
+       var urlhash = getHashFromUrl();
+
+       // entry要素のみを切り出す
+       var entries = xmlData.getElementsByTagName("entry");
+
+       for (var i = 0; i < entries.length; i++) {
+               // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
+               var entry = new Entry(entries[i]);
+
+               // idの値と比較を行う
+               if (urlhash == entry.id) {
+                       // 一致した場合は該当記事を表示する
+                       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+                       generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
+
+                       fetchEntriesSemaphore.buf.push(entry);
+                       return true;
+               }
+       }
+
+       // セマフォのカウンタを減少させます (Ajaxとの同期のため)
+       fetchEntriesSemaphore.count--;
+
+       // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
+       if (fetchEntriesSemaphore.count == 0) {
+               var entries = fetchEntriesSemaphore.buf;
+
+               if (entries.length == 0) {
+                       notFoundError();
+                       return false;
+               }
+       }
+}
+
+/**
+ * 渡された文字列と一致するfeed1.0:id(sha-1)要素を持った記事を検索し、表示します
+ * @param {String} urlhash feed1.0:id(sha-1)要素と一致する文字列
+ */
+function searchIdMode(urlhash) {
+       // ロードエフェクト表示
+       loadingEffect();
+
+       // ログXMLファイルを読み込む
+       var loader = new jQuery.ajax({
+               url : logXmlUrl + '?time=' + urlSuffix,
+               method : "GET",
+               error : showError,
+               success : function(xmlData) {
+                       // ファイルパスの要素のみを抽出する
+                       var separateTag = xmlData.getElementsByTagName("file");
+                       var urls = new Array(separateTag.length);
+
+                       // すべてのファイルパスを配列に格納する
+                       for (var i = 0; i < separateTag.length; i++) {
+                               // "path"ノードの値を格納
+                               urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
+                       }
+
+                       // セマフォを初期化
+                       fetchEntriesSemaphore.init();
+                       fetchEntriesSemaphore.urls = urls;
+                       fetchEntriesSemaphore.count = urls.length;
+
+                       // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
+                       for (var i = 0; i < separateTag.length; i++) {
+                               // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
+                               var xhr = new jQuery.ajax({
+                                       url : urls[i],
+                                       method : "GET",
+                                       success : fetchIdEntries
+                               });
+                               fetchEntriesSemaphore.xhrs.push(xhr);
+                       }
+               }
+       });
+}
+
+/**
+ * ID検索用のjQueryコールバック関数
+ */
+function fetchIdEntries(xmlData) {
+       // 既に検索結果が算出されていた場合は、何もしない
+       if (fetchEntriesSemaphore.buf > 0) {
+               return true;
+       }
+
+       // IDを取得
+       var id = getParamFromUrl("id");
+
+       // entry要素のみを切り出す
+       var entries = xmlData.getElementsByTagName("entry");
+
+       for (var i = 0; i < entries.length; i++) {
+               // entryタグ内部のidノードの値のみ抽出し、入力されたhashと比較を行う
+               var entry = new Entry(entries[i]);
+
+               // idの値と比較を行う
+               if (id == CryptoJS.SHA1(entry.id).toString()) {
+                       // 一致した場合は該当記事を表示する
+                       $("#feedblog_writearea").html('<div id="feedblog_drawpanel" class="feedblog_drawpanel"><div id="feedblog_drawitem" class="feedblog_drawitem">&nbsp;<\/div><\/div>');
+                       generatePanel(entry, "feedblog_drawitem", "feedblog_drawpanel", false);
+
+                       fetchEntriesSemaphore.buf.push(entry);
+                       return true;
+               }
+
+       }
+
+       // セマフォのカウンタを減少させます (Ajaxとの同期のため)
+       fetchEntriesSemaphore.count--;
+
+       // 最後のファイルまで探索しても記事が見つからなかった場合はエラーを表示します。
+       if (fetchEntriesSemaphore.count == 0) {
+               var entries = fetchEntriesSemaphore.buf;
+
+               if (entries.length == 0) {
+                       notFoundError();
+                       return false;
+               }
+       }
+
+}
+
+/**
+ * 渡された文字列と一致するfeed1.0:category:termタグ要素を持った記事を検索し、表示します
+ * @param {String} tag feed1.0:category:term要素と一致する文字列
+ * @param {boolean} showLimit 省略表示をする場合はtrue,全件表示する場合はfalse
+ */
+function searchTagMode(tag, showLimit) {
+       // 処理中エフェクト表示
+       executingEffect();
+
+       // ログXMLファイルを読み込む
+       var loader = new jQuery.ajax({
+               url : logXmlUrl + '?time=' + urlSuffix,
+               method : "GET",
+               error : showError,
+               success : function(xmlData) {
+                       // ファイルパスの要素のみを抽出する
+                       var separateTag = xmlData.getElementsByTagName("file");
+                       var urls = new Array(separateTag.length);
+
+                       // すべてのファイルパスを配列に格納する
+                       for (var i = 0; i < separateTag.length; i++) {
+                               // "path"ノードの値を格納
+                               urls[i] = separateTag[i].getElementsByTagName("path")[0].firstChild.nodeValue;
+                       }
+
+                       // セマフォを初期化
+                       fetchEntriesSemaphore.init();
+                       fetchEntriesSemaphore.urls = urls;
+                       fetchEntriesSemaphore.count = urls.length;
+                       fetchEntriesSemaphore.entryIndex = 0;
+                       fetchEntriesSemaphore.entryCount = 0;
+                       fetchEntriesSemaphore.showLimit = showLimit;
+
+                       // ファイルパス配列に格納されているすべての記事に対し、探索を開始する
+                       for (var i = 0; i < separateTag.length; i++) {
+                               // ファイルパス配列の要素からリクエストを生成し、対象データをロードする
+                               var xhr = new jQuery.ajax({
+                                       url : urls[i],
+                                       method : "GET",
+                                       success : fetchTagEntries
+                               });
+                               fetchEntriesSemaphore.xhrs.push(xhr);
+                       }
+               }
+       });
+}
+
+/**
+ * タグ検索用のjQueryコールバック関数
+ */
+function fetchTagEntries(xmlData) {
+       // 既に記事の表示が行われている場合、何も実施しない
+       if (fetchEntriesSemaphore.buf.length > 0) {
+               return true;
+       }
+
+       // タグを取得する
+       var tag = getParamFromUrl("tag");
+
+       // entry要素のみを切り出す
+       var entries = xmlData.getElementsByTagName("entry");
+
+       for (var j = 0; j < entries.length; j++) {
+               var entry = new Entry(entries[j]);
+
+               for (var k = 0; k < entry.category.length; k++) {
+                       // タグのIDが一致したら格納
+                       if (tag == entry.category.eq(k).attr("term")) {
+                               // 表示数限界を超えていた場合、表示処理を実施しない
+                               if (fetchEntriesSemaphore.showLimit == true && fetchEntriesSemaphore.entryIndex >= showLength) {
+                                       // 検索一致件数をカウント
+                                       fetchEntriesSemaphore.entryCount++;
+                               } else {
+                                       // 検索一致件数をカウント
+                                       fetchEntriesSemaphore.entryCount++;
+                                       // entryを格納する
+                                       fetchEntriesSemaphore.entryIndex = showEntriesAdd(entry, fetchEntriesSemaphore.entryIndex);
+                               }
+                       }
+               }
+       }
+
+       // セマフォのカウンタを減少させます (Ajaxとの同期のため)
+       fetchEntriesSemaphore.count--;
+
+       if (fetchEntriesSemaphore.count == 0) {
+               if (fetchEntriesSemaphore.entryIndex == 0) {
+                       notFoundError();
+                       return false;
+               } else {
+                       executingEffectComplete();
+                       return true;
+               }
+       }
+}
+
+/**
+ * 検索結果を分割して表示します
+ * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
+ * @param {int} showLength 一回の画面に表示する記事数
+ * @param {int} startIndex 表示を開始する記事のインデックス
+ */
+function showEntriesRange(showLength, startIndex) {
+       // メモリ上から記事データをロード
+       var entries = loadedEntries;
+
+       // 表示インデックスが範囲外の場合はエラーパネルを表示して終了
+       if (startIndex < 0 || (entries.length <= startIndex && entries.length != 0)) {
+               showError();
+               return;
+       }
+
+       var stringBuffer = [];
+
+       // リミッターを設定する
+       var loopLimit = (showLength + startIndex > entries.length) ? entries.length : showLength + startIndex;
+       var indexShowEntries = loopLimit + 1;
+
+       // 情報メニュー表示用バッファです
+       var menuInfoBuffer = [];
+       menuInfoBuffer.push("<div class='feedblog_pager_shownumber'>");
+       var viewStartIndex = 0;
+       entries.length == 0 ? viewStartIndex = 0 : viewStartIndex = startIndex + 1;
+       menuInfoBuffer.push(viewStartIndex + "件~" + loopLimit + "件(全" + entries.length + "件)目の記事を表示中<br/>");
+       menuInfoBuffer.push("</div>");
+
+       // ページ移動メニュー表示用バッファです
+       var menuMoveBuffer = [];
+       menuMoveBuffer.push("<ul class='feedblog_pager'>");
+
+       // ブランクエリアを挟む
+       menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
+
+       // 左パネルの表示制御
+       if (startIndex - showLength >= 0) {
+               menuMoveBuffer.push("\<li class='feedblog_pager_goback'><span class='feedblog_pager_goback' onclick='showEntriesRange(" + showLength + ", " + (startIndex - showLength) + "); return false;'>\< 前の" + showLength + "件を表示</span\></li>");
+       } else {
+               menuMoveBuffer.push("\<li class='feedblog_pager_goback'>\< 前の" + showLength + "件を表示</a\></li>");
+       }
+
+       // 中央のパネルの表示制御
+       menuMoveBuffer.push("<li class='feedblog_pager_center'>[ ");
+       var menuNumbers = Math.ceil(entries.length / showLength);
+       for ( i = 0; i < menuNumbers; i++) {
+               if (startIndex / showLength == i) {
+                       menuMoveBuffer.push(i + " ");
+               } else {
+                       menuMoveBuffer.push("<span class='feedblog_pager_center' onclick='showEntriesRange(" + showLength + ", " + (i * showLength) + "); return false;'>");
+                       menuMoveBuffer.push(i);
+                       menuMoveBuffer.push("</span> ");
+               }
+       }
+       menuMoveBuffer.push("]</li>");
+
+       // 右パネルの表示制御
+       if (entries.length > startIndex + showLength) {
+               menuMoveBuffer.push("\<li class='feedblog_pager_gonext'><span class='feedblog_pager_gonext' onclick='showEntriesRange(" + showLength + ", " + (startIndex + showLength) + "); return false;'>\次の" + showLength + "件を表示 \></span\></li>");
+       } else {
+               menuMoveBuffer.push("\<li class='feedblog_pager_gonext'>次の" + showLength + "件を表示 \></li>");
+       }
+
+       // ブランクエリアを挟む
+       menuMoveBuffer.push("<li class='feedblog_pager_blank'></li>");
+
+       menuMoveBuffer.push("</ul>");
+
+       // メニューを結合してパネル(前方)に組み込みます
+       stringBuffer.push("<div class='feedblog_pager_wrapper'>");
+       stringBuffer.push(menuInfoBuffer.join(""));
+       stringBuffer.push(menuMoveBuffer.join(""));
+       stringBuffer.push("</div>");
+
+       // 記事描画部分のパネルを生成します
+       for (var i = startIndex; i < loopLimit; i++) {
+               stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
+               stringBuffer.push(i);
+               stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
+               stringBuffer.push(i);
+               stringBuffer.push('"><\/div><\/div>');
+       }
+
+       // メニューを結合してパネル(後方)に組み込みます
+       stringBuffer.push("<div class='feedblog_pager_wrapper'>");
+       stringBuffer.push(menuMoveBuffer.join(""));
+       stringBuffer.push(menuInfoBuffer.join(""));
+       stringBuffer.push("</div>");
+
+       $("#feedblog_writearea").html(stringBuffer.join(""));
+
+       for (var i = startIndex; i < loopLimit; i++) {
+               // 各要素をオブジェクトに格納します
+               var entry = entries[i];
+
+               // すべてのパネルをオープン状態で生成します
+               generatePanel(entry, "feedblog_drawitem" + i, "feedblog_drawpanel" + i, false);
+       }
+}
+
+/**
+ * Entry要素を追加表示する為の関数
+ * class - div.feedblog_pager, ul.feedblog_pager, li.feedblog_pager などなど
+ * @param {Entry} 末尾に追加するEntryオブジェクト
+ * @param {int} 現在のEntry数
+ * @return {int} 新規記事を追加し終えた後のEntry数
+ */
+function showEntriesAdd(entry, entryIndex) {
+
+       var stringBuffer = [];
+
+       // 記事描画部分のパネルを生成します
+       var newIndex = entryIndex + 1;
+       stringBuffer.push('<div class="feedblog_drawpanel" id="feedblog_drawpanel');
+       stringBuffer.push(newIndex);
+       stringBuffer.push('"><div class="feedblog_drawitem" id="feedblog_drawitem');
+       stringBuffer.push(newIndex);
+       stringBuffer.push('"><\/div><\/div>');
+
+       // 記事をパネルに描画します
+       $("#feedblog_writearea").html($("#feedblog_writearea").html() + stringBuffer.join(""));
+       generatePanel(entry, "feedblog_drawitem" + newIndex, "feedblog_drawpanel" + newIndex, false);
+
+       return newIndex;
+}
+