- cp949 에 없는 글자는 「?」 로 바꿔 씀(22MB 공사 저장 중 멈춤) - 기계경비 본문 Index(−3·−2·−1) 유지 - % 단위 수량은 소수로 써서 음수·큰 값도 되돌아옴 - 경쟁사 예제 estx 2건 저장값 대조·구버전 왕복 시험 추가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
240 lines
6.1 KiB
JavaScript
240 lines
6.1 KiB
JavaScript
// 구버전 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,
|
|
};
|