@shbernal/ts-xlsx 1.0.0

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.
Files changed (223) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +230 -0
  3. package/dist/core/address.d.ts +49 -0
  4. package/dist/core/address.js +125 -0
  5. package/dist/core/autofilter.d.ts +55 -0
  6. package/dist/core/autofilter.js +48 -0
  7. package/dist/core/cell.d.ts +132 -0
  8. package/dist/core/cell.js +249 -0
  9. package/dist/core/color-resolution.d.ts +58 -0
  10. package/dist/core/color-resolution.js +170 -0
  11. package/dist/core/column.d.ts +87 -0
  12. package/dist/core/column.js +167 -0
  13. package/dist/core/comment-thread.d.ts +116 -0
  14. package/dist/core/comment-thread.js +62 -0
  15. package/dist/core/conditional-formatting-overlay.d.ts +15 -0
  16. package/dist/core/conditional-formatting-overlay.js +24 -0
  17. package/dist/core/conditional-formatting.d.ts +70 -0
  18. package/dist/core/conditional-formatting.js +35 -0
  19. package/dist/core/containers.d.ts +2 -0
  20. package/dist/core/containers.js +25 -0
  21. package/dist/core/data-validation-overlay.d.ts +28 -0
  22. package/dist/core/data-validation-overlay.js +56 -0
  23. package/dist/core/data-validation.d.ts +38 -0
  24. package/dist/core/data-validation.js +13 -0
  25. package/dist/core/date.d.ts +25 -0
  26. package/dist/core/date.js +58 -0
  27. package/dist/core/formula.d.ts +49 -0
  28. package/dist/core/formula.js +319 -0
  29. package/dist/core/grid-edits.d.ts +22 -0
  30. package/dist/core/grid-edits.js +206 -0
  31. package/dist/core/image.d.ts +76 -0
  32. package/dist/core/image.js +65 -0
  33. package/dist/core/internal.d.ts +11 -0
  34. package/dist/core/internal.js +29 -0
  35. package/dist/core/merge.d.ts +13 -0
  36. package/dist/core/merge.js +26 -0
  37. package/dist/core/modern-functions.d.ts +1 -0
  38. package/dist/core/modern-functions.js +165 -0
  39. package/dist/core/page-setup.d.ts +94 -0
  40. package/dist/core/page-setup.js +6 -0
  41. package/dist/core/pivot-table.d.ts +113 -0
  42. package/dist/core/pivot-table.js +249 -0
  43. package/dist/core/preserved.d.ts +58 -0
  44. package/dist/core/preserved.js +5 -0
  45. package/dist/core/protection.d.ts +67 -0
  46. package/dist/core/protection.js +68 -0
  47. package/dist/core/range.d.ts +112 -0
  48. package/dist/core/range.js +283 -0
  49. package/dist/core/row.d.ts +73 -0
  50. package/dist/core/row.js +135 -0
  51. package/dist/core/style.d.ts +246 -0
  52. package/dist/core/style.js +125 -0
  53. package/dist/core/table-style.d.ts +67 -0
  54. package/dist/core/table-style.js +90 -0
  55. package/dist/core/table.d.ts +194 -0
  56. package/dist/core/table.js +291 -0
  57. package/dist/core/theme.d.ts +90 -0
  58. package/dist/core/theme.js +272 -0
  59. package/dist/core/value.d.ts +115 -0
  60. package/dist/core/value.js +144 -0
  61. package/dist/core/workbook-protection.d.ts +27 -0
  62. package/dist/core/workbook-protection.js +25 -0
  63. package/dist/core/workbook.d.ts +528 -0
  64. package/dist/core/workbook.js +732 -0
  65. package/dist/core/worksheet-model.d.ts +34 -0
  66. package/dist/core/worksheet-model.js +100 -0
  67. package/dist/core/worksheet.d.ts +647 -0
  68. package/dist/core/worksheet.js +1025 -0
  69. package/dist/customui/errors.d.ts +15 -0
  70. package/dist/customui/errors.js +15 -0
  71. package/dist/customui/index.d.ts +2 -0
  72. package/dist/customui/index.js +7 -0
  73. package/dist/customui/ribbon.d.ts +86 -0
  74. package/dist/customui/ribbon.js +180 -0
  75. package/dist/entries/core.d.ts +23 -0
  76. package/dist/entries/core.js +20 -0
  77. package/dist/entries/csv.d.ts +2 -0
  78. package/dist/entries/csv.js +6 -0
  79. package/dist/entries/customui.d.ts +1 -0
  80. package/dist/entries/customui.js +3 -0
  81. package/dist/entries/errors.d.ts +7 -0
  82. package/dist/entries/errors.js +20 -0
  83. package/dist/entries/vba.d.ts +2 -0
  84. package/dist/entries/vba.js +7 -0
  85. package/dist/entries/xlsb.d.ts +1 -0
  86. package/dist/entries/xlsb.js +7 -0
  87. package/dist/entries/xlsx.d.ts +5 -0
  88. package/dist/entries/xlsx.js +18 -0
  89. package/dist/errors.d.ts +65 -0
  90. package/dist/errors.js +66 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.js +19 -0
  93. package/dist/io/csv/read.d.ts +15 -0
  94. package/dist/io/csv/read.js +113 -0
  95. package/dist/io/csv/write.d.ts +28 -0
  96. package/dist/io/csv/write.js +124 -0
  97. package/dist/io/opc/errors.d.ts +46 -0
  98. package/dist/io/opc/errors.js +49 -0
  99. package/dist/io/opc/inflate.d.ts +15 -0
  100. package/dist/io/opc/inflate.js +91 -0
  101. package/dist/io/opc/namespaces.d.ts +18 -0
  102. package/dist/io/opc/namespaces.js +18 -0
  103. package/dist/io/opc/part-paths.d.ts +4 -0
  104. package/dist/io/opc/part-paths.js +35 -0
  105. package/dist/io/opc/read-opc.d.ts +24 -0
  106. package/dist/io/opc/read-opc.js +153 -0
  107. package/dist/io/opc/read-options.d.ts +10 -0
  108. package/dist/io/opc/read-options.js +7 -0
  109. package/dist/io/opc/rels.d.ts +15 -0
  110. package/dist/io/opc/rels.js +28 -0
  111. package/dist/io/opc/sniff-format.d.ts +27 -0
  112. package/dist/io/opc/sniff-format.js +92 -0
  113. package/dist/io/style/xf-style.d.ts +47 -0
  114. package/dist/io/style/xf-style.js +95 -0
  115. package/dist/io/xlsb/errors.d.ts +15 -0
  116. package/dist/io/xlsb/errors.js +15 -0
  117. package/dist/io/xlsb/formula.d.ts +48 -0
  118. package/dist/io/xlsb/formula.js +398 -0
  119. package/dist/io/xlsb/primitives.d.ts +86 -0
  120. package/dist/io/xlsb/primitives.js +230 -0
  121. package/dist/io/xlsb/ptg-functions.d.ts +16 -0
  122. package/dist/io/xlsb/ptg-functions.js +659 -0
  123. package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
  124. package/dist/io/xlsb/read-shared-strings.js +22 -0
  125. package/dist/io/xlsb/read-styles.d.ts +4 -0
  126. package/dist/io/xlsb/read-styles.js +386 -0
  127. package/dist/io/xlsb/read-worksheet.d.ts +9 -0
  128. package/dist/io/xlsb/read-worksheet.js +301 -0
  129. package/dist/io/xlsb/read.d.ts +20 -0
  130. package/dist/io/xlsb/read.js +199 -0
  131. package/dist/io/xlsb/record-stream.d.ts +14 -0
  132. package/dist/io/xlsb/record-stream.js +56 -0
  133. package/dist/io/xlsb/record-types.d.ts +55 -0
  134. package/dist/io/xlsb/record-types.js +73 -0
  135. package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
  136. package/dist/io/xlsx/cell-accumulator.js +172 -0
  137. package/dist/io/xlsx/cell-value.d.ts +32 -0
  138. package/dist/io/xlsx/cell-value.js +85 -0
  139. package/dist/io/xlsx/comments.d.ts +74 -0
  140. package/dist/io/xlsx/comments.js +243 -0
  141. package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
  142. package/dist/io/xlsx/conditional-formatting.js +464 -0
  143. package/dist/io/xlsx/data-validation.d.ts +20 -0
  144. package/dist/io/xlsx/data-validation.js +258 -0
  145. package/dist/io/xlsx/edit-vba.d.ts +23 -0
  146. package/dist/io/xlsx/edit-vba.js +139 -0
  147. package/dist/io/xlsx/errors.d.ts +17 -0
  148. package/dist/io/xlsx/errors.js +17 -0
  149. package/dist/io/xlsx/hyperlinks.d.ts +42 -0
  150. package/dist/io/xlsx/hyperlinks.js +139 -0
  151. package/dist/io/xlsx/images.d.ts +34 -0
  152. package/dist/io/xlsx/images.js +218 -0
  153. package/dist/io/xlsx/namespaces.d.ts +55 -0
  154. package/dist/io/xlsx/namespaces.js +55 -0
  155. package/dist/io/xlsx/package-plan.d.ts +103 -0
  156. package/dist/io/xlsx/package-plan.js +168 -0
  157. package/dist/io/xlsx/pivot-read.d.ts +5 -0
  158. package/dist/io/xlsx/pivot-read.js +134 -0
  159. package/dist/io/xlsx/pivot.d.ts +12 -0
  160. package/dist/io/xlsx/pivot.js +141 -0
  161. package/dist/io/xlsx/read-rows.d.ts +85 -0
  162. package/dist/io/xlsx/read-rows.js +286 -0
  163. package/dist/io/xlsx/read-styles.d.ts +8 -0
  164. package/dist/io/xlsx/read-styles.js +551 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +4 -0
  166. package/dist/io/xlsx/read-worksheet.js +528 -0
  167. package/dist/io/xlsx/read.d.ts +32 -0
  168. package/dist/io/xlsx/read.js +680 -0
  169. package/dist/io/xlsx/relationships.d.ts +33 -0
  170. package/dist/io/xlsx/relationships.js +47 -0
  171. package/dist/io/xlsx/rich-runs.d.ts +13 -0
  172. package/dist/io/xlsx/rich-runs.js +59 -0
  173. package/dist/io/xlsx/rich-text.d.ts +7 -0
  174. package/dist/io/xlsx/rich-text.js +23 -0
  175. package/dist/io/xlsx/shared-formulas.d.ts +6 -0
  176. package/dist/io/xlsx/shared-formulas.js +52 -0
  177. package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
  178. package/dist/io/xlsx/shared-strings-read.js +70 -0
  179. package/dist/io/xlsx/shared-strings.d.ts +17 -0
  180. package/dist/io/xlsx/shared-strings.js +50 -0
  181. package/dist/io/xlsx/sheet-properties.d.ts +13 -0
  182. package/dist/io/xlsx/sheet-properties.js +211 -0
  183. package/dist/io/xlsx/styles.d.ts +138 -0
  184. package/dist/io/xlsx/styles.js +821 -0
  185. package/dist/io/xlsx/tables.d.ts +9 -0
  186. package/dist/io/xlsx/tables.js +208 -0
  187. package/dist/io/xlsx/threaded-comments.d.ts +102 -0
  188. package/dist/io/xlsx/threaded-comments.js +308 -0
  189. package/dist/io/xlsx/workbook-xml.d.ts +12 -0
  190. package/dist/io/xlsx/workbook-xml.js +353 -0
  191. package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
  192. package/dist/io/xlsx/worksheet-xml.js +643 -0
  193. package/dist/io/xlsx/write-stream.d.ts +171 -0
  194. package/dist/io/xlsx/write-stream.js +399 -0
  195. package/dist/io/xlsx/write.d.ts +81 -0
  196. package/dist/io/xlsx/write.js +396 -0
  197. package/dist/io/xlsx/x14-ext.d.ts +9 -0
  198. package/dist/io/xlsx/x14-ext.js +12 -0
  199. package/dist/vba/cfb-writer.d.ts +19 -0
  200. package/dist/vba/cfb-writer.js +277 -0
  201. package/dist/vba/cfb.d.ts +16 -0
  202. package/dist/vba/cfb.js +324 -0
  203. package/dist/vba/codepage.d.ts +18 -0
  204. package/dist/vba/codepage.js +87 -0
  205. package/dist/vba/errors.d.ts +24 -0
  206. package/dist/vba/errors.js +24 -0
  207. package/dist/vba/index.d.ts +5 -0
  208. package/dist/vba/index.js +15 -0
  209. package/dist/vba/ms-ovba.d.ts +12 -0
  210. package/dist/vba/ms-ovba.js +176 -0
  211. package/dist/vba/project-editor.d.ts +62 -0
  212. package/dist/vba/project-editor.js +377 -0
  213. package/dist/vba/project.d.ts +45 -0
  214. package/dist/vba/project.js +160 -0
  215. package/dist/vba/vba-encoding.d.ts +14 -0
  216. package/dist/vba/vba-encoding.js +39 -0
  217. package/dist/xml/errors.d.ts +15 -0
  218. package/dist/xml/errors.js +15 -0
  219. package/dist/xml/xml-read.d.ts +107 -0
  220. package/dist/xml/xml-read.js +283 -0
  221. package/dist/xml/xml.d.ts +41 -0
  222. package/dist/xml/xml.js +86 -0
  223. package/package.json +117 -0
@@ -0,0 +1,87 @@
1
+ // The VBA project stores module names and source as MBCS bytes in the project code page
2
+ // (`PROJECTCODEPAGE`, [MS-OVBA] 2.3.4.2). To recover text we decode those bytes with the matching
3
+ // encoding rather than assuming latin1 — a CJK or Cyrillic project would otherwise mojibake.
4
+ import { VbaAuthorError, VbaParseError } from "./errors.js";
5
+ // Windows code-page numbers → the WHATWG encoding label `TextDecoder` understands. Node's built-in
6
+ // ICU covers all of these. Only the code pages a VBA project realistically declares are listed; an
7
+ // unlisted one falls back to windows-1252 (the overwhelmingly common Western-European default) so a
8
+ // rare project still decodes readably instead of throwing.
9
+ const CODEPAGE_LABEL = new Map([
10
+ [1250, 'windows-1250'],
11
+ [1251, 'windows-1251'],
12
+ [1252, 'windows-1252'],
13
+ [1253, 'windows-1253'],
14
+ [1254, 'windows-1254'],
15
+ [1255, 'windows-1255'],
16
+ [1256, 'windows-1256'],
17
+ [1257, 'windows-1257'],
18
+ [1258, 'windows-1258'],
19
+ [874, 'windows-874'],
20
+ [932, 'shift_jis'],
21
+ [936, 'gbk'],
22
+ [949, 'euc-kr'],
23
+ [950, 'big5'],
24
+ [10000, 'macintosh'],
25
+ [20866, 'koi8-r'],
26
+ [21866, 'koi8-u'],
27
+ [28591, 'iso-8859-1'],
28
+ [28592, 'iso-8859-2'],
29
+ [65001, 'utf-8'],
30
+ ]);
31
+ /**
32
+ * A `TextDecoder` for the given VBA project code page. Non-fatal (malformed bytes become U+FFFD rather
33
+ * than throwing) because recovered source is for reading, not re-encoding — a stray byte must not sink
34
+ * the whole extraction. An unknown code page falls back to windows-1252.
35
+ */
36
+ export function decoderForCodePage(codePage) {
37
+ const label = CODEPAGE_LABEL.get(codePage) ?? 'windows-1252';
38
+ try {
39
+ return new TextDecoder(label, { fatal: false });
40
+ }
41
+ catch {
42
+ // A runtime whose ICU lacks the label (a minimal build) still gets usable Western-European text
43
+ // rather than a hard failure on an otherwise-valid project.
44
+ try {
45
+ return new TextDecoder('windows-1252', { fatal: false });
46
+ }
47
+ catch (cause) {
48
+ throw new VbaParseError('runtime TextDecoder cannot decode the VBA project code page', {
49
+ cause,
50
+ });
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * The write-side inverse of {@link decoderForCodePage}: an encoder for a project's code page, built by
56
+ * decoding every byte 0x00..0xFF through the matching decoder and inverting the map. This is exact for
57
+ * the single-byte code pages a VBA project realistically declares. Under a multi-byte code page (Shift
58
+ * JIS, GBK, …) the high bytes are lead bytes, not standalone characters, so only ASCII inverts and any
59
+ * non-ASCII character is rejected — fail-closed rather than emit a corrupt byte. A character the chosen
60
+ * code page cannot represent throws {@link VbaAuthorError}.
61
+ */
62
+ export function encoderForCodePage(codePage) {
63
+ const decoder = decoderForCodePage(codePage);
64
+ const charToByte = new Map();
65
+ const one = new Uint8Array(1);
66
+ for (let b = 0x00; b <= 0xff; b++) {
67
+ one[0] = b;
68
+ const decoded = decoder.decode(one);
69
+ if (decoded.length !== 1)
70
+ continue; // a lead byte / incomplete sequence under a multi-byte code page
71
+ const code = decoded.charCodeAt(0);
72
+ if (code !== 0xfffd && !charToByte.has(code))
73
+ charToByte.set(code, b);
74
+ }
75
+ return (text) => {
76
+ const out = new Uint8Array(text.length);
77
+ for (let i = 0; i < text.length; i++) {
78
+ const code = text.charCodeAt(i);
79
+ const byte = charToByte.get(code);
80
+ if (byte === undefined) {
81
+ throw new VbaAuthorError(`character U+${code.toString(16).toUpperCase().padStart(4, '0')} is not representable in code page ${codePage}`);
82
+ }
83
+ out[i] = byte;
84
+ }
85
+ return out;
86
+ };
87
+ }
@@ -0,0 +1,24 @@
1
+ import { XlsxError } from '../errors.ts';
2
+ /**
3
+ * Thrown when a VBA project (`vbaProject.bin`) is present but cannot be parsed — a malformed
4
+ * compound file, a corrupt compressed stream, or a `dir` record that does not conform to [MS-OVBA].
5
+ * A workbook with no macros never produces this: {@link Workbook.vbaProject} is `undefined` instead.
6
+ *
7
+ * The parser treats the blob as hostile input (a spreadsheet library parses untrusted files), so every
8
+ * malformed structure fails closed with this error rather than crashing, hanging, or over-allocating.
9
+ */
10
+ export declare class VbaParseError extends XlsxError {
11
+ readonly name = "VbaParseError";
12
+ readonly code = "malformed-input";
13
+ }
14
+ /**
15
+ * Thrown when authoring a VBA project (synthesizing a `vbaProject.bin` from module source) is asked to
16
+ * produce something that cannot be encoded to a well-formed container — a stream name longer than the
17
+ * [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed the
18
+ * writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
19
+ * {@link VbaParseError} (which reports a malformed blob *read* from an untrusted file).
20
+ */
21
+ export declare class VbaAuthorError extends XlsxError {
22
+ readonly name = "VbaAuthorError";
23
+ readonly code = "authoring";
24
+ }
@@ -0,0 +1,24 @@
1
+ import { XlsxError } from "../errors.js";
2
+ /**
3
+ * Thrown when a VBA project (`vbaProject.bin`) is present but cannot be parsed — a malformed
4
+ * compound file, a corrupt compressed stream, or a `dir` record that does not conform to [MS-OVBA].
5
+ * A workbook with no macros never produces this: {@link Workbook.vbaProject} is `undefined` instead.
6
+ *
7
+ * The parser treats the blob as hostile input (a spreadsheet library parses untrusted files), so every
8
+ * malformed structure fails closed with this error rather than crashing, hanging, or over-allocating.
9
+ */
10
+ export class VbaParseError extends XlsxError {
11
+ name = 'VbaParseError';
12
+ code = 'malformed-input';
13
+ }
14
+ /**
15
+ * Thrown when authoring a VBA project (synthesizing a `vbaProject.bin` from module source) is asked to
16
+ * produce something that cannot be encoded to a well-formed container — a stream name longer than the
17
+ * [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed the
18
+ * writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
19
+ * {@link VbaParseError} (which reports a malformed blob *read* from an untrusted file).
20
+ */
21
+ export class VbaAuthorError extends XlsxError {
22
+ name = 'VbaAuthorError';
23
+ code = 'authoring';
24
+ }
@@ -0,0 +1,5 @@
1
+ export { type CfbNode, type CfbStorage, type CfbStream, writeCompoundFile, } from './cfb-writer.ts';
2
+ export { VbaAuthorError, VbaParseError } from './errors.ts';
3
+ export { compressContainer, decompressContainer } from './ms-ovba.ts';
4
+ export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, type VbaModule, type VbaModuleKind, type VbaProject, type VbaProjectSignature, type VbaProjectSignatureKind, vbaProjectSignatureKind, } from './project.ts';
5
+ export { addVbaReference, removeVbaModule, type VbaLibraryReference, } from './project-editor.ts';
@@ -0,0 +1,15 @@
1
+ // Native VBA read, plus pure-TS structural edits: decode a macro-enabled workbook's `vbaProject.bin`
2
+ // into readable module source, and remove a module or add a library reference from an existing project
3
+ // by splicing the original bytes (every module's compiled p-code preserved untouched).
4
+ //
5
+ // Authoring or editing module SOURCE is NOT here. Excel runs a module's compiled p-code, not its source,
6
+ // and only a real Excel can produce source-matched p-code — so that lives in the offline
7
+ // `tools/vba-compiler` (VBIDE), whose output is attached via `Workbook.vbaProjectBytes`. The read path
8
+ // is a projection over bytes the model preserves opaquely on round-trip. For the design invariants and
9
+ // the wider VBA feature map see the ADRs: read view (docs/decisions/0016), authoring (0017/0019), and
10
+ // structural edits (0018/0019), plus docs/knowledge/specs/xlsm-macro-preservation.md.
11
+ export { writeCompoundFile, } from "./cfb-writer.js";
12
+ export { VbaAuthorError, VbaParseError } from "./errors.js";
13
+ export { compressContainer, decompressContainer } from "./ms-ovba.js";
14
+ export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, vbaProjectSignatureKind, } from "./project.js";
15
+ export { addVbaReference, removeVbaModule, } from "./project-editor.js";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Decompress an MS-OVBA CompressedContainer beginning at `start` in `buf`.
3
+ * @param maxOutput hard ceiling on decompressed bytes; exceeding it throws (bomb guard).
4
+ */
5
+ export declare function decompressContainer(buf: Uint8Array, start?: number, maxOutput?: number): Uint8Array;
6
+ /**
7
+ * Compress `data` into an MS-OVBA CompressedContainer — the inverse of {@link decompressContainer}.
8
+ * Every 4096-decompressed-byte window is emitted as a compressed chunk of literal and copy tokens, or
9
+ * stored verbatim when compression would not shrink it (so the encoded chunk never exceeds the 12-bit
10
+ * size field). The result re-expands to `data` byte-for-byte.
11
+ */
12
+ export declare function compressContainer(data: Uint8Array): Uint8Array;
@@ -0,0 +1,176 @@
1
+ // MS-OVBA §2.4.1 — compression and decompression of a "CompressedContainer".
2
+ //
3
+ // VBA module source and the project `dir` stream are stored in Office's own run-length compression,
4
+ // NOT deflate. A container is a 0x01 signature byte followed by one or more chunks; each chunk
5
+ // decompresses to at most 4096 bytes and is either a raw 4096-byte copy or a stream of literal/copy
6
+ // tokens. Reference: [MS-OVBA] 2.4.1.3.6 (decompressing a CompressedContainer), 2.4.1.3.19.3 (the
7
+ // CopyToken bit-packing), and 2.4.1.3.7 (compressing a chunk).
8
+ //
9
+ // The decompressor is a hostile-input parser: the container comes from an untrusted file, so every
10
+ // length and back-reference is bounds-checked and the total output is capped. A malformed container
11
+ // fails closed with a VbaParseError rather than over-allocating, looping, or reading out of bounds. The
12
+ // compressor is the authoring inverse — it is fed our own bytes, and its output re-expands to the input
13
+ // byte-for-byte (the round-trip is the correctness contract).
14
+ import { VbaParseError } from "./errors.js";
15
+ // A decompressed chunk covers at most 4096 bytes; both directions honour this window ([MS-OVBA]
16
+ // 2.4.1.3.6). The chunk header's bits 12-14 carry a fixed 0b011 signature, bit 15 the compressed flag.
17
+ const MAX_CHUNK_DECOMPRESSED = 4096;
18
+ const CHUNK_SIGNATURE = 0b011 << 12;
19
+ const CHUNK_COMPRESSED_FLAG = 0x8000;
20
+ // A single VBA project is well under a megabyte; 64 MiB is far above any legitimate container yet
21
+ // bounds a decompression bomb (a small container that expands without limit) to a survivable size.
22
+ const DEFAULT_MAX_OUTPUT = 64 * 1024 * 1024;
23
+ /**
24
+ * Decompress an MS-OVBA CompressedContainer beginning at `start` in `buf`.
25
+ * @param maxOutput hard ceiling on decompressed bytes; exceeding it throws (bomb guard).
26
+ */
27
+ export function decompressContainer(buf, start = 0, maxOutput = DEFAULT_MAX_OUTPUT) {
28
+ if (start >= buf.length) {
29
+ throw new VbaParseError(`compressed container starts past end of stream (${start} >= ${buf.length})`);
30
+ }
31
+ if (buf[start] !== 0x01) {
32
+ throw new VbaParseError(`compressed container must begin with a 0x01 signature byte, found 0x${(buf[start] ?? 0).toString(16)}`);
33
+ }
34
+ const out = [];
35
+ let pos = start + 1;
36
+ while (pos + 2 <= buf.length) {
37
+ const header = readU16(buf, pos);
38
+ pos += 2;
39
+ // Bits 0-11: (chunk data size - 1). Bit 15: compressed flag. Bits 12-14: the fixed 0b011 signature.
40
+ const chunkDataSize = (header & 0x0fff) + 1;
41
+ const compressed = (header & 0x8000) !== 0;
42
+ if (((header >> 12) & 0x7) !== 0b011) {
43
+ throw new VbaParseError(`chunk header has a bad 0b011 signature (0x${header.toString(16)})`);
44
+ }
45
+ const chunkEnd = pos + chunkDataSize;
46
+ if (chunkEnd > buf.length) {
47
+ throw new VbaParseError(`chunk data (${chunkDataSize} bytes) runs past end of stream`);
48
+ }
49
+ if (!compressed) {
50
+ // A raw chunk carries its bytes verbatim (Excel emits one only when compression would expand).
51
+ for (let i = pos; i < chunkEnd; i++)
52
+ out.push(buf[i]);
53
+ guardOutput(out.length, maxOutput);
54
+ pos = chunkEnd;
55
+ continue;
56
+ }
57
+ const chunkStart = out.length; // decompressed offset where this chunk began
58
+ while (pos < chunkEnd) {
59
+ const flagByte = buf[pos++];
60
+ for (let bit = 0; bit < 8 && pos < chunkEnd; bit++) {
61
+ const isCopy = (flagByte >> bit) & 1;
62
+ if (!isCopy) {
63
+ out.push(buf[pos++]);
64
+ guardOutput(out.length, maxOutput);
65
+ continue;
66
+ }
67
+ if (pos + 2 > chunkEnd) {
68
+ throw new VbaParseError('copy token truncated at chunk end');
69
+ }
70
+ const token = readU16(buf, pos);
71
+ pos += 2;
72
+ const { lengthMask, bitCount } = copyTokenHelp(out.length - chunkStart);
73
+ const length = (token & lengthMask) + 3;
74
+ const copyOffset = (token >> (16 - bitCount)) + 1;
75
+ const src = out.length - copyOffset;
76
+ if (src < chunkStart) {
77
+ // A back-reference may only reach data emitted since this chunk began ([MS-OVBA] 2.4.1.3.19).
78
+ throw new VbaParseError('copy token references before the start of its chunk');
79
+ }
80
+ // Byte-by-byte so overlapping runs (run-length expansion) grow correctly.
81
+ for (let i = 0; i < length; i++)
82
+ out.push(out[src + i]);
83
+ guardOutput(out.length, maxOutput);
84
+ }
85
+ }
86
+ pos = chunkEnd;
87
+ }
88
+ return Uint8Array.from(out);
89
+ }
90
+ /**
91
+ * Compress `data` into an MS-OVBA CompressedContainer — the inverse of {@link decompressContainer}.
92
+ * Every 4096-decompressed-byte window is emitted as a compressed chunk of literal and copy tokens, or
93
+ * stored verbatim when compression would not shrink it (so the encoded chunk never exceeds the 12-bit
94
+ * size field). The result re-expands to `data` byte-for-byte.
95
+ */
96
+ export function compressContainer(data) {
97
+ const out = [0x01]; // container signature; an empty input yields just this byte
98
+ for (let start = 0; start < data.length; start += MAX_CHUNK_DECOMPRESSED) {
99
+ const chunk = data.subarray(start, Math.min(start + MAX_CHUNK_DECOMPRESSED, data.length));
100
+ const tokens = compressChunk(chunk);
101
+ // Prefer the token stream only when it is strictly smaller; otherwise store the chunk raw. Both
102
+ // encode their exact length in the header, so the decompressor reconstructs the window either way.
103
+ const compressed = tokens.length < chunk.length;
104
+ const body = compressed ? tokens : chunk;
105
+ const header = (compressed ? CHUNK_COMPRESSED_FLAG : 0) | CHUNK_SIGNATURE | ((body.length - 1) & 0x0fff);
106
+ out.push(header & 0xff, (header >> 8) & 0xff);
107
+ for (const b of body)
108
+ out.push(b);
109
+ }
110
+ return Uint8Array.from(out);
111
+ }
112
+ // Encode one decompressed chunk (≤ 4096 bytes) as a sequence of MS-OVBA token groups: a flag byte whose
113
+ // bits mark the next up-to-8 tokens as literal (0) or copy (1). A copy token replaces a run of 3+ bytes
114
+ // that recurs earlier in the *same* chunk; matches may overlap the current position (run-length growth),
115
+ // which the decompressor reproduces byte-by-byte. The bit split between the offset and length fields
116
+ // widens as the chunk fills, exactly as the decoder computes it, so both agree on every token's shape.
117
+ function compressChunk(chunk) {
118
+ const tokens = [];
119
+ let pos = 0;
120
+ while (pos < chunk.length) {
121
+ const flagIndex = tokens.length;
122
+ tokens.push(0);
123
+ let flags = 0;
124
+ for (let bit = 0; bit < 8 && pos < chunk.length; bit++) {
125
+ const { lengthMask, bitCount } = copyTokenHelp(pos);
126
+ const maxLength = lengthMask + 3;
127
+ const windowStart = Math.max(0, pos - (1 << bitCount));
128
+ let bestLength = 0;
129
+ let bestOffset = 0;
130
+ // Scan nearest-first so equal-length matches keep the smallest offset (a marginally cheaper token).
131
+ for (let cand = pos - 1; cand >= windowStart; cand--) {
132
+ let len = 0;
133
+ while (len < maxLength &&
134
+ pos + len < chunk.length &&
135
+ chunk[cand + len] === chunk[pos + len]) {
136
+ len++;
137
+ }
138
+ if (len > bestLength) {
139
+ bestLength = len;
140
+ bestOffset = pos - cand;
141
+ if (bestLength === maxLength)
142
+ break; // cannot improve
143
+ }
144
+ }
145
+ if (bestLength >= 3) {
146
+ const token = ((bestOffset - 1) << (16 - bitCount)) | (bestLength - 3);
147
+ tokens.push(token & 0xff, (token >> 8) & 0xff);
148
+ flags |= 1 << bit;
149
+ pos += bestLength;
150
+ }
151
+ else {
152
+ tokens.push(chunk[pos]);
153
+ pos++;
154
+ }
155
+ }
156
+ tokens[flagIndex] = flags;
157
+ }
158
+ return tokens;
159
+ }
160
+ function guardOutput(size, maxOutput) {
161
+ if (size > maxOutput) {
162
+ throw new VbaParseError(`decompressed output exceeds the ${maxOutput}-byte ceiling (possible bomb)`);
163
+ }
164
+ }
165
+ /**
166
+ * Bit split for a CopyToken given how many bytes have been emitted since the current chunk began
167
+ * ([MS-OVBA] 2.4.1.3.19.3): the offset field grows and the length field shrinks as the chunk fills.
168
+ */
169
+ function copyTokenHelp(decompressedSoFar) {
170
+ const bitCount = Math.max(Math.ceil(Math.log2(Math.max(decompressedSoFar, 1))), 4);
171
+ const lengthMask = 0xffff >> bitCount;
172
+ return { lengthMask, bitCount };
173
+ }
174
+ function readU16(buf, at) {
175
+ return buf[at] | (buf[at + 1] << 8);
176
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Remove a standard module from an existing `vbaProject.bin`, returning new bytes that carry every
3
+ * remaining module, reference, and host-info record unchanged. It drops the module's `VBA/<name>`
4
+ * stream, its MODULE record block in `dir` (decrementing `MODULES_COUNT`), and its `Module=`/`Class=` +
5
+ * workspace lines in `PROJECT`/`PROJECTwm`.
6
+ *
7
+ * Only `procedural` and `class` modules can be removed this way — removing a `document` module (e.g.
8
+ * `ThisWorkbook`) or a `designer` module (a UserForm) would leave the host referencing code that no
9
+ * longer exists, since their names are tied to a worksheet/workbook `codeName` or a designer storage
10
+ * this project-level primitive has no visibility into. Editing such a module's code-behind is a job for
11
+ * the offline `tools/vba-compiler` (in-place mode), which drives the real host.
12
+ *
13
+ * @throws {VbaParseError} if `bin` is not a parseable VBA project (validated before any edit).
14
+ * @throws {VbaAuthorError} if `name` is not in the project, or names a `document`/`designer` module.
15
+ */
16
+ export declare function removeVbaModule(bin: Uint8Array, name: string): Uint8Array;
17
+ /**
18
+ * A registered (COM Automation type-library) reference to add to an existing VBA project — the shape of
19
+ * a real "add a reference to Microsoft Scripting Runtime" call. Project references (to another VBA
20
+ * project) and control references (to an ActiveX control library) are out of scope — see
21
+ * {@link addVbaReference}.
22
+ */
23
+ export interface VbaLibraryReference {
24
+ /**
25
+ * The reference's namespace name in the VBA editor — what a qualified reference like
26
+ * `Scripting.Dictionary` resolves through. Must be a valid VBA identifier, at most 31 characters, as
27
+ * real type libraries use (e.g. `Scripting`, `Office`, `stdole`).
28
+ */
29
+ readonly name: string;
30
+ /**
31
+ * The friendly name shown in the References dialog, e.g. `Microsoft Scripting Runtime`. Real projects
32
+ * usually keep this distinct from {@link name}; defaults to {@link name} if omitted.
33
+ */
34
+ readonly displayName?: string;
35
+ /** The type library's GUID, e.g. `{420B2830-E718-11CF-893D-00A0C9054228}` (braces optional). */
36
+ readonly guid: string;
37
+ /** The type library's major version — an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMajorVersion`). */
38
+ readonly majorVersion: number;
39
+ /** The type library's minor version — an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMinorVersion`). */
40
+ readonly minorVersion: number;
41
+ /**
42
+ * The type library's LCID — an integer in `[0, 0xFFFFFFFF]`. Defaults to `0` (locale-neutral), the
43
+ * overwhelming common case (every reference in a real project observed while building this had `0`).
44
+ */
45
+ readonly lcid?: number;
46
+ /** Absolute Windows path to the type library file, e.g. `C:\Windows\System32\scrrun.dll`. */
47
+ readonly path: string;
48
+ }
49
+ /**
50
+ * Add a registered (COM type-library) reference to an existing `vbaProject.bin`, returning new bytes
51
+ * that carry every existing module, reference, and host-info record unchanged. It grows the project's
52
+ * `dir` stream by one `REFERENCENAME` + `REFERENCEREGISTERED` record pair, positioned immediately before
53
+ * `MODULES_COUNT` (references have no count field of their own — `MODULES_COUNT` simply marks where the
54
+ * reference array ends). It needs no change to `PROJECT`/`PROJECTwm`: a real Excel-authored `PROJECT`
55
+ * stream carries no `Reference=` line at all — references live only in `dir` (confirmed against a genuine
56
+ * Excel-authored project).
57
+ *
58
+ * @throws {VbaParseError} if `bin` is not a parseable VBA project (validated before any edit).
59
+ * @throws {VbaAuthorError} if any field of `ref` is invalid (see {@link VbaLibraryReference}), or the
60
+ * assembled reference text has a character the project's code page cannot represent.
61
+ */
62
+ export declare function addVbaReference(bin: Uint8Array, ref: VbaLibraryReference): Uint8Array;