@shbernal/ts-xlsx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +230 -0
- package/dist/core/address.d.ts +49 -0
- package/dist/core/address.js +125 -0
- package/dist/core/autofilter.d.ts +55 -0
- package/dist/core/autofilter.js +48 -0
- package/dist/core/cell.d.ts +132 -0
- package/dist/core/cell.js +249 -0
- package/dist/core/color-resolution.d.ts +58 -0
- package/dist/core/color-resolution.js +170 -0
- package/dist/core/column.d.ts +87 -0
- package/dist/core/column.js +167 -0
- package/dist/core/comment-thread.d.ts +116 -0
- package/dist/core/comment-thread.js +62 -0
- package/dist/core/conditional-formatting-overlay.d.ts +15 -0
- package/dist/core/conditional-formatting-overlay.js +24 -0
- package/dist/core/conditional-formatting.d.ts +70 -0
- package/dist/core/conditional-formatting.js +35 -0
- package/dist/core/containers.d.ts +2 -0
- package/dist/core/containers.js +25 -0
- package/dist/core/data-validation-overlay.d.ts +28 -0
- package/dist/core/data-validation-overlay.js +56 -0
- package/dist/core/data-validation.d.ts +38 -0
- package/dist/core/data-validation.js +13 -0
- package/dist/core/date.d.ts +25 -0
- package/dist/core/date.js +58 -0
- package/dist/core/formula.d.ts +49 -0
- package/dist/core/formula.js +319 -0
- package/dist/core/grid-edits.d.ts +22 -0
- package/dist/core/grid-edits.js +206 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +65 -0
- package/dist/core/internal.d.ts +11 -0
- package/dist/core/internal.js +29 -0
- package/dist/core/merge.d.ts +13 -0
- package/dist/core/merge.js +26 -0
- package/dist/core/modern-functions.d.ts +1 -0
- package/dist/core/modern-functions.js +165 -0
- package/dist/core/page-setup.d.ts +94 -0
- package/dist/core/page-setup.js +6 -0
- package/dist/core/pivot-table.d.ts +113 -0
- package/dist/core/pivot-table.js +249 -0
- package/dist/core/preserved.d.ts +58 -0
- package/dist/core/preserved.js +5 -0
- package/dist/core/protection.d.ts +67 -0
- package/dist/core/protection.js +68 -0
- package/dist/core/range.d.ts +112 -0
- package/dist/core/range.js +283 -0
- package/dist/core/row.d.ts +73 -0
- package/dist/core/row.js +135 -0
- package/dist/core/style.d.ts +246 -0
- package/dist/core/style.js +125 -0
- package/dist/core/table-style.d.ts +67 -0
- package/dist/core/table-style.js +90 -0
- package/dist/core/table.d.ts +194 -0
- package/dist/core/table.js +291 -0
- package/dist/core/theme.d.ts +90 -0
- package/dist/core/theme.js +272 -0
- package/dist/core/value.d.ts +115 -0
- package/dist/core/value.js +144 -0
- package/dist/core/workbook-protection.d.ts +27 -0
- package/dist/core/workbook-protection.js +25 -0
- package/dist/core/workbook.d.ts +528 -0
- package/dist/core/workbook.js +732 -0
- package/dist/core/worksheet-model.d.ts +34 -0
- package/dist/core/worksheet-model.js +100 -0
- package/dist/core/worksheet.d.ts +647 -0
- package/dist/core/worksheet.js +1025 -0
- package/dist/customui/errors.d.ts +15 -0
- package/dist/customui/errors.js +15 -0
- package/dist/customui/index.d.ts +2 -0
- package/dist/customui/index.js +7 -0
- package/dist/customui/ribbon.d.ts +86 -0
- package/dist/customui/ribbon.js +180 -0
- package/dist/entries/core.d.ts +23 -0
- package/dist/entries/core.js +20 -0
- package/dist/entries/csv.d.ts +2 -0
- package/dist/entries/csv.js +6 -0
- package/dist/entries/customui.d.ts +1 -0
- package/dist/entries/customui.js +3 -0
- package/dist/entries/errors.d.ts +7 -0
- package/dist/entries/errors.js +20 -0
- package/dist/entries/vba.d.ts +2 -0
- package/dist/entries/vba.js +7 -0
- package/dist/entries/xlsb.d.ts +1 -0
- package/dist/entries/xlsb.js +7 -0
- package/dist/entries/xlsx.d.ts +5 -0
- package/dist/entries/xlsx.js +18 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +19 -0
- package/dist/io/csv/read.d.ts +15 -0
- package/dist/io/csv/read.js +113 -0
- package/dist/io/csv/write.d.ts +28 -0
- package/dist/io/csv/write.js +124 -0
- package/dist/io/opc/errors.d.ts +46 -0
- package/dist/io/opc/errors.js +49 -0
- package/dist/io/opc/inflate.d.ts +15 -0
- package/dist/io/opc/inflate.js +91 -0
- package/dist/io/opc/namespaces.d.ts +18 -0
- package/dist/io/opc/namespaces.js +18 -0
- package/dist/io/opc/part-paths.d.ts +4 -0
- package/dist/io/opc/part-paths.js +35 -0
- package/dist/io/opc/read-opc.d.ts +24 -0
- package/dist/io/opc/read-opc.js +153 -0
- package/dist/io/opc/read-options.d.ts +10 -0
- package/dist/io/opc/read-options.js +7 -0
- package/dist/io/opc/rels.d.ts +15 -0
- package/dist/io/opc/rels.js +28 -0
- package/dist/io/opc/sniff-format.d.ts +27 -0
- package/dist/io/opc/sniff-format.js +92 -0
- package/dist/io/style/xf-style.d.ts +47 -0
- package/dist/io/style/xf-style.js +95 -0
- package/dist/io/xlsb/errors.d.ts +15 -0
- package/dist/io/xlsb/errors.js +15 -0
- package/dist/io/xlsb/formula.d.ts +48 -0
- package/dist/io/xlsb/formula.js +398 -0
- package/dist/io/xlsb/primitives.d.ts +86 -0
- package/dist/io/xlsb/primitives.js +230 -0
- package/dist/io/xlsb/ptg-functions.d.ts +16 -0
- package/dist/io/xlsb/ptg-functions.js +659 -0
- package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
- package/dist/io/xlsb/read-shared-strings.js +22 -0
- package/dist/io/xlsb/read-styles.d.ts +4 -0
- package/dist/io/xlsb/read-styles.js +386 -0
- package/dist/io/xlsb/read-worksheet.d.ts +9 -0
- package/dist/io/xlsb/read-worksheet.js +301 -0
- package/dist/io/xlsb/read.d.ts +20 -0
- package/dist/io/xlsb/read.js +199 -0
- package/dist/io/xlsb/record-stream.d.ts +14 -0
- package/dist/io/xlsb/record-stream.js +56 -0
- package/dist/io/xlsb/record-types.d.ts +55 -0
- package/dist/io/xlsb/record-types.js +73 -0
- package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
- package/dist/io/xlsx/cell-accumulator.js +172 -0
- package/dist/io/xlsx/cell-value.d.ts +32 -0
- package/dist/io/xlsx/cell-value.js +85 -0
- package/dist/io/xlsx/comments.d.ts +74 -0
- package/dist/io/xlsx/comments.js +243 -0
- package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
- package/dist/io/xlsx/conditional-formatting.js +464 -0
- package/dist/io/xlsx/data-validation.d.ts +20 -0
- package/dist/io/xlsx/data-validation.js +258 -0
- package/dist/io/xlsx/edit-vba.d.ts +23 -0
- package/dist/io/xlsx/edit-vba.js +139 -0
- package/dist/io/xlsx/errors.d.ts +17 -0
- package/dist/io/xlsx/errors.js +17 -0
- package/dist/io/xlsx/hyperlinks.d.ts +42 -0
- package/dist/io/xlsx/hyperlinks.js +139 -0
- package/dist/io/xlsx/images.d.ts +34 -0
- package/dist/io/xlsx/images.js +218 -0
- package/dist/io/xlsx/namespaces.d.ts +55 -0
- package/dist/io/xlsx/namespaces.js +55 -0
- package/dist/io/xlsx/package-plan.d.ts +103 -0
- package/dist/io/xlsx/package-plan.js +168 -0
- package/dist/io/xlsx/pivot-read.d.ts +5 -0
- package/dist/io/xlsx/pivot-read.js +134 -0
- package/dist/io/xlsx/pivot.d.ts +12 -0
- package/dist/io/xlsx/pivot.js +141 -0
- package/dist/io/xlsx/read-rows.d.ts +85 -0
- package/dist/io/xlsx/read-rows.js +286 -0
- package/dist/io/xlsx/read-styles.d.ts +8 -0
- package/dist/io/xlsx/read-styles.js +551 -0
- package/dist/io/xlsx/read-worksheet.d.ts +4 -0
- package/dist/io/xlsx/read-worksheet.js +528 -0
- package/dist/io/xlsx/read.d.ts +32 -0
- package/dist/io/xlsx/read.js +680 -0
- package/dist/io/xlsx/relationships.d.ts +33 -0
- package/dist/io/xlsx/relationships.js +47 -0
- package/dist/io/xlsx/rich-runs.d.ts +13 -0
- package/dist/io/xlsx/rich-runs.js +59 -0
- package/dist/io/xlsx/rich-text.d.ts +7 -0
- package/dist/io/xlsx/rich-text.js +23 -0
- package/dist/io/xlsx/shared-formulas.d.ts +6 -0
- package/dist/io/xlsx/shared-formulas.js +52 -0
- package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
- package/dist/io/xlsx/shared-strings-read.js +70 -0
- package/dist/io/xlsx/shared-strings.d.ts +17 -0
- package/dist/io/xlsx/shared-strings.js +50 -0
- package/dist/io/xlsx/sheet-properties.d.ts +13 -0
- package/dist/io/xlsx/sheet-properties.js +211 -0
- package/dist/io/xlsx/styles.d.ts +138 -0
- package/dist/io/xlsx/styles.js +821 -0
- package/dist/io/xlsx/tables.d.ts +9 -0
- package/dist/io/xlsx/tables.js +208 -0
- package/dist/io/xlsx/threaded-comments.d.ts +102 -0
- package/dist/io/xlsx/threaded-comments.js +308 -0
- package/dist/io/xlsx/workbook-xml.d.ts +12 -0
- package/dist/io/xlsx/workbook-xml.js +353 -0
- package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
- package/dist/io/xlsx/worksheet-xml.js +643 -0
- package/dist/io/xlsx/write-stream.d.ts +171 -0
- package/dist/io/xlsx/write-stream.js +399 -0
- package/dist/io/xlsx/write.d.ts +81 -0
- package/dist/io/xlsx/write.js +396 -0
- package/dist/io/xlsx/x14-ext.d.ts +9 -0
- package/dist/io/xlsx/x14-ext.js +12 -0
- package/dist/vba/cfb-writer.d.ts +19 -0
- package/dist/vba/cfb-writer.js +277 -0
- package/dist/vba/cfb.d.ts +16 -0
- package/dist/vba/cfb.js +324 -0
- package/dist/vba/codepage.d.ts +18 -0
- package/dist/vba/codepage.js +87 -0
- package/dist/vba/errors.d.ts +24 -0
- package/dist/vba/errors.js +24 -0
- package/dist/vba/index.d.ts +5 -0
- package/dist/vba/index.js +15 -0
- package/dist/vba/ms-ovba.d.ts +12 -0
- package/dist/vba/ms-ovba.js +176 -0
- package/dist/vba/project-editor.d.ts +62 -0
- package/dist/vba/project-editor.js +377 -0
- package/dist/vba/project.d.ts +45 -0
- package/dist/vba/project.js +160 -0
- package/dist/vba/vba-encoding.d.ts +14 -0
- package/dist/vba/vba-encoding.js +39 -0
- package/dist/xml/errors.d.ts +15 -0
- package/dist/xml/errors.js +15 -0
- package/dist/xml/xml-read.d.ts +107 -0
- package/dist/xml/xml-read.js +283 -0
- package/dist/xml/xml.d.ts +41 -0
- package/dist/xml/xml.js +86 -0
- package/package.json +117 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { XlsxError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when XML text cannot be scanned into parse events — an unterminated tag, comment, CDATA
|
|
4
|
+
* section, markup declaration or processing instruction.
|
|
5
|
+
*
|
|
6
|
+
* This is a *typed* failure rather than the native `SyntaxError` it reads like, because the text
|
|
7
|
+
* being parsed is almost always a part lifted out of an untrusted `.xlsx`. A caller wrapping
|
|
8
|
+
* `readXlsx` needs to tell "the file I was handed is corrupt" from "something in my own code threw a
|
|
9
|
+
* `SyntaxError`", and a native error gives them no way to. The message still names the construct that
|
|
10
|
+
* did not terminate; only the type changed.
|
|
11
|
+
*/
|
|
12
|
+
export class XmlParseError extends XlsxError {
|
|
13
|
+
name = 'XmlParseError';
|
|
14
|
+
code = 'malformed-input';
|
|
15
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export interface XmlAttributes {
|
|
2
|
+
readonly [name: string]: string;
|
|
3
|
+
}
|
|
4
|
+
export interface SaxHandlers {
|
|
5
|
+
/** An element start. `selfClosing` is true for `<x/>`; no matching {@link onClose} fires for it. */
|
|
6
|
+
onOpen(name: string, attrs: XmlAttributes, selfClosing: boolean): void;
|
|
7
|
+
/** A run of character data (already entity-decoded; CDATA delivered verbatim). Omit to ignore text. */
|
|
8
|
+
onText?(text: string): void;
|
|
9
|
+
/** An element end (`</x>`); the synthetic end of a self-closing element is *not* reported here.
|
|
10
|
+
* Omit to ignore closes. */
|
|
11
|
+
onClose?(name: string): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One parse event from {@link xmlEvents}. The payloads match {@link SaxHandlers} exactly: `text`
|
|
15
|
+
* is already entity-decoded (or verbatim CDATA), and a `<x/>` yields one `open` with
|
|
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`.
|
|
18
|
+
*/
|
|
19
|
+
export type XmlEvent = {
|
|
20
|
+
readonly kind: 'open';
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly attrs: XmlAttributes;
|
|
23
|
+
readonly selfClosing: boolean;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'text';
|
|
26
|
+
readonly text: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: 'close';
|
|
29
|
+
readonly name: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
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
|
|
34
|
+
* to expand it to, and refusing to invent one is what makes entity-expansion attacks
|
|
35
|
+
* impossible.
|
|
36
|
+
*/
|
|
37
|
+
export declare function decodeEntities(value: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Scan an XML document as a *pull* stream of {@link XmlEvent}s in a single O(n) pass with no
|
|
40
|
+
* recursion. This is the parser's core; {@link parseXml} is a thin push adapter over it. A
|
|
41
|
+
* 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.
|
|
43
|
+
*
|
|
44
|
+
* Throws {@link XmlParseError} on malformed markup.
|
|
45
|
+
*/
|
|
46
|
+
export declare function xmlEvents(source: string): Generator<XmlEvent>;
|
|
47
|
+
/** An element start surfaced by {@link openElements}: its qualified `name`, the namespace-stripped
|
|
48
|
+
* `local` name the filter matched on, and its already-decoded `attrs`. */
|
|
49
|
+
export interface OpenElement {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly local: string;
|
|
52
|
+
readonly attrs: XmlAttributes;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Yield each element start in `source` as an {@link OpenElement}, optionally restricted to the given
|
|
56
|
+
* local names. This is the pull shape for the ubiquitous "scan opens, read attributes" pass: a caller
|
|
57
|
+
* writes a plain `for..of` and reads `attrs` directly, instead of threading a mutable accumulator out
|
|
58
|
+
* through a {@link parseXml} `onOpen` closure. With no names every start is yielded; with one or more,
|
|
59
|
+
* only starts whose namespace-stripped name matches one. Text and close events are skipped.
|
|
60
|
+
*
|
|
61
|
+
* Throws {@link XmlParseError} on malformed markup.
|
|
62
|
+
*/
|
|
63
|
+
export declare function openElements(source: string, ...localNames: string[]): Generator<OpenElement>;
|
|
64
|
+
/**
|
|
65
|
+
* 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
|
|
67
|
+
* event shape of `<x></x>`. This lets a consumer commit such an element from its close handling
|
|
68
|
+
* alone, instead of hand-coding a parallel self-closing branch: {@link xmlEvents} fires no close
|
|
69
|
+
* 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.
|
|
72
|
+
*/
|
|
73
|
+
export declare function closeEmptyElements(events: Iterable<XmlEvent>, names: ReadonlySet<string>): Generator<XmlEvent>;
|
|
74
|
+
/** Options for {@link parseXml}. */
|
|
75
|
+
export interface ParseXmlOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Local names whose self-closing form should also fire {@link SaxHandlers.onClose}. For each name
|
|
78
|
+
* listed, `<x/>` is delivered as an open followed by a close (via {@link closeEmptyElements}), so a
|
|
79
|
+
* handler commits the element once in `onClose` rather than duplicating that logic in a self-closing
|
|
80
|
+
* branch. Only name elements the handler is safe to run on close when empty.
|
|
81
|
+
*/
|
|
82
|
+
readonly closeEmptyElements?: ReadonlySet<string>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 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.
|
|
87
|
+
* Throws {@link XmlParseError} on malformed markup.
|
|
88
|
+
*/
|
|
89
|
+
export declare function parseXml(source: string, handlers: SaxHandlers, options?: ParseXmlOptions): void;
|
|
90
|
+
/** Strip a namespace prefix from a qualified name (`r:id` → `id`, `sheet` → `sheet`). */
|
|
91
|
+
export declare function localName(qualified: string): string;
|
|
92
|
+
/** An OOXML boolean that is on when present with no value (`<b/>` is bold) and off only on an
|
|
93
|
+
* explicit `"0"`/`"false"`; absence reads as on. */
|
|
94
|
+
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. */
|
|
97
|
+
export declare function boolStrict(val: string | undefined): boolean;
|
|
98
|
+
/** An optional OOXML boolean: `undefined` when the attribute is absent or carries an unrecognised
|
|
99
|
+
* token, otherwise its `"1"`/`"true"` vs `"0"`/`"false"` value. Lets a caller store only the
|
|
100
|
+
* attributes the source actually carried, so a re-write stays byte-clean. */
|
|
101
|
+
export declare function boolTristate(val: string | undefined): boolean | undefined;
|
|
102
|
+
/** Read an operand's text as a number only when it is a canonical decimal literal (optional sign,
|
|
103
|
+
* digits, optional fraction). A cell reference, defined name, expression, or exotically-spelled
|
|
104
|
+
* number (`1E5`, hex) keeps its verbatim text, so it is neither coerced to `NaN` and lost nor
|
|
105
|
+
* re-spelled into a number that would not re-write byte-clean. Callers layer their own type rules
|
|
106
|
+
* (a data-validation `list`/`custom` operand stays a string regardless of what it looks like). */
|
|
107
|
+
export declare function coerceNumericLiteral(text: string): string | number;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// A lean, hostile-input-safe SAX pull parser for the read path.
|
|
2
|
+
//
|
|
3
|
+
// OOXML uses a small, regular subset of XML, so the reader does not need — and must not
|
|
4
|
+
// pay for — a general-purpose DOM library (see ADR 0004). This scans the source in a
|
|
5
|
+
// single O(n) pass with no recursion, emitting open/text/close events; the OOXML reader
|
|
6
|
+
// consumes them and builds only the model, so peak memory tracks real content rather
|
|
7
|
+
// than document structure.
|
|
8
|
+
//
|
|
9
|
+
// Security posture: entities are *decoded, never expanded*. Only the five predefined
|
|
10
|
+
// entities and numeric character references are recognised; DTDs and `<!ENTITY>`
|
|
11
|
+
// definitions are skipped, so entity-expansion (billion-laughs) and external-entity
|
|
12
|
+
// (XXE) attacks are structurally impossible here, not merely mitigated.
|
|
13
|
+
import { XmlParseError } from "./errors.js";
|
|
14
|
+
const PREDEFINED_ENTITIES = {
|
|
15
|
+
amp: '&',
|
|
16
|
+
lt: '<',
|
|
17
|
+
gt: '>',
|
|
18
|
+
quot: '"',
|
|
19
|
+
apos: "'",
|
|
20
|
+
};
|
|
21
|
+
const ENTITY = /&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);/g;
|
|
22
|
+
/**
|
|
23
|
+
* Decode XML character references and the five predefined entities. An unrecognised
|
|
24
|
+
* `&name;` is left verbatim rather than expanded — there is no DTD, so there is nothing
|
|
25
|
+
* to expand it to, and refusing to invent one is what makes entity-expansion attacks
|
|
26
|
+
* impossible.
|
|
27
|
+
*/
|
|
28
|
+
export function decodeEntities(value) {
|
|
29
|
+
if (!value.includes('&'))
|
|
30
|
+
return value;
|
|
31
|
+
return value.replace(ENTITY, (match, body) => {
|
|
32
|
+
if (body.charCodeAt(0) === 0x23 /* # */) {
|
|
33
|
+
const codePoint = body.charCodeAt(1) === 0x78 /* x */
|
|
34
|
+
? parseInt(body.slice(2), 16)
|
|
35
|
+
: parseInt(body.slice(1), 10);
|
|
36
|
+
if (!Number.isInteger(codePoint) || codePoint < 0 || codePoint > 0x10ffff)
|
|
37
|
+
return match;
|
|
38
|
+
try {
|
|
39
|
+
return String.fromCodePoint(codePoint);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return match;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const named = PREDEFINED_ENTITIES[body];
|
|
46
|
+
return named ?? match;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Attribute values cannot contain their own delimiter and cannot contain a literal `<`,
|
|
50
|
+
// so a delimiter-respecting scan finds a tag's end even when an attribute value holds a
|
|
51
|
+
// `>` (legal but rare). Names may carry a namespace prefix (`r:id`, `xml:space`).
|
|
52
|
+
const ATTRIBUTE = /([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
53
|
+
function parseAttributes(source) {
|
|
54
|
+
const attrs = {};
|
|
55
|
+
ATTRIBUTE.lastIndex = 0;
|
|
56
|
+
let match = ATTRIBUTE.exec(source);
|
|
57
|
+
while (match !== null) {
|
|
58
|
+
const value = match[2] ?? match[3] ?? '';
|
|
59
|
+
attrs[match[1]] = decodeEntities(value);
|
|
60
|
+
match = ATTRIBUTE.exec(source);
|
|
61
|
+
}
|
|
62
|
+
return attrs;
|
|
63
|
+
}
|
|
64
|
+
// Scan to the tag's closing `>`, honouring quoted attribute values so a `>` inside a
|
|
65
|
+
// value does not end the tag prematurely.
|
|
66
|
+
function findTagEnd(source, start) {
|
|
67
|
+
let quote = '';
|
|
68
|
+
for (let i = start + 1; i < source.length; i++) {
|
|
69
|
+
const ch = source[i];
|
|
70
|
+
if (quote !== '') {
|
|
71
|
+
if (ch === quote)
|
|
72
|
+
quote = '';
|
|
73
|
+
}
|
|
74
|
+
else if (ch === '"' || ch === "'") {
|
|
75
|
+
quote = ch;
|
|
76
|
+
}
|
|
77
|
+
else if (ch === '>') {
|
|
78
|
+
return i;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw new XmlParseError('unterminated tag: missing ">"');
|
|
82
|
+
}
|
|
83
|
+
// A `<!DOCTYPE …>` may contain a bracketed internal subset with its own `>`; balance the
|
|
84
|
+
// brackets so the declaration is skipped whole. We never act on its contents.
|
|
85
|
+
function skipDeclaration(source, start) {
|
|
86
|
+
let depth = 0;
|
|
87
|
+
for (let i = start; i < source.length; i++) {
|
|
88
|
+
const ch = source[i];
|
|
89
|
+
if (ch === '[')
|
|
90
|
+
depth++;
|
|
91
|
+
else if (ch === ']')
|
|
92
|
+
depth--;
|
|
93
|
+
else if (ch === '>' && depth <= 0)
|
|
94
|
+
return i + 1;
|
|
95
|
+
}
|
|
96
|
+
throw new XmlParseError('unterminated markup declaration: missing ">"');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Scan an XML document as a *pull* stream of {@link XmlEvent}s in a single O(n) pass with no
|
|
100
|
+
* recursion. This is the parser's core; {@link parseXml} is a thin push adapter over it. A
|
|
101
|
+
* consumer that must produce output incrementally (the streaming row reader) pulls events and
|
|
102
|
+
* yields as it goes, holding only its own running state — a push callback cannot.
|
|
103
|
+
*
|
|
104
|
+
* Throws {@link XmlParseError} on malformed markup.
|
|
105
|
+
*/
|
|
106
|
+
export function* xmlEvents(source) {
|
|
107
|
+
const length = source.length;
|
|
108
|
+
let i = 0;
|
|
109
|
+
while (i < length) {
|
|
110
|
+
const lt = source.indexOf('<', i);
|
|
111
|
+
if (lt === -1) {
|
|
112
|
+
const chunk = source.slice(i);
|
|
113
|
+
if (chunk.length > 0)
|
|
114
|
+
yield { kind: 'text', text: decodeEntities(normalizeLineEndings(chunk)) };
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (lt > i) {
|
|
118
|
+
const chunk = source.slice(i, lt);
|
|
119
|
+
if (chunk.length > 0)
|
|
120
|
+
yield { kind: 'text', text: decodeEntities(normalizeLineEndings(chunk)) };
|
|
121
|
+
}
|
|
122
|
+
if (source.startsWith('<!--', lt)) {
|
|
123
|
+
const end = source.indexOf('-->', lt + 4);
|
|
124
|
+
if (end === -1)
|
|
125
|
+
throw new XmlParseError('unterminated comment');
|
|
126
|
+
i = end + 3;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (source.startsWith('<![CDATA[', lt)) {
|
|
130
|
+
const end = source.indexOf(']]>', lt + 9);
|
|
131
|
+
if (end === -1)
|
|
132
|
+
throw new XmlParseError('unterminated CDATA section');
|
|
133
|
+
yield { kind: 'text', text: source.slice(lt + 9, end) };
|
|
134
|
+
i = end + 3;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (source.startsWith('<?', lt)) {
|
|
138
|
+
const end = source.indexOf('?>', lt + 2);
|
|
139
|
+
if (end === -1)
|
|
140
|
+
throw new XmlParseError('unterminated processing instruction');
|
|
141
|
+
i = end + 2;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (source.startsWith('<!', lt)) {
|
|
145
|
+
i = skipDeclaration(source, lt);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const gt = findTagEnd(source, lt);
|
|
149
|
+
const raw = source.slice(lt + 1, gt);
|
|
150
|
+
if (raw.charCodeAt(0) === 0x2f /* / */) {
|
|
151
|
+
yield { kind: 'close', name: raw.slice(1).trim() };
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const selfClosing = raw.charCodeAt(raw.length - 1) === 0x2f;
|
|
155
|
+
const body = selfClosing ? raw.slice(0, -1) : raw;
|
|
156
|
+
const nameEnd = firstWhitespace(body);
|
|
157
|
+
const name = nameEnd === -1 ? body : body.slice(0, nameEnd);
|
|
158
|
+
const attrs = nameEnd === -1 ? {} : parseAttributes(body.slice(nameEnd));
|
|
159
|
+
yield { kind: 'open', name, attrs, selfClosing };
|
|
160
|
+
}
|
|
161
|
+
i = gt + 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Yield each element start in `source` as an {@link OpenElement}, optionally restricted to the given
|
|
166
|
+
* local names. This is the pull shape for the ubiquitous "scan opens, read attributes" pass: a caller
|
|
167
|
+
* writes a plain `for..of` and reads `attrs` directly, instead of threading a mutable accumulator out
|
|
168
|
+
* through a {@link parseXml} `onOpen` closure. With no names every start is yielded; with one or more,
|
|
169
|
+
* only starts whose namespace-stripped name matches one. Text and close events are skipped.
|
|
170
|
+
*
|
|
171
|
+
* Throws {@link XmlParseError} on malformed markup.
|
|
172
|
+
*/
|
|
173
|
+
export function* openElements(source, ...localNames) {
|
|
174
|
+
const filter = localNames.length > 0 ? new Set(localNames) : undefined;
|
|
175
|
+
for (const event of xmlEvents(source)) {
|
|
176
|
+
if (event.kind !== 'open')
|
|
177
|
+
continue;
|
|
178
|
+
const local = localName(event.name);
|
|
179
|
+
if (filter !== undefined && !filter.has(local))
|
|
180
|
+
continue;
|
|
181
|
+
yield { name: event.name, local, attrs: event.attrs };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Wrap an {@link XmlEvent} stream so a self-closing `<x/>` whose local name is in `names` is
|
|
186
|
+
* presented as an open (with `selfClosing: false`) immediately followed by a close — the exact
|
|
187
|
+
* event shape of `<x></x>`. This lets a consumer commit such an element from its close handling
|
|
188
|
+
* alone, instead of hand-coding a parallel self-closing branch: {@link xmlEvents} fires no close
|
|
189
|
+
* for `<x/>`, and forgetting that branch silently drops the empty element. Names not in the set
|
|
190
|
+
* pass through untouched, so an element whose close would wrongly act on absent content — an empty
|
|
191
|
+
* `<v/>`/`<f/>` that must not commit captured text — is left as a bare self-closing open.
|
|
192
|
+
*/
|
|
193
|
+
export function* closeEmptyElements(events, names) {
|
|
194
|
+
for (const event of events) {
|
|
195
|
+
if (event.kind === 'open' && event.selfClosing && names.has(localName(event.name))) {
|
|
196
|
+
yield { kind: 'open', name: event.name, attrs: event.attrs, selfClosing: false };
|
|
197
|
+
yield { kind: 'close', name: event.name };
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
yield event;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Parse an XML document, dispatching SAX events to `handlers`. A thin push adapter over
|
|
206
|
+
* {@link xmlEvents} — one scanning core serves both the callback and the pull consumers.
|
|
207
|
+
* Throws {@link XmlParseError} on malformed markup.
|
|
208
|
+
*/
|
|
209
|
+
export function parseXml(source, handlers, options) {
|
|
210
|
+
const events = options?.closeEmptyElements
|
|
211
|
+
? closeEmptyElements(xmlEvents(source), options.closeEmptyElements)
|
|
212
|
+
: xmlEvents(source);
|
|
213
|
+
for (const event of events) {
|
|
214
|
+
switch (event.kind) {
|
|
215
|
+
case 'open':
|
|
216
|
+
handlers.onOpen(event.name, event.attrs, event.selfClosing);
|
|
217
|
+
break;
|
|
218
|
+
case 'text':
|
|
219
|
+
handlers.onText?.(event.text);
|
|
220
|
+
break;
|
|
221
|
+
case 'close':
|
|
222
|
+
handlers.onClose?.(event.name);
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// XML end-of-line handling (spec §2.11): a literal CRLF or lone CR in character data is
|
|
228
|
+
// normalized to a single LF, so a value's in-cell line breaks read back identically whatever
|
|
229
|
+
// newline convention the producer wrote. Normalization precedes entity decoding, so a
|
|
230
|
+
// deliberately-encoded carriage return ( ) survives it — the escape hatch for a real CR.
|
|
231
|
+
// CDATA is delivered verbatim (it bypasses this), matching the reader's CDATA contract.
|
|
232
|
+
function normalizeLineEndings(chunk) {
|
|
233
|
+
if (!chunk.includes('\r'))
|
|
234
|
+
return chunk;
|
|
235
|
+
return chunk.replace(/\r\n?/g, '\n');
|
|
236
|
+
}
|
|
237
|
+
function firstWhitespace(source) {
|
|
238
|
+
for (let i = 0; i < source.length; i++) {
|
|
239
|
+
const code = source.charCodeAt(i);
|
|
240
|
+
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d)
|
|
241
|
+
return i;
|
|
242
|
+
}
|
|
243
|
+
return -1;
|
|
244
|
+
}
|
|
245
|
+
/** Strip a namespace prefix from a qualified name (`r:id` → `id`, `sheet` → `sheet`). */
|
|
246
|
+
export function localName(qualified) {
|
|
247
|
+
const colon = qualified.indexOf(':');
|
|
248
|
+
return colon === -1 ? qualified : qualified.slice(colon + 1);
|
|
249
|
+
}
|
|
250
|
+
// OOXML spells booleans three ways, and the reader needs all three. A `<b/>`-style font flag
|
|
251
|
+
// defaults to on when present with no value, so its absence is meaningful (`boolPresent`). Most
|
|
252
|
+
// attributes are plain xsd:booleans that are off unless an explicit "1"/"true" turns them on
|
|
253
|
+
// (`boolStrict`). An optional attribute that must round-trip byte-clean has to distinguish absent
|
|
254
|
+
// from present-and-false and drop an unrecognised token rather than coerce it (`boolTristate`).
|
|
255
|
+
/** An OOXML boolean that is on when present with no value (`<b/>` is bold) and off only on an
|
|
256
|
+
* explicit `"0"`/`"false"`; absence reads as on. */
|
|
257
|
+
export function boolPresent(val) {
|
|
258
|
+
return val === undefined || (val !== '0' && val !== 'false');
|
|
259
|
+
}
|
|
260
|
+
/** An OOXML boolean that is on only when explicitly `"1"`/`"true"`; anything else — including
|
|
261
|
+
* absence and a truthy-looking `"0"` — is off. */
|
|
262
|
+
export function boolStrict(val) {
|
|
263
|
+
return val === '1' || val === 'true';
|
|
264
|
+
}
|
|
265
|
+
/** An optional OOXML boolean: `undefined` when the attribute is absent or carries an unrecognised
|
|
266
|
+
* token, otherwise its `"1"`/`"true"` vs `"0"`/`"false"` value. Lets a caller store only the
|
|
267
|
+
* attributes the source actually carried, so a re-write stays byte-clean. */
|
|
268
|
+
export function boolTristate(val) {
|
|
269
|
+
if (val === '1' || val === 'true')
|
|
270
|
+
return true;
|
|
271
|
+
if (val === '0' || val === 'false')
|
|
272
|
+
return false;
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
/** Read an operand's text as a number only when it is a canonical decimal literal (optional sign,
|
|
276
|
+
* digits, optional fraction). A cell reference, defined name, expression, or exotically-spelled
|
|
277
|
+
* number (`1E5`, hex) keeps its verbatim text, so it is neither coerced to `NaN` and lost nor
|
|
278
|
+
* re-spelled into a number that would not re-write byte-clean. Callers layer their own type rules
|
|
279
|
+
* (a data-validation `list`/`custom` operand stays a string regardless of what it looks like). */
|
|
280
|
+
export function coerceNumericLiteral(text) {
|
|
281
|
+
const trimmed = text.trim();
|
|
282
|
+
return /^-?\d+(?:\.\d+)?$/.test(trimmed) ? Number(trimmed) : text;
|
|
283
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Escape a string for use as XML element text. */
|
|
2
|
+
export declare function escapeText(value: string): string;
|
|
3
|
+
/** Escape a string for use inside a double-quoted XML attribute value. */
|
|
4
|
+
export declare function escapeAttr(value: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Whether an element's text must be wrapped with `xml:space="preserve"` to survive a
|
|
7
|
+
* round-trip. Leading/trailing whitespace is otherwise collapsed by consumers, so a
|
|
8
|
+
* string cell value that begins or ends with a space needs the marker.
|
|
9
|
+
*/
|
|
10
|
+
export declare function needsSpacePreserve(value: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* A `<t>` text element carrying an escaped string, marked `xml:space="preserve"` when its
|
|
13
|
+
* whitespace would otherwise be collapsed. Shared by every string-bearing element — a plain
|
|
14
|
+
* inline string cell, a rich-text run — so all decode identically on the way back.
|
|
15
|
+
*/
|
|
16
|
+
export declare function textElement(value: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Render a formula operand for serialisation: a number becomes its literal, a string is stripped of
|
|
19
|
+
* the single optional leading '=' an author may write (OOXML stores the expression without it, e.g.
|
|
20
|
+
* `=A1>0` on disk is `A1>0`). The result is unescaped — the caller escapes it for its target,
|
|
21
|
+
* whether that is element text or an attribute value.
|
|
22
|
+
*/
|
|
23
|
+
export declare function stripFormulaEquals(value: string | number): string;
|
|
24
|
+
/**
|
|
25
|
+
* A boolean attribute rendered with a leading space (` name="1"` / ` name="0"`), or '' when the value
|
|
26
|
+
* is undefined. OOXML booleans serialise as 1/0; emitting the explicit `="0"` lets a writer force a
|
|
27
|
+
* flag off against a consumer's default, while an unset (undefined) flag stays out of the element
|
|
28
|
+
* entirely — the two-state-plus-absent contract every flag writer here shares.
|
|
29
|
+
*/
|
|
30
|
+
export declare function boolAttr(name: string, value: boolean | undefined): string;
|
|
31
|
+
/**
|
|
32
|
+
* A numeric attribute rendered with a leading space (` name="42"`), or '' when the value is undefined
|
|
33
|
+
* — so a count an author never set stays out of the element rather than fabricating a default.
|
|
34
|
+
*/
|
|
35
|
+
export declare function attr(name: string, value: number | undefined): string;
|
|
36
|
+
/**
|
|
37
|
+
* A finite number serialises as its shortest round-trippable decimal; a non-finite one
|
|
38
|
+
* has no OOXML numeric representation, so the writer refuses it rather than emit `NaN`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function numberText(value: number): string;
|
|
41
|
+
export declare const XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
|
package/dist/xml/xml.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Minimal XML serialisation helpers for the write path.
|
|
2
|
+
//
|
|
3
|
+
// Writing OOXML needs only correct escaping and well-formed structure; parsing (the
|
|
4
|
+
// reader's concern) is a separate, later decision, so no XML library is on the write
|
|
5
|
+
// path. Escaping is the one hard, security-relevant requirement — an unescaped `<`,
|
|
6
|
+
// `&`, or `"` produces a malformed package a consumer rejects — so it lives here,
|
|
7
|
+
// audited once, rather than sprinkled through the part emitters.
|
|
8
|
+
import { AuthoringError } from "../errors.js";
|
|
9
|
+
const TEXT_ESCAPES = {
|
|
10
|
+
'&': '&',
|
|
11
|
+
'<': '<',
|
|
12
|
+
'>': '>',
|
|
13
|
+
};
|
|
14
|
+
const ATTR_ESCAPES = {
|
|
15
|
+
'&': '&',
|
|
16
|
+
'<': '<',
|
|
17
|
+
'>': '>',
|
|
18
|
+
'"': '"',
|
|
19
|
+
"'": ''',
|
|
20
|
+
'\n': ' ',
|
|
21
|
+
'\r': ' ',
|
|
22
|
+
'\t': '	',
|
|
23
|
+
};
|
|
24
|
+
/** Escape a string for use as XML element text. */
|
|
25
|
+
export function escapeText(value) {
|
|
26
|
+
return value.replace(/[&<>]/g, (ch) => TEXT_ESCAPES[ch]);
|
|
27
|
+
}
|
|
28
|
+
/** Escape a string for use inside a double-quoted XML attribute value. */
|
|
29
|
+
export function escapeAttr(value) {
|
|
30
|
+
return value.replace(/[&<>"'\n\r\t]/g, (ch) => ATTR_ESCAPES[ch]);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Whether an element's text must be wrapped with `xml:space="preserve"` to survive a
|
|
34
|
+
* round-trip. Leading/trailing whitespace is otherwise collapsed by consumers, so a
|
|
35
|
+
* string cell value that begins or ends with a space needs the marker.
|
|
36
|
+
*/
|
|
37
|
+
export function needsSpacePreserve(value) {
|
|
38
|
+
return value.length > 0 && (value !== value.trim() || /[\n\r\t]/.test(value));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A `<t>` text element carrying an escaped string, marked `xml:space="preserve"` when its
|
|
42
|
+
* whitespace would otherwise be collapsed. Shared by every string-bearing element — a plain
|
|
43
|
+
* inline string cell, a rich-text run — so all decode identically on the way back.
|
|
44
|
+
*/
|
|
45
|
+
export function textElement(value) {
|
|
46
|
+
const space = needsSpacePreserve(value) ? ' xml:space="preserve"' : '';
|
|
47
|
+
return `<t${space}>${escapeText(value)}</t>`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Render a formula operand for serialisation: a number becomes its literal, a string is stripped of
|
|
51
|
+
* the single optional leading '=' an author may write (OOXML stores the expression without it, e.g.
|
|
52
|
+
* `=A1>0` on disk is `A1>0`). The result is unescaped — the caller escapes it for its target,
|
|
53
|
+
* whether that is element text or an attribute value.
|
|
54
|
+
*/
|
|
55
|
+
export function stripFormulaEquals(value) {
|
|
56
|
+
if (typeof value === 'number')
|
|
57
|
+
return String(value);
|
|
58
|
+
return value.startsWith('=') ? value.slice(1) : value;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A boolean attribute rendered with a leading space (` name="1"` / ` name="0"`), or '' when the value
|
|
62
|
+
* is undefined. OOXML booleans serialise as 1/0; emitting the explicit `="0"` lets a writer force a
|
|
63
|
+
* flag off against a consumer's default, while an unset (undefined) flag stays out of the element
|
|
64
|
+
* entirely — the two-state-plus-absent contract every flag writer here shares.
|
|
65
|
+
*/
|
|
66
|
+
export function boolAttr(name, value) {
|
|
67
|
+
return value === undefined ? '' : ` ${name}="${value ? 1 : 0}"`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A numeric attribute rendered with a leading space (` name="42"`), or '' when the value is undefined
|
|
71
|
+
* — so a count an author never set stays out of the element rather than fabricating a default.
|
|
72
|
+
*/
|
|
73
|
+
export function attr(name, value) {
|
|
74
|
+
return value === undefined ? '' : ` ${name}="${value}"`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A finite number serialises as its shortest round-trippable decimal; a non-finite one
|
|
78
|
+
* has no OOXML numeric representation, so the writer refuses it rather than emit `NaN`.
|
|
79
|
+
*/
|
|
80
|
+
export function numberText(value) {
|
|
81
|
+
if (!Number.isFinite(value)) {
|
|
82
|
+
throw new AuthoringError(`cannot write a non-finite number (${value}) — it has no OOXML representation`);
|
|
83
|
+
}
|
|
84
|
+
return String(value);
|
|
85
|
+
}
|
|
86
|
+
export const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
|
package/package.json
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shbernal/ts-xlsx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A TypeScript-first library for reading and writing xlsx (OOXML) spreadsheets.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "shbernal",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/shbernal/ts-xlsx.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/shbernal/ts-xlsx#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/shbernal/ts-xlsx/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24"
|
|
18
|
+
},
|
|
19
|
+
"packageManager": "pnpm@11.11.0",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./core": {
|
|
27
|
+
"types": "./dist/entries/core.d.ts",
|
|
28
|
+
"default": "./dist/entries/core.js"
|
|
29
|
+
},
|
|
30
|
+
"./xlsx": {
|
|
31
|
+
"types": "./dist/entries/xlsx.d.ts",
|
|
32
|
+
"default": "./dist/entries/xlsx.js"
|
|
33
|
+
},
|
|
34
|
+
"./xlsb": {
|
|
35
|
+
"types": "./dist/entries/xlsb.d.ts",
|
|
36
|
+
"default": "./dist/entries/xlsb.js"
|
|
37
|
+
},
|
|
38
|
+
"./csv": {
|
|
39
|
+
"types": "./dist/entries/csv.d.ts",
|
|
40
|
+
"default": "./dist/entries/csv.js"
|
|
41
|
+
},
|
|
42
|
+
"./vba": {
|
|
43
|
+
"types": "./dist/entries/vba.d.ts",
|
|
44
|
+
"default": "./dist/entries/vba.js"
|
|
45
|
+
},
|
|
46
|
+
"./customui": {
|
|
47
|
+
"types": "./dist/entries/customui.d.ts",
|
|
48
|
+
"default": "./dist/entries/customui.js"
|
|
49
|
+
},
|
|
50
|
+
"./errors": {
|
|
51
|
+
"types": "./dist/entries/errors.d.ts",
|
|
52
|
+
"default": "./dist/entries/errors.js"
|
|
53
|
+
},
|
|
54
|
+
"./package.json": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
"main": "./dist/index.js",
|
|
57
|
+
"types": "./dist/index.d.ts",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"files": [
|
|
62
|
+
"dist",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"README.md"
|
|
65
|
+
],
|
|
66
|
+
"scripts": {
|
|
67
|
+
"prepare": "lefthook install",
|
|
68
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
69
|
+
"build": "pnpm run clean && tsc -p tsconfig.build.json",
|
|
70
|
+
"size": "node scripts/size-budget.ts",
|
|
71
|
+
"smoke:dist": "node scripts/smoke-dist.ts",
|
|
72
|
+
"docs": "node scripts/gen-docs.ts",
|
|
73
|
+
"docs:check": "node scripts/gen-docs.ts && git add --intent-to-add -- docs/api && git diff --exit-code -- docs/api",
|
|
74
|
+
"constitution:check": "node scripts/check-constitution.ts",
|
|
75
|
+
"layering:check": "node scripts/check-layering.ts",
|
|
76
|
+
"entries:check": "node scripts/check-entries.ts",
|
|
77
|
+
"lint": "biome check --error-on-warnings src scripts test tools",
|
|
78
|
+
"lint:fix": "biome check --write --error-on-warnings src scripts test tools",
|
|
79
|
+
"format": "biome format --write src scripts test tools",
|
|
80
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
81
|
+
"typecheck:test": "tsc --noEmit -p tsconfig.test.json",
|
|
82
|
+
"test:src": "node --test \"src/**/*.test.ts\"",
|
|
83
|
+
"test:coverage": "node --test --experimental-test-coverage \"src/**/*.test.ts\"",
|
|
84
|
+
"corpus": "node test/corpus/run.ts",
|
|
85
|
+
"corpus:dist": "node test/corpus/run.ts --target dist",
|
|
86
|
+
"verify": "node scripts/verify.ts --full",
|
|
87
|
+
"verify:quick": "node scripts/verify.ts --quick",
|
|
88
|
+
"test": "node scripts/verify.ts --full",
|
|
89
|
+
"prepublishOnly": "pnpm run build && pnpm run test && pnpm run smoke:dist && pnpm run size",
|
|
90
|
+
"validate:ooxml": "node scripts/ooxml-validator.ts",
|
|
91
|
+
"test:ooxml": "node test/ooxml-validation/run.ts",
|
|
92
|
+
"harvest": "node scripts/harvest/fetch-issue.ts",
|
|
93
|
+
"harvest:list": "node scripts/harvest/list-backlog.ts",
|
|
94
|
+
"harvest:all": "node scripts/harvest/harvest-all.ts",
|
|
95
|
+
"harvest:status": "node scripts/harvest/status.ts"
|
|
96
|
+
},
|
|
97
|
+
"keywords": [
|
|
98
|
+
"xlsx",
|
|
99
|
+
"ooxml",
|
|
100
|
+
"excel",
|
|
101
|
+
"spreadsheet",
|
|
102
|
+
"csv",
|
|
103
|
+
"typescript",
|
|
104
|
+
"workbook",
|
|
105
|
+
"streaming"
|
|
106
|
+
],
|
|
107
|
+
"dependencies": {
|
|
108
|
+
"fflate": "^0.8.3"
|
|
109
|
+
},
|
|
110
|
+
"devDependencies": {
|
|
111
|
+
"@biomejs/biome": "2.5.4",
|
|
112
|
+
"@types/node": "^24.13.3",
|
|
113
|
+
"jszip": "^3.10.1",
|
|
114
|
+
"lefthook": "^2.1.10",
|
|
115
|
+
"typescript": "^6.0.3"
|
|
116
|
+
}
|
|
117
|
+
}
|