はてなブログのバックアップデータは,MovableType形式だ。
このMTデータを解析して,記事ごとに特定のタグがいくつあるか?を
一覧表に集計するJavaScriptフォーム。
下記のフォームをベースに改良した。
一般テキストからテンプレートマッチングで項目を一覧表形式で抽出するJSコード (パターンの繰り返しから正規表現で連続キャプチャ) - ソフトウェア勉強ログとサンプルコード
http://source-code-student.hatenablog.jp/entry/20150102/p2
ソースコード
<h2>ハテブロのMTエクスポート内容から,見出しの不足を検出するフォーム</h2> ブログのベースURL:( http://○○○.hatenablog.jp/entry/ )<br> <input type="text" id="t_base_url" size="50"><br> <br> エクスポート内容をコピペ:<br> <textarea id="ta_honbun" cols="80" rows="20"></textarea><br> <br> <input type="button" value="分析を開始" onclick="f()"><br> <br> 動作ログ:<br> <textarea id="ta_log" cols="80" rows="20"></textarea><br> <br> 結果:<br> <pre id="pre_result_table" style="padding:40px;margin:10px;background-color:lightgray;height:300px;overflow-y:scroll;overflow-x:scroll;" ></pre> <style> textarea{ overflow-x : scroll; } table.capture_results td{ padding : 3px; background-color : whitesmoke; } </style> <script> // メイン処理 function f(){ clear_log(); $("pre_result_table").innerHTML = "処理中です。"; // 入力情報を取得 var arr_honbun = $("ta_honbun").value.replace(/\r\n/g, "\n").split("\n"); var base_url = $("t_base_url").value; // 現在の記事URL var current_article_url; // この記事内の見出したち var headers; var bqs; // 全情報の格納配列 var infos = []; // 本文の全行に対して分析しながらループ(すごく重い処理) arr_honbun.eachAsync(function( h_line, h_line_cnt ){ // この中の処理はすごく重い。1400行ぐらいでブラウザの応用が停止の警告が出る。 // なので,ここから先は同期的に実行せず,うまく非同期化する。 /* log( "本文の" + h_line_cnt + "行目を検査:" + h_line ); */ // 新記事か? var m = h_line.match( /^BASENAME: (.+)$/ ); if( m ){ // 前の記事の情報があれば格納 if( current_article_url ){ infos.push({ url : base_url + current_article_url, headers : headers, bqs : bqs }); } // 新記事のURLを格納 current_article_url = m[1]; log("新記事を検出:" + current_article_url + " at " + h_line_cnt ); // 見出しを初期化 headers = []; bqs = []; return; } // 見出しか? var m2 = h_line.match( /<h4>(.+)<\/h4>/ ); if( m2 ){ // 見出しを格納 headers.push( m2[1] ); log("見出しを検出:" + m2[1] + " at " + h_line_cnt ); return; } // BQの終了か? var m3 = h_line.match( /<\/blockquote>/ ); if( m3 ){ // 見出しを格納 bqs.push( true ); log("BQの終了を検出" + " at " + h_line_cnt ); return; } }).afterThat(function(){ // 重い処理が終わったら・・・ // さいご余った分を記録 infos.push({ url : base_url + current_article_url, headers : headers, bqs : bqs }); log("抽出が完了"); // 結果表示 showDetectedResultsAsTable( infos ); log("全処理が完了しました。"); }); } // 見出しを検出する処理のプランを返す。 function getDetectHeaderPlan( arr_honbun, base_url ){ // 実行予定だけを返す return promise; } // -------- Promise動作 // -------------- 非同期のループ処理 --------------- // 配列のイテレータ,非同期 Array.prototype.eachAsync = function( func ){ return new ArrayTaskServer({ target_arr : this, callback : func }); }; // タスク格納器 var ArrayTaskServer = function(params){ // パラメータ this.target_arr = params.target_arr; this.callback = params.callback; // 初期化 this.after_tasks = []; this.current_task_index = 0; // 処理開始 this.kick(); }; ArrayTaskServer.prototype = { target_arr : null, callback : null, current_task_index : null, // 配列処理の完了後の処理タスク after_tasks : null, // 配列処理 kick : function(){ var _this = this; setTimeout( function(){ // 配列内の要素を指定 var item = _this.target_arr[ _this.current_task_index ]; // 処理 _this.callback( item, _this.current_task_index ); // 次へゆくか if( _this.target_arr.length <= _this.current_task_index + 1 ){ _this.start_after_tasks(); }else{ _this.current_task_index ++; _this.kick(); } }, 0 ) }, // 配列処理の終了後のタスク登録 afterThat : function( func ){ // 格納 this.after_tasks.push( func ); }, // 配列処理終了後のタスク実行開始 start_after_tasks : function(){ this.after_tasks.each(function(func){ func(); }); } }; // ------ etc // 抽出結果をテーブル形式で一覧表示 function showDetectedResultsAsTable( infos ){ log("結果表示:" + infos.length + "行のテーブル"); // 全行 var trs = infos.map(function( info, index ){ // 全項目 var tds = "<td><a href='" + info.url + "'>" + info.url + "</a></td>" + "<td> 見出し数 " + info.headers.length + " </td>" + "<td> BQ数 " + info.bqs.length + " </td>" + "<td> トータル数 " + ( info.headers.length + info.bqs.length ) + " </td>" ; return "<tr>" + tds + "</tr>"; }).join("\n"); var table_html = "<table border=1 class='capture_results'><tbody>" + trs + "</tbody></table>"; // 表示 $( "pre_result_table" ).innerHTML = table_html; } // 配列のイテレータ Array.prototype.each = function( func ){ for( var i = 0; i < this.length; i ++ ){ func.call( this, this[i], i ); } return this; // チェインを継続 }; // map Array.prototype.map = function( func ){ var _arr = []; this.each(function( item, ind ){ _arr.push( func.call( _arr, item, ind ) ); }); return _arr; }; function $( dom_id ){ return document.getElementById( dom_id ); } function log( s ){ if( ! s ) s = ""; $("ta_log").value = ("" + s) + "\n" + $("ta_log").value; } function clear_log(){ $("ta_log").value = ""; } </script>
工夫した点:
- 配列の要素ごとにPromiseオブジェクトを作ると,各要素のスキャンが始まる前にブラウザが固まってしまう。そこで,Promiseオブジェクトを全く作成せずに,非同期での配列スキャンが開始できるようにした。