@shbernal/ts-xlsx 1.3.1 → 2.1.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 (186) hide show
  1. package/README.md +20 -10
  2. package/dist/bytes.d.ts +24 -0
  3. package/dist/bytes.js +34 -0
  4. package/dist/core/address.d.ts +72 -3
  5. package/dist/core/address.js +50 -10
  6. package/dist/core/autofilter.d.ts +13 -2
  7. package/dist/core/autofilter.js +35 -10
  8. package/dist/core/cell.d.ts +17 -16
  9. package/dist/core/cell.js +3 -7
  10. package/dist/core/color-resolution.d.ts +8 -8
  11. package/dist/core/color-resolution.js +7 -8
  12. package/dist/core/column.d.ts +8 -9
  13. package/dist/core/column.js +2 -4
  14. package/dist/core/comment-thread.d.ts +32 -23
  15. package/dist/core/comment-thread.js +4 -4
  16. package/dist/core/conditional-formatting-overlay.d.ts +6 -1
  17. package/dist/core/conditional-formatting-overlay.js +11 -0
  18. package/dist/core/conditional-formatting.d.ts +48 -8
  19. package/dist/core/conditional-formatting.js +88 -0
  20. package/dist/core/data-validation-overlay.d.ts +12 -1
  21. package/dist/core/data-validation-overlay.js +21 -2
  22. package/dist/core/data-validation.d.ts +12 -5
  23. package/dist/core/data-validation.js +34 -0
  24. package/dist/core/date.d.ts +3 -3
  25. package/dist/core/formula.d.ts +6 -6
  26. package/dist/core/grid-edits.d.ts +16 -0
  27. package/dist/core/grid-edits.js +29 -11
  28. package/dist/core/grid-shift.d.ts +19 -0
  29. package/dist/core/grid-shift.js +8 -0
  30. package/dist/core/image.d.ts +39 -2
  31. package/dist/core/image.js +10 -0
  32. package/dist/core/internal.d.ts +9 -1
  33. package/dist/core/limits.d.ts +4 -4
  34. package/dist/core/merge.d.ts +17 -12
  35. package/dist/core/merge.js +36 -5
  36. package/dist/core/page-setup.d.ts +20 -6
  37. package/dist/core/page-setup.js +8 -1
  38. package/dist/core/pivot-table.d.ts +4 -4
  39. package/dist/core/pivot-table.js +29 -20
  40. package/dist/core/preserved.d.ts +3 -3
  41. package/dist/core/protection.d.ts +7 -4
  42. package/dist/core/protection.js +20 -14
  43. package/dist/core/range.d.ts +16 -14
  44. package/dist/core/range.js +5 -5
  45. package/dist/core/row-input.d.ts +9 -8
  46. package/dist/core/row-input.js +12 -10
  47. package/dist/core/row.d.ts +7 -8
  48. package/dist/core/row.js +2 -4
  49. package/dist/core/style.d.ts +41 -14
  50. package/dist/core/style.js +82 -67
  51. package/dist/core/table-style.d.ts +9 -9
  52. package/dist/core/table-style.js +1 -1
  53. package/dist/core/table.d.ts +53 -51
  54. package/dist/core/table.js +83 -41
  55. package/dist/core/theme.d.ts +16 -44
  56. package/dist/core/theme.js +6 -148
  57. package/dist/core/value.d.ts +7 -7
  58. package/dist/core/workbook-protection.d.ts +2 -2
  59. package/dist/core/workbook-styles.d.ts +26 -0
  60. package/dist/core/workbook-styles.js +48 -0
  61. package/dist/core/workbook-theme.d.ts +25 -0
  62. package/dist/core/workbook-theme.js +49 -0
  63. package/dist/core/workbook-vba.d.ts +17 -0
  64. package/dist/core/workbook-vba.js +79 -0
  65. package/dist/core/workbook.d.ts +133 -87
  66. package/dist/core/workbook.js +74 -133
  67. package/dist/core/worksheet-comments.d.ts +17 -0
  68. package/dist/core/worksheet-comments.js +70 -0
  69. package/dist/core/worksheet-model.d.ts +4 -4
  70. package/dist/core/worksheet-model.js +1 -0
  71. package/dist/core/worksheet-pictures.d.ts +29 -0
  72. package/dist/core/worksheet-pictures.js +42 -0
  73. package/dist/core/worksheet.d.ts +100 -79
  74. package/dist/core/worksheet.js +63 -142
  75. package/dist/customui/errors.d.ts +1 -1
  76. package/dist/customui/ribbon.d.ts +3 -3
  77. package/dist/customui/ribbon.js +24 -23
  78. package/dist/entries/core.d.ts +7 -7
  79. package/dist/entries/core.js +1 -1
  80. package/dist/entries/csv.d.ts +1 -1
  81. package/dist/entries/node-unavailable.d.ts +15 -0
  82. package/dist/entries/node-unavailable.js +20 -0
  83. package/dist/entries/node.d.ts +1 -0
  84. package/dist/entries/node.js +1 -0
  85. package/dist/entries/vba.d.ts +1 -1
  86. package/dist/entries/vba.js +1 -1
  87. package/dist/entries/xlsx.d.ts +1 -1
  88. package/dist/entries/xlsx.js +1 -1
  89. package/dist/errors.d.ts +12 -12
  90. package/dist/io/csv/read.js +2 -3
  91. package/dist/io/csv/write.d.ts +19 -4
  92. package/dist/io/csv/write.js +34 -4
  93. package/dist/io/opc/errors.d.ts +6 -6
  94. package/dist/io/opc/inflate.d.ts +1 -1
  95. package/dist/io/opc/inflate.js +2 -12
  96. package/dist/io/opc/namespaces.d.ts +1 -1
  97. package/dist/io/opc/read-opc.d.ts +19 -3
  98. package/dist/io/opc/read-opc.js +14 -8
  99. package/dist/io/opc/read-options.d.ts +2 -2
  100. package/dist/io/opc/rels.d.ts +11 -0
  101. package/dist/io/opc/rels.js +3 -2
  102. package/dist/io/opc/sniff-format.d.ts +2 -2
  103. package/dist/io/style/xf-style.d.ts +44 -7
  104. package/dist/io/style/xf-style.js +27 -0
  105. package/dist/io/xlsb/errors.d.ts +1 -1
  106. package/dist/io/xlsb/formula.d.ts +4 -4
  107. package/dist/io/xlsb/primitives.d.ts +8 -8
  108. package/dist/io/xlsb/ptg-functions.d.ts +1 -1
  109. package/dist/io/xlsb/read-styles.js +2 -20
  110. package/dist/io/xlsb/read.d.ts +2 -2
  111. package/dist/io/xlsx/cell-accumulator.d.ts +26 -9
  112. package/dist/io/xlsx/cell-accumulator.js +89 -46
  113. package/dist/io/xlsx/cell-value.d.ts +3 -3
  114. package/dist/io/xlsx/cell-value.js +9 -6
  115. package/dist/io/xlsx/color-xml.d.ts +1 -1
  116. package/dist/io/xlsx/color-xml.js +18 -23
  117. package/dist/io/xlsx/comments.d.ts +5 -5
  118. package/dist/io/xlsx/comments.js +17 -8
  119. package/dist/io/xlsx/conditional-formatting.d.ts +4 -4
  120. package/dist/io/xlsx/conditional-formatting.js +64 -58
  121. package/dist/io/xlsx/data-validation.d.ts +3 -3
  122. package/dist/io/xlsx/data-validation.js +35 -33
  123. package/dist/io/xlsx/errors.d.ts +3 -3
  124. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  125. package/dist/io/xlsx/hyperlinks.js +10 -18
  126. package/dist/io/xlsx/images.d.ts +1 -1
  127. package/dist/io/xlsx/images.js +25 -28
  128. package/dist/io/xlsx/namespaces.d.ts +2 -2
  129. package/dist/io/xlsx/package-plan.js +5 -2
  130. package/dist/io/xlsx/read-pivot.js +4 -13
  131. package/dist/io/xlsx/read-rows.d.ts +13 -13
  132. package/dist/io/xlsx/read-rows.js +19 -55
  133. package/dist/io/xlsx/read-shared-strings.js +7 -44
  134. package/dist/io/xlsx/read-styles.d.ts +3 -3
  135. package/dist/io/xlsx/read-styles.js +42 -66
  136. package/dist/io/xlsx/read-worksheet.js +73 -129
  137. package/dist/io/xlsx/read.d.ts +4 -6
  138. package/dist/io/xlsx/read.js +104 -142
  139. package/dist/io/xlsx/rich-runs.d.ts +41 -7
  140. package/dist/io/xlsx/rich-runs.js +94 -30
  141. package/dist/io/xlsx/rich-text.d.ts +1 -1
  142. package/dist/io/xlsx/shared-strings.d.ts +2 -2
  143. package/dist/io/xlsx/sheet-properties.d.ts +1 -1
  144. package/dist/io/xlsx/sheet-properties.js +14 -10
  145. package/dist/io/xlsx/styles.d.ts +9 -22
  146. package/dist/io/xlsx/styles.js +65 -89
  147. package/dist/io/xlsx/tables.d.ts +2 -2
  148. package/dist/io/xlsx/tables.js +20 -25
  149. package/dist/io/xlsx/theme-xml.d.ts +37 -0
  150. package/dist/io/xlsx/theme-xml.js +145 -0
  151. package/dist/io/xlsx/threaded-comments.d.ts +18 -27
  152. package/dist/io/xlsx/threaded-comments.js +26 -38
  153. package/dist/io/xlsx/workbook-xml.js +14 -8
  154. package/dist/io/xlsx/worksheet-xml.d.ts +2 -2
  155. package/dist/io/xlsx/worksheet-xml.js +40 -48
  156. package/dist/io/xlsx/write-stream.d.ts +23 -16
  157. package/dist/io/xlsx/write-stream.js +13 -14
  158. package/dist/io/xlsx/write.d.ts +8 -7
  159. package/dist/io/xlsx/write.js +6 -3
  160. package/dist/io/xlsx/x14-ext.d.ts +1 -1
  161. package/dist/sha512.d.ts +2 -0
  162. package/dist/sha512.js +146 -0
  163. package/dist/vba/bytes.d.ts +11 -0
  164. package/dist/vba/bytes.js +29 -0
  165. package/dist/vba/cfb-writer.d.ts +1 -1
  166. package/dist/vba/cfb.d.ts +1 -1
  167. package/dist/vba/cfb.js +15 -51
  168. package/dist/vba/codepage.d.ts +2 -2
  169. package/dist/vba/dir-records.d.ts +53 -0
  170. package/dist/vba/dir-records.js +28 -0
  171. package/dist/vba/errors.d.ts +2 -2
  172. package/dist/vba/index.d.ts +2 -2
  173. package/dist/vba/index.js +2 -2
  174. package/dist/vba/ms-ovba.d.ts +1 -1
  175. package/dist/vba/ms-ovba.js +1 -3
  176. package/dist/vba/project-editor.d.ts +9 -9
  177. package/dist/vba/project-editor.js +6 -44
  178. package/dist/vba/project.d.ts +5 -5
  179. package/dist/vba/project.js +3 -28
  180. package/dist/vba/vba-encoding.d.ts +1 -1
  181. package/dist/xml/errors.d.ts +1 -1
  182. package/dist/xml/xml-read.d.ts +98 -9
  183. package/dist/xml/xml-read.js +70 -0
  184. package/dist/xml/xml.d.ts +74 -10
  185. package/dist/xml/xml.js +39 -7
  186. package/package.json +59 -36
@@ -0,0 +1,53 @@
1
+ /** PROJECTCODEPAGE: the code page every MBCS name in the project decodes through. */
2
+ export declare const REC_PROJECT_CODEPAGE = 3;
3
+ /** PROJECTVERSION: the record whose `Size` under-counts its payload by the trailing VersionMinor. */
4
+ export declare const REC_PROJECT_VERSION = 9;
5
+ /** REFERENCEREGISTERED: a reference to a registered type library, by libid. */
6
+ export declare const REC_REFERENCE_REGISTERED = 13;
7
+ /** PROJECTMODULES MODULES_COUNT: how many module blocks follow, and the first record after the
8
+ * reference array, which has no count of its own. */
9
+ export declare const REC_MODULES_COUNT = 15;
10
+ /** REFERENCENAME: a reference's MBCS name. */
11
+ export declare const REC_REFERENCE_NAME = 22;
12
+ /** MODULENAME: opens a module's record block. */
13
+ export declare const REC_MODULE_NAME = 25;
14
+ /** MODULESTREAMNAME: the storage name the module's p-code lives under. */
15
+ export declare const REC_MODULE_STREAMNAME = 26;
16
+ /** MODULETYPE for a standard module. */
17
+ export declare const REC_MODULE_TYPE_PROCEDURAL = 33;
18
+ /** MODULETYPE for a document module (`ThisWorkbook`, a sheet's code-behind). */
19
+ export declare const REC_MODULE_TYPE_DOCUMENT = 34;
20
+ /** MODULEENDOFBLOCK: closes a module's record block. */
21
+ export declare const REC_MODULE_TERMINATOR = 43;
22
+ /** MODULEOFFSET: where the module's source begins inside its stream. */
23
+ export declare const REC_MODULE_OFFSET = 49;
24
+ /**
25
+ * REFERENCENAME's Unicode half. A *literal* `0x003E` marker rather than a nested record id, but it is
26
+ * laid out as its own Id+Size+data TLV, so a generic walk sees REFERENCENAME as two chained records,
27
+ * exactly like MODULENAME/MODULENAME_UNICODE. Verified against a real Excel-authored dir stream
28
+ * (2026-07-23).
29
+ */
30
+ export declare const REC_REFERENCE_NAME_UNICODE = 62;
31
+ /** One record as the walk sees it. */
32
+ export interface DirRecord {
33
+ /** The record id; one of the `REC_*` constants above, or a record this library does not consume. */
34
+ readonly id: number;
35
+ /** Offset of the record's `Id` field: where a splice that drops the record cuts from. */
36
+ readonly recordStart: number;
37
+ /** Offset of the record's payload. */
38
+ readonly dataStart: number;
39
+ /** The `Size` field, which is the payload length for every record but PROJECTVERSION. */
40
+ readonly size: number;
41
+ /** Offset of the next record, PROJECTVERSION's uncounted VersionMinor already skipped. Never
42
+ * recompute this from `dataStart + size`: that is the misalignment this module exists to prevent. */
43
+ readonly end: number;
44
+ }
45
+ /**
46
+ * Walk a decompressed `dir` stream, yielding each record in order. Consumers may stop early.
47
+ *
48
+ * @param context the phrase a truncated record's error carries, naming what the caller was doing:
49
+ * the message reaches a user who has no idea what a TLV is but does know they were removing a
50
+ * module.
51
+ * @throws {VbaParseError} when a record's payload runs past the end of the stream.
52
+ */
53
+ export declare function dirRecords(dir: Uint8Array, context: string): Generator<DirRecord>;
@@ -0,0 +1,28 @@
1
+ import { readU16, readU32 } from './bytes.js';
2
+ import { VbaParseError } from './errors.js';
3
+ export const REC_PROJECT_CODEPAGE = 0x0003;
4
+ export const REC_PROJECT_VERSION = 0x0009;
5
+ export const REC_REFERENCE_REGISTERED = 0x000d;
6
+ export const REC_MODULES_COUNT = 0x000f;
7
+ export const REC_REFERENCE_NAME = 0x0016;
8
+ export const REC_MODULE_NAME = 0x0019;
9
+ export const REC_MODULE_STREAMNAME = 0x001a;
10
+ export const REC_MODULE_TYPE_PROCEDURAL = 0x0021;
11
+ export const REC_MODULE_TYPE_DOCUMENT = 0x0022;
12
+ export const REC_MODULE_TERMINATOR = 0x002b;
13
+ export const REC_MODULE_OFFSET = 0x0031;
14
+ export const REC_REFERENCE_NAME_UNICODE = 0x003e;
15
+ export function* dirRecords(dir, context) {
16
+ let pos = 0;
17
+ while (pos + 6 <= dir.length) {
18
+ const id = readU16(dir, pos);
19
+ const size = readU32(dir, pos + 2);
20
+ const dataStart = pos + 6;
21
+ if (dataStart + size > dir.length) {
22
+ throw new VbaParseError(`dir record 0x${id.toString(16)} ${context}`);
23
+ }
24
+ const end = dataStart + size + (id === REC_PROJECT_VERSION ? 2 : 0);
25
+ yield { id, recordStart: pos, dataStart, size, end };
26
+ pos = end;
27
+ }
28
+ }
@@ -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,6 +1,8 @@
1
- import { CompoundFile } from './cfb.js';
1
+ import { readU16 } 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';
5
+ import { dirRecords, REC_MODULE_NAME, REC_MODULE_STREAMNAME, REC_MODULE_TERMINATOR, REC_MODULES_COUNT, REC_REFERENCE_NAME, REC_REFERENCE_NAME_UNICODE, REC_REFERENCE_REGISTERED, } from './dir-records.js';
4
6
  import { VbaAuthorError, VbaParseError } from './errors.js';
5
7
  import { compressContainer, decompressContainer } from './ms-ovba.js';
6
8
  import { parseVbaProject } from './project.js';
@@ -9,14 +11,6 @@ const DIR_STREAM = 'dir';
9
11
  const PROJECT_STREAM = 'PROJECT';
10
12
  const PROJECTWM_STREAM = 'PROJECTwm';
11
13
  const VBA_STORAGE = 'VBA';
12
- const REC_MODULE_NAME = 0x0019;
13
- const REC_MODULE_STREAMNAME = 0x001a;
14
- const REC_MODULE_TERMINATOR = 0x002b;
15
- const REC_PROJECT_VERSION = 0x0009;
16
- const REC_MODULES_COUNT = 0x000f;
17
- const REC_REFERENCE_NAME = 0x0016;
18
- const REC_REFERENCE_NAME_UNICODE = 0x003e;
19
- const REC_REFERENCE_REGISTERED = 0x000d;
20
14
  export function removeVbaModule(bin, name) {
21
15
  const project = parseVbaProject(bin);
22
16
  const nameKey = name.toUpperCase();
@@ -123,18 +117,7 @@ function buildReferenceDirRecords(ref, encode) {
123
117
  }
124
118
  function insertReferenceDirRecords(dir, records) {
125
119
  let insertAt = -1;
126
- let pos = 0;
127
- while (pos + 6 <= dir.length) {
128
- const recordStart = pos;
129
- const id = readU16(dir, pos);
130
- const size = readU32(dir, pos + 2);
131
- const dataStart = pos + 6;
132
- if (dataStart + size > dir.length) {
133
- throw new VbaParseError(`dir record 0x${id.toString(16)} overruns while adding a reference`);
134
- }
135
- pos = dataStart + size;
136
- if (id === REC_PROJECT_VERSION)
137
- pos += 2;
120
+ for (const { id, recordStart } of dirRecords(dir, 'overruns while adding a reference')) {
138
121
  if (id === REC_MODULES_COUNT) {
139
122
  insertAt = recordStart;
140
123
  break;
@@ -156,18 +139,7 @@ function removeModuleDirRecord(dir, streamName, codePage) {
156
139
  let removeStart = -1;
157
140
  let removeEnd = -1;
158
141
  let currentStream;
159
- let pos = 0;
160
- while (pos + 6 <= dir.length) {
161
- const recordStart = pos;
162
- const id = readU16(dir, pos);
163
- const size = readU32(dir, pos + 2);
164
- const dataStart = pos + 6;
165
- if (dataStart + size > dir.length) {
166
- throw new VbaParseError(`dir record 0x${id.toString(16)} overruns while removing a module`);
167
- }
168
- pos = dataStart + size;
169
- if (id === REC_PROJECT_VERSION)
170
- pos += 2;
142
+ for (const { id, recordStart, dataStart, size, end } of dirRecords(dir, 'overruns while removing a module')) {
171
143
  if (id === REC_MODULES_COUNT) {
172
144
  if (size < 2)
173
145
  throw new VbaParseError('PROJECTMODULES MODULES_COUNT record is malformed');
@@ -182,7 +154,7 @@ function removeModuleDirRecord(dir, streamName, codePage) {
182
154
  else if (id === REC_MODULE_TERMINATOR) {
183
155
  if (currentStream === streamName) {
184
156
  removeStart = blockStart;
185
- removeEnd = pos;
157
+ removeEnd = end;
186
158
  }
187
159
  currentStream = undefined;
188
160
  blockStart = -1;
@@ -280,13 +252,3 @@ function replaceStreams(nodes, replacements, applied) {
280
252
  return { name: node.name, children: replaceStreams(node.children, replacements, applied) };
281
253
  });
282
254
  }
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,5 +1,7 @@
1
+ import { readU16, readU32 } from './bytes.js';
1
2
  import { CompoundFile } from './cfb.js';
2
3
  import { decoderForCodePage } from './codepage.js';
4
+ import { dirRecords, REC_MODULE_NAME, REC_MODULE_OFFSET, REC_MODULE_STREAMNAME, REC_MODULE_TERMINATOR, REC_MODULE_TYPE_DOCUMENT, REC_MODULE_TYPE_PROCEDURAL, REC_PROJECT_CODEPAGE, } from './dir-records.js';
3
5
  import { VbaParseError } from './errors.js';
4
6
  import { decompressContainer } from './ms-ovba.js';
5
7
  export const VBA_PROJECT_REL_TYPE = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject';
@@ -13,14 +15,6 @@ const SIGNATURE_KIND_BY_REL_SEGMENT = {
13
15
  export function vbaProjectSignatureKind(relType) {
14
16
  return SIGNATURE_KIND_BY_REL_SEGMENT[relType.slice(relType.lastIndexOf('/') + 1)];
15
17
  }
16
- const REC_PROJECT_CODEPAGE = 0x0003;
17
- const REC_MODULE_NAME = 0x0019;
18
- const REC_MODULE_STREAMNAME = 0x001a;
19
- const REC_MODULE_TYPE_PROCEDURAL = 0x0021;
20
- const REC_MODULE_TYPE_DOCUMENT = 0x0022;
21
- const REC_MODULE_OFFSET = 0x0031;
22
- const REC_MODULE_TERMINATOR = 0x002b;
23
- const REC_PROJECT_VERSION = 0x0009;
24
18
  export function parseVbaProject(bin) {
25
19
  const cfb = new CompoundFile(bin);
26
20
  const dirCompressed = cfb.readStream('dir');
@@ -30,16 +24,7 @@ export function parseVbaProject(bin) {
30
24
  let codePage = 1252;
31
25
  const rawModules = [];
32
26
  let pending = {};
33
- let pos = 0;
34
- while (pos + 6 <= dir.length) {
35
- const id = readU16(dir, pos);
36
- const size = readU32(dir, pos + 2);
37
- const dataStart = pos + 6;
38
- if (dataStart + size > dir.length)
39
- throw new VbaParseError(`dir record 0x${id.toString(16)} overruns stream`);
40
- pos = dataStart + size;
41
- if (id === REC_PROJECT_VERSION)
42
- pos += 2;
27
+ for (const { id, dataStart, size } of dirRecords(dir, 'overruns stream')) {
43
28
  switch (id) {
44
29
  case REC_PROJECT_CODEPAGE:
45
30
  if (size >= 2)
@@ -116,13 +101,3 @@ function readProjectStreamKinds(cfb, decoder) {
116
101
  }
117
102
  return kinds;
118
103
  }
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,16 +112,85 @@ 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
105
134
  * re-spelled into a number that would not re-write byte-clean. Callers layer their own type rules
106
135
  * (a data-validation `list`/`custom` operand stays a string regardless of what it looks like). */
107
136
  export declare function coerceNumericLiteral(text: string): string | number;
137
+ /** Narrow an enumerated attribute through its guard; `undefined` when absent or not a member. */
138
+ export declare function enumToken<T extends string>(val: string | undefined, isMember: (candidate: string) => candidate is T): T | undefined;
139
+ /**
140
+ * Gathers one element's character data across the open/text/close events a SAX parse delivers it in.
141
+ *
142
+ * Nine parsers used to open-code this: latch a flag and clear a buffer on the open, append every
143
+ * chunk while latched, consume the buffer and unlatch on the close. Six spellings of one idea, and
144
+ * none of them honoured the one thing {@link SaxHandlers.onOpen} warns about. A self-closing `<x/>`
145
+ * fires no matching close, so `<t/>`, `<text/>`, `<xm:f/>` and `<totalsRowFormula/>`, all legal and
146
+ * all written by real files, latched a capture that nothing would ever close. What kept that from
147
+ * corrupting anything was the order the next open happened to reset things in, which is an accident
148
+ * rather than a property anyone chose, on a path that reads untrusted input. Taking `selfClosing`
149
+ * here makes it structural, once.
150
+ *
151
+ * The other thing the open-coded versions disagreed on is what an unrelated element opening
152
+ * mid-capture should do. Ending the capture is never what a caller wants: the text belongs to the
153
+ * element that opened it, and a nested or sibling element is not that element. So an open that is
154
+ * not for a captured name leaves an capture in progress alone, and {@link close} answers only for
155
+ * the element that started it.
156
+ *
157
+ * Decoding stays outside. A `<t>` needs `decodeSpreadsheetText` over the whole element and never
158
+ * over a chunk, an `<xm:f>` needs nothing, and a coordinate needs a number: the caller knows which.
159
+ */
160
+ export declare class TextCapture {
161
+ #private;
162
+ /** @param names the element local name, or the set of names this instance may capture. */
163
+ constructor(names: string | Iterable<string>);
164
+ /** Whether a capture is currently open. */
165
+ get capturing(): boolean;
166
+ /** Begin capturing `local` if it is one of this instance's names and is not self-closing. */
167
+ open(local: string, selfClosing: boolean): void;
168
+ /** Feed a chunk of character data; ignored when no capture is open. */
169
+ text(chunk: string): void;
170
+ /** The gathered text when `local` closes the captured element, else `undefined`. Unlatches. */
171
+ close(local: string): string | undefined;
172
+ }
173
+ /**
174
+ * Yield each named element's text as that element closes, as `{local, text}`.
175
+ *
176
+ * The third member of the pull-shaped family beside {@link openElements} ("scan opens, read
177
+ * attributes") and {@link closeEmptyElements}: this one is "capture these elements' text, tell me
178
+ * each as it closes". A parser whose whole job is reading a handful of text elements out of a part
179
+ * writes a `for..of` over it instead of a {@link parseXml} handler triple whose open and text arms
180
+ * are the same three lines every time.
181
+ *
182
+ * It is deliberately not for every {@link TextCapture} caller. A parser that interleaves capture
183
+ * with per-element state of its own (a `<dataValidation>` gathering formulae, a `<tableColumn>`
184
+ * attaching a totals formula to the column it is inside) needs the open and attribute events too,
185
+ * and stays bespoke; forcing it through here would trade a handler triple for a second pass.
186
+ *
187
+ * A self-closing `<x/>` carries no text and fires no close, so it yields nothing, which is the
188
+ * behaviour {@link TextCapture} exists to make structural rather than a branch each caller
189
+ * remembers.
190
+ *
191
+ * Throws {@link XmlParseError} on malformed markup.
192
+ */
193
+ export declare function capturedText(source: string, names: string | Iterable<string>): Generator<{
194
+ local: string;
195
+ text: string;
196
+ }>;
@@ -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,7 +203,72 @@ 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;
204
227
  }
228
+ export function enumToken(val, isMember) {
229
+ return val !== undefined && isMember(val) ? val : undefined;
230
+ }
231
+ export class TextCapture {
232
+ #names;
233
+ #capturing;
234
+ #text = '';
235
+ constructor(names) {
236
+ this.#names = new Set(typeof names === 'string' ? [names] : names);
237
+ }
238
+ get capturing() {
239
+ return this.#capturing !== undefined;
240
+ }
241
+ open(local, selfClosing) {
242
+ if (selfClosing || !this.#names.has(local))
243
+ return;
244
+ this.#capturing = local;
245
+ this.#text = '';
246
+ }
247
+ text(chunk) {
248
+ if (this.#capturing !== undefined)
249
+ this.#text += chunk;
250
+ }
251
+ close(local) {
252
+ if (this.#capturing !== local)
253
+ return undefined;
254
+ this.#capturing = undefined;
255
+ return this.#text;
256
+ }
257
+ }
258
+ export function* capturedText(source, names) {
259
+ const capture = new TextCapture(names);
260
+ for (const event of xmlEvents(source)) {
261
+ if (event.kind === 'open') {
262
+ capture.open(localName(event.name), event.selfClosing);
263
+ }
264
+ else if (event.kind === 'text') {
265
+ capture.text(event.text);
266
+ }
267
+ else {
268
+ const local = localName(event.name);
269
+ const text = capture.close(local);
270
+ if (text !== undefined)
271
+ yield { local, text };
272
+ }
273
+ }
274
+ }