概要
「JavaScriptでTSVデータをパースするライブラリ」を更新した。
もとからある機能:
- TSVデータの取得元として,DOM要素内のinnerHTML,または関数オブジェクト内のコメントを指定できる。
今回の修正点:
- TSVデータのセル内の値に,\tという記法によって,タブ文字を含むことができるようになった。
- (ver0.3) 見出し行をデータとして含めないでスキップ可能になった。
詳しくは下記コードのコメント内の使用法を読むこと。
コード
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 MIT License リリースノート ver 0.1 新規作成。 ver 0.2 タブ文字\tをセルの値として含むことができるように修正。 ver 0.3 data_line_begins_from オプションを追加。見出し行をデータ内容として含めないようにする。 コンセプト: 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)の条件は満たされない。注意すること。 */ // HTML特殊文字をJS文字に変換するためのテーブル var ht_special_chars = [ ["&", "&"] // 対処すべき特殊文字が増えたらここに追加 ]; // ログ出力 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 data_line_begins_from = obj.data_line_begins_from; if( ! data_line_begins_from ){ data_line_begins_from = 0; } // 情報源からテキストを取得 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, data_line_begins_from ); // 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, data_line_begins_from ){ 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 ++ ){ for( var i = data_line_begins_from; 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; }