Files
Aislo/tools/est-test/legacy-estplus-layout.cjs
T
eomsangdonandClaude Opus 5.5 708aeb5cc4 feat(est-test): 구버전 EST Plus(.est) 불러오기·저장·단가비교
- 394/565 바이트 레코드(Extended·ShortString) 읽기·쓰기, 표별 변환과 설계변경 c* 파일
- 호표 코드 매기기·품명규격으로 연결 코드 붙이기, 저장 금액 유지
- 단가비교 창(4종, 차례 짝, 합계 다름 표시, 더블클릭 이동), Ctrl+Alt+E · Ctrl+Shift+F12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 20:04:26 +09:00

223 lines
5.7 KiB
JavaScript

// 구버전 EST Plus 레코드 형식(spec_estplus.md): 머리·표별 가변부 자리, Extended·ShortString 읽기·쓰기, 레코드 풀기.
const { cp949 } = require("./export-estx.cjs");
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 = {
HEAD,
AMOUNTS,
BODY,
KIND,
LINK_IN,
LINK_OUT,
readX,
writeX,
decoder,
readS,
writeS,
records,
decode,
};