JavaScriptでTSV(タブ区切り文字列)をパースするライブラリver0.2。Excelからのコピペ内容を表形式でそのままJSにインポート可能

概要

JavaScriptでTSVデータをパースするライブラリ」を更新した。

もとからある機能:

  • TSVデータの取得元として,DOM要素内のinnerHTML,または関数オブジェクト内のコメントを指定できる。

今回の修正点:

  • TSVデータのセル内の値に,\tという記法によって,タブ文字を含むことができるようになった。


詳しくは下記コードのコメント内の使用法を読むこと。

コード

TsvParser.js

/*

	TsvParser.js 

	〜TSVをパースするライブラリ〜
	http://source-code-student.hatenablog.jp/entry/2019/02/05/JavaScript%E3%81%A7TSV%EF%BC%88%E3%82%BF%E3%83%96%E5%8C%BA%E5%88%87%E3%82%8A%E6%96%87%E5%AD%97%E5%88%97%EF%BC%89%E3%82%92%E3%83%91%E3%83%BC%E3%82%B9%E3%81%99%E3%82%8B%E3%83%A9%E3%82%A4


	@author id:SourceCode-Student
	ver 0.2, MIT License
	
	
	コンセプト:
		
		MS Excelからテキストにコピペしたデータを,JSでそのまま表形式で扱えたらいいな
		という発想による。
		
		HTML上で,マスタデータとして表形式のデータが必要になる場合,
		データベースを利用するかわりに,コピペしたテキストを利用できれば
		RDBMSの導入が不要となる。
		また,コピペ時に余分に手を加えなくて済むのであれば,Excelの内容変更時のマスターデータのメンテナンスも楽になる。
		
		MS Excelからテキストにセル範囲をコピペすると,タブ区切りデータ(TSV)となる。
		改行を含むセルは""で囲まれる。
		
		JSで複数行のデータを扱うには,HTML要素からinnerHTMLを取得するか,
		functionオブジェクトの内部コメントを使用する方法がある。
		本ライブラリは,この両方の方法に対応している。
	
	
	使い方:
		
		(1)import_source_as_tsvで,DOM要素またはfunctionオブジェクトをTSVソースとして指定する。

		(2)ソースとしてDOM要素を指定した場合,その要素のinnerHTMLをTSVとしてパースする。

			・パースに際して,HTML特殊文字がJS文字にデコードされる。設定変数ht_special_charsを参照。
				例えば,innerHTMLに&という文字が使われている場合,
				この設定変数に記載があるので,TSV内でも&ではなく&に変換される。

			・innerHTMLは,先頭や末尾に無意味な改行を含まないように注意すること。
				divタグ内部であれば,<div>と</div>の間にあるすべての改行がinnerHTMLとして認識されるため。
			
			・DOM要素内の記法の例を示す。

<div id="div_data">No	大文字	小文字
1	A	a
2	B	b</div>
			
				この場合,
				import_source_as_tsv({ src_dom_id : "div_data", columns : ["No", "Omoji", "Komoji"] }) とすれば
				TSVパースできる。

		(3)ソースとしてfunctionオブジェクトを指定した場合,
			functionオブジェクト内の/*  */の内部の複数行文字列がTSVとしてパースされる。
				
			・パースに際して,HTML特殊文字のデコードは行われない。
			
			・functionオブジェクトの定義の例を示す。

var data_func = (function(){/*
No	大文字	小文字
1	A	a
2	B	b
*/});

			この場合,
			import_source_as_tsv({ src_func : data_func, columns : ["No", "Omoji", "Komoji"] }) とすれば
			TSVパースできる。

		(4)import_source_as_tsvの引数内で,columnsにはハッシュオブジェクトのプロパティ名を配列で渡す。
			前述の例であれば,パース済みのTSVとして下記のような配列が返る。
			
				[
					{ No : 1, Omoji : "A", Komoji : a },
					{ No : 2, Omoji : "B", Komoji : b }
				]
		
		(5)TSVは,セル内に改行を含むことができる。改行を含むセルは,セルの最初と最後が"で囲まれている必要がある。
			記法の例:
		
<div id="div_data">No	大文字	小文字
1	"改行を
含むことが
できます。"	a
2	B	b</div>


		(6)TSVは,セル内に"という文字を含む場合,\" のようにエスケープされていなければならない。
		
		(7)TSVは,セル内に\tという文字を含む場合,タブ文字そのものではなく \t と書かれていなければならない。
			タブ文字そのものを書くと,TSVのセル区切りとして認識されてしまうため。
			記法の例:

var sample_data_func = (function(){/*
区切り文字
 
 
\t
\"


*/});

		(8)MS ExcelのUI上から,セル範囲をコピペしてテキストエディタに張り付けた場合,
			上記(5)の条件は自動的に満たされるが,上記(6)や(7)の条件は満たされない。注意すること。



	リリースノート
		ver 0.1  新規作成。
		ver 0.2  タブ文字\tをセルの値として含むことができるように修正。

*/



// HTML特殊文字をJS文字に変換するためのテーブル
var ht_special_chars = [
	["&amp;", "&"]
	// 対処すべき特殊文字が増えたらここに追加
];


// ログ出力
function log_tsv_parse( s ){
	console.log( s );
}


// あるDOM要素の中身(innerHTML)をTSVとして配列にインポート
// 各セルの値はHTML特殊文字がデコードされる

// ある情報源から,TSVをロードし,ハッシュの配列で返す。
// 情報源はDOM要素のinnerHTML,またはfunctionオブジェクト。
function import_source_as_tsv( obj ){
	
	// 設定項目
	var src_dom_id = obj.src_dom_id;
	var src_func = obj.src_func;
	var col_names = obj.columns;
	
	// 情報源からテキストを取得
	var src_text;
	var use_html_dom_src = false;
	if( src_dom_id ){
		use_html_dom_src = true;
	}
	if( use_html_dom_src ){
		// 要素(divなど)の中身を見る
		src_text = document.getElementById( src_dom_id ).innerHTML;
	}else{
		
		// 関数オブジェクトの中身を見る(/*  */でコメントアウトされた行内)
		src_text = src_func.toString().replace(/\r\n/g, "\n").match(/\/\*\n([^]*)\n\*\//)[1];
	}
	
	// TSVとしてパース
	var objs =  parse_text_as_tsv( src_text, col_names );
	
	// HTML内部の場合
	if( use_html_dom_src ){
		
		// HTML特殊文字をデコード
		// 全行で
		for( var i = 0; i < objs.length; i ++ ){
			var record = objs[i];
		
			// 全カラムで
			for( var j = 0; j < col_names.length; j ++ ){

				// 一つのカラム名
				var col_name = col_names[j];
			
				// 全特殊文字を
				for( var k = 0; k < ht_special_chars.length; k ++ ){
					
					var ch_from = ht_special_chars[k][0];
					var ch_to   = ht_special_chars[k][1];
					
					// セル内で全置換
					record[ col_name ] = record[ col_name ]
						.replace( new RegExp( ch_from, "g" ), ch_to )
					;
					
				}
				
				log_tsv_parse( "このカラムのHTML特殊文字のデコード終了。record[ col_name ]=" + record[ col_name ] );
			}
		}
		log_tsv_parse( "HTML特殊文字の全置換が完了" );
	}
	
	return objs;
}


// ある文字列をTSVとして配列にインポート
// ""で囲まれた改行付きセルデータも対応。
// ""内では"は現れうるものとする。
function parse_text_as_tsv( str, col_names ){
	
	log_tsv_parse( str );
	//log( "パース対象の文字列:" + str );
	
	// 改行コード変換
	str = str.replace(/\r\n/g, "\n");

	// 解析を開始
	var cur = 0;
	var inside_quoted_cell = false;
	var just_escaped
	var continue_flag = true;
	var buff = "";
	var line = [];
	var lines = [];
	while( continue_flag ){
		
		// 1文字取り出す
		var ch = str.charAt( cur );
		log_tsv_parse( "ch = " + ch );

		
		// エスケープ済みの"か
		if(
			just_escaped
			&&
			ch == '"'
		){
			buff += '"';
			log_tsv_parse('エスケープ済みの"を追加');
			//log('エスケープ済みの"を追加');
			
			// エスケープを解除
			just_escaped = false;
			
		}
		else
		// \tか (TSV上ではタブ文字を表現できないので,"\t"として表記して代用する)
		if(
			just_escaped
			&&
			ch == 't'
		){
			buff += "\t";
			log_tsv_parse('\tを追加');
			
			// エスケープを解除
			just_escaped = false;
		}
		else
		{
		
			// エスケープを解除
			just_escaped = false;
			
			// 前の文字が\だったら,"のエスケープではないので,バッファに追加
			if( 
				( buff.length > 0 )
				&&
				( buff.charAt( buff.length - 1 ) == '\\' )
			){
				buff += '\\';
			}


			// エスケープ記号か
			if( ch == '\\' ){
				//log("エスケープ記号を発見");
				just_escaped = true;
			}
			else
			if(
				( ch == '"' )
			)
			{
				// セル先頭の"か
				if( 
					( buff.length == 0 )
					&&
					( ch == '"' )
				){
					// 改行含みのセルとみなす。
					// 先頭に"という文字を入力したいならば,¥"とするべきである。
					inside_quoted_cell = true;
					
					log_tsv_parse("改行を含むセルを検出");
				}
				else
				// セル末尾の改行ラッパーの"か
				if(
					inside_quoted_cell
					&&
					(
						// 全データの末尾
						( cur == ( str.length - 1 ) )
						||
						( str.charAt( cur + 1 ) == "\t" )
						||
						( str.charAt( cur + 1 ) == "\n" )
					)
				){
					// この"は無視される
					
					// ラッパーを解除
					inside_quoted_cell = false;
				}
				else
				{
					buff += '"';
				}
			}
			else
			if( ch == "\n" )
			{
				
				// 改行ありセルの内部か?
				if( inside_quoted_cell ){
					//buff += "\n";
					buff += "<br>";
				}else{
					// 1セルの終わりとする
					log_tsv_parse("1セルの終わり。buff="+ buff);
					line.push( buff );
					buff = "";

					// 1レコードの終わりとする
					log_tsv_parse("1レコードの終わり。line="+ line);
					lines.push( line );
					
					// 新しい行
					line = [];
				}
			}
			else
			if( ch == "\t" ){

				// 改行ありセルの内部か?
				if( inside_quoted_cell ){
					buff += "\t";
				}else{
					// 1セルの終わりとする
					log_tsv_parse("1セルの終わり。buff="+ buff);
					line.push( buff );
					buff = "";
					
					// 新しいセル
				}
			
			}
			else
			{
				buff += ch;
			}

		}
		
		
		// 次の文字へ
		cur ++;
		
		// 最後の文字まで来たか?
		if( cur >= str.length ){
		
			// 1セルの終わりとする
			line.push( buff );

			// 1レコードの終わりとする
			log_tsv_parse("1レコードの終わり。line="+ line);
			lines.push( line );

			// 解析を終了する
			continue_flag = false;
			
			// 強制解除
			inside_quoted_cell = false;
		}

	} // end while
	log_tsv_parse( "全行のスキャン終了。lines=" + lines );
	//log( "全行のスキャン終了。lines=" + lines );

	
	// ハッシュに詰め替える
	var objs = [];

	// 全レコード
	for( var i = 0; i < lines.length; i ++ ){
		var obj = {};
		var line = lines[i];

		// 全カラム
		for( var j = 0; j < col_names.length; j ++ ){
			var col_name = col_names[ j ];
			
			// ハッシュに詰める
			obj[ col_name ] = line[ j ];
		}
		
		objs.push( obj );
	}
	log_tsv_parse( "ハッシュへの詰め替え終了。objs.length=" + objs.length );
	log_tsv_parse( "objs=" + objs );
	
	// 返却
	return objs;
}