@shbernal/ts-xlsx 2.1.0 → 3.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 (100) hide show
  1. package/dist/core/autofilter.d.ts +1 -1
  2. package/dist/core/autofilter.js +3 -5
  3. package/dist/core/axis-handle.d.ts +8 -0
  4. package/dist/core/axis-handle.js +14 -0
  5. package/dist/core/cell.d.ts +24 -0
  6. package/dist/core/cell.js +9 -1
  7. package/dist/core/column.d.ts +4 -1
  8. package/dist/core/column.js +31 -35
  9. package/dist/core/conditional-formatting.d.ts +5 -5
  10. package/dist/core/conditional-formatting.js +11 -25
  11. package/dist/core/data-validation.d.ts +3 -3
  12. package/dist/core/data-validation.js +7 -15
  13. package/dist/core/image.d.ts +1 -1
  14. package/dist/core/image.js +2 -4
  15. package/dist/core/merge-index.d.ts +31 -0
  16. package/dist/core/merge-index.js +60 -0
  17. package/dist/core/merge.d.ts +0 -10
  18. package/dist/core/merge.js +0 -8
  19. package/dist/core/page-setup.d.ts +72 -2
  20. package/dist/core/page-setup.js +34 -8
  21. package/dist/core/pivot-table.d.ts +1 -1
  22. package/dist/core/pivot-table.js +4 -8
  23. package/dist/core/row.d.ts +4 -1
  24. package/dist/core/row.js +19 -23
  25. package/dist/core/style.d.ts +51 -7
  26. package/dist/core/style.js +29 -34
  27. package/dist/core/table.d.ts +1 -1
  28. package/dist/core/table.js +3 -5
  29. package/dist/core/used-extent.d.ts +44 -0
  30. package/dist/core/used-extent.js +102 -0
  31. package/dist/core/worksheet.d.ts +17 -6
  32. package/dist/core/worksheet.js +48 -42
  33. package/dist/customui/ribbon.js +4 -6
  34. package/dist/io/opc/inflate.js +1 -1
  35. package/dist/io/opc/part-paths.d.ts +1 -0
  36. package/dist/io/opc/part-paths.js +15 -0
  37. package/dist/io/opc/read-opc.d.ts +22 -3
  38. package/dist/io/opc/read-opc.js +11 -39
  39. package/dist/io/opc/rels.d.ts +1 -6
  40. package/dist/io/opc/rels.js +1 -4
  41. package/dist/io/style/xf-style.d.ts +26 -2
  42. package/dist/io/style/xf-style.js +6 -0
  43. package/dist/io/xlsb/read-styles.js +6 -3
  44. package/dist/io/xlsb/read.d.ts +1 -1
  45. package/dist/io/xlsb/read.js +5 -8
  46. package/dist/io/xlsx/cell-accumulator.d.ts +1 -1
  47. package/dist/io/xlsx/cell-accumulator.js +1 -1
  48. package/dist/io/xlsx/cell-value.js +1 -1
  49. package/dist/io/xlsx/color-xml.js +1 -1
  50. package/dist/io/xlsx/comments.js +2 -1
  51. package/dist/io/xlsx/conditional-formatting.d.ts +3 -2
  52. package/dist/io/xlsx/conditional-formatting.js +22 -21
  53. package/dist/io/xlsx/data-validation.d.ts +9 -7
  54. package/dist/io/xlsx/data-validation.js +10 -9
  55. package/dist/io/xlsx/edit-vba.js +4 -11
  56. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  57. package/dist/io/xlsx/hyperlinks.js +20 -17
  58. package/dist/io/xlsx/images.js +15 -14
  59. package/dist/io/xlsx/package-plan.d.ts +9 -1
  60. package/dist/io/xlsx/package-plan.js +29 -16
  61. package/dist/io/xlsx/part-names.d.ts +41 -0
  62. package/dist/io/xlsx/part-names.js +46 -0
  63. package/dist/io/xlsx/read-pivot.js +2 -1
  64. package/dist/io/xlsx/read-rows.js +12 -13
  65. package/dist/io/xlsx/read-shared-strings.js +2 -1
  66. package/dist/io/xlsx/read-styles.d.ts +1 -1
  67. package/dist/io/xlsx/read-styles.js +63 -42
  68. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  69. package/dist/io/xlsx/read-worksheet.js +43 -56
  70. package/dist/io/xlsx/read.d.ts +2 -3
  71. package/dist/io/xlsx/read.js +81 -73
  72. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  73. package/dist/io/xlsx/rich-runs.js +2 -1
  74. package/dist/io/xlsx/sheet-properties.js +21 -28
  75. package/dist/io/xlsx/styles.js +23 -20
  76. package/dist/io/xlsx/tables.js +2 -1
  77. package/dist/io/xlsx/theme-xml.js +1 -1
  78. package/dist/io/xlsx/threaded-comments.js +2 -1
  79. package/dist/io/xlsx/workbook-xml.js +29 -28
  80. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  81. package/dist/io/xlsx/worksheet-xml.js +14 -16
  82. package/dist/io/xlsx/write-stream.d.ts +6 -0
  83. package/dist/io/xlsx/write-stream.js +30 -22
  84. package/dist/io/xlsx/write.d.ts +5 -0
  85. package/dist/io/xlsx/write.js +155 -131
  86. package/dist/token-set.d.ts +15 -0
  87. package/dist/token-set.js +4 -0
  88. package/dist/vba/cfb-format.d.ts +24 -0
  89. package/dist/vba/cfb-format.js +12 -0
  90. package/dist/vba/cfb-writer.js +17 -17
  91. package/dist/vba/cfb.js +1 -7
  92. package/dist/vba/ms-ovba.js +32 -11
  93. package/dist/vba/project.js +13 -13
  94. package/dist/vba/vba-encoding.js +1 -1
  95. package/dist/xml/xml-read.d.ts +59 -74
  96. package/dist/xml/xml-read.js +83 -176
  97. package/dist/xml/xml-scan.d.ts +100 -0
  98. package/dist/xml/xml-scan.js +208 -0
  99. package/dist/xml/xml.d.ts +1 -1
  100. package/package.json +2 -3
@@ -0,0 +1,100 @@
1
+ export interface XmlAttributes {
2
+ readonly [name: string]: string;
3
+ }
4
+ export type XmlEvent = {
5
+ readonly kind: 'open';
6
+ readonly name: string;
7
+ readonly attrs: XmlAttributes;
8
+ readonly selfClosing: boolean;
9
+ } | {
10
+ readonly kind: 'text';
11
+ readonly text: string;
12
+ } | {
13
+ readonly kind: 'close';
14
+ readonly name: string;
15
+ };
16
+ /**
17
+ * Decode XML character references and the five predefined entities. An unrecognised
18
+ * `&name;` is left verbatim rather than expanded: there is no DTD, so there is nothing
19
+ * to expand it to, and refusing to invent one is what makes entity-expansion attacks
20
+ * impossible.
21
+ */
22
+ export declare function decodeEntities(value: string): string;
23
+ /**
24
+ * The SpreadsheetML `_xHHHH_` escape, in the only place it may appear: a complete cell-text value.
25
+ *
26
+ * The mirror of `escapeSpreadsheetText` in `./xml.ts`, and it sits here rather than beside it for
27
+ * the same reason `decodeEntities` sits apart from `escapeText`: the write helpers carry an
28
+ * `AuthoringError` and a whole serialisation vocabulary the reader has no business importing.
29
+ *
30
+ * **One left-to-right pass, and that is load-bearing.** `005F` maps to `_` like any other code
31
+ * point, with no special case, because a single pass already gives the underscore escape its
32
+ * meaning: in `_x005F_x0041_` the match at 0 yields `_` and scanning resumes at `x0041_`, which has
33
+ * no leading underscore left to start an escape. So the value reads back as the literal seven
34
+ * characters `_x0041_` the author wrote. Decoding `_x005F_` in a pass of its own, before or after
35
+ * the rest, collapses that to `A` and loses the distinction the encoder went to trouble to keep.
36
+ * Excel agrees: it reads that cell as `_x0041_`.
37
+ *
38
+ * The decode is unconditional, not a repair of characters XML cannot carry. Excel reads
39
+ * `a_x0009_b` as a tab even though a literal tab would have been perfectly legal there, so a
40
+ * decoder that only handled the illegal range would disagree with Excel on files Excel wrote.
41
+ */
42
+ export declare function decodeSpreadsheetText(value: string): string;
43
+ export declare function parseAttributes(source: string): XmlAttributes;
44
+ export type Markup = {
45
+ readonly kind: 'comment' | 'pi' | 'declaration';
46
+ readonly next: number;
47
+ } | {
48
+ readonly kind: 'cdata';
49
+ readonly contentStart: number;
50
+ readonly contentEnd: number;
51
+ readonly next: number;
52
+ };
53
+ export declare function markupAt(source: string, lt: number): Markup | undefined;
54
+ export interface Tag {
55
+ readonly close: boolean;
56
+ readonly name: string;
57
+ readonly attrSource: string;
58
+ readonly selfClosing: boolean;
59
+ /** One past the tag's `>`, so a caller capturing verbatim source can slice up to it. */
60
+ readonly next: number;
61
+ }
62
+ export declare function tagAt(source: string, lt: number): Tag;
63
+ /**
64
+ * Scan an XML document as a *pull* stream of {@link XmlEvent}s in a single O(n) pass with no
65
+ * recursion. This is the parser's core; {@link parseXml} is a thin push adapter over it. A
66
+ * consumer that must produce output incrementally (the streaming row reader) pulls events and
67
+ * yields as it goes, holding only its own running state; a push callback cannot.
68
+ *
69
+ * Throws {@link XmlParseError} on malformed markup.
70
+ */
71
+ export declare function xmlEvents(source: string): Generator<XmlEvent>;
72
+ /** Strip a namespace prefix from a qualified name (`r:id` → `id`, `sheet` → `sheet`). */
73
+ export declare function localName(qualified: string): string;
74
+ /** An OOXML boolean that is on when present with no value (`<b/>` is bold) and off only on an
75
+ * explicit `"0"`/`"false"`; absence reads as on. */
76
+ export declare function boolPresent(val: string | undefined): boolean;
77
+ /** An OOXML boolean that is on only when explicitly `"1"`/`"true"`; anything else, including
78
+ * absence and a truthy-looking `"0"`, is off. */
79
+ export declare function boolStrict(val: string | undefined): boolean;
80
+ /** An optional OOXML boolean: `undefined` when the attribute is absent or carries an unrecognised
81
+ * token, otherwise its `"1"`/`"true"` vs `"0"`/`"false"` value. Lets a caller store only the
82
+ * attributes the source actually carried, so a re-write stays byte-clean. */
83
+ export declare function boolTristate(val: string | undefined): boolean | undefined;
84
+ /** An OOXML integer attribute at or above `min` (default: unbounded below); `undefined` when the
85
+ * attribute is absent, blank, fractional, not a number, or below the floor. Integers past
86
+ * `Number.MAX_SAFE_INTEGER` read as `undefined` too: no index or count is usable out there, and
87
+ * arithmetic on one silently lies. */
88
+ export declare function numInteger(val: string | undefined, min?: number): number | undefined;
89
+ /** An OOXML decimal attribute at or above `min` (default: unbounded below); `undefined` when the
90
+ * attribute is absent, blank, not a number, or below the floor. Infinities are not finite numbers
91
+ * and read as `undefined`. */
92
+ export declare function numFinite(val: string | undefined, min?: number): number | undefined;
93
+ /** Read an operand's text as a number only when it is a canonical decimal literal (optional sign,
94
+ * digits, optional fraction). A cell reference, defined name, expression, or exotically-spelled
95
+ * number (`1E5`, hex) keeps its verbatim text, so it is neither coerced to `NaN` and lost nor
96
+ * re-spelled into a number that would not re-write byte-clean. Callers layer their own type rules
97
+ * (a data-validation `list`/`custom` operand stays a string regardless of what it looks like). */
98
+ export declare function coerceNumericLiteral(text: string): string | number;
99
+ /** Narrow an enumerated attribute through its guard; `undefined` when absent or not a member. */
100
+ export declare function enumToken<T extends string>(val: string | undefined, isMember: (candidate: string) => candidate is T): T | undefined;
@@ -0,0 +1,208 @@
1
+ import { XmlParseError } from './errors.js';
2
+ const PREDEFINED_ENTITIES = new Map([
3
+ ['amp', '&'],
4
+ ['lt', '<'],
5
+ ['gt', '>'],
6
+ ['quot', '"'],
7
+ ['apos', "'"],
8
+ ]);
9
+ const ENTITY = /&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);/g;
10
+ export function decodeEntities(value) {
11
+ if (!value.includes('&'))
12
+ return value;
13
+ return value.replace(ENTITY, (match, body) => {
14
+ if (body.charCodeAt(0) === 0x23) {
15
+ const codePoint = body.charCodeAt(1) === 0x78
16
+ ? Number.parseInt(body.slice(2), 16)
17
+ : Number.parseInt(body.slice(1), 10);
18
+ if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff)
19
+ return match;
20
+ try {
21
+ return String.fromCodePoint(codePoint);
22
+ }
23
+ catch {
24
+ return match;
25
+ }
26
+ }
27
+ return PREDEFINED_ENTITIES.get(body) ?? match;
28
+ });
29
+ }
30
+ export function decodeSpreadsheetText(value) {
31
+ if (!value.includes('_'))
32
+ return value;
33
+ return value.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(Number.parseInt(hex, 16)));
34
+ }
35
+ const ATTRIBUTE = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
36
+ export function parseAttributes(source) {
37
+ const attrs = Object.create(null);
38
+ ATTRIBUTE.lastIndex = 0;
39
+ let match = ATTRIBUTE.exec(source);
40
+ while (match !== null) {
41
+ const value = match[2] ?? match[3] ?? '';
42
+ attrs[match[1]] = decodeEntities(value);
43
+ match = ATTRIBUTE.exec(source);
44
+ }
45
+ return attrs;
46
+ }
47
+ function findTagEnd(source, start) {
48
+ let quote = '';
49
+ for (let i = start + 1; i < source.length; i++) {
50
+ const ch = source[i];
51
+ if (quote !== '') {
52
+ if (ch === quote)
53
+ quote = '';
54
+ }
55
+ else if (ch === '"' || ch === "'") {
56
+ quote = ch;
57
+ }
58
+ else if (ch === '>') {
59
+ return i;
60
+ }
61
+ }
62
+ throw new XmlParseError('unterminated tag: missing ">"');
63
+ }
64
+ function skipDeclaration(source, start) {
65
+ let depth = 0;
66
+ for (let i = start; i < source.length; i++) {
67
+ const ch = source[i];
68
+ if (ch === '[')
69
+ depth++;
70
+ else if (ch === ']')
71
+ depth--;
72
+ else if (ch === '>' && depth <= 0)
73
+ return i + 1;
74
+ }
75
+ throw new XmlParseError('unterminated markup declaration: missing ">"');
76
+ }
77
+ export function markupAt(source, lt) {
78
+ if (source.startsWith('<!--', lt)) {
79
+ const end = source.indexOf('-->', lt + 4);
80
+ if (end === -1)
81
+ throw new XmlParseError('unterminated comment');
82
+ return { kind: 'comment', next: end + 3 };
83
+ }
84
+ if (source.startsWith('<![CDATA[', lt)) {
85
+ const end = source.indexOf(']]>', lt + 9);
86
+ if (end === -1)
87
+ throw new XmlParseError('unterminated CDATA section');
88
+ return { kind: 'cdata', contentStart: lt + 9, contentEnd: end, next: end + 3 };
89
+ }
90
+ if (source.startsWith('<?', lt)) {
91
+ const end = source.indexOf('?>', lt + 2);
92
+ if (end === -1)
93
+ throw new XmlParseError('unterminated processing instruction');
94
+ return { kind: 'pi', next: end + 2 };
95
+ }
96
+ if (source.startsWith('<!', lt))
97
+ return { kind: 'declaration', next: skipDeclaration(source, lt) };
98
+ return undefined;
99
+ }
100
+ export function tagAt(source, lt) {
101
+ const gt = findTagEnd(source, lt);
102
+ const raw = source.slice(lt + 1, gt);
103
+ const next = gt + 1;
104
+ if (raw.charCodeAt(0) === 0x2f) {
105
+ return { close: true, name: raw.slice(1).trim(), attrSource: '', selfClosing: false, next };
106
+ }
107
+ const selfClosing = raw.charCodeAt(raw.length - 1) === 0x2f;
108
+ const body = selfClosing ? raw.slice(0, -1) : raw;
109
+ const nameEnd = firstWhitespace(body);
110
+ return {
111
+ close: false,
112
+ name: nameEnd === -1 ? body : body.slice(0, nameEnd),
113
+ attrSource: nameEnd === -1 ? '' : body.slice(nameEnd),
114
+ selfClosing,
115
+ next,
116
+ };
117
+ }
118
+ export function* xmlEvents(source) {
119
+ const length = source.length;
120
+ let i = 0;
121
+ while (i < length) {
122
+ const lt = source.indexOf('<', i);
123
+ if (lt === -1) {
124
+ const chunk = source.slice(i);
125
+ if (chunk.length > 0)
126
+ yield { kind: 'text', text: decodeEntities(normalizeLineEndings(chunk)) };
127
+ return;
128
+ }
129
+ if (lt > i) {
130
+ const chunk = source.slice(i, lt);
131
+ if (chunk.length > 0)
132
+ yield { kind: 'text', text: decodeEntities(normalizeLineEndings(chunk)) };
133
+ }
134
+ const markup = markupAt(source, lt);
135
+ if (markup !== undefined) {
136
+ if (markup.kind === 'cdata') {
137
+ yield { kind: 'text', text: source.slice(markup.contentStart, markup.contentEnd) };
138
+ }
139
+ i = markup.next;
140
+ continue;
141
+ }
142
+ const tag = tagAt(source, lt);
143
+ yield tag.close
144
+ ? { kind: 'close', name: tag.name }
145
+ : {
146
+ kind: 'open',
147
+ name: tag.name,
148
+ attrs: parseAttributes(tag.attrSource),
149
+ selfClosing: tag.selfClosing,
150
+ };
151
+ i = tag.next;
152
+ }
153
+ }
154
+ function normalizeLineEndings(chunk) {
155
+ if (!chunk.includes('\r'))
156
+ return chunk;
157
+ return chunk.replace(/\r\n?/g, '\n');
158
+ }
159
+ function firstWhitespace(source) {
160
+ for (let i = 0; i < source.length; i++) {
161
+ const code = source.charCodeAt(i);
162
+ if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d)
163
+ return i;
164
+ }
165
+ return -1;
166
+ }
167
+ export function localName(qualified) {
168
+ const colon = qualified.indexOf(':');
169
+ return colon === -1 ? qualified : qualified.slice(colon + 1);
170
+ }
171
+ export function boolPresent(val) {
172
+ return val === undefined || (val !== '0' && val !== 'false');
173
+ }
174
+ export function boolStrict(val) {
175
+ return val === '1' || val === 'true';
176
+ }
177
+ export function boolTristate(val) {
178
+ if (val === '1' || val === 'true')
179
+ return true;
180
+ if (val === '0' || val === 'false')
181
+ return false;
182
+ return undefined;
183
+ }
184
+ export function numInteger(val, min = -Number.MAX_SAFE_INTEGER) {
185
+ const n = parseAttrNumber(val);
186
+ if (n === undefined || !Number.isSafeInteger(n) || n < min)
187
+ return undefined;
188
+ return n;
189
+ }
190
+ export function numFinite(val, min = -Infinity) {
191
+ const n = parseAttrNumber(val);
192
+ if (n === undefined || n < min)
193
+ return undefined;
194
+ return n;
195
+ }
196
+ function parseAttrNumber(val) {
197
+ if (val === undefined || val.trim() === '')
198
+ return undefined;
199
+ const n = Number(val);
200
+ return Number.isFinite(n) ? n : undefined;
201
+ }
202
+ export function coerceNumericLiteral(text) {
203
+ const trimmed = text.trim();
204
+ return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? Number(trimmed) : text;
205
+ }
206
+ export function enumToken(val, isMember) {
207
+ return val !== undefined && isMember(val) ? val : undefined;
208
+ }
package/dist/xml/xml.d.ts CHANGED
@@ -25,7 +25,7 @@ export declare function escapeAttr(value: string): string;
25
25
  * `escapeText` runs last and sees no unrepresentable character left, so its guard is a
26
26
  * standing proof that the escape was complete rather than a second check of the same thing.
27
27
  *
28
- * Reversed by `decodeSpreadsheetText` in `./xml-read.ts`, whose single left-to-right pass is what
28
+ * Reversed by `decodeSpreadsheetText` in `./xml-scan.ts`, whose single left-to-right pass is what
29
29
  * makes the `_x005F_` step above reversible. Change either and read its comment first.
30
30
  */
31
31
  export declare function escapeSpreadsheetText(value: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shbernal/ts-xlsx",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "A TypeScript-first library for reading and writing xlsx (OOXML) spreadsheets.",
5
5
  "keywords": [
6
6
  "csv",
@@ -77,8 +77,7 @@
77
77
  },
78
78
  "scripts": {
79
79
  "prepare": "node scripts/install-hooks.ts",
80
- "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
81
- "build": "pnpm run clean && tsc -p tsconfig.build.json && tsc -p tsconfig.build.dts.json",
80
+ "build": "node scripts/build.ts",
82
81
  "size": "node scripts/size-budget.ts",
83
82
  "smoke:dist": "node scripts/smoke-dist.ts",
84
83
  "site:prepare": "node www/scripts/sync-docs.ts",