@shbernal/ts-xlsx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +230 -0
  3. package/dist/core/address.d.ts +49 -0
  4. package/dist/core/address.js +125 -0
  5. package/dist/core/autofilter.d.ts +55 -0
  6. package/dist/core/autofilter.js +48 -0
  7. package/dist/core/cell.d.ts +132 -0
  8. package/dist/core/cell.js +249 -0
  9. package/dist/core/color-resolution.d.ts +58 -0
  10. package/dist/core/color-resolution.js +170 -0
  11. package/dist/core/column.d.ts +87 -0
  12. package/dist/core/column.js +167 -0
  13. package/dist/core/comment-thread.d.ts +116 -0
  14. package/dist/core/comment-thread.js +62 -0
  15. package/dist/core/conditional-formatting-overlay.d.ts +15 -0
  16. package/dist/core/conditional-formatting-overlay.js +24 -0
  17. package/dist/core/conditional-formatting.d.ts +70 -0
  18. package/dist/core/conditional-formatting.js +35 -0
  19. package/dist/core/containers.d.ts +2 -0
  20. package/dist/core/containers.js +25 -0
  21. package/dist/core/data-validation-overlay.d.ts +28 -0
  22. package/dist/core/data-validation-overlay.js +56 -0
  23. package/dist/core/data-validation.d.ts +38 -0
  24. package/dist/core/data-validation.js +13 -0
  25. package/dist/core/date.d.ts +25 -0
  26. package/dist/core/date.js +58 -0
  27. package/dist/core/formula.d.ts +49 -0
  28. package/dist/core/formula.js +319 -0
  29. package/dist/core/grid-edits.d.ts +22 -0
  30. package/dist/core/grid-edits.js +206 -0
  31. package/dist/core/image.d.ts +76 -0
  32. package/dist/core/image.js +65 -0
  33. package/dist/core/internal.d.ts +11 -0
  34. package/dist/core/internal.js +29 -0
  35. package/dist/core/merge.d.ts +13 -0
  36. package/dist/core/merge.js +26 -0
  37. package/dist/core/modern-functions.d.ts +1 -0
  38. package/dist/core/modern-functions.js +165 -0
  39. package/dist/core/page-setup.d.ts +94 -0
  40. package/dist/core/page-setup.js +6 -0
  41. package/dist/core/pivot-table.d.ts +113 -0
  42. package/dist/core/pivot-table.js +249 -0
  43. package/dist/core/preserved.d.ts +58 -0
  44. package/dist/core/preserved.js +5 -0
  45. package/dist/core/protection.d.ts +67 -0
  46. package/dist/core/protection.js +68 -0
  47. package/dist/core/range.d.ts +112 -0
  48. package/dist/core/range.js +283 -0
  49. package/dist/core/row.d.ts +73 -0
  50. package/dist/core/row.js +135 -0
  51. package/dist/core/style.d.ts +246 -0
  52. package/dist/core/style.js +125 -0
  53. package/dist/core/table-style.d.ts +67 -0
  54. package/dist/core/table-style.js +90 -0
  55. package/dist/core/table.d.ts +194 -0
  56. package/dist/core/table.js +291 -0
  57. package/dist/core/theme.d.ts +90 -0
  58. package/dist/core/theme.js +272 -0
  59. package/dist/core/value.d.ts +115 -0
  60. package/dist/core/value.js +144 -0
  61. package/dist/core/workbook-protection.d.ts +27 -0
  62. package/dist/core/workbook-protection.js +25 -0
  63. package/dist/core/workbook.d.ts +528 -0
  64. package/dist/core/workbook.js +732 -0
  65. package/dist/core/worksheet-model.d.ts +34 -0
  66. package/dist/core/worksheet-model.js +100 -0
  67. package/dist/core/worksheet.d.ts +647 -0
  68. package/dist/core/worksheet.js +1025 -0
  69. package/dist/customui/errors.d.ts +15 -0
  70. package/dist/customui/errors.js +15 -0
  71. package/dist/customui/index.d.ts +2 -0
  72. package/dist/customui/index.js +7 -0
  73. package/dist/customui/ribbon.d.ts +86 -0
  74. package/dist/customui/ribbon.js +180 -0
  75. package/dist/entries/core.d.ts +23 -0
  76. package/dist/entries/core.js +20 -0
  77. package/dist/entries/csv.d.ts +2 -0
  78. package/dist/entries/csv.js +6 -0
  79. package/dist/entries/customui.d.ts +1 -0
  80. package/dist/entries/customui.js +3 -0
  81. package/dist/entries/errors.d.ts +7 -0
  82. package/dist/entries/errors.js +20 -0
  83. package/dist/entries/vba.d.ts +2 -0
  84. package/dist/entries/vba.js +7 -0
  85. package/dist/entries/xlsb.d.ts +1 -0
  86. package/dist/entries/xlsb.js +7 -0
  87. package/dist/entries/xlsx.d.ts +5 -0
  88. package/dist/entries/xlsx.js +18 -0
  89. package/dist/errors.d.ts +65 -0
  90. package/dist/errors.js +66 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.js +19 -0
  93. package/dist/io/csv/read.d.ts +15 -0
  94. package/dist/io/csv/read.js +113 -0
  95. package/dist/io/csv/write.d.ts +28 -0
  96. package/dist/io/csv/write.js +124 -0
  97. package/dist/io/opc/errors.d.ts +46 -0
  98. package/dist/io/opc/errors.js +49 -0
  99. package/dist/io/opc/inflate.d.ts +15 -0
  100. package/dist/io/opc/inflate.js +91 -0
  101. package/dist/io/opc/namespaces.d.ts +18 -0
  102. package/dist/io/opc/namespaces.js +18 -0
  103. package/dist/io/opc/part-paths.d.ts +4 -0
  104. package/dist/io/opc/part-paths.js +35 -0
  105. package/dist/io/opc/read-opc.d.ts +24 -0
  106. package/dist/io/opc/read-opc.js +153 -0
  107. package/dist/io/opc/read-options.d.ts +10 -0
  108. package/dist/io/opc/read-options.js +7 -0
  109. package/dist/io/opc/rels.d.ts +15 -0
  110. package/dist/io/opc/rels.js +28 -0
  111. package/dist/io/opc/sniff-format.d.ts +27 -0
  112. package/dist/io/opc/sniff-format.js +92 -0
  113. package/dist/io/style/xf-style.d.ts +47 -0
  114. package/dist/io/style/xf-style.js +95 -0
  115. package/dist/io/xlsb/errors.d.ts +15 -0
  116. package/dist/io/xlsb/errors.js +15 -0
  117. package/dist/io/xlsb/formula.d.ts +48 -0
  118. package/dist/io/xlsb/formula.js +398 -0
  119. package/dist/io/xlsb/primitives.d.ts +86 -0
  120. package/dist/io/xlsb/primitives.js +230 -0
  121. package/dist/io/xlsb/ptg-functions.d.ts +16 -0
  122. package/dist/io/xlsb/ptg-functions.js +659 -0
  123. package/dist/io/xlsb/read-shared-strings.d.ts +2 -0
  124. package/dist/io/xlsb/read-shared-strings.js +22 -0
  125. package/dist/io/xlsb/read-styles.d.ts +4 -0
  126. package/dist/io/xlsb/read-styles.js +386 -0
  127. package/dist/io/xlsb/read-worksheet.d.ts +9 -0
  128. package/dist/io/xlsb/read-worksheet.js +301 -0
  129. package/dist/io/xlsb/read.d.ts +20 -0
  130. package/dist/io/xlsb/read.js +199 -0
  131. package/dist/io/xlsb/record-stream.d.ts +14 -0
  132. package/dist/io/xlsb/record-stream.js +56 -0
  133. package/dist/io/xlsb/record-types.d.ts +55 -0
  134. package/dist/io/xlsb/record-types.js +73 -0
  135. package/dist/io/xlsx/cell-accumulator.d.ts +25 -0
  136. package/dist/io/xlsx/cell-accumulator.js +172 -0
  137. package/dist/io/xlsx/cell-value.d.ts +32 -0
  138. package/dist/io/xlsx/cell-value.js +85 -0
  139. package/dist/io/xlsx/comments.d.ts +74 -0
  140. package/dist/io/xlsx/comments.js +243 -0
  141. package/dist/io/xlsx/conditional-formatting.d.ts +32 -0
  142. package/dist/io/xlsx/conditional-formatting.js +464 -0
  143. package/dist/io/xlsx/data-validation.d.ts +20 -0
  144. package/dist/io/xlsx/data-validation.js +258 -0
  145. package/dist/io/xlsx/edit-vba.d.ts +23 -0
  146. package/dist/io/xlsx/edit-vba.js +139 -0
  147. package/dist/io/xlsx/errors.d.ts +17 -0
  148. package/dist/io/xlsx/errors.js +17 -0
  149. package/dist/io/xlsx/hyperlinks.d.ts +42 -0
  150. package/dist/io/xlsx/hyperlinks.js +139 -0
  151. package/dist/io/xlsx/images.d.ts +34 -0
  152. package/dist/io/xlsx/images.js +218 -0
  153. package/dist/io/xlsx/namespaces.d.ts +55 -0
  154. package/dist/io/xlsx/namespaces.js +55 -0
  155. package/dist/io/xlsx/package-plan.d.ts +103 -0
  156. package/dist/io/xlsx/package-plan.js +168 -0
  157. package/dist/io/xlsx/pivot-read.d.ts +5 -0
  158. package/dist/io/xlsx/pivot-read.js +134 -0
  159. package/dist/io/xlsx/pivot.d.ts +12 -0
  160. package/dist/io/xlsx/pivot.js +141 -0
  161. package/dist/io/xlsx/read-rows.d.ts +85 -0
  162. package/dist/io/xlsx/read-rows.js +286 -0
  163. package/dist/io/xlsx/read-styles.d.ts +8 -0
  164. package/dist/io/xlsx/read-styles.js +551 -0
  165. package/dist/io/xlsx/read-worksheet.d.ts +4 -0
  166. package/dist/io/xlsx/read-worksheet.js +528 -0
  167. package/dist/io/xlsx/read.d.ts +32 -0
  168. package/dist/io/xlsx/read.js +680 -0
  169. package/dist/io/xlsx/relationships.d.ts +33 -0
  170. package/dist/io/xlsx/relationships.js +47 -0
  171. package/dist/io/xlsx/rich-runs.d.ts +13 -0
  172. package/dist/io/xlsx/rich-runs.js +59 -0
  173. package/dist/io/xlsx/rich-text.d.ts +7 -0
  174. package/dist/io/xlsx/rich-text.js +23 -0
  175. package/dist/io/xlsx/shared-formulas.d.ts +6 -0
  176. package/dist/io/xlsx/shared-formulas.js +52 -0
  177. package/dist/io/xlsx/shared-strings-read.d.ts +2 -0
  178. package/dist/io/xlsx/shared-strings-read.js +70 -0
  179. package/dist/io/xlsx/shared-strings.d.ts +17 -0
  180. package/dist/io/xlsx/shared-strings.js +50 -0
  181. package/dist/io/xlsx/sheet-properties.d.ts +13 -0
  182. package/dist/io/xlsx/sheet-properties.js +211 -0
  183. package/dist/io/xlsx/styles.d.ts +138 -0
  184. package/dist/io/xlsx/styles.js +821 -0
  185. package/dist/io/xlsx/tables.d.ts +9 -0
  186. package/dist/io/xlsx/tables.js +208 -0
  187. package/dist/io/xlsx/threaded-comments.d.ts +102 -0
  188. package/dist/io/xlsx/threaded-comments.js +308 -0
  189. package/dist/io/xlsx/workbook-xml.d.ts +12 -0
  190. package/dist/io/xlsx/workbook-xml.js +353 -0
  191. package/dist/io/xlsx/worksheet-xml.d.ts +86 -0
  192. package/dist/io/xlsx/worksheet-xml.js +643 -0
  193. package/dist/io/xlsx/write-stream.d.ts +171 -0
  194. package/dist/io/xlsx/write-stream.js +399 -0
  195. package/dist/io/xlsx/write.d.ts +81 -0
  196. package/dist/io/xlsx/write.js +396 -0
  197. package/dist/io/xlsx/x14-ext.d.ts +9 -0
  198. package/dist/io/xlsx/x14-ext.js +12 -0
  199. package/dist/vba/cfb-writer.d.ts +19 -0
  200. package/dist/vba/cfb-writer.js +277 -0
  201. package/dist/vba/cfb.d.ts +16 -0
  202. package/dist/vba/cfb.js +324 -0
  203. package/dist/vba/codepage.d.ts +18 -0
  204. package/dist/vba/codepage.js +87 -0
  205. package/dist/vba/errors.d.ts +24 -0
  206. package/dist/vba/errors.js +24 -0
  207. package/dist/vba/index.d.ts +5 -0
  208. package/dist/vba/index.js +15 -0
  209. package/dist/vba/ms-ovba.d.ts +12 -0
  210. package/dist/vba/ms-ovba.js +176 -0
  211. package/dist/vba/project-editor.d.ts +62 -0
  212. package/dist/vba/project-editor.js +377 -0
  213. package/dist/vba/project.d.ts +45 -0
  214. package/dist/vba/project.js +160 -0
  215. package/dist/vba/vba-encoding.d.ts +14 -0
  216. package/dist/vba/vba-encoding.js +39 -0
  217. package/dist/xml/errors.d.ts +15 -0
  218. package/dist/xml/errors.js +15 -0
  219. package/dist/xml/xml-read.d.ts +107 -0
  220. package/dist/xml/xml-read.js +283 -0
  221. package/dist/xml/xml.d.ts +41 -0
  222. package/dist/xml/xml.js +86 -0
  223. package/package.json +117 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Print-scaling and orientation settings. These map onto two OOXML elements: `fitToPage` is the
3
+ * `<pageSetUpPr>` flag (a `<sheetPr>` child) that switches Excel from fixed-zoom to fit-to-page
4
+ * scaling, while the rest are `<pageSetup>` attributes. Excel honours `scale` only when `fitToPage`
5
+ * is off and the `fitToWidth`/`fitToHeight` page counts only when it is on, but the model carries
6
+ * whatever the author set — an unset field is omitted so a round-trip never fabricates one. An
7
+ * empty object emits neither element.
8
+ */
9
+ export interface PageSetup {
10
+ /** Switch to fit-to-page scaling. Emitted as `<pageSetUpPr fitToPage="1">`. */
11
+ fitToPage?: boolean;
12
+ /** Pages wide to fit onto; `0` means "unbounded" (fit only by height). */
13
+ fitToWidth?: number;
14
+ /** Pages tall to fit onto; `0` means "unbounded" (fit only by width). */
15
+ fitToHeight?: number;
16
+ /** Fixed print zoom as a percentage; Excel honours it only when `fitToPage` is off. */
17
+ scale?: number;
18
+ /** Paper orientation. */
19
+ orientation?: 'portrait' | 'landscape';
20
+ /** Order pages are numbered/printed in across a multi-page sheet. */
21
+ pageOrder?: 'downThenOver' | 'overThenDown';
22
+ /**
23
+ * Paper size as Excel's 1-based enumeration index (e.g. `9` = A4, `1` = US Letter). Carried as an
24
+ * opaque integer — the model does not map it to physical dimensions, only preserves whatever the
25
+ * author or source file set.
26
+ */
27
+ paperSize?: number;
28
+ /**
29
+ * The printer-settings blob a source file bound to this sheet's `<pageSetup>` via an `r:id`
30
+ * relationship, held verbatim. Excel stores the platform-specific `DEVMODE` (paper tray, duplex,
31
+ * DPI, …) in this opaque binary part; the model does not interpret it, only round-trips the exact
32
+ * bytes so re-writing a file that carried one does not silently drop the user's print configuration.
33
+ */
34
+ printerSettings?: Uint8Array;
35
+ }
36
+ /**
37
+ * Print-toggle flags from the `<printOptions>` element. Each maps to a boolean OOXML attribute that
38
+ * defaults false — except `gridLinesSet`, which defaults true and gates whether `gridLines` is
39
+ * honoured. The model stores only what the source or caller set, so an unset flag is omitted and a
40
+ * round-trip never fabricates one; an empty object emits no element at all.
41
+ */
42
+ export interface PrintOptions {
43
+ /** Centre the printed content horizontally on the page. */
44
+ horizontalCentered?: boolean;
45
+ /** Centre the printed content vertically on the page. */
46
+ verticalCentered?: boolean;
47
+ /** Print the row and column headings (the `1,2,3…` / `A,B,C…` gutters). */
48
+ headings?: boolean;
49
+ /** Print the cell gridlines. */
50
+ gridLines?: boolean;
51
+ /** Whether the `gridLines` flag is authoritative; when `false`, Excel ignores `gridLines`. */
52
+ gridLinesSet?: boolean;
53
+ }
54
+ /**
55
+ * A manual page break (`<brk>`). For a row break, `id` is the row the layout splits *before*; for a
56
+ * column break it is the column. `max` bounds the break's extent across the other axis (Excel writes
57
+ * the last row/column index) and `man` marks it author-set rather than automatic — the model preserves
58
+ * whatever the source carried so a round-trip reproduces the break's span exactly.
59
+ */
60
+ export interface PageBreak {
61
+ /** The row (or column) the break precedes. */
62
+ readonly id: number;
63
+ /** The break's far extent across the other axis, if the source declared one. */
64
+ readonly max?: number;
65
+ /** `true` when the break is manual (author-set); Excel-authored breaks always are. */
66
+ readonly man?: boolean;
67
+ }
68
+ /**
69
+ * Print margins, in inches. OOXML's `<pageMargins>` requires all six to be present, but
70
+ * the model stores only what the caller set; the writer fills the untouched ones with
71
+ * valid defaults. An empty object means the element is omitted entirely.
72
+ */
73
+ export interface PageMargins {
74
+ left?: number;
75
+ right?: number;
76
+ top?: number;
77
+ bottom?: number;
78
+ header?: number;
79
+ footer?: number;
80
+ }
81
+ /**
82
+ * Page header/footer text, one string per page class. Excel only honours the even- and
83
+ * first-page variants when the writer also sets the gating flags (`differentOddEven`,
84
+ * `differentFirst`); the writer derives those from which variants are present. An empty
85
+ * object means the element is omitted entirely.
86
+ */
87
+ export interface HeaderFooter {
88
+ oddHeader?: string;
89
+ oddFooter?: string;
90
+ evenHeader?: string;
91
+ evenFooter?: string;
92
+ firstHeader?: string;
93
+ firstFooter?: string;
94
+ }
@@ -0,0 +1,6 @@
1
+ // The print/page-layout data shapes a worksheet carries: how it scales and orients on paper, which
2
+ // print toggles are set, where manual page breaks fall, the margins, and the header/footer text. Each
3
+ // is a pure data shape mapping onto an OOXML print element (`<pageSetup>`, `<printOptions>`, `<brk>`,
4
+ // `<pageMargins>`, `<headerFooter>`); the model stores only what an author or source file set, so an
5
+ // unset field is omitted and a round-trip never fabricates one.
6
+ export {};
@@ -0,0 +1,113 @@
1
+ import type { Worksheet } from './worksheet.ts';
2
+ /** The aggregation a pivot's value field applies. These are OOXML's `ST_DataConsolidateFunction`
3
+ * names verbatim, so a metric doubles as its `<dataField subtotal="…">` value. Excel performs the
4
+ * aggregation itself on refresh; the writer only records which function to apply. */
5
+ export type PivotMetric = 'sum' | 'count' | 'countNums' | 'average' | 'max' | 'min' | 'product' | 'stdDev' | 'stdDevp' | 'var' | 'varp';
6
+ /** Narrow a raw `subtotal` attribute (or any string) to a known {@link PivotMetric}. */
7
+ export declare function isPivotMetric(value: string): value is PivotMetric;
8
+ /** Map an OOXML `<dataField subtotal="…">` value back to its metric. The attribute is absent for
9
+ * `sum` (Excel's implicit default), so `undefined` reads as `sum`; an unrecognised value also reads
10
+ * as `sum` rather than throwing, because reconstructing an existing file is a lenient operation —
11
+ * the strict rejection of unknown metrics belongs on the authoring path, not the read path. */
12
+ export declare function pivotMetricFromSubtotal(subtotal: string | undefined): PivotMetric;
13
+ /** One field in a loaded pivot's cache catalogue, in declared order; the pivot refers to it by index. */
14
+ export interface ParsedPivotField {
15
+ readonly name: string;
16
+ }
17
+ /** The kind of data a pivot cache draws from, mirroring OOXML's `ST_SourceType`. Only `worksheet`
18
+ * carries a {@link ParsedPivotSource.sheet}/{@link ParsedPivotSource.ref}; every other kind draws from
19
+ * data the reader does not model (an external connection, a range consolidation, or a scenario), and
20
+ * `unknown` covers a `type` the file declares that is none of these. */
21
+ export type PivotSourceKind = 'worksheet' | 'external' | 'consolidation' | 'scenario' | 'unknown';
22
+ /** Where a pivot cache draws its rows from. {@link kind} names the source type; {@link sheet} and
23
+ * {@link ref} locate the range only when it is `worksheet` and are empty strings otherwise, so a
24
+ * consumer can tell a genuinely non-worksheet source apart from a worksheet source that failed to
25
+ * parse (the former reports its {@link kind}, the latter stays `worksheet` with empty coordinates). */
26
+ export interface ParsedPivotSource {
27
+ readonly kind: PivotSourceKind;
28
+ readonly sheet: string;
29
+ readonly ref: string;
30
+ }
31
+ /** The semantic model reconstructed from a loaded pivot's `pivotTableDefinition` and its
32
+ * `pivotCacheDefinition` (see `io/xlsx/pivot-read.ts`). Field roles are indices into {@link fields};
33
+ * {@link metric} is the aggregation the value field applies. This mirrors the authoring model's shape
34
+ * without requiring the source sheet it was built from, so a pivot loaded from a package is
35
+ * inspectable data rather than an opaque preserved blob. It is a read-only view: the writer emits a
36
+ * loaded pivot from its preserved parts, not from this model, so exposing it never double-emits. */
37
+ export interface ParsedPivotTable {
38
+ readonly name: string;
39
+ readonly cacheId: string;
40
+ readonly source: ParsedPivotSource;
41
+ readonly fields: readonly ParsedPivotField[];
42
+ readonly rowFields: readonly number[];
43
+ readonly columnFields: readonly number[];
44
+ /** Index into {@link fields} of the aggregated field, or -1 when no `<dataField>` was declared. */
45
+ readonly valueField: number;
46
+ readonly valueFieldName: string;
47
+ /** The `<dataField>`'s own caption ("Average of Amount"), which Excel shows on the data column. */
48
+ readonly valueCaption: string;
49
+ readonly metric: PivotMetric;
50
+ }
51
+ /** How a pivot table is authored: a source sheet and the header names that drive each axis.
52
+ * `rows`/`columns`/`values` name columns by their header text in the source's first row. */
53
+ export interface PivotTableOptions {
54
+ readonly source: Worksheet;
55
+ readonly rows: readonly string[];
56
+ readonly columns: readonly string[];
57
+ readonly values: readonly string[];
58
+ readonly metric?: PivotMetric;
59
+ }
60
+ /** One distinct value in a cache field's shared-items catalogue, or an inline record cell. A
61
+ * `blank` is a missing source value, serialised as `<m/>` rather than an empty string. */
62
+ export type PivotItem = {
63
+ readonly kind: 'string';
64
+ readonly value: string;
65
+ } | {
66
+ readonly kind: 'number';
67
+ readonly value: number;
68
+ } | {
69
+ readonly kind: 'blank';
70
+ };
71
+ /** The numeric summary Excel expects on a non-shared field whose every present value is a number. */
72
+ export interface PivotNumericSummary {
73
+ readonly allInteger: boolean;
74
+ readonly min: number;
75
+ readonly max: number;
76
+ }
77
+ /** One field of the pivot cache. An axis field (row or column) carries a `sharedItems` catalogue its
78
+ * records reference by index; any other field stores its values inline in the records and, when they
79
+ * are all numeric, describes them with a `numeric` summary. */
80
+ export interface PivotCacheField {
81
+ readonly name: string;
82
+ readonly sharedItems: readonly PivotItem[] | null;
83
+ readonly numeric: PivotNumericSummary | null;
84
+ readonly containsBlank: boolean;
85
+ }
86
+ /** One cell of a cache record: an index into a shared-items catalogue, or an inline value. */
87
+ export type PivotRecordCell = {
88
+ readonly kind: 'index';
89
+ readonly index: number;
90
+ } | PivotItem;
91
+ /**
92
+ * A pivot table built over a source sheet's data. Construction reads the source once and computes
93
+ * the full cache (fields + records) and the axis-field wiring the renderer needs; nothing here
94
+ * touches XML.
95
+ *
96
+ * Supported shape: exactly one value field aggregated by `sum`, at least one row field and one
97
+ * column field. An unsupported request throws at authoring time rather than emitting a corrupt file.
98
+ */
99
+ export declare class PivotTable {
100
+ readonly metric: PivotMetric;
101
+ readonly sourceSheetName: string;
102
+ /** The `A1:C4` source range: the header row through the last data row, across the field columns. */
103
+ readonly sourceRef: string;
104
+ readonly cacheFields: readonly PivotCacheField[];
105
+ readonly records: readonly (readonly PivotRecordCell[])[];
106
+ /** Indices into {@link cacheFields} of the row-axis, column-axis, and value fields. */
107
+ readonly rowFields: readonly number[];
108
+ readonly columnFields: readonly number[];
109
+ readonly valueField: number;
110
+ constructor(options: PivotTableOptions);
111
+ /** The value field's header name, used to label the aggregated data column ("Sum of Amount"). */
112
+ get valueFieldName(): string;
113
+ }
@@ -0,0 +1,249 @@
1
+ // A pivot table authored from a model.
2
+ //
3
+ // A pivot summarises a source range: its distinct field values become row/column axes and a value
4
+ // field is aggregated across them. OOXML splits that into three parts — a `pivotCacheDefinition`
5
+ // (the field catalogue), a `pivotCacheRecords` (a copy of the source rows, with axis-field cells
6
+ // swapped for indices into the catalogue), and a `pivotTableDefinition` (the layout on the
7
+ // destination sheet). This module owns the *semantic* computation of all three; the OOXML rendering
8
+ // lives in `io/xlsx/pivot.ts`.
9
+ //
10
+ // The source data is captured when the pivot is added — the model reads the source sheet's cells
11
+ // once, here, so the pivot is a stable snapshot independent of later edits to the source.
12
+ import { AuthoringError, InternalError } from "../errors.js";
13
+ import { encodeAddress } from "./address.js";
14
+ import { isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, richTextToPlain, } from "./value.js";
15
+ const PIVOT_METRICS = new Set([
16
+ 'sum',
17
+ 'count',
18
+ 'countNums',
19
+ 'average',
20
+ 'max',
21
+ 'min',
22
+ 'product',
23
+ 'stdDev',
24
+ 'stdDevp',
25
+ 'var',
26
+ 'varp',
27
+ ]);
28
+ /** Narrow a raw `subtotal` attribute (or any string) to a known {@link PivotMetric}. */
29
+ export function isPivotMetric(value) {
30
+ return PIVOT_METRICS.has(value);
31
+ }
32
+ /** Map an OOXML `<dataField subtotal="…">` value back to its metric. The attribute is absent for
33
+ * `sum` (Excel's implicit default), so `undefined` reads as `sum`; an unrecognised value also reads
34
+ * as `sum` rather than throwing, because reconstructing an existing file is a lenient operation —
35
+ * the strict rejection of unknown metrics belongs on the authoring path, not the read path. */
36
+ export function pivotMetricFromSubtotal(subtotal) {
37
+ if (subtotal === undefined)
38
+ return 'sum';
39
+ return isPivotMetric(subtotal) ? subtotal : 'sum';
40
+ }
41
+ const BLANK = { kind: 'blank' };
42
+ /**
43
+ * A pivot table built over a source sheet's data. Construction reads the source once and computes
44
+ * the full cache (fields + records) and the axis-field wiring the renderer needs; nothing here
45
+ * touches XML.
46
+ *
47
+ * Supported shape: exactly one value field aggregated by `sum`, at least one row field and one
48
+ * column field. An unsupported request throws at authoring time rather than emitting a corrupt file.
49
+ */
50
+ export class PivotTable {
51
+ metric;
52
+ sourceSheetName;
53
+ /** The `A1:C4` source range: the header row through the last data row, across the field columns. */
54
+ sourceRef;
55
+ cacheFields;
56
+ records;
57
+ /** Indices into {@link cacheFields} of the row-axis, column-axis, and value fields. */
58
+ rowFields;
59
+ columnFields;
60
+ valueField;
61
+ constructor(options) {
62
+ const metric = options.metric ?? 'sum';
63
+ if (!PIVOT_METRICS.has(metric)) {
64
+ throw new AuthoringError(`unsupported pivot metric "${metric}" — expected one of ${[...PIVOT_METRICS].join(', ')}`);
65
+ }
66
+ this.metric = metric;
67
+ const source = options.source;
68
+ const columnCount = source.columnCount;
69
+ const lastRow = source.rowCount;
70
+ if (columnCount < 1 || lastRow < 2) {
71
+ throw new AuthoringError('a pivot source needs a header row and at least one data row');
72
+ }
73
+ // Every non-blank header cell in row 1 defines a field, in ascending column order.
74
+ const fields = [];
75
+ for (let col = 1; col <= columnCount; col++) {
76
+ const name = textOf(scalarOf(source.getCell(encodeAddress(col, 1)).value));
77
+ if (name !== '')
78
+ fields.push({ name, col });
79
+ }
80
+ if (fields.length === 0)
81
+ throw new AuthoringError('the pivot source header row is empty');
82
+ const resolve = (role, name) => {
83
+ const index = fields.findIndex((field) => field.name === name);
84
+ if (index < 0) {
85
+ throw new AuthoringError(`pivot ${role} field "${name}" is not a column header in the source sheet`);
86
+ }
87
+ return index;
88
+ };
89
+ if (options.rows.length === 0)
90
+ throw new AuthoringError('a pivot table needs at least one row field');
91
+ if (options.columns.length === 0)
92
+ throw new AuthoringError('a pivot table needs at least one column field');
93
+ const [valueName, ...extraValues] = options.values;
94
+ if (valueName === undefined || extraValues.length > 0) {
95
+ throw new AuthoringError('a pivot table needs exactly one value field');
96
+ }
97
+ this.rowFields = options.rows.map((name) => resolve('row', name));
98
+ this.columnFields = options.columns.map((name) => resolve('column', name));
99
+ this.valueField = resolve('value', valueName);
100
+ const axisFields = new Set([...this.rowFields, ...this.columnFields]);
101
+ // fields is non-empty (guarded above); the source span runs from its first to its last column.
102
+ const firstField = fields[0];
103
+ const lastField = fields[fields.length - 1];
104
+ if (firstField === undefined || lastField === undefined) {
105
+ throw new AuthoringError('the pivot source header row is empty');
106
+ }
107
+ this.sourceSheetName = source.name;
108
+ this.sourceRef = `${encodeAddress(firstField.col, 1)}:${encodeAddress(lastField.col, lastRow)}`;
109
+ // Read the source body once, field by field, so the same scan feeds both the shared-items
110
+ // catalogues and the records that reference them.
111
+ const dataRowCount = lastRow - 1;
112
+ const columnScalars = fields.map((field) => {
113
+ const scalars = [];
114
+ for (let row = 2; row <= lastRow; row++) {
115
+ scalars.push(scalarOf(source.getCell(encodeAddress(field.col, row)).value));
116
+ }
117
+ return scalars;
118
+ });
119
+ const catalogues = fields.map(() => null);
120
+ this.cacheFields = fields.map((field, fieldIndex) => {
121
+ const scalars = scalarsForField(columnScalars, fieldIndex);
122
+ const containsBlank = scalars.some((scalar) => scalar.kind === 'blank');
123
+ if (axisFields.has(fieldIndex)) {
124
+ const items = [];
125
+ const catalogue = new Map();
126
+ for (const scalar of scalars) {
127
+ const key = itemKey(scalar);
128
+ if (!catalogue.has(key)) {
129
+ catalogue.set(key, items.length);
130
+ items.push(scalar);
131
+ }
132
+ }
133
+ catalogues[fieldIndex] = catalogue;
134
+ return { name: field.name, sharedItems: items, numeric: null, containsBlank };
135
+ }
136
+ return { name: field.name, sharedItems: null, numeric: numericSummary(scalars), containsBlank };
137
+ });
138
+ const records = [];
139
+ for (let row = 0; row < dataRowCount; row++) {
140
+ records.push(fields.map((_field, fieldIndex) => {
141
+ const scalar = scalarsForField(columnScalars, fieldIndex)[row];
142
+ if (scalar === undefined) {
143
+ throw new InternalError(`pivot record row ${row} is out of range for field ${fieldIndex} — every column was ` +
144
+ 'scanned for the same dataRowCount above, so this index is always in range');
145
+ }
146
+ const catalogue = catalogues[fieldIndex];
147
+ if (!catalogue)
148
+ return scalar;
149
+ // Every scalar was catalogued in the shared-items pass above, so this always hits.
150
+ const index = catalogue.get(itemKey(scalar));
151
+ if (index === undefined)
152
+ throw new InternalError('pivot record references an uncatalogued item');
153
+ return { kind: 'index', index };
154
+ }));
155
+ }
156
+ this.records = records;
157
+ }
158
+ /** The value field's header name, used to label the aggregated data column ("Sum of Amount"). */
159
+ get valueFieldName() {
160
+ const field = this.cacheFields[this.valueField];
161
+ if (field === undefined) {
162
+ throw new InternalError(`pivot valueField index ${this.valueField} is out of range — resolve() validated it against ` +
163
+ 'the same fields array cacheFields was built from');
164
+ }
165
+ return field.name;
166
+ }
167
+ }
168
+ // columnScalars[fieldIndex] is always present: it was built by mapping the same `fields` array this
169
+ // index is drawn from. Centralised here so the invariant is asserted once rather than cast away at
170
+ // each of its two call sites.
171
+ function scalarsForField(columnScalars, fieldIndex) {
172
+ const scalars = columnScalars[fieldIndex];
173
+ if (scalars === undefined) {
174
+ throw new InternalError(`pivot field index ${fieldIndex} is out of range for columnScalars — it was built from the same fields array`);
175
+ }
176
+ return scalars;
177
+ }
178
+ /** A stable dedup key for a shared item: kind-tagged so the number `1` and the string `"1"` differ. */
179
+ function itemKey(item) {
180
+ switch (item.kind) {
181
+ case 'string':
182
+ return `s:${item.value}`;
183
+ case 'number':
184
+ return `n:${item.value}`;
185
+ case 'blank':
186
+ return 'b';
187
+ }
188
+ }
189
+ /** The numeric summary for an inline field, or null when any present value is non-numeric (a string
190
+ * present means the field is not a pure numeric column; blanks alone do not disqualify it). */
191
+ function numericSummary(scalars) {
192
+ let min = Infinity;
193
+ let max = -Infinity;
194
+ let allInteger = true;
195
+ let sawNumber = false;
196
+ for (const scalar of scalars) {
197
+ if (scalar.kind === 'blank')
198
+ continue;
199
+ if (scalar.kind !== 'number')
200
+ return null;
201
+ sawNumber = true;
202
+ if (scalar.value < min)
203
+ min = scalar.value;
204
+ if (scalar.value > max)
205
+ max = scalar.value;
206
+ if (!Number.isInteger(scalar.value))
207
+ allInteger = false;
208
+ }
209
+ return sawNumber ? { allInteger, min, max } : null;
210
+ }
211
+ /** The string form of a shared item, used for header names (a blank header contributes no field). */
212
+ function textOf(item) {
213
+ return item.kind === 'blank' ? '' : String(item.value);
214
+ }
215
+ /**
216
+ * Reduce any cell value to the scalar a pivot cache can hold: a number, a string, or a blank. Only
217
+ * finite numbers stay numeric (a NaN would corrupt the cache); every other kind is flattened to its
218
+ * displayed text so hostile or exotic source content can never throw or leak an object into the XML.
219
+ */
220
+ function scalarOf(value) {
221
+ if (value === null)
222
+ return BLANK;
223
+ switch (typeof value) {
224
+ case 'number':
225
+ return Number.isFinite(value) ? { kind: 'number', value } : BLANK;
226
+ case 'string':
227
+ return { kind: 'string', value };
228
+ case 'boolean':
229
+ return { kind: 'string', value: value ? 'TRUE' : 'FALSE' };
230
+ default:
231
+ break;
232
+ }
233
+ if (value instanceof Date)
234
+ return { kind: 'string', value: value.toISOString() };
235
+ if (isRichTextValue(value))
236
+ return { kind: 'string', value: richTextToPlain(value) };
237
+ if (isHyperlinkValue(value)) {
238
+ return {
239
+ kind: 'string',
240
+ value: typeof value.text === 'string' ? value.text : richTextToPlain(value.text),
241
+ };
242
+ }
243
+ if (isErrorValue(value))
244
+ return { kind: 'string', value: value.error };
245
+ if (isFormulaValue(value) || isSharedFormulaValue(value)) {
246
+ return value.result === undefined ? BLANK : scalarOf(value.result);
247
+ }
248
+ return BLANK;
249
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * One outbound relationship of a {@link PreservedPart}: the id it carries inside its own rels part,
3
+ * the relationship Type URI, and its target. An internal relationship's `targetPath` is the resolved
4
+ * package path of the part it points at (the writer re-numbers and rewires it); an `external`
5
+ * relationship's `targetPath` is the raw `Target` verbatim (a linked workbook's path or URL) — it is
6
+ * outside the package, so it is emitted unchanged with `TargetMode="External"` and never remapped.
7
+ * Preserving external relationships is what keeps an `externalLink` part's pointer to its source
8
+ * workbook alive, so a round-trip does not orphan the `[n]` external references formulas resolve through.
9
+ */
10
+ export interface PreservedRelationship {
11
+ readonly id: string;
12
+ readonly type: string;
13
+ readonly targetPath: string;
14
+ readonly external?: boolean;
15
+ }
16
+ /**
17
+ * A package part the model does not interpret, captured verbatim so a round-trip re-emits it intact.
18
+ * `bytes` are the raw part contents, `contentType` how the source package declared it, and `rels` its
19
+ * outbound relationships (empty when the part references nothing). The writer re-numbers the part to a
20
+ * fresh, collision-proof path and rewires `rels` accordingly, but never touches `bytes`.
21
+ */
22
+ export interface PreservedPart {
23
+ readonly path: string;
24
+ readonly contentType: string;
25
+ readonly bytes: Uint8Array;
26
+ readonly rels: readonly PreservedRelationship[];
27
+ }
28
+ /**
29
+ * A worksheet-level reference to package content the model does not model — preserved verbatim across
30
+ * a round-trip instead of being silently dropped. `element` is the worksheet child that wires the
31
+ * reference (`<drawing>` for a vector-shape drawing, `<legacyDrawingHF>` for a header/footer image),
32
+ * or `undefined` when the sheet wires it by relationship alone (a pivot table or slicer Excel
33
+ * discovers by scanning the sheet's rels, with no worksheet child pointing at it). `relType` is the
34
+ * relationship Type URI to re-emit; `entryPath` is the part it points at; `parts` is the transitive
35
+ * closure of parts that reference reaches (the entry included), each re-emitted with its relationships
36
+ * rewired.
37
+ */
38
+ export interface PreservedWorksheetReference {
39
+ readonly element: 'drawing' | 'legacyDrawingHF' | undefined;
40
+ readonly relType: string;
41
+ readonly entryPath: string;
42
+ readonly parts: readonly PreservedPart[];
43
+ }
44
+ /**
45
+ * A package-root reference to content the model does not model, wired from the package's own
46
+ * `_rels/.rels` rather than the workbook part's rels — the ribbon-customisation parts
47
+ * (`customUI/customUI14.xml`), custom document properties (`docProps/custom.xml`), a thumbnail, and
48
+ * anything else hung off the root. The writer regenerates the root rels for the parts it models
49
+ * (workbook, core/app properties), so these would be dropped unless captured here and re-declared.
50
+ * `relType` is the relationship Type URI to re-emit, `entryPath` the part it targets (kept at its
51
+ * original path, since the writer generates nothing of these kinds to collide with), and `parts` the
52
+ * transitive closure the reference reaches.
53
+ */
54
+ export interface PreservedRootReference {
55
+ readonly relType: string;
56
+ readonly entryPath: string;
57
+ readonly parts: readonly PreservedPart[];
58
+ }
@@ -0,0 +1,5 @@
1
+ // Package content the model does not interpret, captured verbatim so a round-trip re-emits it intact.
2
+ // A preserved reference names a worksheet- or workbook-level relationship (a chart, vector drawing,
3
+ // slicer, pivot table) the model does not model; its transitive closure of parts is held as raw bytes
4
+ // with their content types and rewired relationships, and re-emitted unchanged on write.
5
+ export {};
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Whether each protected-sheet operation stays available to a user. Every flag is an
3
+ * *allow* flag: `true` keeps the operation permitted, `false` forbids it, and an absent
4
+ * flag falls to Excel's default for that operation (most editing operations default to
5
+ * forbidden once a sheet is protected; selecting cells defaults to permitted).
6
+ */
7
+ export interface SheetProtectionFlags {
8
+ /** Select locked cells (Excel permits this by default). */
9
+ readonly selectLockedCells?: boolean;
10
+ /** Select unlocked cells (permitted by default). */
11
+ readonly selectUnlockedCells?: boolean;
12
+ readonly formatCells?: boolean;
13
+ readonly formatColumns?: boolean;
14
+ readonly formatRows?: boolean;
15
+ readonly insertColumns?: boolean;
16
+ readonly insertRows?: boolean;
17
+ readonly insertHyperlinks?: boolean;
18
+ readonly deleteColumns?: boolean;
19
+ readonly deleteRows?: boolean;
20
+ readonly sort?: boolean;
21
+ readonly autoFilter?: boolean;
22
+ readonly pivotTables?: boolean;
23
+ readonly objects?: boolean;
24
+ readonly scenarios?: boolean;
25
+ }
26
+ /** {@link SheetProtectionFlags} plus the password-hardening knob accepted by `protect`. */
27
+ export interface SheetProtectionOptions extends SheetProtectionFlags {
28
+ /**
29
+ * Iteration count for the password hash. Higher is slower to brute-force; Excel writes
30
+ * 100000 by default. Ignored when no password is given.
31
+ */
32
+ readonly spinCount?: number;
33
+ }
34
+ /**
35
+ * A password-derived credential, in OOXML's agile form: the hash algorithm, the salted
36
+ * iterated hash of the password, the salt, and the iteration count — everything a consumer
37
+ * needs to verify a supplied password without the password ever being stored.
38
+ */
39
+ export interface SheetProtectionCredential {
40
+ readonly algorithmName: string;
41
+ readonly hashValue: string;
42
+ readonly saltValue: string;
43
+ readonly spinCount: number;
44
+ }
45
+ /** A sheet's protection: which operations stay allowed, and the optional password guard. */
46
+ export interface SheetProtection {
47
+ readonly flags: SheetProtectionFlags;
48
+ readonly credential?: SheetProtectionCredential;
49
+ }
50
+ /**
51
+ * The OOXML encoding table for the protection flags: each `<sheetProtection>` attribute paired
52
+ * with whether that operation is *forbidden by default* once a sheet is protected. Both directions
53
+ * key off this one list — the writer turns an author allow-flag into an attribute (omitting values
54
+ * equal to the default), the reader turns an attribute back into an allow-flag — so serialization
55
+ * and deserialization can never fall out of step. Most editing operations default to forbidden
56
+ * under protection; selecting cells and the object/scenario operations default to permitted.
57
+ */
58
+ export declare const SHEET_PROTECTION_FLAGS: readonly {
59
+ readonly key: keyof SheetProtectionFlags;
60
+ readonly defaultForbidden: boolean;
61
+ }[];
62
+ /**
63
+ * Derive a fresh {@link SheetProtectionCredential} for a password. Each call generates a new
64
+ * random salt, so protecting two sheets with the same password yields different credentials —
65
+ * the salt is real randomness, not a stub.
66
+ */
67
+ export declare function deriveCredential(password: string, spinCount?: number): SheetProtectionCredential;
@@ -0,0 +1,68 @@
1
+ // Worksheet-level protection: the `<sheetProtection>` state that makes the per-cell
2
+ // `locked`/`hidden` flags enforceable. A cell's protection does nothing until the sheet
3
+ // itself is protected; this module models that sheet-level switch and the optional
4
+ // password credential that guards lifting it.
5
+ //
6
+ // The option surface is stated in the AUTHOR's terms — each flag answers "may a user do
7
+ // this while the sheet is protected?" (`sort: true` = sorting stays available). OOXML
8
+ // encodes the inverse ("1" LOCKS an operation, "0"/omission PERMITS it) and its per-
9
+ // attribute defaults differ; that encoding table is {@link SHEET_PROTECTION_FLAGS} below,
10
+ // shared by the writer and reader, while the translation that consumes it lives in the io layer.
11
+ import { createHash, randomBytes } from 'node:crypto';
12
+ /**
13
+ * The OOXML encoding table for the protection flags: each `<sheetProtection>` attribute paired
14
+ * with whether that operation is *forbidden by default* once a sheet is protected. Both directions
15
+ * key off this one list — the writer turns an author allow-flag into an attribute (omitting values
16
+ * equal to the default), the reader turns an attribute back into an allow-flag — so serialization
17
+ * and deserialization can never fall out of step. Most editing operations default to forbidden
18
+ * under protection; selecting cells and the object/scenario operations default to permitted.
19
+ */
20
+ export const SHEET_PROTECTION_FLAGS = [
21
+ { key: 'formatCells', defaultForbidden: true },
22
+ { key: 'formatColumns', defaultForbidden: true },
23
+ { key: 'formatRows', defaultForbidden: true },
24
+ { key: 'insertColumns', defaultForbidden: true },
25
+ { key: 'insertRows', defaultForbidden: true },
26
+ { key: 'insertHyperlinks', defaultForbidden: true },
27
+ { key: 'deleteColumns', defaultForbidden: true },
28
+ { key: 'deleteRows', defaultForbidden: true },
29
+ { key: 'sort', defaultForbidden: true },
30
+ { key: 'autoFilter', defaultForbidden: true },
31
+ { key: 'pivotTables', defaultForbidden: true },
32
+ { key: 'objects', defaultForbidden: false },
33
+ { key: 'scenarios', defaultForbidden: false },
34
+ { key: 'selectLockedCells', defaultForbidden: false },
35
+ { key: 'selectUnlockedCells', defaultForbidden: false },
36
+ ];
37
+ // OOXML's agile hashing (ECMA-376 / MS-OFFCRYPTO): the password is UTF-16LE, prefixed with
38
+ // the salt for the first hash, then re-hashed `spinCount` times with a little-endian uint32
39
+ // iteration counter mixed in. SHA-512 is the modern choice Excel writes.
40
+ const ALGORITHM_NAME = 'SHA-512';
41
+ const HASH = 'sha512';
42
+ const DEFAULT_SPIN_COUNT = 100000;
43
+ const SALT_BYTES = 16;
44
+ /**
45
+ * Derive a fresh {@link SheetProtectionCredential} for a password. Each call generates a new
46
+ * random salt, so protecting two sheets with the same password yields different credentials —
47
+ * the salt is real randomness, not a stub.
48
+ */
49
+ export function deriveCredential(password, spinCount = DEFAULT_SPIN_COUNT) {
50
+ const salt = randomBytes(SALT_BYTES);
51
+ const secret = Buffer.from(password, 'utf16le');
52
+ let hash = createHash(HASH)
53
+ .update(Buffer.concat([salt, secret]))
54
+ .digest();
55
+ const iteration = Buffer.alloc(4);
56
+ for (let i = 0; i < spinCount; i++) {
57
+ iteration.writeUInt32LE(i, 0);
58
+ hash = createHash(HASH)
59
+ .update(Buffer.concat([hash, iteration]))
60
+ .digest();
61
+ }
62
+ return {
63
+ algorithmName: ALGORITHM_NAME,
64
+ hashValue: hash.toString('base64'),
65
+ saltValue: salt.toString('base64'),
66
+ spinCount,
67
+ };
68
+ }