// 구버전 EST Plus 레코드 형식(spec_estplus.md): 머리·표별 가변부 자리, Extended·ShortString 읽기·쓰기, 레코드 풀기. const { cp949: strict } = require("./export-estx.cjs"); // cp949 에 없는 글자(제어 기호 밖 특수 문자 등)는 「?」 로 바꿔 씀 — 구형 레코드는 cp949 만 담음 function cp949(text) { try { return strict(text); } catch { return Buffer.concat( [...String(text)].map((ch) => { try { return strict(ch); } catch { return Buffer.from("?"); } }), ); } } const HEAD = { 394: { code: [10, 11], name: [22, 50], size: [73, 50], unite: [124, 8], bigo: [133, 20], memo1: 154, mul: 162, memo2: 166, page: 170, checked: 172, newItem: 173, check: 174, sheet: 175, kind: 189, }, 565: { code: [10, 17], name: [28, 50], size: [79, 100], unite: [180, 10], bigo: [191, 50], memo1: 242, mul: 250, memo2: 254, page: 258, checked: 260, newItem: 261, check: 262, sheet: 263, kind: 287, }, }; const AMOUNTS = ["GN", "TU", "TC", "NU", "NC", "GU", "GC", "JU", "JC"]; // 표별 가변부: [필드, 형, 394 자리, 565 자리] — X 실수 · L 정수 · b 부호바이트 · B 바이트 · S 문자(길이 394/565) const BODY = { dtm: [ ["Level", "b", 190, 288], ["Pr", "S", 213, 311, 4, 24], ...AMOUNTS.map((f, i) => [f, "X", 218 + 10 * i, 336 + 10 * i]), ["SKind", "L", 368, 486], ["OCheck", "L", 372, 490], ["SCheck", "L", 376, 494], ["Gangub", "L", 380, 498], ["Biul", "X", 384, 508], ], hdt: [ ["Level", "b", 190, 310], ...AMOUNTS.map((f, i) => [f, "X", 191 + 10 * i, 311 + 10 * i]), ["SKind", "L", 281, 441], ["OCheck", "L", 285, 445], ["SCheck", "L", 289, 449], ["Gangub", "L", 293, 453], ["Biul", "X", 297, 463], ], cct: [ ["Level", "b", 190, 310], ["Diget", "b", 191, 311], ["DigetIndex", "b", 192, 312], ["TC", "X", 193, 313], ["NC", "X", 203, 323], ["GC", "X", 213, 333], ["JC", "X", 223, 343], ["SKind", "L", 234, 364], ["OCheck", "L", 238, 368], ["SCheck", "L", 242, 372], ["Biul", "X", 250, 380], ], mdt: [ ["X190", "X", 190, 288], ["X200", "X", 200, 298], ["X210", "X", 210, 308], ["X220", "X", 220, 318], ], ldt: [["TC", "X", 230, 328]], sdt: [ ...[1, 2, 3, 4, 5, 6].map((n) => [`Cost${n}`, "X", 180 + 10 * n, 278 + 10 * n]), ["TC", "X", 250, 348], ...[1, 2, 3, 4, 5, 6].map((n) => [`Page${n}`, "S", 270 + 11 * n, 386 + 25 * n, 10, 24]), ], sil: [ ["TC", "X", 211, 288], ["NC", "X", 231, 298], ["GC", "X", 251, 308], ["JC", "X", 271, 318], ["NCRatio", "X", 297, 368], ], sti: [ ["GN", "X", 0, 288], ["TC", "X", 0, 298], ["NC", "X", 0, 308], ["GC", "X", 0, 318], ["JC", "X", 0, 328], ["Ratio", "X", 0, 378], ["NachalRatio", "X", 0, 388], ["Diget", "B", 0, 398], ["DigetIndex", "B", 0, 399], ], wdt: [ ["Cost", "X", 190, 288], ["Diget", "X", 250, 348], ["EndLineIndex", "B", 280, 408], ["Bimog1", "S", 281, 411, 10, 24], ["Bimog2", "S", 292, 436, 10, 24], ["Gubun", "S", 303, 461, 10, 24], ], }; const KIND = { dtm: 1, hdt: 2, cct: 3, mdt: 4, sdt: 5, ldt: 6, wdt: 7, sil: 21, swdt: 22, sti: 101, }; const LINK_IN = { 2: 103, 3: 104, 4: 105, 20: 220, 51: 68 }; for (let i = 11; i <= 18; i++) LINK_IN[i] = 200 + i; const LINK_OUT = Object.fromEntries(Object.entries(LINK_IN).map(([a, b]) => [b, Number(a)])); // Extended(80비트) ↔ 수 function readX(buf, at) { const lo = buf.readUInt32LE(at), hi = buf.readUInt32LE(at + 4), se = buf.readUInt16LE(at + 8), exp = se & 0x7fff; if (!exp && !hi && !lo) return 0; const v = (hi * 2 ** 32 + lo) * 2 ** (exp - 16383 - 63); return se & 0x8000 ? -v : v; } function writeX(buf, at, value) { let v = Number(value) || 0; if (!v || !Number.isFinite(v)) return buf.fill(0, at, at + 10); const sign = v < 0 ? 0x8000 : 0; v = Math.abs(v); let exp = Math.floor(Math.log2(v)); if (2 ** exp > v) exp--; if (2 ** (exp + 1) <= v) exp++; const mant = BigInt(Math.round(v * 2 ** (63 - exp))); // 53 비트 정밀도 안 buf.writeUInt32LE(Number(mant & 0xffffffffn), at); buf.writeUInt32LE(Number((mant >> 32n) & 0xffffffffn), at + 4); buf.writeUInt16LE(sign | (exp + 16383), at + 8); } const decoder = new TextDecoder("euc-kr"); const readS = (buf, at, max) => decoder.decode(buf.subarray(at + 1, at + 1 + Math.min(buf[at], max))); function writeS(buf, at, max, text) { const out = []; let used = 0; for (const ch of String(text ?? "")) { const bytes = cp949(ch); if (used + bytes.length > max) break; out.push(bytes); used += bytes.length; } buf[at] = used; Buffer.concat(out).copy(buf, at + 1); } function records(buf, size) { const out = []; for (let at = 0; at + size <= buf.length; at += size) out.push(buf.subarray(at, at + size)); return out; } // 레코드 → 머리 + 가변부 값 function decode(rec, size, type) { const h = HEAD[size], s = (k) => readS(rec, h[k][0], h[k][1]); const row = { Index: rec.readInt32LE(5), link: rec[9], code: s("code").trim(), Name: s("name"), Size: s("size"), Unite: s("unite"), Bigo: s("bigo"), MullangIndex: rec.readInt32LE(h.mul), PageSkip: rec[h.page], Checked: rec[h.checked] > 0, NewItem: rec[h.newItem] > 0, check: rec[h.check], ExcelSheetIndex: rec[h.sheet], }; for (const [f, t, a394, a565, n394, n565] of BODY[type] || []) { const at = size === 394 ? a394 : a565; if (!at && size === 394) continue; row[f] = t === "X" ? readX(rec, at) : t === "L" ? rec.readInt32LE(at) : t === "b" ? rec.readInt8(at) : t === "B" ? rec[at] : readS(rec, at, size === 394 ? n394 : n565).trim(); } return row; } module.exports = { cp949, HEAD, AMOUNTS, BODY, KIND, LINK_IN, LINK_OUT, readX, writeX, decoder, readS, writeS, records, decode, };