Source: src/timeSeries/timeSeries-FileParser.js

import * as Util from '../util/util.js';
import {HJN} from '../tatLogDiver/tatLogDiver-HJN.js';

/**
 * @memberof TimeSeries
 * @class FileParser
 * @classdesc ファイルをパースして読み込む
 *            <p>
 *            パース条件指定画面生成つき
 */
export default (function() {
    /** @static */

    /** constructor */
    function FileParser(){
        if(!(this instanceof FileParser)) return new FileParser();
    }
    
    /** @private */

    // public
    /**
     * ファイルが新たに指定された時、eTatOriginalを再構築するか否(データを追加する)か
     * 
     * @memberof TimeSeries.FileParser
     * @return {boolean} 再構築モードするときtrue、データを追加するときfalse
     */
    FileParser.prototype.isNewETAT = function() { // #23
        return Util.Config.File.getConfig("NEWFILE") === "NEWDATA"; // #76
    }
    
    /**
     * 「ファイルから次の1レコードを取得するutil」 を取得する
     * 
     * @memberof TimeSeries.FileParser
     */
    FileParser.prototype.createGetterOfLine = function(file) {

        /**
         * @memberof TimeSeries.FileParser
         * @class GetterOfLine
         * @classdesc ファイルから1レコード取得する
         *            <p>
         *            ファクトリのFileParserが保持する改行コードを用いて、ファイルから1レコードを取得する
         * 
         * @example try{ var getterOfLine = FileParser.createGetterOfLine(file),
         *          fileInfo;<br>
         *          for(var i = 0; i < n; i++) { <br>
         *          line = getterOfLine.next(); fileInfo += line.str + "<BR>"; }<br>
         *          }catch (e) {<br>
         *          console.error("改行コードの無いファイルは扱えません]%o",e); }
         */
        function GetterOfLine(file, maxLength){ /* constructor */
            if(!(this instanceof GetterOfLine)) return new GetterOfLine(file, maxLength);

            this.file = file;
            this.buf = new Uint8Array(file);
            this.maxLength = maxLength || this.buf.length,
            this.confLF = Util.Config.File.getConfig("LF");  // 改行コードor固定レコード長
                                                                // #76
            this.from = 0;
            this.to = 0;
            this.len = 0;
            this.line = {file: this.file, pos: 0, array: null, str: "", isEoF: false };
        }
        // public
        /**
         * 次の1レコードを取得する
         * 
         * @memberof TimeSeries.FileParser.GetterOfLine
         * @name next
         */
        if (Util.Config.File.getValueByKey("LF") === "LF_FIX"){ // 固定長のとき #76
            GetterOfLine.prototype.next = function () { // 次の1レコードを取得する
                if(this.from >= this.maxLength ){   // ファイル末尾のとき
                    this.line = {file: this.file, pos: this.maxLength, array: null, str: "", isEoF: true };
                } else {
                    this.len = Math.min(this.maxLength - this.from, this.confLF);
                    var array = new Uint8Array(this.file, this.from, this.len);
                    this.line = {
                            file: this.file,
                            pos: this.from,
                            array: array,
                            str: String.fromCharCode.apply(null, array),
                            isEoF: false };
                }
                this.from += this.confLF;   // 次の行を指しておく
                return this.line;
            };
        } else { // 可変長のとき
            GetterOfLine.prototype.next = function () { // 次の1レコードを取得する
                if(this.from >= this.maxLength ){   // ファイル末尾のとき
                    this.line = {file: this.file, pos: this.maxLength, array: null, str: "", isEoF: true };
                } else {
                    this.to = this.buf.indexOf(this.confLF, this.from);
                    if(this.to < 0) this.to = this.maxLength;   // 最終レコード(EOFで改行コードなし)のとき
                    this.len = Math.min(this.to - this.from, 1024);
                    var array = new Uint8Array(this.file, this.from, this.len);
                    this.line = {
                            file: this.file,
                            pos: this.from,
                            array: array,
                            str: String.fromCharCode.apply(null, array),
                            isEoF: false };
                }
                this.from = this.to + 2;    // 次の行を指しておく
                return this.line;
            };
        }
        return new GetterOfLine(file);
    };
    
    
    /**
     * eTatのフィルター
     * 
     * @memberof TimeSeries
     */
    FileParser.prototype.createFilter = function() { // #34
       /**
         * @memberof TimeSeries.FileParser
         * @class Filter
         * @classdesc FileParserのフィルター
         *            <p>
         *            ファクトリのFileParserが保持するフィルタ条件を用いるフィルターを取得する
         */
        function Filter(){ /* constructor */
            if(!(this instanceof Filter)) return new Filter();
            var c = Util.Config.Filter; // #76
            
            this.confF_TIME_FROM = Util.S2D(c.getConfig("F_TIME_FROM"));    // 時刻(X)の最小値フィルター
            this.confF_TIME_TO   = Util.S2D(c.getConfig("F_TIME_TO"));      // 時刻(X)の最大値フィルター
            this.confF_TIME = (isNaN(this.confF_TIME_FROM) && isNaN(this.confF_TIME_TO))
                            ? false : true; // 時刻(x)フィルター指定の有無
            
            this.confF_TAT_FROM = c.getConfig("F_TAT_FROM") || 0; // 時間(Y)の最小値フィルター
            this.confF_TAT_TO   = c.getConfig("F_TAT_TO") || Number.MAX_VALUE; // 時間(Y)の最大値フィルター
            this.confF_TAT = (this.confF_TAT_FROM === 0 && this.confF_TAT_TO === Number.MAX_VALUE)
                            ? false : true; // 時間(y)フィルター指定の有無

            this.confF_TEXT = c.getConfig("F_TEXT") || null; // テキストフィルタの条件(使用しない、Include,Exclude
            if (this.confF_TEXT === "F_TEXT_INCLUDE") {
                this.confF_TEXT = true;
            } else if (this.confF_TEXT === "F_TEXT_EXCLUDE") {
                this.confF_TEXT = false;
            } else { // "F_TEXT_NON"
                this.confF_TEXT = null;
            }
            
            this.confF_TEXT_LEN = c.getConfig("F_TEXT_LEN") || null;    // フィルタテキストのバイト長
            this.confF_TEXT_POS = c.getConfig("F_TEXT_POS") || 0;       // フィルタテキストの先頭バイト位置
            this.confF_TEXT_COL = (c.getConfig("F_TEXT_COL") || 3) - 1; // フィルタテキストのカラム位置(先頭:0)
            this.confF_TEXT_REG = new RegExp(c.getConfig("F_TEXT_REG") || ".*");    // フィルタテキストの正規表現
            
            this.confF_IS = (this.confF_TIME === true 
                            || this.confF_TAT === true || this.confF_TEXT != null)
                          ? true : false; // フィルタ指定の有無
            
            c = new Util.Config("File"); // #76
            this.confF_SEP = c.getConfig("SEP").charCodeAt(0);
        }
        
        // class method
        // private
        /**
         * フィルター条件で判定する
         * 
         * @memberof TimeSeries.Filter
         */
        Filter.prototype._isIn = function (e) {
            // フィルタ指定が無いときフィルタしない(初期表示時に無駄な処理をしない)
            if (this.confF_IS === false) return true;
            // 時刻(x)フィルタの判定 (conf指定なしのとき NaNとの比較となりfalseとなる)
            if (e.x < this.confF_TIME_FROM || this.confF_TIME_TO < e.x ) {
                return false;
            }
            // 時間(y)フィルタの判定
            if (e.y < this.confF_TAT_FROM || this.confF_TAT_TO < e.y){
                return false;
            }
            // テキストフィルタの判定
            if (this.confF_TEXT === null) {
                return true; // フィルタ指定なし
            }
            var text = "";
            if (e.pos === undefined) { // テキスト読み込みでないとき(自動生成データのとき)
                // レコードを取得する #62
                text = HJN.chart.fileParser.getRecordAsText(e); // #61
                // 指定正規表現に合致するか判定し、Include/Exclude指定に応じてリターンする
                return this.confF_TEXT === this.confF_TEXT_REG.test(text);
            } else { // ファイル読み込みのとき
                // レコードを取得する
                var arr = new Uint8Array(HJN.filesArrayBuffer[e.fileIdx+1], e.pos, e.len);
                // CSVレコードの指定カラムを取得する(arr)
                var colPos = 0;
                for (var i = 0; i < this.confF_TEXT_COL; i++) {
                    colPos = arr.indexOf(this.confF_SEP,colPos + 1);
                }
                if (colPos === -1){
                    // 指定数のカラムが無い場合、Includeは処理対象外、Excludeは処理対象
                    return !this.confF_TEXT;
                }
                var col = arr.slice(colPos, arr.length);
                // 判定用文字列を取得する
                text = col.slice(this.confF_TEXT_POS, this.confF_TEXT_POS + this.confF_TEXT_LEN);
                // 指定正規表現に合致するか判定し、Include/Exclude指定に応じてリターンする
                return this.confF_TEXT === this.confF_TEXT_REG.test(String.fromCharCode.apply(null, text));
            }
            return true;
        };
        
        // public
        /**
         * eTatをフィルターする
         * 
         * @memberof TimeSeries.Filter
         * @param {eTat}
         *            eTat フィルター処理対象のeTat
         * @return {eTat} eTat フィルターされたeTat
         * 
         */
        Filter.prototype.filter = function (eTat) {
            if (!eTat) return [];
            return eTat.filter(this._isIn, this);
        };

        return new Filter();
    };


    /**
     * 「1レコードからx:時刻(数値:ミリ秒),y:Tat(数値:秒)を取得するutil」を取得する
     * 
     * @memberof TimeSeries.Filter
     */
    FileParser.prototype.createGetterOfXY = function() {

        /**
         * @memberof TimeSeries.FileParser
         * @class GetterOfXY
         * @classdesc 1レコードをパースし、XとYをレコード取得する
         *            <p>
         *            ファクトリのFileParserが保持するレコードフォーマット情報を用いて、ファイルの指定レコードからX(data)とY(value)を取得する
         */
        function GetterOfXY(){ /* constructor */
            if(!(this instanceof GetterOfXY)) return new GetterOfXY();

            var c = new Util.Config("File"); // #76
            this.confSEP = c.getConfig("SEP");   // セパレータ
            
            this.confTIME_COL = c.getConfig("TIME_COL") - 1 || 0;    // 時刻(X)のカラム位置
            this.confTIME_POS = (c.getConfig("TIME_POS") || 1) - 1;  // 時刻(X)の先頭バイト位置
            this.confTIME_LEN = (c.getConfig("TIME_LEN") || 0);      // 時刻(X)のバイト長
            this.confTIME_FORM = c.getConfig("TIME_FORM");           // 時刻(X)の文字フォーマット指定
            this.confTIME_YMD = (c.getConfig("TIME_YMD") || '"YYYY/MM/DD hh.mm.ss.000"'); // #42
                                                                    // 時刻(X)のYMDフォーマット
                                                                    // #92
            this.paseDateConf = {  // YYYY/MM/DD hh:mm:dd.ss.000 #41
                YYYY: this.confTIME_YMD.indexOf("YYYY"),
                MM: this.confTIME_YMD.indexOf("MM"),
                DD: this.confTIME_YMD.indexOf("DD"),
                hh: this.confTIME_YMD.indexOf("hh"),
                mm: this.confTIME_YMD.indexOf("mm"),
                ss: this.confTIME_YMD.indexOf("ss"),
                p000: this.confTIME_YMD.indexOf("0"), // #92
            };
            this.isYMD = (this.confTIME_FORM === "TIME_FORM_YMD");
            // 時刻(X)の数値単位(1or1000,YMDのとき1)
            this.confTIME_UNIT = this.isYMD? 1 : (c.getConfig("TIME_UNIT") || 1);
            
            
            this.confTAT_COL = c.getConfig("TAT_COL") - 1 || 1;      // 時間(Y)のカラム位置
            this.confTAT_POS = (c.getConfig("TAT_POS") || 1) - 1;    // 時間(Y)の先頭バイト位置
            this.confTAT_LEN = (c.getConfig("TAT_LEN") || 0);        // 時間(Y)のバイト長
            this.confTAT_FORM = c.getConfig("TAT_FORM");             // 時間(Y)のフォーマット指定
            this.confTAT_UNIT = c.getConfig("TAT_UNIT") || 1;        // 時間(Y)の数値単位(1/1000)
            this.confENDIAN =  c.getConfig("ENDIAN");    // リトルエンディアンはtrue、ビッグエンディアンはfalse
            this.isLittle = (function(){
                // long用に4バイト取得する
                var buf = new ArrayBuffer(4);               
                // true:bufに、リトルエンディアン指定で1を書き込む
                new DataView(buf).setUint32(0, 1, true);
                // プラットフォームのエンディアンを使用するUint32Arrayと比較する
                return (new Uint32Array(buf)[0] === 1);     
            }());
            
            this.dateAndValue = {date: 0, value: 0, isError: false };
        }
        
        // class method
        /**
         * 数値(秒)、時間(時分秒)をパースして数値(秒*unit)を取得する<br>
         * 数値でない場合 NaN を返却する<br>
         * /[0-9,:\. ]+/ に合致する文字列のみを処理する 例: argY: "-1:1:1.2" unit:1000 ->
         * -3661200 ms = -(1*60*60 + 1*60 + 1.2)*1000
         * 
         * @memberof TimeSeries.FileParser.GetterOfXY
         */
        GetterOfXY.parseNumber = function (){ // argY, unit,
            var argY = arguments[0],
                unit = arguments[1];
            if(!argY) {console.log("data Y parse error"); return 0; }
            // 数値を含まないとき NaN を返却する
            var nums =  argY.match(/[0-9,:\. ]+/); // #92
            if (!nums) return NaN; // #93
            // 時分秒(hh:mm:ss.000)を秒にする
            var str = nums[0];
            var ds = (str.indexOf(":") < 0) ? [str] : str.split(":"),   // #40
                pm = (0 <= ds[0]) ? 1 : -1,
                sec = 0.0;
            for(var i = 0; i < ds.length; i++){
                sec += pm * Math.abs(ds[i]) * Math.pow(60, ds.length - i - 1);
            }
            // 単位補正する(ミリ秒指定の場合 unit = 1000)
            return sec * (unit || 1);
        };

        /**
         * Long(4バイトバイナリ)数字をパースして数値(ミリ秒)を取得する
         * 
         * @private
         */
        GetterOfXY.prototype._parseLong = function (arr){
            if (4 <= arr.length ) { // Long(4byte)以上のときunsigned longとして処理する
                // bufの先頭4byteを、指定バイトオーダ(endian)で、符号無32bit intとして参照
                return (new DataView(arr.buffer, 0 , 4)).getUint32(0, this.confENDIAN);
            } else {
                // Long(4バイト)より短いとき、Byte単位に処理する
                if (this.confENDIAN) { // little endianのとき
                    return arr.reduceRight(function(a, b){ return a*256 + b; });
                } else {               // big endianのとき
                    return arr.reduce(function(a, b){ return a*256 + b; });
                }
            }
        };

        // public
        /**
         * レコードからXとYを取得する
         * 
         * @memberof TimeSeries.FileParser.GetterOfXY
         */
        GetterOfXY.prototype.parse = function (line) {
            // セパレータでカラム分割する
            var posMax = Math.max(this.confTIME_COL, this.confTAT_COL),
                sep = this.confSEP.charCodeAt(0),   // 区切り文字のUint値
                pos = 0,
                nextPos = line.array.indexOf(sep),  // 行末(次の区切り文字位置)
                x = 0,
                y = -1;
            for (var i = 0; i <= posMax; i++) {
                if (i === this.confTIME_COL){
                    // パース対象フィールドを切り出す
                    var posX =  pos + this.confTIME_POS;
                    var arrX = (0 < this.confTIME_LEN) 
                             ? line.array.slice(posX, posX + this.confTIME_LEN)
                             : line.array.slice(posX, nextPos);
                    var strX = "";
                    // フィールドをパースする
                    if (this.isYMD){    // 年月日時分秒の文字列のとき
                        strX = String.fromCharCode.apply(null,arrX);
                        x = Util.S2D(strX, this.paseDateConf);
                    } else if (this.confTIME_FORM === "TIME_FORM_TEXT"){    // テキスト数字のUNIX経過時間のとき
                        strX = String.fromCharCode.apply(null,arrX);
                        x = GetterOfXY.parseNumber(strX);
                    } else{ // this.confTIME_FORM === "TIME_FORM_LONG"
                            // longのUNIX経過時間のとき
                        x = this._parseLong(arrX);
                    }
                    // 単位を補正する
                    x *= this.confTIME_UNIT;
                }
                if (i === this.confTAT_COL){
                    // パース対象フィールドを切り出す
                    var posY =  pos + this.confTAT_POS;
                    var arrY = (0 < this.confTAT_LEN) 
                             ? line.array.slice(posY, posY + this.confTAT_LEN)
                             : line.array.slice(posY, nextPos);
                    // フィールドをパースする
                    if (this.confTAT_FORM === "TAT_FORM_TEXT"){
                        // テキスト数字によるUNIX経過時間のとき
                        var strY = String.fromCharCode.apply(null,arrY);
                        y = GetterOfXY.parseNumber(strY);
                    } else{
                        // TAT_FORM_TEXT === "TAT_FORM_LONG" 数値によるUNIX経過時間のとき
                        y = this._parseLong(arrY);
                    }
                    // 単位を補正する
                    y *= this.confTAT_UNIT;
                }
                pos = nextPos + 1;
                nextPos = line.array.indexOf(sep, pos);
                if (nextPos < 0) nextPos = line.array.length;
            }
            
            if(0 < x && 0 <= y){ // 正常時
                return {x: x, y: y, isError: false };
            } else {            // エラー時、( NaN の場合を含む)
                return {x: x, y: y, isError: true };
            }
        };
        
        return new GetterOfXY();
    };
    
    /**
     * eTatの指定行の編集元レコードを、テキストフォーマットに変換して取得する
     * 
     * @memberof TimeSeries.FileParser
     * @param {Object}
     *            e eTat[n]:eTatの指定行
     * @return {String} eTatの指定行の表示用テキスト
     */
    FileParser.prototype.getRecordAsText = function (e) { // #62 ADD
        if (!e) return "";
        var text = "";
        if (typeof e.pos === "undefined") { // 生成データのとき
            // 生成データをCSVのログデータとして編集する #61
            text = Util.D2S(e.x, "yyyy/MM/dd hh:mm:ss.000", true) // #92
                    + ", " + e.y + ", " + e.message; // #53
            // 状態遷移履歴を追加する #62
            if (e.history){
                e.history.forEach(function(h){
                    var timeStr = "";
                    if (typeof(h.time) === "number") {
                        timeStr = Util.D2S(h.time, "mm:ss.000", true) + " seq:" // #92
                    }
                    text += " [" + h.sequenceIdx + ":" + h.status + "]" // #61
                        + timeStr + Util.D2S(h.sequenceTime, "mm:ss.000", true); // #92
                }, this);
            }
        } else { // ファイル読込のとき
            // ファイルの該当行を Uint8Arrayに登録する
            var buff = new Uint8Array(e.len + 2);
            var file = HJN.filesArrayBuffer[e.fileIdx]; // #23
            buff.set(new Uint8Array(file, e.pos,
                    Math.min(e.len + 2, file.byteLength - e.pos)));
            // ログデータを編集する
            text = String.fromCharCode.apply(null, buff);
        }
        return text;
        
    };
 
    // new
    return FileParser;
}());