早上還是照例去星巴克報到,開始寫作業。因為要處理的都是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鐵定不通,後面再來處理吧。