@shbernal/ts-xlsx 1.2.0 → 2.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 (161) hide show
  1. package/README.md +5 -0
  2. package/dist/core/address.d.ts +49 -2
  3. package/dist/core/address.js +31 -3
  4. package/dist/core/autofilter.d.ts +2 -2
  5. package/dist/core/cell.d.ts +17 -16
  6. package/dist/core/cell.js +3 -7
  7. package/dist/core/color-resolution.d.ts +7 -7
  8. package/dist/core/column.d.ts +8 -9
  9. package/dist/core/column.js +2 -4
  10. package/dist/core/comment-thread.d.ts +32 -23
  11. package/dist/core/comment-thread.js +4 -4
  12. package/dist/core/conditional-formatting-overlay.d.ts +1 -1
  13. package/dist/core/conditional-formatting.d.ts +3 -3
  14. package/dist/core/data-validation-overlay.d.ts +1 -1
  15. package/dist/core/data-validation.d.ts +3 -3
  16. package/dist/core/date.d.ts +3 -3
  17. package/dist/core/formula.d.ts +6 -6
  18. package/dist/core/grid-edits.js +3 -3
  19. package/dist/core/image.d.ts +37 -2
  20. package/dist/core/image.js +6 -0
  21. package/dist/core/internal.d.ts +9 -1
  22. package/dist/core/limits.d.ts +4 -4
  23. package/dist/core/merge.d.ts +6 -12
  24. package/dist/core/merge.js +0 -3
  25. package/dist/core/page-setup.d.ts +4 -4
  26. package/dist/core/pivot-table.d.ts +2 -4
  27. package/dist/core/pivot-table.js +5 -5
  28. package/dist/core/preserved.d.ts +3 -3
  29. package/dist/core/protection.d.ts +4 -4
  30. package/dist/core/range.d.ts +16 -14
  31. package/dist/core/range.js +5 -5
  32. package/dist/core/row-input.d.ts +2 -8
  33. package/dist/core/row-input.js +2 -2
  34. package/dist/core/row.d.ts +7 -8
  35. package/dist/core/row.js +2 -4
  36. package/dist/core/style.d.ts +14 -14
  37. package/dist/core/table-style.d.ts +9 -9
  38. package/dist/core/table-style.js +1 -1
  39. package/dist/core/table.d.ts +30 -42
  40. package/dist/core/table.js +16 -10
  41. package/dist/core/theme.d.ts +13 -43
  42. package/dist/core/theme.js +2 -148
  43. package/dist/core/value.d.ts +7 -7
  44. package/dist/core/workbook-protection.d.ts +2 -2
  45. package/dist/core/workbook-styles.d.ts +26 -0
  46. package/dist/core/workbook-styles.js +48 -0
  47. package/dist/core/workbook-theme.d.ts +25 -0
  48. package/dist/core/workbook-theme.js +49 -0
  49. package/dist/core/workbook-vba.d.ts +17 -0
  50. package/dist/core/workbook-vba.js +79 -0
  51. package/dist/core/workbook.d.ts +141 -83
  52. package/dist/core/workbook.js +74 -133
  53. package/dist/core/worksheet-comments.d.ts +9 -0
  54. package/dist/core/worksheet-comments.js +52 -0
  55. package/dist/core/worksheet-model.d.ts +4 -4
  56. package/dist/core/worksheet-model.js +1 -0
  57. package/dist/core/worksheet-pictures.d.ts +29 -0
  58. package/dist/core/worksheet-pictures.js +42 -0
  59. package/dist/core/worksheet.d.ts +94 -77
  60. package/dist/core/worksheet.js +36 -95
  61. package/dist/customui/errors.d.ts +1 -1
  62. package/dist/customui/ribbon.d.ts +3 -3
  63. package/dist/entries/core.d.ts +4 -4
  64. package/dist/entries/core.js +1 -1
  65. package/dist/entries/vba.d.ts +1 -1
  66. package/dist/entries/vba.js +1 -1
  67. package/dist/entries/xlsx.d.ts +1 -0
  68. package/dist/entries/xlsx.js +1 -0
  69. package/dist/errors.d.ts +12 -12
  70. package/dist/io/csv/read.js +1 -2
  71. package/dist/io/csv/write.d.ts +8 -3
  72. package/dist/io/csv/write.js +17 -2
  73. package/dist/io/opc/errors.d.ts +6 -6
  74. package/dist/io/opc/inflate.d.ts +1 -1
  75. package/dist/io/opc/namespaces.d.ts +1 -1
  76. package/dist/io/opc/read-opc.d.ts +19 -3
  77. package/dist/io/opc/read-opc.js +14 -8
  78. package/dist/io/opc/read-options.d.ts +2 -2
  79. package/dist/io/opc/sniff-format.d.ts +2 -2
  80. package/dist/io/opc/zip-mtime.d.ts +5 -0
  81. package/dist/io/opc/zip-mtime.js +1 -0
  82. package/dist/io/style/xf-style.d.ts +38 -6
  83. package/dist/io/style/xf-style.js +22 -0
  84. package/dist/io/xlsb/errors.d.ts +1 -1
  85. package/dist/io/xlsb/formula.d.ts +4 -4
  86. package/dist/io/xlsb/primitives.d.ts +8 -8
  87. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  88. package/dist/io/xlsb/read-styles.js +2 -20
  89. package/dist/io/xlsb/read.d.ts +2 -2
  90. package/dist/io/xlsx/cell-accumulator.d.ts +25 -6
  91. package/dist/io/xlsx/cell-accumulator.js +111 -30
  92. package/dist/io/xlsx/cell-value.d.ts +2 -2
  93. package/dist/io/xlsx/cell-value.js +3 -3
  94. package/dist/io/xlsx/color-xml.js +11 -17
  95. package/dist/io/xlsx/comments.d.ts +5 -5
  96. package/dist/io/xlsx/comments.js +13 -6
  97. package/dist/io/xlsx/conditional-formatting.d.ts +3 -3
  98. package/dist/io/xlsx/conditional-formatting.js +7 -16
  99. package/dist/io/xlsx/data-validation.d.ts +2 -2
  100. package/dist/io/xlsx/data-validation.js +1 -1
  101. package/dist/io/xlsx/edit-vba.js +2 -1
  102. package/dist/io/xlsx/errors.d.ts +3 -3
  103. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  104. package/dist/io/xlsx/hyperlinks.js +5 -5
  105. package/dist/io/xlsx/images.d.ts +1 -1
  106. package/dist/io/xlsx/images.js +8 -8
  107. package/dist/io/xlsx/namespaces.d.ts +2 -2
  108. package/dist/io/xlsx/package-plan.js +5 -2
  109. package/dist/io/xlsx/read-pivot.js +2 -5
  110. package/dist/io/xlsx/read-rows.d.ts +13 -13
  111. package/dist/io/xlsx/read-rows.js +19 -55
  112. package/dist/io/xlsx/read-shared-strings.js +7 -5
  113. package/dist/io/xlsx/read-styles.d.ts +3 -3
  114. package/dist/io/xlsx/read-styles.js +40 -65
  115. package/dist/io/xlsx/read-worksheet.js +67 -115
  116. package/dist/io/xlsx/read.d.ts +3 -5
  117. package/dist/io/xlsx/read.js +94 -71
  118. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  119. package/dist/io/xlsx/rich-runs.js +1 -1
  120. package/dist/io/xlsx/rich-text.d.ts +1 -1
  121. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  122. package/dist/io/xlsx/sheet-properties.js +5 -4
  123. package/dist/io/xlsx/styles.d.ts +8 -9
  124. package/dist/io/xlsx/styles.js +4 -3
  125. package/dist/io/xlsx/tables.d.ts +2 -2
  126. package/dist/io/xlsx/tables.js +8 -13
  127. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  128. package/dist/io/xlsx/theme-xml.js +142 -0
  129. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  130. package/dist/io/xlsx/threaded-comments.js +9 -21
  131. package/dist/io/xlsx/workbook-xml.d.ts +1 -1
  132. package/dist/io/xlsx/workbook-xml.js +6 -2
  133. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  134. package/dist/io/xlsx/worksheet-xml.js +7 -12
  135. package/dist/io/xlsx/write-stream.d.ts +15 -15
  136. package/dist/io/xlsx/write-stream.js +5 -3
  137. package/dist/io/xlsx/write.d.ts +12 -7
  138. package/dist/io/xlsx/write.js +10 -6
  139. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  140. package/dist/vba/bytes.d.ts +12 -0
  141. package/dist/vba/bytes.js +40 -0
  142. package/dist/vba/cfb-writer.d.ts +1 -1
  143. package/dist/vba/cfb.d.ts +1 -1
  144. package/dist/vba/cfb.js +15 -51
  145. package/dist/vba/codepage.d.ts +2 -2
  146. package/dist/vba/errors.d.ts +2 -2
  147. package/dist/vba/index.d.ts +2 -2
  148. package/dist/vba/index.js +2 -2
  149. package/dist/vba/ms-ovba.d.ts +1 -1
  150. package/dist/vba/ms-ovba.js +1 -3
  151. package/dist/vba/project-editor.d.ts +9 -9
  152. package/dist/vba/project-editor.js +2 -11
  153. package/dist/vba/project.d.ts +5 -5
  154. package/dist/vba/project.js +1 -10
  155. package/dist/vba/vba-encoding.d.ts +1 -1
  156. package/dist/xml/errors.d.ts +1 -1
  157. package/dist/xml/xml-read.d.ts +38 -9
  158. package/dist/xml/xml-read.js +23 -0
  159. package/dist/xml/xml.d.ts +31 -9
  160. package/dist/xml/xml.js +23 -3
  161. package/package.json +41 -35
@@ -1,8 +1,8 @@
1
1
  import { strToU8, zip, zipSync } from 'fflate';
2
- import { DEFAULT_THEME_XML } from '../../core/theme.js';
3
2
  import { AuthoringError } from '../../errors.js';
4
3
  import { THEME_PART_PATH } from '../opc/part-paths.js';
5
4
  import { relsPartXml } from '../opc/rels.js';
5
+ import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
6
6
  import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
7
7
  import { collectHyperlinks, planHyperlinks } from './hyperlinks.js';
8
8
  import { drawingRelsXml, drawingXml } from './images.js';
@@ -12,17 +12,18 @@ import { REL } from './relationships.js';
12
12
  import { SharedStringTable } from './shared-strings.js';
13
13
  import { StyleRegistry } from './styles.js';
14
14
  import { tableXml } from './tables.js';
15
+ import { applyThemeOverrides, DEFAULT_THEME_XML } from './theme-xml.js';
15
16
  import { personsXml, threadedCommentsXml } from './threaded-comments.js';
16
17
  import { appPropsXml, contentTypesXml, corePropsXml, FIXED_WORKBOOK_REL_COUNT, rootRelsXml, workbookRelsXml, workbookXml, } from './workbook-xml.js';
17
18
  import { worksheetRelsXml, worksheetXml, } from './worksheet-xml.js';
18
19
  export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
19
20
  export function writeXlsx(workbook, options = {}) {
20
- return zipSync(buildPackageParts(workbook, options), { level: 6 });
21
+ return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
21
22
  }
22
23
  export async function writeXlsxAsync(workbook, options = {}) {
23
24
  const parts = buildPackageParts(workbook, options);
24
25
  return await new Promise((resolve, reject) => {
25
- zip(parts, { level: 6 }, (error, data) => {
26
+ zip(parts, { level: 6, mtime: FIXED_ENTRY_MTIME }, (error, data) => {
26
27
  if (error)
27
28
  reject(error);
28
29
  else
@@ -65,7 +66,7 @@ function resolveSheetReferences(plan) {
65
66
  export function buildPackageParts(workbook, options = {}) {
66
67
  const sheets = workbook.worksheets;
67
68
  if (sheets.length === 0) {
68
- throw new AuthoringError('cannot write a workbook with no worksheets a zero-sheet package is corrupt to Excel');
69
+ throw new AuthoringError('cannot write a workbook with no worksheets: a zero-sheet package is corrupt to Excel');
69
70
  }
70
71
  const sharedStrings = options.useSharedStrings ? new SharedStringTable() : null;
71
72
  const media = planMedia(workbook, sheets);
@@ -177,13 +178,16 @@ export function buildPackageParts(workbook, options = {}) {
177
178
  '[Content_Types].xml': strToU8(contentTypesXml(sheets.length, allTables, commentNumbers, drawingNumbers, printerSettingsNumbers, media.extensions, hasSharedStrings, preserved.parts, allPivots, preservedWorkbookRels, threadedCommentNumbers, persons.length > 0)),
178
179
  '_rels/.rels': strToU8(rootRelsXml(preserved.root)),
179
180
  'docProps/core.xml': strToU8(corePropsXml(workbook.properties)),
180
- 'docProps/app.xml': strToU8(appPropsXml()),
181
+ 'docProps/app.xml': strToU8(appPropsXml(workbook.properties)),
181
182
  'xl/workbook.xml': strToU8(workbookXml(workbook, preservedWorkbookRels, allPivots)),
182
183
  'xl/_rels/workbook.xml.rels': strToU8(workbookRelsXml(sheets.length, hasSharedStrings, personsRelId, preservedWorkbookRels, allPivots)),
183
184
  'xl/styles.xml': strToU8(styles.toXml()),
184
185
  };
185
186
  if (!preserved.themeEmitted) {
186
- files[THEME_PART_PATH] = strToU8(workbook.authoredThemeXml() ?? DEFAULT_THEME_XML);
187
+ const overrides = workbook.themeOverrides;
188
+ files[THEME_PART_PATH] = strToU8(overrides === undefined
189
+ ? DEFAULT_THEME_XML
190
+ : applyThemeOverrides(DEFAULT_THEME_XML, overrides));
187
191
  }
188
192
  if (hasSharedStrings) {
189
193
  files['xl/sharedStrings.xml'] = strToU8(sharedStrings.toXml());
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Wrap an x14 extension body in the `<ext>` envelope every worksheet- and workbook-level x14
3
3
  * extension shares: a `uri` scoping the extension to its feature (a fixed GUID from
4
- * {@link namespaces.ts}) plus the `xmlns:x14` binding the extension namespace on the element itself
4
+ * {@link namespaces.ts}) plus the `xmlns:x14` binding the extension namespace on the element itself.
5
5
  * Excel declares it per `<ext>`, never at the part root. Callers needing an `<extLst>` wrapper add it
6
6
  * around the result; the producers the worksheet serialiser gathers return this bare so several exts
7
7
  * compose under a single shared `<extLst>`.
@@ -0,0 +1,12 @@
1
+ /** Read a little-endian `uint16`. @throws {VbaParseError} if the two bytes are not both in `buf`. */
2
+ export declare function readU16(buf: Uint8Array, at: number): number;
3
+ /** Read a little-endian `uint32`. @throws {VbaParseError} if the four bytes are not all in `buf`. */
4
+ export declare function readU32(buf: Uint8Array, at: number): number;
5
+ /**
6
+ * Decode UTF-16LE code units: the encoding [MS-CFB] uses for directory-entry names and [MS-OVBA] for
7
+ * every "Unicode" name field. A trailing odd byte is dropped: these fields are length-prefixed by the
8
+ * producer and a half code unit carries nothing to decode.
9
+ */
10
+ export declare function decodeUtf16le(bytes: Uint8Array): string;
11
+ /** Join byte chunks into one buffer. */
12
+ export declare function concat(chunks: Uint8Array[]): Uint8Array;
@@ -0,0 +1,40 @@
1
+ import { VbaParseError } from './errors.js';
2
+ function truncated(at, need, length) {
3
+ return new VbaParseError(`read of ${need} bytes at offset ${at} runs past the end of a ${length}-byte buffer`);
4
+ }
5
+ export function readU16(buf, at) {
6
+ const b0 = buf[at];
7
+ const b1 = buf[at + 1];
8
+ if (b0 === undefined || b1 === undefined)
9
+ throw truncated(at, 2, buf.length);
10
+ return b0 | (b1 << 8);
11
+ }
12
+ export function readU32(buf, at) {
13
+ const b0 = buf[at];
14
+ const b1 = buf[at + 1];
15
+ const b2 = buf[at + 2];
16
+ const b3 = buf[at + 3];
17
+ if (b0 === undefined || b1 === undefined || b2 === undefined || b3 === undefined) {
18
+ throw truncated(at, 4, buf.length);
19
+ }
20
+ return (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
21
+ }
22
+ export function decodeUtf16le(bytes) {
23
+ let s = '';
24
+ for (let i = 0; i + 1 < bytes.length; i += 2) {
25
+ s += String.fromCharCode(readU16(bytes, i));
26
+ }
27
+ return s;
28
+ }
29
+ export function concat(chunks) {
30
+ let total = 0;
31
+ for (const c of chunks)
32
+ total += c.length;
33
+ const out = new Uint8Array(total);
34
+ let off = 0;
35
+ for (const c of chunks) {
36
+ out.set(c, off);
37
+ off += c.length;
38
+ }
39
+ return out;
40
+ }
@@ -14,6 +14,6 @@ export type CfbNode = CfbStream | CfbStorage;
14
14
  * by linear scan and by tree navigation.
15
15
  *
16
16
  * @throws {VbaAuthorError} if any name is empty or exceeds 31 characters, sibling names collide, or the
17
- * project is so large it would need more than 109 FAT sectors (~7 MB far beyond any real project).
17
+ * project is so large it would need more than 109 FAT sectors (~7 MB, far beyond any real project).
18
18
  */
19
19
  export declare function writeCompoundFile(root: readonly CfbNode[]): Uint8Array;
package/dist/vba/cfb.d.ts CHANGED
@@ -8,7 +8,7 @@ export declare class CompoundFile {
8
8
  readStream(name: string): Uint8Array | undefined;
9
9
  /**
10
10
  * Reconstruct the container's top-level children as the writer's node shape, recursing into every
11
- * storage so a caller can swap one stream and re-emit the whole hierarchy with {@link writeCompoundFile}.
11
+ * storage, so a caller can swap one stream and re-emit the whole hierarchy with {@link writeCompoundFile}.
12
12
  * Walks the red-black sibling tree each storage navigates (not the linear directory scan), so any part
13
13
  * a host reaches is carried through. Cycle- and bounds-guarded like every other chain walk here.
14
14
  */
package/dist/vba/cfb.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { concat, decodeUtf16le, readU16, readU32 } from './bytes.js';
1
2
  import { VbaParseError } from './errors.js';
2
3
  const MAX_REGULAR_SECTOR = 0xfffffffa;
3
4
  const NOSTREAM = 0xffffffff;
@@ -10,7 +11,6 @@ const CFB_SIGNATURE_HI = 0xe11ab1a1;
10
11
  const DIR_ENTRY_SIZE = 128;
11
12
  export class CompoundFile {
12
13
  #buf;
13
- #view;
14
14
  #sectorSize;
15
15
  #miniSectorSize;
16
16
  #miniCutoff;
@@ -21,14 +21,13 @@ export class CompoundFile {
21
21
  #miniStream;
22
22
  constructor(buf) {
23
23
  this.#buf = buf;
24
- this.#view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
25
24
  if (buf.length < 512)
26
25
  throw new VbaParseError('compound file shorter than its 512-byte header');
27
- if (this.#u32(0) !== CFB_SIGNATURE_LO || this.#u32(4) !== CFB_SIGNATURE_HI) {
26
+ if (readU32(this.#buf, 0) !== CFB_SIGNATURE_LO || readU32(this.#buf, 4) !== CFB_SIGNATURE_HI) {
28
27
  throw new VbaParseError('not a compound file (bad OLE2 signature)');
29
28
  }
30
- const sectorShift = this.#u16(30);
31
- const miniSectorShift = this.#u16(32);
29
+ const sectorShift = readU16(this.#buf, 30);
30
+ const miniSectorShift = readU16(this.#buf, 32);
32
31
  if (sectorShift !== 9 && sectorShift !== 12) {
33
32
  throw new VbaParseError(`unsupported sector shift ${sectorShift}`);
34
33
  }
@@ -37,12 +36,12 @@ export class CompoundFile {
37
36
  this.#sectorSize = 1 << sectorShift;
38
37
  this.#miniSectorSize = 1 << miniSectorShift;
39
38
  this.#maxSector = Math.floor(buf.length / this.#sectorSize);
40
- const numFatSectors = this.#u32(44);
41
- const firstDirSector = this.#u32(48);
42
- this.#miniCutoff = this.#u32(56);
43
- const firstMiniFatSector = this.#u32(60);
44
- const firstDifatSector = this.#u32(68);
45
- const numDifatSectors = this.#u32(72);
39
+ const numFatSectors = readU32(this.#buf, 44);
40
+ const firstDirSector = readU32(this.#buf, 48);
41
+ this.#miniCutoff = readU32(this.#buf, 56);
42
+ const firstMiniFatSector = readU32(this.#buf, 60);
43
+ const firstDifatSector = readU32(this.#buf, 68);
44
+ const numDifatSectors = readU32(this.#buf, 72);
46
45
  const fatSectorIds = this.#readDifat(numFatSectors, firstDifatSector, numDifatSectors);
47
46
  this.#fat = this.#readFat(fatSectorIds);
48
47
  this.#miniFat = this.#readChainValues(firstMiniFatSector);
@@ -103,7 +102,7 @@ export class CompoundFile {
103
102
  #readDifat(numFatSectors, firstDifat, numDifat) {
104
103
  const ids = [];
105
104
  for (let i = 0; i < 109 && ids.length < numFatSectors; i++) {
106
- const v = this.#u32(76 + i * 4);
105
+ const v = readU32(this.#buf, 76 + i * 4);
107
106
  if (v >= MAX_REGULAR_SECTOR)
108
107
  break;
109
108
  ids.push(v);
@@ -117,11 +116,11 @@ export class CompoundFile {
117
116
  seen.add(sector);
118
117
  const base = this.#dataSectorOffset(sector);
119
118
  for (let i = 0; i < perSector; i++) {
120
- const v = this.#u32(base + i * 4);
119
+ const v = readU32(this.#buf, base + i * 4);
121
120
  if (v < MAX_REGULAR_SECTOR)
122
121
  ids.push(v);
123
122
  }
124
- sector = this.#u32(base + perSector * 4);
123
+ sector = readU32(this.#buf, base + perSector * 4);
125
124
  }
126
125
  return ids;
127
126
  }
@@ -131,7 +130,7 @@ export class CompoundFile {
131
130
  for (const sid of fatSectorIds) {
132
131
  const base = this.#dataSectorOffset(sid);
133
132
  for (let i = 0; i < perSector; i++)
134
- fat.push(this.#u32(base + i * 4));
133
+ fat.push(readU32(this.#buf, base + i * 4));
135
134
  }
136
135
  return fat;
137
136
  }
@@ -146,7 +145,7 @@ export class CompoundFile {
146
145
  seen.add(sector);
147
146
  const base = this.#dataSectorOffset(sector);
148
147
  for (let i = 0; i < perSector; i++)
149
- values.push(this.#u32(base + i * 4));
148
+ values.push(readU32(this.#buf, base + i * 4));
150
149
  sector = this.#nextInFat(sector);
151
150
  }
152
151
  return values;
@@ -256,39 +255,4 @@ export class CompoundFile {
256
255
  }
257
256
  return base;
258
257
  }
259
- #u16(at) {
260
- return this.#view.getUint16(at, true);
261
- }
262
- #u32(at) {
263
- return this.#view.getUint32(at, true);
264
- }
265
- }
266
- function readU16(buf, at) {
267
- return buf[at] | (buf[at + 1] << 8);
268
- }
269
- function readU32(buf, at) {
270
- return ((buf[at] |
271
- (buf[at + 1] << 8) |
272
- (buf[at + 2] << 16) |
273
- (buf[at + 3] << 24)) >>>
274
- 0);
275
- }
276
- function decodeUtf16le(bytes) {
277
- let s = '';
278
- for (let i = 0; i + 1 < bytes.length; i += 2) {
279
- s += String.fromCharCode(bytes[i] | (bytes[i + 1] << 8));
280
- }
281
- return s;
282
- }
283
- function concat(chunks) {
284
- let total = 0;
285
- for (const c of chunks)
286
- total += c.length;
287
- const out = new Uint8Array(total);
288
- let off = 0;
289
- for (const c of chunks) {
290
- out.set(c, off);
291
- off += c.length;
292
- }
293
- return out;
294
258
  }
@@ -3,7 +3,7 @@ export type Decoder = InstanceType<typeof TextDecoder>;
3
3
  export type Encoder = (text: string) => Uint8Array;
4
4
  /**
5
5
  * A `TextDecoder` for the given VBA project code page. Non-fatal (malformed bytes become U+FFFD rather
6
- * than throwing) because recovered source is for reading, not re-encoding a stray byte must not sink
6
+ * than throwing) because recovered source is for reading, not re-encoding: a stray byte must not sink
7
7
  * the whole extraction. An unknown code page falls back to windows-1252.
8
8
  */
9
9
  export declare function decoderForCodePage(codePage: number): Decoder;
@@ -12,7 +12,7 @@ export declare function decoderForCodePage(codePage: number): Decoder;
12
12
  * decoding every byte 0x00..0xFF through the matching decoder and inverting the map. This is exact for
13
13
  * the single-byte code pages a VBA project realistically declares. Under a multi-byte code page (Shift
14
14
  * JIS, GBK, …) the high bytes are lead bytes, not standalone characters, so only ASCII inverts and any
15
- * non-ASCII character is rejected fail-closed rather than emit a corrupt byte. A character the chosen
15
+ * non-ASCII character is rejected, failing closed rather than emitting a corrupt byte. A character the chosen
16
16
  * code page cannot represent throws {@link VbaAuthorError}.
17
17
  */
18
18
  export declare function encoderForCodePage(codePage: number): Encoder;
@@ -1,6 +1,6 @@
1
1
  import { XlsxError } from '../errors.ts';
2
2
  /**
3
- * Thrown when a VBA project (`vbaProject.bin`) is present but cannot be parsed a malformed
3
+ * Thrown when a VBA project (`vbaProject.bin`) is present but cannot be parsed: a malformed
4
4
  * compound file, a corrupt compressed stream, or a `dir` record that does not conform to [MS-OVBA].
5
5
  * A workbook with no macros never produces this: {@link Workbook.vbaProject} is `undefined` instead.
6
6
  *
@@ -13,7 +13,7 @@ export declare class VbaParseError extends XlsxError {
13
13
  }
14
14
  /**
15
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
16
+ * produce something that cannot be encoded to a well-formed container: a stream name longer than the
17
17
  * [MS-CFB] 31-character limit, a duplicate stream name, or a project so large it would exceed the
18
18
  * writer's single-header DIFAT bound. This is a caller-side contract violation, distinct from
19
19
  * {@link VbaParseError} (which reports a malformed blob *read* from an untrusted file).
@@ -1,5 +1,5 @@
1
- export { type CfbNode, type CfbStorage, type CfbStream, writeCompoundFile, } from './cfb-writer.ts';
1
+ export { type CfbNode, type CfbStorage, type CfbStream, writeCompoundFile } from './cfb-writer.ts';
2
2
  export { VbaAuthorError, VbaParseError } from './errors.ts';
3
3
  export { compressContainer, decompressContainer } from './ms-ovba.ts';
4
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';
5
+ export { addVbaReference, removeVbaModule, type VbaLibraryReference } from './project-editor.ts';
package/dist/vba/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { writeCompoundFile, } from './cfb-writer.js';
1
+ export { writeCompoundFile } from './cfb-writer.js';
2
2
  export { VbaAuthorError, VbaParseError } from './errors.js';
3
3
  export { compressContainer, decompressContainer } from './ms-ovba.js';
4
4
  export { parseVbaProject, VBA_PROJECT_CONTENT_TYPE, VBA_PROJECT_PART_PATH, VBA_PROJECT_REL_TYPE, vbaProjectSignatureKind, } from './project.js';
5
- export { addVbaReference, removeVbaModule, } from './project-editor.js';
5
+ export { addVbaReference, removeVbaModule } from './project-editor.js';
@@ -4,7 +4,7 @@
4
4
  */
5
5
  export declare function decompressContainer(buf: Uint8Array, start?: number, maxOutput?: number): Uint8Array;
6
6
  /**
7
- * Compress `data` into an MS-OVBA CompressedContainer the inverse of {@link decompressContainer}.
7
+ * Compress `data` into an MS-OVBA CompressedContainer: the inverse of {@link decompressContainer}.
8
8
  * Every 4096-decompressed-byte window is emitted as a compressed chunk of literal and copy tokens, or
9
9
  * stored verbatim when compression would not shrink it (so the encoded chunk never exceeds the 12-bit
10
10
  * size field). The result re-expands to `data` byte-for-byte.
@@ -1,3 +1,4 @@
1
+ import { readU16 } from './bytes.js';
1
2
  import { VbaParseError } from './errors.js';
2
3
  const MAX_CHUNK_DECOMPRESSED = 4096;
3
4
  const CHUNK_SIGNATURE = 0b011 << 12;
@@ -128,6 +129,3 @@ function copyTokenHelp(decompressedSoFar) {
128
129
  const lengthMask = 0xffff >> bitCount;
129
130
  return { lengthMask, bitCount };
130
131
  }
131
- function readU16(buf, at) {
132
- return buf[at] | (buf[at + 1] << 8);
133
- }
@@ -4,7 +4,7 @@
4
4
  * stream, its MODULE record block in `dir` (decrementing `MODULES_COUNT`), and its `Module=`/`Class=` +
5
5
  * workspace lines in `PROJECT`/`PROJECTwm`.
6
6
  *
7
- * Only `procedural` and `class` modules can be removed this way removing a `document` module (e.g.
7
+ * Only `procedural` and `class` modules can be removed this way. Removing a `document` module (e.g.
8
8
  * `ThisWorkbook`) or a `designer` module (a UserForm) would leave the host referencing code that no
9
9
  * longer exists, since their names are tied to a worksheet/workbook `codeName` or a designer storage
10
10
  * this project-level primitive has no visibility into. Editing such a module's code-behind is a job for
@@ -15,14 +15,14 @@
15
15
  */
16
16
  export declare function removeVbaModule(bin: Uint8Array, name: string): Uint8Array;
17
17
  /**
18
- * A registered (COM Automation type-library) reference to add to an existing VBA project the shape of
18
+ * A registered (COM Automation type-library) reference to add to an existing VBA project: the shape of
19
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
20
+ * project) and control references (to an ActiveX control library) are out of scope. See
21
21
  * {@link addVbaReference}.
22
22
  */
23
23
  export interface VbaLibraryReference {
24
24
  /**
25
- * The reference's namespace name in the VBA editor what a qualified reference like
25
+ * The reference's namespace name in the VBA editor: what a qualified reference like
26
26
  * `Scripting.Dictionary` resolves through. Must be a valid VBA identifier, at most 31 characters, as
27
27
  * real type libraries use (e.g. `Scripting`, `Office`, `stdole`).
28
28
  */
@@ -34,12 +34,12 @@ export interface VbaLibraryReference {
34
34
  readonly displayName?: string;
35
35
  /** The type library's GUID, e.g. `{420B2830-E718-11CF-893D-00A0C9054228}` (braces optional). */
36
36
  readonly guid: string;
37
- /** The type library's major version an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMajorVersion`). */
37
+ /** The type library's major version, an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMajorVersion`). */
38
38
  readonly majorVersion: number;
39
- /** The type library's minor version an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMinorVersion`). */
39
+ /** The type library's minor version, an integer in `[0, 0xFFFF]` ([MS-OVBA] `LibidMinorVersion`). */
40
40
  readonly minorVersion: number;
41
41
  /**
42
- * The type library's LCID an integer in `[0, 0xFFFFFFFF]`. Defaults to `0` (locale-neutral), the
42
+ * The type library's LCID, an integer in `[0, 0xFFFFFFFF]`. Defaults to `0` (locale-neutral), the
43
43
  * overwhelming common case (every reference in a real project observed while building this had `0`).
44
44
  */
45
45
  readonly lcid?: number;
@@ -50,9 +50,9 @@ export interface VbaLibraryReference {
50
50
  * Add a registered (COM type-library) reference to an existing `vbaProject.bin`, returning new bytes
51
51
  * that carry every existing module, reference, and host-info record unchanged. It grows the project's
52
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
53
+ * `MODULES_COUNT` (references have no count field of their own; `MODULES_COUNT` simply marks where the
54
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
55
+ * stream carries no `Reference=` line at all: references live only in `dir` (confirmed against a genuine
56
56
  * Excel-authored project).
57
57
  *
58
58
  * @throws {VbaParseError} if `bin` is not a parseable VBA project (validated before any edit).
@@ -1,5 +1,6 @@
1
- import { CompoundFile } from './cfb.js';
1
+ import { readU16, readU32 } from './bytes.js';
2
2
  import { writeCompoundFile } from './cfb-writer.js';
3
+ import { CompoundFile } from './cfb.js';
3
4
  import { decoderForCodePage, encoderForCodePage } from './codepage.js';
4
5
  import { VbaAuthorError, VbaParseError } from './errors.js';
5
6
  import { compressContainer, decompressContainer } from './ms-ovba.js';
@@ -280,13 +281,3 @@ function replaceStreams(nodes, replacements, applied) {
280
281
  return { name: node.name, children: replaceStreams(node.children, replacements, applied) };
281
282
  });
282
283
  }
283
- function readU16(buf, at) {
284
- return buf[at] | (buf[at + 1] << 8);
285
- }
286
- function readU32(buf, at) {
287
- return ((buf[at] |
288
- (buf[at + 1] << 8) |
289
- (buf[at + 2] << 16) |
290
- (buf[at + 3] << 24)) >>>
291
- 0);
292
- }
@@ -1,9 +1,9 @@
1
- /** How a module participates in the project the classification the VBA editor shows. */
1
+ /** How a module participates in the project: the classification the VBA editor shows. */
2
2
  export type VbaModuleKind = 'procedural' | 'document' | 'class' | 'designer';
3
3
  export interface VbaModule {
4
4
  /** The module's code name as seen in the VBA editor, e.g. `ThisWorkbook`, `JsonConverter`. */
5
5
  readonly name: string;
6
- /** The CFB stream the module's bytes live in usually equal to {@link name}. */
6
+ /** The CFB stream the module's bytes live in, usually equal to {@link name}. */
7
7
  readonly streamName: string;
8
8
  /** Procedural (`.bas`), document code-behind, class module, or designer (UserForm). */
9
9
  readonly kind: VbaModuleKind;
@@ -20,17 +20,17 @@ export declare const VBA_PROJECT_REL_TYPE = "http://schemas.microsoft.com/office
20
20
  export declare const VBA_PROJECT_CONTENT_TYPE = "application/vnd.ms-office.vbaProject";
21
21
  export declare const VBA_PROJECT_PART_PATH = "xl/vbaProject.bin";
22
22
  /**
23
- * Which generation of VBA project signature a part is Office emits up to three sibling signature
23
+ * Which generation of VBA project signature a part is. Office emits up to three sibling signature
24
24
  * parts off `vbaProject.bin`'s own rels over the same project bytes ([MS-OFFMACRO2]): the original
25
25
  * `legacy` signature, the `agile` (V2) successor, and the `v3` scheme that closes a tampering hole
26
26
  * the earlier two left open (KB5000676). All three can coexist in one package.
27
27
  */
28
28
  export type VbaProjectSignatureKind = 'legacy' | 'agile' | 'v3';
29
- /** One digital signature over a workbook's VBA project its generation and its raw signature bytes. */
29
+ /** One digital signature over a workbook's VBA project: its generation and its raw signature bytes. */
30
30
  export interface VbaProjectSignature {
31
31
  readonly kind: VbaProjectSignatureKind;
32
32
  /**
33
- * The raw signature part bytes (a PKCS#7/CMS blob), passed through verbatim this library does not
33
+ * The raw signature part bytes (a PKCS#7/CMS blob), passed through verbatim. This library does not
34
34
  * parse or cryptographically verify them. Their presence means "a signature is attached," never
35
35
  * "this signature is valid."
36
36
  */
@@ -1,3 +1,4 @@
1
+ import { readU16, readU32 } from './bytes.js';
1
2
  import { CompoundFile } from './cfb.js';
2
3
  import { decoderForCodePage } from './codepage.js';
3
4
  import { VbaParseError } from './errors.js';
@@ -116,13 +117,3 @@ function readProjectStreamKinds(cfb, decoder) {
116
117
  }
117
118
  return kinds;
118
119
  }
119
- function readU16(buf, at) {
120
- return buf[at] | (buf[at + 1] << 8);
121
- }
122
- function readU32(buf, at) {
123
- return ((buf[at] |
124
- (buf[at + 1] << 8) |
125
- (buf[at + 2] << 16) |
126
- (buf[at + 3] << 24)) >>>
127
- 0);
128
- }
@@ -10,5 +10,5 @@ export declare function validateVbaName(name: string, what: 'project' | 'module'
10
10
  export declare function push(out: number[], id: number, data: number[]): void;
11
11
  export declare function u16(n: number): number[];
12
12
  export declare function u32(n: number): number[];
13
- /** A name as NUL-free UTF-16LE code units the encoding [MS-OVBA] uses for every "Unicode" name field. */
13
+ /** A name as NUL-free UTF-16LE code units: the encoding [MS-OVBA] uses for every "Unicode" name field. */
14
14
  export declare function utf16le(s: string): number[];
@@ -1,6 +1,6 @@
1
1
  import { XlsxError } from '../errors.ts';
2
2
  /**
3
- * Thrown when XML text cannot be scanned into parse events an unterminated tag, comment, CDATA
3
+ * Thrown when XML text cannot be scanned into parse events: an unterminated tag, comment, CDATA
4
4
  * section, markup declaration or processing instruction.
5
5
  *
6
6
  * This is a *typed* failure rather than the native `SyntaxError` it reads like, because the text
@@ -14,7 +14,7 @@ export interface SaxHandlers {
14
14
  * One parse event from {@link xmlEvents}. The payloads match {@link SaxHandlers} exactly: `text`
15
15
  * is already entity-decoded (or verbatim CDATA), and a `<x/>` yields one `open` with
16
16
  * `selfClosing: true` and no matching `close`. The discriminated `kind` lets a *pull* consumer
17
- * drive the parse the shape the streaming reader needs, where a push callback cannot `yield`.
17
+ * drive the parse: the shape the streaming reader needs, where a push callback cannot `yield`.
18
18
  */
19
19
  export type XmlEvent = {
20
20
  readonly kind: 'open';
@@ -30,16 +30,36 @@ export type XmlEvent = {
30
30
  };
31
31
  /**
32
32
  * Decode XML character references and the five predefined entities. An unrecognised
33
- * `&name;` is left verbatim rather than expanded there is no DTD, so there is nothing
33
+ * `&name;` is left verbatim rather than expanded: there is no DTD, so there is nothing
34
34
  * to expand it to, and refusing to invent one is what makes entity-expansion attacks
35
35
  * impossible.
36
36
  */
37
37
  export declare function decodeEntities(value: string): string;
38
+ /**
39
+ * The SpreadsheetML `_xHHHH_` escape, in the only place it may appear: a complete cell-text value.
40
+ *
41
+ * The mirror of `escapeSpreadsheetText` in `./xml.ts`, and it sits here rather than beside it for
42
+ * the same reason `decodeEntities` sits apart from `escapeText`: the write helpers carry an
43
+ * `AuthoringError` and a whole serialisation vocabulary the reader has no business importing.
44
+ *
45
+ * **One left-to-right pass, and that is load-bearing.** `005F` maps to `_` like any other code
46
+ * point, with no special case, because a single pass already gives the underscore escape its
47
+ * meaning: in `_x005F_x0041_` the match at 0 yields `_` and scanning resumes at `x0041_`, which has
48
+ * no leading underscore left to start an escape. So the value reads back as the literal seven
49
+ * characters `_x0041_` the author wrote. Decoding `_x005F_` in a pass of its own, before or after
50
+ * the rest, collapses that to `A` and loses the distinction the encoder went to trouble to keep.
51
+ * Excel agrees: it reads that cell as `_x0041_`.
52
+ *
53
+ * The decode is unconditional, not a repair of characters XML cannot carry. Excel reads
54
+ * `a_x0009_b` as a tab even though a literal tab would have been perfectly legal there, so a
55
+ * decoder that only handled the illegal range would disagree with Excel on files Excel wrote.
56
+ */
57
+ export declare function decodeSpreadsheetText(value: string): string;
38
58
  /**
39
59
  * Scan an XML document as a *pull* stream of {@link XmlEvent}s in a single O(n) pass with no
40
60
  * recursion. This is the parser's core; {@link parseXml} is a thin push adapter over it. A
41
61
  * consumer that must produce output incrementally (the streaming row reader) pulls events and
42
- * yields as it goes, holding only its own running state a push callback cannot.
62
+ * yields as it goes, holding only its own running state; a push callback cannot.
43
63
  *
44
64
  * Throws {@link XmlParseError} on malformed markup.
45
65
  */
@@ -63,12 +83,12 @@ export interface OpenElement {
63
83
  export declare function openElements(source: string, ...localNames: string[]): Generator<OpenElement>;
64
84
  /**
65
85
  * Wrap an {@link XmlEvent} stream so a self-closing `<x/>` whose local name is in `names` is
66
- * presented as an open (with `selfClosing: false`) immediately followed by a close the exact
86
+ * presented as an open (with `selfClosing: false`) immediately followed by a close: the exact
67
87
  * event shape of `<x></x>`. This lets a consumer commit such an element from its close handling
68
88
  * alone, instead of hand-coding a parallel self-closing branch: {@link xmlEvents} fires no close
69
89
  * for `<x/>`, and forgetting that branch silently drops the empty element. Names not in the set
70
- * pass through untouched, so an element whose close would wrongly act on absent content an empty
71
- * `<v/>`/`<f/>` that must not commit captured text is left as a bare self-closing open.
90
+ * pass through untouched, so an element whose close would wrongly act on absent content (an empty
91
+ * `<v/>`/`<f/>` that must not commit captured text) is left as a bare self-closing open.
72
92
  */
73
93
  export declare function closeEmptyElements(events: Iterable<XmlEvent>, names: ReadonlySet<string>): Generator<XmlEvent>;
74
94
  /** Options for {@link parseXml}. */
@@ -83,7 +103,7 @@ export interface ParseXmlOptions {
83
103
  }
84
104
  /**
85
105
  * Parse an XML document, dispatching SAX events to `handlers`. A thin push adapter over
86
- * {@link xmlEvents} one scanning core serves both the callback and the pull consumers.
106
+ * {@link xmlEvents}: one scanning core serves both the callback and the pull consumers.
87
107
  * Throws {@link XmlParseError} on malformed markup.
88
108
  */
89
109
  export declare function parseXml(source: string, handlers: SaxHandlers, options?: ParseXmlOptions): void;
@@ -92,13 +112,22 @@ export declare function localName(qualified: string): string;
92
112
  /** An OOXML boolean that is on when present with no value (`<b/>` is bold) and off only on an
93
113
  * explicit `"0"`/`"false"`; absence reads as on. */
94
114
  export declare function boolPresent(val: string | undefined): boolean;
95
- /** An OOXML boolean that is on only when explicitly `"1"`/`"true"`; anything else including
96
- * absence and a truthy-looking `"0"` is off. */
115
+ /** An OOXML boolean that is on only when explicitly `"1"`/`"true"`; anything else, including
116
+ * absence and a truthy-looking `"0"`, is off. */
97
117
  export declare function boolStrict(val: string | undefined): boolean;
98
118
  /** An optional OOXML boolean: `undefined` when the attribute is absent or carries an unrecognised
99
119
  * token, otherwise its `"1"`/`"true"` vs `"0"`/`"false"` value. Lets a caller store only the
100
120
  * attributes the source actually carried, so a re-write stays byte-clean. */
101
121
  export declare function boolTristate(val: string | undefined): boolean | undefined;
122
+ /** An OOXML integer attribute at or above `min` (default: unbounded below); `undefined` when the
123
+ * attribute is absent, blank, fractional, not a number, or below the floor. Integers past
124
+ * `Number.MAX_SAFE_INTEGER` read as `undefined` too: no index or count is usable out there, and
125
+ * arithmetic on one silently lies. */
126
+ export declare function numInteger(val: string | undefined, min?: number): number | undefined;
127
+ /** An OOXML decimal attribute at or above `min` (default: unbounded below); `undefined` when the
128
+ * attribute is absent, blank, not a number, or below the floor. Infinities are not finite numbers
129
+ * and read as `undefined`. */
130
+ export declare function numFinite(val: string | undefined, min?: number): number | undefined;
102
131
  /** Read an operand's text as a number only when it is a canonical decimal literal (optional sign,
103
132
  * digits, optional fraction). A cell reference, defined name, expression, or exotically-spelled
104
133
  * number (`1E5`, hex) keeps its verbatim text, so it is neither coerced to `NaN` and lost nor
@@ -28,6 +28,11 @@ export function decodeEntities(value) {
28
28
  return named ?? match;
29
29
  });
30
30
  }
31
+ export function decodeSpreadsheetText(value) {
32
+ if (!value.includes('_'))
33
+ return value;
34
+ return value.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(parseInt(hex, 16)));
35
+ }
31
36
  const ATTRIBUTE = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
32
37
  function parseAttributes(source) {
33
38
  const attrs = {};
@@ -198,6 +203,24 @@ export function boolTristate(val) {
198
203
  return false;
199
204
  return undefined;
200
205
  }
206
+ export function numInteger(val, min = -Number.MAX_SAFE_INTEGER) {
207
+ const n = parseAttrNumber(val);
208
+ if (n === undefined || !Number.isSafeInteger(n) || n < min)
209
+ return undefined;
210
+ return n;
211
+ }
212
+ export function numFinite(val, min = -Infinity) {
213
+ const n = parseAttrNumber(val);
214
+ if (n === undefined || n < min)
215
+ return undefined;
216
+ return n;
217
+ }
218
+ function parseAttrNumber(val) {
219
+ if (val === undefined || val.trim() === '')
220
+ return undefined;
221
+ const n = Number(val);
222
+ return Number.isFinite(n) ? n : undefined;
223
+ }
201
224
  export function coerceNumericLiteral(text) {
202
225
  const trimmed = text.trim();
203
226
  return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? Number(trimmed) : text;