iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 2
2
自我挑戰組

中年失業大叔的耍廢日記系列 第 2

第二天:既然不是假日,還是稍微工作一下

早上還是照例去星巴克報到,開始寫作業。因為要處理的都是xml,但是用DOM處理有點囉唆,所以寫了簡單又有彈性的工具,先用一個簡單的樹來保存XML資訊,然後透過讓節點自己呼叫跟tag關連的callback來構成mutual recursion的方式來遍歷節點,轉成要的格式。簡單的結構就是定義Node,裡面有個run方法,所以管他叫:xmlrun。隨著工作進行,會隨時調整剖析的方法,有彈性一點會比較好處理。

唉呀,出問題了。目前的作業是要透過node-opcua-coreaas從xml建立一個opcua/coreaas節點,但是處理conceptDescriptions時發現,他似乎有些地方沒完全支援IEC61360的規格(沒支援多語系的langString tag)...這樣當作範例的資料會無法完整把資料輸入...這還只是雛形,我怕後面根據完整的xsd把要支援的功能做出來反而會出更多問題阿XD

不管他,先交付一個版本然後包裝Docker...嗯?三洋通知中午會過來修洗衣機XD,等一下得先回家一趟了。

因為我的MBP電池開始膨脹,打字會搖晃,上蓋也蓋不緊,下午乾脆去Dr. A換電池,順便去QB House理髮...為什麼下午人這麼多阿Orz,理完髮去拿MBP已經五點多...

回來看一下在前端處理zip檔案格式東西,我隨便取個名稱叫做zipfs,目前長這樣:

(function () {
    _zipfs.uintToString = uintToString;
    _zipfs.arrayBufferToBase64 = arrayBufferToBase64;
    _zipfs.stringToHtmlEntity = stringToHtmlEntity;
    _zipfs.formatMSDOSDate = formatMSDOSDate;
    _zipfs.formatMSDOSTime = formatMSDOSTime;
    
    //return _zipfs;
    let env = '';
    let _enc = 'utf-8';

    if('undefined' !== typeof module && 'undefined' !== typeof module.exports) {
        module.exports = _zipfs;
    }

    if('undefined' !== typeof window) {
        window.zipfs = _zipfs;
        env = 'browser';
    }

    /**
     * 
     * @param {*} buf : ArrayBuffer
     * @param {*} inflate : function implemented the inflate action
     * @param {*} encoding : optional, specify the encoding for file namef
     * @param {*} cb : callback
     */
    function _zipfs(buf, inflate, encoding, cb) {
        //console.log('zipfs enter');
        if(typeof encoding === 'function' && typeof cb === 'undefgined') {
            cb = encoding;
            encoding = 'utf-8';
        }
        _enc = !!encoding ? encoding : 'utf-8';
        buf = new Uint8Array(buf);
        try {
            let entries = cdr(buf);
            //console.log('entries: ', entries);
            console.log(entries.length);
            let result = entries.map(mapper(buf, inflate));
            console.log('done');
            if (!!cb && 'function' === typeof cb) {
                cb(null, result);
            } else {
                return result;
            }
        } catch (e) {
            if (!!cb && 'function' === typeof cb) {
                cb(e);
            } else {
                throw e;
            }
        }
    }

    /**
     * 
     * @param {*} arr : Uint8Array
     * @param {*} inflate : function implemented the inflate action
     */
    function mapper(arr, inflate) {
        //console.log('mapper enter');
        return function (e) {
            let directory = readcentraldirectory(arr, e);
            let entry = readentry(arr, directory.file_entry_offset, inflate);
            return entry;
        };
    }

    /**
     * 
     * @param {*} arr : UInt8Array
     */
    function cdr(arr) {
        //console.log('cdr enter');
        return searchrecord(arr, [0x50, 0x4B, 0x01, 0x02]);
    }

    /**
     * 
     * @param {*} arr : UInt8Array
     * @param {*} inp : Array, the data to match
     */
    function searchrecord(arr, inp) {
        //console.log('searchrecord enter');
        let ret = [];
        for (let i = 0; i < arr.length; i++) {
            let found = search(arr, inp, i);
            if (found > -1) {
                i = found;
                ret.push(found)
            }
        }
        return ret;
    }

    /**
     * 
     * @param {*} arr : UInt8Array
     * @param {*} inp : Array, the data to match
     * @param {*} off : offset to start search
     */
    function search(arr, inp, off) {
        //console.log('search enter');
        let start = off;
        while (arr.length - start > inp.length) {
            if (inp.every((v, i) => v === arr[start + i])) return start;
            start++;
        }
        return -1;
        //return buf.indexOf(Buffer.from(inp), off);
    }

    /**
     * 
     * @param {*} arr : UInt8Array
     * @param {*} loc : location to start
     * @param {*} inflate : function implemented the inflate action
     */
    function readentry(arr, loc, inflate) {
        //console.log('readentry enter');
        let version_required = readUInt16LE(arr, loc + 4);
        let flags = readUInt16LE(arr, loc + 6);
        let compression_method = readUInt16LE(arr, loc + 8);
        let last_modified_time = readUInt16LE(arr, loc + 10);
        let last_modified_date = readUInt16LE(arr, loc + 12);
        let crc32 = readUInt32LE(arr, loc + 14);
        let compressed_size = readUInt32LE(arr, loc + 18);
        let uncompressed_size = readUInt32LE(arr, loc + 22);
        let n = readUInt16LE(arr, loc + 26);
        let m = readUInt16LE(arr, loc + 28);
        //let file_name = arr.toString('utf8', loc + 30, loc + 30 + n);
        let file_name = uintToString(arr.slice(loc + 30, loc + 30 + n));
        let data = arr.slice(loc + 30 + n + m, loc + 30 + n + m + compressed_size - 1);
        let content = compression_method === 8 ? inflate(data) : data;
        return {
            version_required,
            flags,
            compression_method,
            last_modified_time,
            last_modified_date,
            crc32,
            compressed_size,
            uncompressed_size,
            n,
            m,
            file_name,
            content
        };
    }

    /**
     * 
     * @param {*} arr : UInt8Array
     * @param {*} loc : location to start
     */
    function readcentraldirectory(arr, loc) {
        //console.log('readcentraldirectory enter');
        let version_made = readUInt16LE(arr, loc + 4);
        let version_required = readUInt16LE(arr, loc + 6);
        let flags = readUInt16LE(arr, loc + 8);
        let compression_method = readUInt16LE(arr, loc + 10);
        let last_modified_time = readUInt16LE(arr, loc + 12);
        let last_modified_date = readUInt16LE(arr, loc + 14);
        let crc32 = readUInt32LE(arr, loc + 16);
        let compressed_size = readUInt32LE(arr, loc + 20);
        let uncompressed_size = readUInt32LE(arr, loc + 24);
        let n = readUInt16LE(arr, loc + 28);
        let m = readUInt16LE(arr, loc + 30);
        let k = readUInt16LE(arr, loc + 32);
        let disk_number = readUInt16LE(arr, loc + 34);
        let internal_file_attributes = readUInt16LE(arr, loc + 36);
        let external_file_attributes = readUInt32LE(arr, loc + 38);
        let file_entry_offset = readUInt32LE(arr, loc + 42);
        //let file_name = buf.slice('utf8', loc + 46, loc + 46 + n);
        let file_name = uintToString(arr.slice(loc + 46, loc + 46 + n));
        let extra_field = arr.slice(loc + 46 + n, loc + 46 + n + m);
        let file_comment = arr.slice(loc + 46 + n + m, loc + 46 + n + m + k);
        return {
            version_made,
            version_required,
            flags,
            compression_method,
            last_modified_time,
            last_modified_date,
            crc32,
            compressed_size,
            uncompressed_size,
            n,
            m,
            k,
            disk_number,
            internal_file_attributes,
            external_file_attributes,
            file_entry_offset,
            file_name,
            extra_field,
            file_comment
        };
    }

    /**
     * 
     * @param {*} arr : UInt8Array
     * @param {*} loc : location to start
     */
    function readUInt16LE(arr, loc) {
        let view = new DataView(arr.buffer);
        return view.getUint16(loc, true);
    }

    /**
     * 
     * @param {*} arr : UInt8Array
     * @param {*} loc : location to start
     */
    function readUInt32LE(arr, loc) {
        let view = new DataView(arr.buffer);
        return view.getUint32(loc, true);
    }

    /**
     * convert UInt8Array to UTF8 String
     * @param {*} uintArray : UInt8Array
     */
    function uintToString(uintArray) {
        let decodedString = (env === 'browser') ? new TextDecoder(_enc).decode(uintArray) : Buffer.from(uintArray).toString(_enc);
        return decodedString;
    }

    function arrayBufferToBase64(bytes) {
        var binary = '';
        var len = bytes.byteLength;
        for (var i = 0; i < len; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return window.btoa(binary);
    }

    function stringToHtmlEntity (str) {
        return str.replace(
            /[\u00A0-\u9999<>\&]/gim,
            function (i) {
                return '&#' + i.charCodeAt(0) + ';';
            });
    }

    function formatMSDOSDate(n) {
        let y = (n >> 9) + 1980;
        let m = (n & 0b0000000011100000) >> 5;
        let d = n & 0b0000000000011111;
        return `${y}-${(m).toString().padStart(2, '0')}-${(d).toString().padStart(2, '0')}`;
    }

    function formatMSDOSTime(n) {
        let h = n >> 11;
        let m = (n & 0b0000011111100000) >> 5;
        let s = (n & 0b0000000000011111) * 2;
        return `${(h).toString().padStart(2, '0')}:${(m).toString().padStart(2, '0')}:${(s).toString().padStart(2, '0')}`;
    }
})();

zipfs函數有四個參數,第一個是檔案內容,格式是ArrayBuffer,跟FileReader搭配時,要使用他的readAsArrayBufferf()方法。第二個參數是解壓縮用的inflate函數(zip最常見的壓縮方法就是deflate/inflate,inflate用來解壓縮),測試時就拿pako.js來處理,只要把pako的inflateRaw函數傳給他就可以。所以實際上只處理zip檔案格式剖析。第三個參數是可省略的encoding,這是剛加的,因為測試時發現檔名會變成亂碼,所以加上這個可省略的參數來指定檔名的處理方式。最後一個參數是callback,解壓縮完畢後,就會把解壓縮後的檔案資訊傳給他處理。

實際使用的方式,就等到明天來看,順便測試一下各種狀況。(這個也可以當作node.js模組,直接測試也無妨,只是同樣要把inflate函數傳給他。建議還是搭配pako,他的效能很好。

好,今天就這樣結束,來打ESO...(聽聞某人因為打ESO忘記發文,我是不會犯這個錯誤的,除非出遠門...不過十月初要去環島,到時候就隨機應變)

======

補充:
才說node.js也可以,就發現某函數直接呼叫window.btoa(),這樣在node.js鐵定不通,後面再來處理吧。


上一篇
第一天:雖然失業...還是有工作要做
下一篇
第三天:快週末了,還是要稍微工作一下
系列文
中年失業大叔的耍廢日記30

1 則留言

0
海綿寶寶
iT邦大神 1 級 ‧ 2020-09-18 18:45:58

敢問什麼是ESO
/images/emoticon/emoticon19.gif

fillano iT邦超人 1 級 ‧ 2020-09-18 19:05:50 檢舉

Elder Scrolls Online

/images/emoticon/emoticon33.gif

我要留言

立即登入留言