@simplysm/excel 14.0.100 → 14.0.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/biff/biff-model-factory.d.ts +32 -0
- package/dist/biff/biff-model-factory.d.ts.map +1 -0
- package/dist/biff/biff-model-factory.js +88 -0
- package/dist/biff/biff-model-factory.js.map +1 -0
- package/dist/biff/biff-ptg.d.ts +14 -0
- package/dist/biff/biff-ptg.d.ts.map +1 -0
- package/dist/biff/biff-ptg.js +277 -0
- package/dist/biff/biff-ptg.js.map +1 -0
- package/dist/biff/biff-records.d.ts +163 -0
- package/dist/biff/biff-records.d.ts.map +1 -0
- package/dist/biff/biff-records.js +579 -0
- package/dist/biff/biff-records.js.map +1 -0
- package/dist/biff/biff-shared-string-model.d.ts +13 -0
- package/dist/biff/biff-shared-string-model.d.ts.map +1 -0
- package/dist/biff/biff-shared-string-model.js +41 -0
- package/dist/biff/biff-shared-string-model.js.map +1 -0
- package/dist/biff/biff-style-model.d.ts +39 -0
- package/dist/biff/biff-style-model.d.ts.map +1 -0
- package/dist/biff/biff-style-model.js +182 -0
- package/dist/biff/biff-style-model.js.map +1 -0
- package/dist/biff/biff-workbook-model.d.ts +17 -0
- package/dist/biff/biff-workbook-model.d.ts.map +1 -0
- package/dist/biff/biff-workbook-model.js +64 -0
- package/dist/biff/biff-workbook-model.js.map +1 -0
- package/dist/biff/biff-worksheet-model.d.ts +69 -0
- package/dist/biff/biff-worksheet-model.d.ts.map +1 -0
- package/dist/biff/biff-worksheet-model.js +495 -0
- package/dist/biff/biff-worksheet-model.js.map +1 -0
- package/dist/biff/biff12-codec.d.ts +149 -0
- package/dist/biff/biff12-codec.d.ts.map +1 -0
- package/dist/biff/biff12-codec.js +262 -0
- package/dist/biff/biff12-codec.js.map +1 -0
- package/dist/excel-cell.d.ts +0 -6
- package/dist/excel-cell.d.ts.map +1 -1
- package/dist/excel-cell.js +2 -24
- package/dist/excel-cell.js.map +1 -1
- package/dist/excel-col.js.map +1 -1
- package/dist/excel-row.js.map +1 -1
- package/dist/excel-workbook.d.ts +4 -21
- package/dist/excel-workbook.d.ts.map +1 -1
- package/dist/excel-workbook.js +16 -49
- package/dist/excel-workbook.js.map +1 -1
- package/dist/excel-worksheet.d.ts +0 -11
- package/dist/excel-worksheet.d.ts.map +1 -1
- package/dist/excel-worksheet.js +9 -46
- package/dist/excel-worksheet.js.map +1 -1
- package/dist/excel-wrapper.d.ts +0 -10
- package/dist/excel-wrapper.d.ts.map +1 -1
- package/dist/excel-wrapper.js +0 -10
- package/dist/excel-wrapper.js.map +1 -1
- package/dist/models/excel-format.d.ts +3 -0
- package/dist/models/excel-format.d.ts.map +1 -0
- package/dist/models/excel-format.js +2 -0
- package/dist/models/excel-format.js.map +1 -0
- package/dist/models/excel-model-factory.d.ts +33 -0
- package/dist/models/excel-model-factory.d.ts.map +1 -0
- package/dist/models/excel-model-factory.js +2 -0
- package/dist/models/excel-model-factory.js.map +1 -0
- package/dist/models/excel-model.d.ts +12 -0
- package/dist/models/excel-model.d.ts.map +1 -0
- package/dist/models/excel-model.js +2 -0
- package/dist/models/excel-model.js.map +1 -0
- package/dist/models/i-content-type-model.d.ts +7 -0
- package/dist/models/i-content-type-model.d.ts.map +1 -0
- package/dist/models/i-content-type-model.js +2 -0
- package/dist/models/i-content-type-model.js.map +1 -0
- package/dist/models/i-drawing-model.d.ts +23 -0
- package/dist/models/i-drawing-model.d.ts.map +1 -0
- package/dist/models/i-drawing-model.js +2 -0
- package/dist/models/i-drawing-model.js.map +1 -0
- package/dist/models/i-relationship-model.d.ts +16 -0
- package/dist/models/i-relationship-model.d.ts.map +1 -0
- package/dist/models/i-relationship-model.js +2 -0
- package/dist/models/i-relationship-model.js.map +1 -0
- package/dist/models/i-shared-string-model.d.ts +9 -0
- package/dist/models/i-shared-string-model.d.ts.map +1 -0
- package/dist/models/i-shared-string-model.js +2 -0
- package/dist/models/i-shared-string-model.js.map +1 -0
- package/dist/models/i-style-model.d.ts +18 -0
- package/dist/models/i-style-model.d.ts.map +1 -0
- package/dist/models/i-style-model.js +2 -0
- package/dist/models/i-style-model.js.map +1 -0
- package/dist/models/i-workbook-model.d.ts +17 -0
- package/dist/models/i-workbook-model.d.ts.map +1 -0
- package/dist/models/i-workbook-model.js +2 -0
- package/dist/models/i-workbook-model.js.map +1 -0
- package/dist/models/i-worksheet-model.d.ts +39 -0
- package/dist/models/i-worksheet-model.d.ts.map +1 -0
- package/dist/models/i-worksheet-model.js +2 -0
- package/dist/models/i-worksheet-model.js.map +1 -0
- package/dist/models/shared/excel-cf-spec.d.ts +13 -0
- package/dist/models/shared/excel-cf-spec.d.ts.map +1 -0
- package/dist/models/shared/excel-cf-spec.js +2 -0
- package/dist/models/shared/excel-cf-spec.js.map +1 -0
- package/dist/models/shared/excel-style.d.ts +24 -0
- package/dist/models/shared/excel-style.d.ts.map +1 -0
- package/dist/models/shared/excel-style.js +38 -0
- package/dist/models/shared/excel-style.js.map +1 -0
- package/dist/types.d.ts +0 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/excel-style-data.d.ts +3 -5
- package/dist/utils/excel-style-data.d.ts.map +1 -1
- package/dist/utils/excel-style-data.js +2 -16
- package/dist/utils/excel-style-data.js.map +1 -1
- package/dist/utils/zip-cache.d.ts +46 -9
- package/dist/utils/zip-cache.d.ts.map +1 -1
- package/dist/utils/zip-cache.js +138 -52
- package/dist/utils/zip-cache.js.map +1 -1
- package/dist/xml/excel-xml-content-type.d.ts +8 -4
- package/dist/xml/excel-xml-content-type.d.ts.map +1 -1
- package/dist/xml/excel-xml-content-type.js +13 -6
- package/dist/xml/excel-xml-content-type.js.map +1 -1
- package/dist/xml/excel-xml-drawing.d.ts +8 -4
- package/dist/xml/excel-xml-drawing.d.ts.map +1 -1
- package/dist/xml/excel-xml-drawing.js +14 -7
- package/dist/xml/excel-xml-drawing.js.map +1 -1
- package/dist/xml/excel-xml-relationship.d.ts +12 -4
- package/dist/xml/excel-xml-relationship.d.ts.map +1 -1
- package/dist/xml/excel-xml-relationship.js +22 -12
- package/dist/xml/excel-xml-relationship.js.map +1 -1
- package/dist/xml/excel-xml-shared-string.d.ts +8 -4
- package/dist/xml/excel-xml-shared-string.d.ts.map +1 -1
- package/dist/xml/excel-xml-shared-string.js +16 -9
- package/dist/xml/excel-xml-shared-string.js.map +1 -1
- package/dist/xml/excel-xml-style.d.ts +11 -22
- package/dist/xml/excel-xml-style.d.ts.map +1 -1
- package/dist/xml/excel-xml-style.js +66 -92
- package/dist/xml/excel-xml-style.js.map +1 -1
- package/dist/xml/excel-xml-unknown.d.ts +6 -5
- package/dist/xml/excel-xml-unknown.d.ts.map +1 -1
- package/dist/xml/excel-xml-unknown.js +7 -4
- package/dist/xml/excel-xml-unknown.js.map +1 -1
- package/dist/xml/excel-xml-workbook.d.ts +9 -4
- package/dist/xml/excel-xml-workbook.d.ts.map +1 -1
- package/dist/xml/excel-xml-workbook.js +28 -20
- package/dist/xml/excel-xml-workbook.js.map +1 -1
- package/dist/xml/excel-xml-worksheet.d.ts +12 -7
- package/dist/xml/excel-xml-worksheet.d.ts.map +1 -1
- package/dist/xml/excel-xml-worksheet.js +65 -50
- package/dist/xml/excel-xml-worksheet.js.map +1 -1
- package/dist/xml/xml-model-factory.d.ts +25 -0
- package/dist/xml/xml-model-factory.d.ts.map +1 -0
- package/dist/xml/xml-model-factory.js +66 -0
- package/dist/xml/xml-model-factory.js.map +1 -0
- package/package.json +2 -2
- package/src/biff/biff-model-factory.ts +101 -0
- package/src/biff/biff-ptg.ts +289 -0
- package/src/biff/biff-records.ts +686 -0
- package/src/biff/biff-shared-string-model.ts +52 -0
- package/src/biff/biff-style-model.ts +219 -0
- package/src/biff/biff-workbook-model.ts +83 -0
- package/src/biff/biff-worksheet-model.ts +545 -0
- package/src/biff/biff12-codec.ts +286 -0
- package/src/excel-cell.ts +12 -46
- package/src/excel-col.ts +3 -3
- package/src/excel-row.ts +3 -3
- package/src/excel-workbook.ts +26 -69
- package/src/excel-worksheet.ts +36 -107
- package/src/excel-wrapper.ts +0 -10
- package/src/models/excel-format.ts +2 -0
- package/src/models/excel-model-factory.ts +33 -0
- package/src/models/excel-model.ts +12 -0
- package/src/models/i-content-type-model.ts +7 -0
- package/src/models/i-drawing-model.ts +13 -0
- package/src/models/i-relationship-model.ts +13 -0
- package/src/models/i-shared-string-model.ts +9 -0
- package/src/models/i-style-model.ts +18 -0
- package/src/models/i-workbook-model.ts +17 -0
- package/src/models/i-worksheet-model.ts +40 -0
- package/src/models/shared/excel-cf-spec.ts +24 -0
- package/src/models/shared/excel-style.ts +65 -0
- package/src/types.ts +0 -13
- package/src/utils/excel-style-data.ts +4 -25
- package/src/utils/zip-cache.ts +189 -58
- package/src/xml/excel-xml-content-type.ts +18 -8
- package/src/xml/excel-xml-drawing.ts +19 -9
- package/src/xml/excel-xml-relationship.ts +28 -14
- package/src/xml/excel-xml-shared-string.ts +20 -11
- package/src/xml/excel-xml-style.ts +74 -116
- package/src/xml/excel-xml-unknown.ts +8 -4
- package/src/xml/excel-xml-workbook.ts +34 -22
- package/src/xml/excel-xml-worksheet.ts +73 -53
- package/src/xml/xml-model-factory.ts +83 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
import type { ICfRuleSpec } from "../models/shared/excel-cf-spec";
|
|
2
|
+
import type { ExcelCellType } from "../types";
|
|
3
|
+
import { ExcelUtils } from "../utils/excel-utils";
|
|
4
|
+
import type { IRecord } from "./biff12-codec";
|
|
5
|
+
import {
|
|
6
|
+
concatBytes,
|
|
7
|
+
decodeRkNumber,
|
|
8
|
+
encodeRecord,
|
|
9
|
+
encodeXLWideString,
|
|
10
|
+
readAllRecords,
|
|
11
|
+
readCellPrefix,
|
|
12
|
+
readUint32LE,
|
|
13
|
+
readXLWideString,
|
|
14
|
+
REC,
|
|
15
|
+
writeCellPrefix,
|
|
16
|
+
writeUint32LE,
|
|
17
|
+
} from "./biff12-codec";
|
|
18
|
+
import { encodeFormula } from "./biff-ptg";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* BIFF12 레코드 단위 reader.
|
|
22
|
+
* adtek `biff12.ts` 의 reader 로직을 포팅. 프리미티브는 `biff12-codec.ts`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** `"rId1"` → `1`. 형식 불일치 시 undefined. */
|
|
26
|
+
export function parseRelId(relId: string): number | undefined {
|
|
27
|
+
const m = /^rId(\d+)$/.exec(relId);
|
|
28
|
+
return m ? parseInt(m[1], 10) : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* workbook.bin → 시트 항목 (BrtBundleSh) 순서대로.
|
|
33
|
+
*
|
|
34
|
+
* BrtBundleSh payload ([MS-XLSB] 2.4.301): hsState(4) + iTabID(4) + XLWideString strRelID + XLWideString strName.
|
|
35
|
+
*/
|
|
36
|
+
export function readWorkbookSheets(buf: Uint8Array): { name: string; relId: string }[] {
|
|
37
|
+
const out: { name: string; relId: string }[] = [];
|
|
38
|
+
for (const rec of readAllRecords(buf)) {
|
|
39
|
+
if (rec.type !== REC.BrtBundleSh) continue;
|
|
40
|
+
const rel = readXLWideString(rec.payload, 8);
|
|
41
|
+
const name = readXLWideString(rec.payload, 8 + rel.bytesRead);
|
|
42
|
+
out.push({ name: name.value, relId: rel.value });
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** sharedStrings.bin → 문자열 배열 (BrtSSTItem 만 순서대로). payload: flags(1) + XLWideString. */
|
|
48
|
+
export function readSharedStrings(buf: Uint8Array): string[] {
|
|
49
|
+
const out: string[] = [];
|
|
50
|
+
for (const rec of readAllRecords(buf)) {
|
|
51
|
+
if (rec.type !== REC.BrtSSTItem) continue;
|
|
52
|
+
const { value } = readXLWideString(rec.payload, 1);
|
|
53
|
+
out.push(value);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 디코드된 셀 1개. `val === undefined` 는 blank(스타일만). `cellType` 은 OOXML 시맨틱. */
|
|
59
|
+
export interface IDecodedBiffCell {
|
|
60
|
+
col: number;
|
|
61
|
+
/** cell 의 iStyleRef (cellXFs 인덱스). Short cell 은 0. */
|
|
62
|
+
styleId: number;
|
|
63
|
+
cellType: ExcelCellType | undefined;
|
|
64
|
+
val: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readFloat64(payload: Uint8Array, off: number): number {
|
|
68
|
+
const view = new DataView(payload.buffer, payload.byteOffset + off, 8);
|
|
69
|
+
return view.getFloat64(0, true);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Cell record 1개를 OOXML 시맨틱으로 디코드.
|
|
74
|
+
*
|
|
75
|
+
* - Full cell (Brt*) : payload 첫 4 byte = col, 8 byte prefix 후 값.
|
|
76
|
+
* - Short cell (BrtShort*) : col 정보 없음 (이전 셀 + 1), 4 byte prefix 후 값.
|
|
77
|
+
*
|
|
78
|
+
* SST 인덱스 셀(Isst)은 `cellType="s"`, `val=인덱스문자열` 로 반환 — SST 조회는 상위(ExcelCell)가 수행.
|
|
79
|
+
* inline string(St)은 `cellType="str"`. 숫자(Real/Rk)는 `cellType=undefined`(number).
|
|
80
|
+
*
|
|
81
|
+
* 대상 외 record 는 undefined.
|
|
82
|
+
*/
|
|
83
|
+
export function decodeBiffCell(rec: IRecord, prevCol: number): IDecodedBiffCell | undefined {
|
|
84
|
+
const p = rec.payload;
|
|
85
|
+
const fullStyle = (): number => readCellPrefix(p).iStyleRef;
|
|
86
|
+
switch (rec.type) {
|
|
87
|
+
case REC.BrtCellIsst:
|
|
88
|
+
return {
|
|
89
|
+
col: readUint32LE(p, 0),
|
|
90
|
+
styleId: fullStyle(),
|
|
91
|
+
cellType: "s",
|
|
92
|
+
val: readUint32LE(p, 8).toString(),
|
|
93
|
+
};
|
|
94
|
+
case REC.BrtShortIsst:
|
|
95
|
+
return { col: prevCol + 1, styleId: 0, cellType: "s", val: readUint32LE(p, 4).toString() };
|
|
96
|
+
case REC.BrtCellSt:
|
|
97
|
+
return {
|
|
98
|
+
col: readUint32LE(p, 0),
|
|
99
|
+
styleId: fullStyle(),
|
|
100
|
+
cellType: "str",
|
|
101
|
+
val: readXLWideString(p, 8).value,
|
|
102
|
+
};
|
|
103
|
+
case REC.BrtShortSt:
|
|
104
|
+
return { col: prevCol + 1, styleId: 0, cellType: "str", val: readXLWideString(p, 4).value };
|
|
105
|
+
case REC.BrtCellReal:
|
|
106
|
+
return {
|
|
107
|
+
col: readUint32LE(p, 0),
|
|
108
|
+
styleId: fullStyle(),
|
|
109
|
+
cellType: undefined,
|
|
110
|
+
val: readFloat64(p, 8).toString(),
|
|
111
|
+
};
|
|
112
|
+
case REC.BrtShortReal:
|
|
113
|
+
return { col: prevCol + 1, styleId: 0, cellType: undefined, val: readFloat64(p, 4).toString() };
|
|
114
|
+
case REC.BrtCellRk:
|
|
115
|
+
return {
|
|
116
|
+
col: readUint32LE(p, 0),
|
|
117
|
+
styleId: fullStyle(),
|
|
118
|
+
cellType: undefined,
|
|
119
|
+
val: decodeRkNumber(readUint32LE(p, 8)).toString(),
|
|
120
|
+
};
|
|
121
|
+
case REC.BrtShortRk:
|
|
122
|
+
return {
|
|
123
|
+
col: prevCol + 1,
|
|
124
|
+
styleId: 0,
|
|
125
|
+
cellType: undefined,
|
|
126
|
+
val: decodeRkNumber(readUint32LE(p, 4)).toString(),
|
|
127
|
+
};
|
|
128
|
+
case REC.BrtCellBool:
|
|
129
|
+
return { col: readUint32LE(p, 0), styleId: fullStyle(), cellType: "b", val: p[8] ? "1" : "0" };
|
|
130
|
+
case REC.BrtCellBlank:
|
|
131
|
+
return { col: readUint32LE(p, 0), styleId: fullStyle(), cellType: undefined, val: undefined };
|
|
132
|
+
case REC.BrtShortBlank:
|
|
133
|
+
return { col: prevCol + 1, styleId: 0, cellType: undefined, val: undefined };
|
|
134
|
+
default:
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#region Encoders (Stage 3 writer)
|
|
140
|
+
|
|
141
|
+
/** payload 없는 begin/end 마커 record. */
|
|
142
|
+
export function encodeMarker(type: number): Uint8Array {
|
|
143
|
+
return encodeRecord(type, new Uint8Array(0));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* BrtBundleSh — 워크북의 시트 항목.
|
|
148
|
+
* payload: hsState(4) + iTabID(4) + XLWideString strRelID + XLWideString strName.
|
|
149
|
+
*/
|
|
150
|
+
export function encodeBrtBundleSh(relId: string, name: string, tabId: number): Uint8Array {
|
|
151
|
+
const rel = encodeXLWideString(relId);
|
|
152
|
+
const nm = encodeXLWideString(name);
|
|
153
|
+
const payload = new Uint8Array(8 + rel.length + nm.length);
|
|
154
|
+
writeUint32LE(payload, 0, 0); // hsState = visible
|
|
155
|
+
writeUint32LE(payload, 4, tabId);
|
|
156
|
+
payload.set(rel, 8);
|
|
157
|
+
payload.set(nm, 8 + rel.length);
|
|
158
|
+
return encodeRecord(REC.BrtBundleSh, payload);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** BrtBeginSst — count(4) + uniqueCount(4). */
|
|
162
|
+
export function encodeBrtBeginSst(count: number, uniqueCount: number): Uint8Array {
|
|
163
|
+
const payload = new Uint8Array(8);
|
|
164
|
+
writeUint32LE(payload, 0, count);
|
|
165
|
+
writeUint32LE(payload, 4, uniqueCount);
|
|
166
|
+
return encodeRecord(REC.BrtBeginSst, payload);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** BrtSSTItem — flags(1) + XLWideString. */
|
|
170
|
+
export function encodeBrtSSTItem(value: string): Uint8Array {
|
|
171
|
+
const wide = encodeXLWideString(value);
|
|
172
|
+
const payload = new Uint8Array(1 + wide.length);
|
|
173
|
+
payload[0] = 0; // flags
|
|
174
|
+
payload.set(wide, 1);
|
|
175
|
+
return encodeRecord(REC.BrtSSTItem, payload);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** BrtCellIsst — cell prefix(8) + sharedString 인덱스(4). */
|
|
179
|
+
export function encodeBrtCellIsst(col: number, iStyleRef: number, sstIndex: number): Uint8Array {
|
|
180
|
+
const payload = new Uint8Array(8 + 4);
|
|
181
|
+
writeCellPrefix(payload, 0, col, iStyleRef);
|
|
182
|
+
writeUint32LE(payload, 8, sstIndex);
|
|
183
|
+
return encodeRecord(REC.BrtCellIsst, payload);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** BrtCellReal — cell prefix(8) + IEEE754 double(8). */
|
|
187
|
+
export function encodeBrtCellReal(col: number, iStyleRef: number, value: number): Uint8Array {
|
|
188
|
+
const payload = new Uint8Array(8 + 8);
|
|
189
|
+
writeCellPrefix(payload, 0, col, iStyleRef);
|
|
190
|
+
new DataView(payload.buffer, payload.byteOffset + 8, 8).setFloat64(0, value, true);
|
|
191
|
+
return encodeRecord(REC.BrtCellReal, payload);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** BrtCellSt — cell prefix(8) + XLWideString (inline string). */
|
|
195
|
+
export function encodeBrtCellSt(col: number, iStyleRef: number, value: string): Uint8Array {
|
|
196
|
+
const wide = encodeXLWideString(value);
|
|
197
|
+
const payload = new Uint8Array(8 + wide.length);
|
|
198
|
+
writeCellPrefix(payload, 0, col, iStyleRef);
|
|
199
|
+
payload.set(wide, 8);
|
|
200
|
+
return encodeRecord(REC.BrtCellSt, payload);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** BrtCellBool — cell prefix(8) + boolean(1). */
|
|
204
|
+
export function encodeBrtCellBool(col: number, iStyleRef: number, value: boolean): Uint8Array {
|
|
205
|
+
const payload = new Uint8Array(9);
|
|
206
|
+
writeCellPrefix(payload, 0, col, iStyleRef);
|
|
207
|
+
payload[8] = value ? 1 : 0;
|
|
208
|
+
return encodeRecord(REC.BrtCellBool, payload);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** BrtCellBlank — cell prefix(8) (style 만 가진 빈 셀). */
|
|
212
|
+
export function encodeBrtCellBlank(col: number, iStyleRef: number): Uint8Array {
|
|
213
|
+
const payload = new Uint8Array(8);
|
|
214
|
+
writeCellPrefix(payload, 0, col, iStyleRef);
|
|
215
|
+
return encodeRecord(REC.BrtCellBlank, payload);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* BrtRowHdr — [MS-XLSB] 2.4.726.
|
|
220
|
+
* payload(25): rwT(4) + ixfe(4) + miyRw(2) + 패딩/flags(3) + ncolspan(4) + colFirst(4) + colLast(4).
|
|
221
|
+
*/
|
|
222
|
+
export function encodeBrtRowHdr(rowIndex: number, colFirst: number, colLast: number): Uint8Array {
|
|
223
|
+
const payload = new Uint8Array(25);
|
|
224
|
+
writeUint32LE(payload, 0, rowIndex);
|
|
225
|
+
writeUint32LE(payload, 4, 0); // ixfe
|
|
226
|
+
payload[8] = 0x40; // miyRw 0x0140 = 16pt
|
|
227
|
+
payload[9] = 0x01;
|
|
228
|
+
writeUint32LE(payload, 13, 1); // ncolspan
|
|
229
|
+
writeUint32LE(payload, 17, colFirst);
|
|
230
|
+
writeUint32LE(payload, 21, colLast);
|
|
231
|
+
return encodeRecord(REC.BrtRowHdr, payload);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** begin 컨테이너 레코드 (count u32 payload). */
|
|
235
|
+
export function encodeBeginCount(type: number, count: number): Uint8Array {
|
|
236
|
+
const payload = new Uint8Array(4);
|
|
237
|
+
writeUint32LE(payload, 0, count);
|
|
238
|
+
return encodeRecord(type, payload);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** BrtFmt — numFmtId(u16) + XLWideString formatCode. */
|
|
242
|
+
export function encodeBrtFmt(numFmtId: number, formatCode: string): Uint8Array {
|
|
243
|
+
const code = encodeXLWideString(formatCode);
|
|
244
|
+
const payload = new Uint8Array(2 + code.length);
|
|
245
|
+
payload[0] = numFmtId & 0xff;
|
|
246
|
+
payload[1] = (numFmtId >>> 8) & 0xff;
|
|
247
|
+
payload.set(code, 2);
|
|
248
|
+
return encodeRecord(REC.BrtFmt, payload);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* cellXF용 BrtXF(16B). ixfeParent=0(cellStyleXFs[0] 참조), iFmt=numFmtId, font/fill/border=0.
|
|
253
|
+
* flags(@12)=0x1010 은 정답지 기본값.
|
|
254
|
+
*/
|
|
255
|
+
export function encodeCellXF(numFmtId: number): Uint8Array {
|
|
256
|
+
const p = new Uint8Array(16);
|
|
257
|
+
p[2] = numFmtId & 0xff;
|
|
258
|
+
p[3] = (numFmtId >>> 8) & 0xff;
|
|
259
|
+
p[12] = 0x10;
|
|
260
|
+
p[13] = 0x10;
|
|
261
|
+
return encodeRecord(REC.BrtXF, p);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** BrtWsDim — UncheckedRfX(16): rwFirst, rwLast, colFirst, colLast. */
|
|
265
|
+
export function encodeBrtWsDim(
|
|
266
|
+
rwFirst: number,
|
|
267
|
+
rwLast: number,
|
|
268
|
+
colFirst: number,
|
|
269
|
+
colLast: number,
|
|
270
|
+
): Uint8Array {
|
|
271
|
+
const payload = new Uint8Array(16);
|
|
272
|
+
writeUint32LE(payload, 0, rwFirst);
|
|
273
|
+
writeUint32LE(payload, 4, rwLast);
|
|
274
|
+
writeUint32LE(payload, 8, colFirst);
|
|
275
|
+
writeUint32LE(payload, 12, colLast);
|
|
276
|
+
return encodeRecord(REC.BrtWsDim, payload);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** BrtMergeCell — UncheckedRfX(16): rwFirst, rwLast, colFirst, colLast. */
|
|
280
|
+
export function encodeBrtMergeCell(
|
|
281
|
+
s: { r: number; c: number },
|
|
282
|
+
e: { r: number; c: number },
|
|
283
|
+
): Uint8Array {
|
|
284
|
+
const payload = new Uint8Array(16);
|
|
285
|
+
writeUint32LE(payload, 0, s.r);
|
|
286
|
+
writeUint32LE(payload, 4, e.r);
|
|
287
|
+
writeUint32LE(payload, 8, s.c);
|
|
288
|
+
writeUint32LE(payload, 12, e.c);
|
|
289
|
+
return encodeRecord(REC.BrtMergeCell, payload);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* BrtColInfo — colFirst(u32) + colLast(u32) + coldx(u32) + ixfe(u32) + flags(u16).
|
|
294
|
+
* coldx 는 1/256 문자 단위 너비. flags bit1(fUserSet)=커스텀 너비.
|
|
295
|
+
*/
|
|
296
|
+
export function encodeBrtColInfo(colFirst: number, colLast: number, coldx: number): Uint8Array {
|
|
297
|
+
const payload = new Uint8Array(18);
|
|
298
|
+
writeUint32LE(payload, 0, colFirst);
|
|
299
|
+
writeUint32LE(payload, 4, colLast);
|
|
300
|
+
writeUint32LE(payload, 8, coldx);
|
|
301
|
+
writeUint32LE(payload, 12, 0); // ixfe
|
|
302
|
+
payload[16] = 0x02; // fUserSet
|
|
303
|
+
payload[17] = 0x00;
|
|
304
|
+
return encodeRecord(REC.BrtColInfo, payload);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** BrtFileVersion — 빈 새 워크북(통합 문서1.xlsb) 정답지 byte. */
|
|
308
|
+
export function encodeBrtFileVersion(): Uint8Array {
|
|
309
|
+
return encodeRecord(
|
|
310
|
+
REC.BrtFileVersion,
|
|
311
|
+
Uint8Array.from([
|
|
312
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
313
|
+
0x00, 0x02, 0x00, 0x00, 0x00, 0x78, 0x00, 0x6c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00,
|
|
314
|
+
0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x05, 0x00, 0x00, 0x00, 0x33, 0x00, 0x30, 0x00, 0x30,
|
|
315
|
+
0x00, 0x32, 0x00, 0x36, 0x00,
|
|
316
|
+
]),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** BrtWbProp — 통합 문서 속성. 빈 새 워크북 정답지 byte(12B, codeName 없음). */
|
|
321
|
+
export function encodeBrtWbProp(): Uint8Array {
|
|
322
|
+
return encodeRecord(
|
|
323
|
+
REC.BrtWbProp,
|
|
324
|
+
Uint8Array.from([0x20, 0x00, 0x01, 0x00, 0x3c, 0x16, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** BrtBookView — workbook 창 뷰. 빈 새 워크북 정답지 byte. */
|
|
329
|
+
export function encodeBrtBookView(): Uint8Array {
|
|
330
|
+
return encodeRecord(
|
|
331
|
+
REC.BrtBookView,
|
|
332
|
+
Uint8Array.from([
|
|
333
|
+
0x65, 0x04, 0x00, 0x00, 0x93, 0x12, 0x00, 0x00, 0xfc, 0x6c, 0x00, 0x00, 0x95, 0x5b, 0x00,
|
|
334
|
+
0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78,
|
|
335
|
+
]),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** BrtSheetFormatPr — 시트 기본 서식(행높이 등). 빈 새 워크북 정답지 byte. Excel 필수. */
|
|
340
|
+
export function encodeBrtSheetFormatPr(): Uint8Array {
|
|
341
|
+
return encodeRecord(
|
|
342
|
+
REC.BrtSheetFormatPr,
|
|
343
|
+
Uint8Array.from([0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x4a, 0x01, 0x00, 0x00, 0x00, 0x00]),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** BrtSel — worksheet 선택 영역. boa-sample 정답지 byte 템플릿(pane bottomRight, A1 선택). */
|
|
348
|
+
export function encodeBrtSel(): Uint8Array {
|
|
349
|
+
return encodeRecord(
|
|
350
|
+
REC.BrtSel,
|
|
351
|
+
Uint8Array.from([
|
|
352
|
+
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
353
|
+
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
354
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
355
|
+
]),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** BrtBeginAFilter — ref RfX(16): autoFilter 범위. */
|
|
360
|
+
export function encodeBrtBeginAFilter(
|
|
361
|
+
s: { r: number; c: number },
|
|
362
|
+
e: { r: number; c: number },
|
|
363
|
+
): Uint8Array {
|
|
364
|
+
const payload = new Uint8Array(16);
|
|
365
|
+
writeUint32LE(payload, 0, s.r);
|
|
366
|
+
writeUint32LE(payload, 4, e.r);
|
|
367
|
+
writeUint32LE(payload, 8, s.c);
|
|
368
|
+
writeUint32LE(payload, 12, e.c);
|
|
369
|
+
return encodeRecord(REC.BrtBeginAFilter, payload);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 빈 새 워크북 정답지(통합 문서1.xlsb) BrtBeginWsView. grbit 0x03dc(격자선 ON). @18 wScale 은 encode 가 덮어쓴다.
|
|
373
|
+
const WSVIEW_TEMPLATE = Uint8Array.from(
|
|
374
|
+
"dc 03 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 64 00 00 00 00 00 00 00 00 00 00 00"
|
|
375
|
+
.split(" ")
|
|
376
|
+
.map((x) => parseInt(x, 16)),
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* BrtBeginWsView — 정답지(boa-sample) 30B 템플릿. wScale(zoom, u16@18)만 치환.
|
|
381
|
+
* @param zoom 확대비율(%). 0/100 은 기본.
|
|
382
|
+
*/
|
|
383
|
+
export function encodeBrtBeginWsView(zoom: number): Uint8Array {
|
|
384
|
+
const p = WSVIEW_TEMPLATE.slice();
|
|
385
|
+
p[18] = zoom & 0xff;
|
|
386
|
+
p[19] = (zoom >>> 8) & 0xff;
|
|
387
|
+
return encodeRecord(REC.BrtBeginWsView, p);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* BrtPane(29B) — xnumXSplit(double@0) + ynumYSplit(double@8) + rwTop(u32@16) + colLeft(u32@20)
|
|
392
|
+
* + pnnAcc(u32@24) + flags(u8@28). flags=3(fFrozen|fFrozenNoSplit).
|
|
393
|
+
* pnnAcc: 0=bottomRight, 1=topRight, 2=bottomLeft, 3=topLeft.
|
|
394
|
+
*/
|
|
395
|
+
export function encodeBrtPane(
|
|
396
|
+
xSplit: number,
|
|
397
|
+
ySplit: number,
|
|
398
|
+
rwTop: number,
|
|
399
|
+
colLeft: number,
|
|
400
|
+
pnnAcc: number,
|
|
401
|
+
): Uint8Array {
|
|
402
|
+
const p = new Uint8Array(29);
|
|
403
|
+
const dv = new DataView(p.buffer);
|
|
404
|
+
dv.setFloat64(0, xSplit, true);
|
|
405
|
+
dv.setFloat64(8, ySplit, true);
|
|
406
|
+
writeUint32LE(p, 16, rwTop);
|
|
407
|
+
writeUint32LE(p, 20, colLeft);
|
|
408
|
+
writeUint32LE(p, 24, pnnAcc);
|
|
409
|
+
p[28] = 0x03;
|
|
410
|
+
return encodeRecord(REC.BrtPane, p);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* BrtWsProp — [MS-XLSB] 2.4.823. 레이아웃(23B): flags(3B@0) + brtcolorTab(BrtColor 8B@3)
|
|
415
|
+
* + 상수영역(8B@11, ff×8) + codeName(XLWideString@19, 빈 문자열 4B).
|
|
416
|
+
* BrtColor(@3): byteA(bit0=fValidRGB, bits1-7=xColorType) + index(1) + nTintAndShade(i16)
|
|
417
|
+
* + R + G + B + A.
|
|
418
|
+
*
|
|
419
|
+
* 정답지(`.tmp/탭색.xlsb` sheet1.bin BrtWsProp): 빈 워크북 대비 byte0 bit1(0x02) set,
|
|
420
|
+
* @3=07(theme), @4=05(theme idx accent2), @7-10=E9 71 32 FF(resolved RGBA). 본 함수는 사용자
|
|
421
|
+
* 입력 RGB 를 RGB type(0x05)으로 인코딩(index 0, tint 0).
|
|
422
|
+
*/
|
|
423
|
+
export function encodeBrtWsProp(tabColorArgb?: string): Uint8Array {
|
|
424
|
+
// 빈 새 워크북(통합 문서1.xlsb) 정답지 byte(23B, tabColor auto, codeName 없음).
|
|
425
|
+
const p = Uint8Array.from([
|
|
426
|
+
0xc9, 0x04, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
|
427
|
+
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
|
428
|
+
]);
|
|
429
|
+
if (tabColorArgb != null) {
|
|
430
|
+
// 정답지 색상상태 flag: byte0 bit1 set.
|
|
431
|
+
p[0] |= 0x02;
|
|
432
|
+
// brtcolorTab(@3, 8B): byteA + index + nTintAndShade(2) + R + G + B + A
|
|
433
|
+
p[3] = 0x05; // fValidRGB(0x01) | xColorType=RGB(2<<1)
|
|
434
|
+
p[4] = 0x00; // index (RGB 에서는 무시)
|
|
435
|
+
p[5] = 0x00;
|
|
436
|
+
p[6] = 0x00; // nTintAndShade = 0
|
|
437
|
+
p[7] = parseInt(tabColorArgb.slice(2, 4), 16); // R
|
|
438
|
+
p[8] = parseInt(tabColorArgb.slice(4, 6), 16); // G
|
|
439
|
+
p[9] = parseInt(tabColorArgb.slice(6, 8), 16); // B
|
|
440
|
+
p[10] = parseInt(tabColorArgb.slice(0, 2), 16); // A
|
|
441
|
+
}
|
|
442
|
+
return encodeRecord(REC.BrtWsProp, p);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** BrtDrawing — XLWideString(r:id). 워크시트의 drawing 파트 참조. */
|
|
446
|
+
export function encodeBrtDrawing(relId: string): Uint8Array {
|
|
447
|
+
return encodeRecord(REC.BrtDrawing, encodeXLWideString(relId));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* BrtFmlaNum — 수식 셀(숫자 결과).
|
|
452
|
+
* cellprefix(8) + xnum(double 8, 캐시값=0) + grbit(2) + CellParsedFormula(cce u32 + rgce + cb u32=0).
|
|
453
|
+
*/
|
|
454
|
+
export function encodeBrtFmlaNum(col: number, iStyleRef: number, rgce: Uint8Array): Uint8Array {
|
|
455
|
+
const payload = new Uint8Array(8 + 8 + 2 + 4 + rgce.length + 4);
|
|
456
|
+
writeCellPrefix(payload, 0, col, iStyleRef);
|
|
457
|
+
// xnum(8) = 0, grbit(2) = 0
|
|
458
|
+
writeUint32LE(payload, 18, rgce.length); // cce
|
|
459
|
+
payload.set(rgce, 22);
|
|
460
|
+
writeUint32LE(payload, 22 + rgce.length, 0); // cb
|
|
461
|
+
return encodeRecord(REC.BrtFmlaNum, payload);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// --- 조건부 서식 / dxf : [MS-XLSB] 정확 인코딩 ---
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* XFPropColor([MS-XLSB] 2.5.161, 8B) — A|xclrType(1) + icv(1) + nTintShade(i16) + LongRGBA(R,G,B,A).
|
|
468
|
+
* ARGB "AARRGGBB" 를 RGB type(xclrType=2)으로 인코딩.
|
|
469
|
+
*/
|
|
470
|
+
function encodeXFPropColor(argb: string): Uint8Array {
|
|
471
|
+
return Uint8Array.from([
|
|
472
|
+
0x05, // fValidRGBA(0x01) | xclrType=RGB(2<<1)
|
|
473
|
+
0x00, // icv (RGB 에서 무시)
|
|
474
|
+
0x00,
|
|
475
|
+
0x00, // nTintShade = 0
|
|
476
|
+
parseInt(argb.slice(2, 4), 16), // R
|
|
477
|
+
parseInt(argb.slice(4, 6), 16), // G
|
|
478
|
+
parseInt(argb.slice(6, 8), 16), // B
|
|
479
|
+
parseInt(argb.slice(0, 2), 16), // A
|
|
480
|
+
]);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** XFProp([MS-XLSB] 2.5.159) — xfPropType(u16) + cb(u16, 구조체 전체 크기=4+blob) + blob. */
|
|
484
|
+
function encodeXFProp(type: number, blob: Uint8Array): Uint8Array {
|
|
485
|
+
const out = new Uint8Array(4 + blob.length);
|
|
486
|
+
out[0] = type & 0xff;
|
|
487
|
+
out[1] = (type >>> 8) & 0xff;
|
|
488
|
+
const cb = 4 + blob.length;
|
|
489
|
+
out[2] = cb & 0xff;
|
|
490
|
+
out[3] = (cb >>> 8) & 0xff;
|
|
491
|
+
out.set(blob, 4);
|
|
492
|
+
return out;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* BrtDXF — [MS-XLSB] 2.4.359. header(u16, @bit15 fNewBorder) + XFProps(reserved u16 + cprops u16 + XFProp[]).
|
|
497
|
+
* background → FillPattern(0x00=solid) + bgColor(0x02), fontColor → text color(0x05), bold → Bold(0x19, 700).
|
|
498
|
+
* CF 채우기는 xlsx dxf 와 동일하게 patternType=solid + bgColor 조합(색은 bgColor 에). XFPropColor(RGB), ARGB "AARRGGBB".
|
|
499
|
+
*/
|
|
500
|
+
export function encodeBrtDXF(background?: string, fontColor?: string, bold?: boolean): Uint8Array {
|
|
501
|
+
const props: Uint8Array[] = [];
|
|
502
|
+
if (background != null) {
|
|
503
|
+
props.push(encodeXFProp(0x00, Uint8Array.from([0x01]))); // FillPattern = solid(1)
|
|
504
|
+
props.push(encodeXFProp(0x02, encodeXFPropColor(background))); // bgColor (채우기 색)
|
|
505
|
+
}
|
|
506
|
+
if (fontColor != null) {
|
|
507
|
+
props.push(encodeXFProp(0x05, encodeXFPropColor(fontColor))); // text color
|
|
508
|
+
}
|
|
509
|
+
if (bold === true) {
|
|
510
|
+
props.push(encodeXFProp(0x19, Uint8Array.from([0xbc, 0x02]))); // Bold = 700
|
|
511
|
+
}
|
|
512
|
+
const body = concatBytes(props);
|
|
513
|
+
const payload = new Uint8Array(6 + body.length);
|
|
514
|
+
payload[1] = 0x80; // header: fNewBorder(bit15) — 빈 워크북/boa 정답지 기본값
|
|
515
|
+
payload[4] = props.length & 0xff; // cprops
|
|
516
|
+
payload[5] = (props.length >>> 8) & 0xff;
|
|
517
|
+
payload.set(body, 6);
|
|
518
|
+
return encodeRecord(REC.BrtDXF, payload);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* BrtBeginConditionalFormatting — [MS-XLSB] 2.4.34.
|
|
523
|
+
* ccf(u32, 규칙 수) + flags(u32, fPivot 등=0) + SqRfX(crfx u32 + crfx×RfX{rwFirst,rwLast,colFirst,colLast}).
|
|
524
|
+
* sqref 는 공백 구분 다중 범위 가능.
|
|
525
|
+
*/
|
|
526
|
+
export function encodeBrtBeginConditionalFormatting(sqref: string, ccf: number): Uint8Array {
|
|
527
|
+
const ranges = sqref
|
|
528
|
+
.trim()
|
|
529
|
+
.split(/\s+/)
|
|
530
|
+
.filter((s) => s.length > 0)
|
|
531
|
+
.map((s) => ExcelUtils.parseRangeAddr(s));
|
|
532
|
+
const payload = new Uint8Array(8 + 4 + ranges.length * 16);
|
|
533
|
+
writeUint32LE(payload, 0, ccf);
|
|
534
|
+
writeUint32LE(payload, 4, 0); // flags
|
|
535
|
+
writeUint32LE(payload, 8, ranges.length); // crfx
|
|
536
|
+
let off = 12;
|
|
537
|
+
for (const r of ranges) {
|
|
538
|
+
writeUint32LE(payload, off, r.s.r); // rwFirst
|
|
539
|
+
writeUint32LE(payload, off + 4, r.e.r); // rwLast
|
|
540
|
+
writeUint32LE(payload, off + 8, r.s.c); // colFirst
|
|
541
|
+
writeUint32LE(payload, off + 12, r.e.c); // colLast
|
|
542
|
+
off += 16;
|
|
543
|
+
}
|
|
544
|
+
return encodeRecord(REC.BrtBeginConditionalFormatting, payload);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** CFParsedFormula([MS-XLSB] 2.5.98.6) — cce(u32) + rgce + cb(u32=rgcb 길이=0). */
|
|
548
|
+
function encodeCFParsedFormula(rgce: Uint8Array): Uint8Array {
|
|
549
|
+
const out = new Uint8Array(4 + rgce.length + 4);
|
|
550
|
+
writeUint32LE(out, 0, rgce.length);
|
|
551
|
+
out.set(rgce, 4);
|
|
552
|
+
// 끝 4byte(rgcb 길이) = 0
|
|
553
|
+
return out;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** XLNullableWideString — NULL 은 cchCharacters=0xFFFFFFFF, 그 외는 XLWideString 과 동일. */
|
|
557
|
+
function encodeXLNullableWideString(s?: string): Uint8Array {
|
|
558
|
+
if (s == null) return Uint8Array.from([0xff, 0xff, 0xff, 0xff]);
|
|
559
|
+
return encodeXLWideString(s);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** CFOper([MS-XLSB] 2.5.15) — cellIs operator → iParam. */
|
|
563
|
+
const CF_OPER: Record<string, number> = {
|
|
564
|
+
between: 1,
|
|
565
|
+
notBetween: 2,
|
|
566
|
+
equal: 3,
|
|
567
|
+
notEqual: 4,
|
|
568
|
+
greaterThan: 5,
|
|
569
|
+
lessThan: 6,
|
|
570
|
+
greaterThanOrEqual: 7,
|
|
571
|
+
lessThanOrEqual: 8,
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
/** CFTextOper([MS-XLSB] 2.5.17) — text rule operator → iParam. */
|
|
575
|
+
const CF_TEXTOPER: Record<string, number> = {
|
|
576
|
+
containsText: 0,
|
|
577
|
+
notContains: 1,
|
|
578
|
+
beginsWith: 2,
|
|
579
|
+
endsWith: 3,
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* CONTAINSTEXT 규칙의 구현 수식 rgce. Ptg 시퀀스([MS-XLS] Ftab / [MS-XLSB] 2.5.97).
|
|
584
|
+
* PtgRefN row/col=0(상대) → 범위 내 각 셀이 자기 자신을 참조(범위 무관). ref-class 토큰(0x41/0x42/0x4c).
|
|
585
|
+
*
|
|
586
|
+
* - contains(0): NOT(ISERROR(SEARCH(text, 셀))) ← boa 정답지 검증
|
|
587
|
+
* - notContains(1): ISERROR(SEARCH(text, 셀)) ← boa 정답지 검증
|
|
588
|
+
* - beginsWith(2): LEFT(셀, LEN(text)) = text ← Ftab LEFT=0x73, LEN=0x20 ([MS-XLSB] 확정)
|
|
589
|
+
* - endsWith(3): RIGHT(셀, LEN(text)) = text ← Ftab RIGHT=0x74, LEN=0x20 ([MS-XLSB] 확정)
|
|
590
|
+
*
|
|
591
|
+
* @param textOper CF_TEXTOPER (0=contains, 1=notContains, 2=beginsWith, 3=endsWith)
|
|
592
|
+
*/
|
|
593
|
+
function encodeCfTextRgce(textOper: number, text: string): Uint8Array {
|
|
594
|
+
const ptgStr = (s: string): number[] => {
|
|
595
|
+
const b = [0x17, s.length & 0xff, (s.length >>> 8) & 0xff];
|
|
596
|
+
for (const ch of s) {
|
|
597
|
+
const c = ch.charCodeAt(0);
|
|
598
|
+
b.push(c & 0xff, (c >>> 8) & 0xff);
|
|
599
|
+
}
|
|
600
|
+
return b;
|
|
601
|
+
};
|
|
602
|
+
const ptgRefN = [0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0]; // row u32=0 + col u16=0xC000(상대)
|
|
603
|
+
|
|
604
|
+
if (textOper === 0 || textOper === 1) {
|
|
605
|
+
const out = [...ptgStr(text), ...ptgRefN, 0x42, 0x02, 0x52, 0x00, 0x41, 0x03, 0x00]; // SEARCH(82),ISERROR(3)
|
|
606
|
+
if (textOper === 0) out.push(0x41, 0x26, 0x00); // NOT(38) — contains 만
|
|
607
|
+
return Uint8Array.from(out);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// beginsWith / endsWith: LEFT|RIGHT(셀, LEN(text)) = text
|
|
611
|
+
const fn = textOper === 2 ? 0x73 : 0x74; // LEFT=0x73 / RIGHT=0x74
|
|
612
|
+
return Uint8Array.from([
|
|
613
|
+
...ptgRefN, // 셀
|
|
614
|
+
...ptgStr(text), // text
|
|
615
|
+
0x41, 0x20, 0x00, // PtgFunc LEN(32) → LEN(text)
|
|
616
|
+
0x42, 0x02, fn, 0x00, // PtgFuncVar LEFT|RIGHT argc=2 → LEFT/RIGHT(셀, LEN(text))
|
|
617
|
+
...ptgStr(text), // text
|
|
618
|
+
0x0b, // PtgEq → ... = text
|
|
619
|
+
]);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* BrtBeginCFRule — [MS-XLSB] 2.4.23.
|
|
624
|
+
* iType(4) + iTemplate(4) + dxfId(4) + iPri(4) + iParam(4) + reserved1(4) + reserved2(4)
|
|
625
|
+
* + flags(2) + cbFmla1(4) + cbFmla2(4) + cbFmla3(4) + strParam(XLNullableWideString)
|
|
626
|
+
* + rgce1?(CFParsedFormula) + rgce2?.
|
|
627
|
+
*
|
|
628
|
+
* - cellIs: iType=CELLIS(1), iTemplate=EXPR(0), iParam=CFOper, rgce1(+between/notBetween 시 rgce2).
|
|
629
|
+
* - expression: iType=EXPRIS(2), iTemplate=FMLA(1), rgce1.
|
|
630
|
+
* - text(contains/notContains/begins/ends): iType=EXPRIS(2), iTemplate=CONTAINSTEXT(8),
|
|
631
|
+
* iParam=CFTextOper, strParam=text. rgce1(검색식)은 함수 Ptg 라 미인코딩(cbFmla1=0) — Excel 이
|
|
632
|
+
* strParam+CFTextOper 로 규칙을 재구성하도록 위임(Excel 호환 검증 대상).
|
|
633
|
+
*/
|
|
634
|
+
export function encodeBrtBeginCFRule(
|
|
635
|
+
dxfId: number,
|
|
636
|
+
priority: number,
|
|
637
|
+
spec: ICfRuleSpec,
|
|
638
|
+
): Uint8Array {
|
|
639
|
+
let iType: number;
|
|
640
|
+
let iTemplate: number;
|
|
641
|
+
let iParam = 0;
|
|
642
|
+
let strParam: string | undefined;
|
|
643
|
+
let rgce1: Uint8Array | undefined;
|
|
644
|
+
let rgce2: Uint8Array | undefined;
|
|
645
|
+
|
|
646
|
+
if (spec.type === "cellIs") {
|
|
647
|
+
iType = 1; // CF_TYPE_CELLIS
|
|
648
|
+
iTemplate = 0; // CF_TEMPLATE_EXPR
|
|
649
|
+
iParam = CF_OPER[spec.operator ?? "equal"];
|
|
650
|
+
rgce1 = encodeFormula(spec.formula[0]);
|
|
651
|
+
if (spec.operator === "between" || spec.operator === "notBetween") {
|
|
652
|
+
rgce2 = encodeFormula(spec.formula[1]);
|
|
653
|
+
}
|
|
654
|
+
} else if (spec.type === "expression") {
|
|
655
|
+
iType = 2; // CF_TYPE_EXPRIS
|
|
656
|
+
iTemplate = 1; // CF_TEMPLATE_FMLA
|
|
657
|
+
rgce1 = encodeFormula(spec.formula[0]);
|
|
658
|
+
} else {
|
|
659
|
+
iType = 2; // CF_TYPE_EXPRIS
|
|
660
|
+
iTemplate = 8; // CF_TEMPLATE_CONTAINSTEXT
|
|
661
|
+
iParam = CF_TEXTOPER[spec.operator ?? "containsText"];
|
|
662
|
+
strParam = spec.text;
|
|
663
|
+
// contains/notContains 는 boa 검증, beginsWith/endsWith 는 Ftab 추측(Excel 검증 대상).
|
|
664
|
+
rgce1 = encodeCfTextRgce(iParam, spec.text ?? "");
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const cf1 = encodeCFParsedFormula(rgce1);
|
|
668
|
+
const cf2 = rgce2 != null ? encodeCFParsedFormula(rgce2) : undefined;
|
|
669
|
+
|
|
670
|
+
const head = new Uint8Array(42);
|
|
671
|
+
writeUint32LE(head, 0, iType);
|
|
672
|
+
writeUint32LE(head, 4, iTemplate);
|
|
673
|
+
writeUint32LE(head, 8, dxfId);
|
|
674
|
+
writeUint32LE(head, 12, priority);
|
|
675
|
+
writeUint32LE(head, 16, iParam);
|
|
676
|
+
// reserved1@20, reserved2@24, flags@28(2byte) = 0
|
|
677
|
+
writeUint32LE(head, 30, cf1.length); // cbFmla1
|
|
678
|
+
writeUint32LE(head, 34, cf2 != null ? cf2.length : 0); // cbFmla2
|
|
679
|
+
writeUint32LE(head, 38, 0); // cbFmla3
|
|
680
|
+
|
|
681
|
+
const parts: Uint8Array[] = [head, encodeXLNullableWideString(strParam), cf1];
|
|
682
|
+
if (cf2 != null) parts.push(cf2);
|
|
683
|
+
return encodeRecord(REC.BrtBeginCFRule, concatBytes(parts));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
//#endregion
|