Home Reference Source

src/senko/CSV.js

/**
 * The script is part of SenkoWSH.
 * 
 * AUTHOR:
 *  natade (http://twitter.com/natadea)
 * 
 * LICENSE:
 *  The MIT license https://opensource.org/licenses/MIT
 */

/**
 * CSV形式のテキストなどを扱うクラス
 */
export default class CSV {
	
	/**
	 * CSVテキストから配列を作成
	 * @param {string} text 
	 * @param {string} [separator=","]
	 * @returns {Array<Array<string>>}
	 */
	static parse(text, separator) {
		const iseparator = separator === undefined ? "," : separator;
		// 改行コードの正規化
		const itext = text.replace(/\r\n?|\n/g, "\n");
		const CODE_SEPARATOR = iseparator.charCodeAt(0);
		const CODE_CR    = 0x0D;
		const CODE_LF    = 0x0A;
		const CODE_DOUBLEQUOTES = 0x22;
		const out = [];
		const length = itext.length;
		let element = "";
		let count_rows    = 0;
		let count_columns = 0;
		let isnextelement = false;
		let isnextline    = false;
		for(let i = 0; i < length; i++) {
			let code = itext.charCodeAt(i);
			// 複数行なら一気に全て読み込んでしまう(1文字目がダブルクォーテーションかどうか)
			if((code === CODE_DOUBLEQUOTES)&&(element.length === 0)) {
				i++;
				for(;i < length;i++) {
					code = itext.charCodeAt(i);
					if(code === CODE_DOUBLEQUOTES) {
						// フィールドの終了か?
						// 文字としてのダブルクォーテーションなのか
						if((i + 1) !== (length - 1)) {
							if(itext.charCodeAt(i + 1) === CODE_DOUBLEQUOTES) {
								i++;
								element += "\""; 
							}
							else {
								break;
							}
						}
						else {
							break;
						}
					}
					else {
						element += itext.charAt(i);
					}
				}
			}
			// 複数行以外なら1文字ずつ解析
			else {
				switch(code) {
					case(CODE_SEPARATOR):
						isnextelement = true;
						break;
					case(CODE_CR):
					case(CODE_LF):
						isnextline = true;
						break;
					default:
						break;
				}
				if(isnextelement) {
					isnextelement = false;
					if(out[count_rows] === undefined) {
						out[count_rows] = [];
					}
					out[count_rows][count_columns] = element;
					element = "";
					count_columns += 1;
				}
				else if(isnextline) {
					isnextline = false;
					//文字があったり、改行がある場合は処理
					//例えば CR+LF や 最後のフィールド で改行しているだけなどは無視できる
					if((element !== "")||(count_columns !== 0)) {
						if(out[count_rows] === undefined) {
							out[count_rows] = [];
						}
						out[count_rows][count_columns] = element;
						element = "";
						count_rows    += 1;
						count_columns  = 0;
					}
				}
				else {
					element += itext.charAt(i);
				}
			}
			// 最終行に改行がない場合
			if(i === length - 1) {
				if(count_columns !== 0) {
					out[count_rows][count_columns] = element;
				}
			}
		}
		return out;
	}
	
	/**
	 * 配列からCSVテキストを作成
	 * @param {Array<Array<string>>} csv_array 
	 * @param {string} [separator=","]
	 * @param {string} [newline="\r\n"]
	 * @returns {string}
	 */
	static create(csv_array, separator, newline) {
		const iseparator = separator === undefined ? "," : separator;
		const inewline = newline === undefined ? "\r\n" : newline;
		let out = "";
		const escape = /["\r\n,\t]/;
		if(csv_array !== undefined) {
			for(let i = 0;i < csv_array.length;i++) {
				if(csv_array[i] !== undefined) {
					for(let j = 0;j < csv_array[i].length;j++) {
						let element = csv_array[i][j];
						if(escape.test(element)) {
							element = element.replace(/"/g, "\"\"");
							element = "\"" + element + "\"";
						}
						out += element;
						if(j !== csv_array[i].length - 1) {
							out += iseparator;
						}
					}
				}
				out += inewline;
			}
		}
		return out;
	}

	/**
	 * 1行目に列名が記載しているCSVをJSON配列に変換
	 * @param {Array<Array<string>>} csv_array 
	 * @returns {Array<Object<string, string>>}
	 */
	static toJSONArrayFromCSVArray(csv_array) {
		const title_line = csv_array[0];
		const key_name = [];
		for(let i = 0; i < title_line.length; i++) {
			key_name.push(title_line[i]);
		}
		const json_array = [];
		for(let i = 1; i < csv_array.length; i++) {
			const line = csv_array[i];
			/**
			 * @type {Object<string, string>}
			 * @private
			 */
			const json_data = {};
			for(let j = 0; j < line.length; j++) {
				json_data[key_name[j]] = line[j];
			}
			json_array.push(json_data);
		}
		return json_array;
	}

	/**
	 * 共通の型のJSON配列をCSV配列へ変換
	 * @param {Array<Object<string, string>>} json_array 
	 * @param {Array<string>} [title_array] 
	 * @returns {Array<Array<string>>}
	 */
	static toCSVArrayFromJSONArray(json_array, title_array) {
		const csv_array = [];
		let title_list = null;
		if(title_array === undefined) {
			title_list = [];
			for(const key in json_array[0]) {
				title_list.push(key);
			}
		}
		else {
			title_list = title_array;
		}
		csv_array.push(title_list);
		for(let i = 0; i < json_array.length; i++) {
			const line = json_array[i];
			const data_list = [];
			for(let j = 0; j < title_list.length; j++) {
				data_list.push(line[title_list[j]]);
			}
			csv_array.push(data_list);
		}
		return csv_array;
	}

}