はてなブログのMT形式バックアップデータを解析して,見出し数や特定のタグ数を記事ごとにカウントするJavaScriptコード


はてなブログのバックアップデータは,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オブジェクトを全く作成せずに,非同期での配列スキャンが開始できるようにした。