@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
@@ -4,51 +4,44 @@ import { INTERNAL } from '../../core/internal.js';
4
4
  import { WORKBOOK_PROTECTION_CREDENTIAL_ATTRS, } from '../../core/workbook-protection.js';
5
5
  import { Workbook } from '../../core/workbook.js';
6
6
  import { isVisibility } from '../../core/worksheet.js';
7
- import { boolStrict, capturedText, enumToken, localName, numInteger, openElements, parseXml, } from '../../xml/xml-read.js';
7
+ import { capturedText, openElements, parseXml, parseXmlPasses } from '../../xml/xml-read.js';
8
+ import { boolStrict, enumToken, localName, numInteger } from '../../xml/xml-scan.js';
8
9
  import { UnsupportedFormatError } from '../opc/errors.js';
9
- import { extensionOf } from '../opc/part-paths.js';
10
- import { capturePartClosure, contentTypeResolver, packageAccessors, parseRelationshipRecords, parseRelationships, readPartRelationships, relationshipTargetByType, resolveRelativePart, resolveWorkbookPart, } from '../opc/read-opc.js';
11
- import { DEFAULT_MAX_UNCOMPRESSED } from '../opc/read-options.js';
12
- import { inflateSpreadsheetPackage } from '../opc/sniff-format.js';
10
+ import { extensionOf, resolveRelativePart } from '../opc/part-paths.js';
11
+ import { capturePartClosure, contentTypeResolver, openSpreadsheetPackage, parseRelationshipRecords, readPartRelationships, } from '../opc/read-opc.js';
13
12
  import { readXlsbPackage, XLSB_WORKBOOK_PART } from '../xlsb/read.js';
14
13
  import { applyNotes, parseComments } from './comments.js';
15
- import { parseConditionalFormattings, parseDxfs } from './conditional-formatting.js';
16
- import { applyDataValidations, parseDataValidations, parseExtendedDataValidations, } from './data-validation.js';
17
- import { applyHyperlinks, parseSheetHyperlinks } from './hyperlinks.js';
14
+ import { conditionalFormattingPass } from './conditional-formatting.js';
15
+ import { applyDataValidations, dataValidationPass, extendedDataValidationPass, } from './data-validation.js';
16
+ import { applyHyperlinks, sheetHyperlinkPass } from './hyperlinks.js';
18
17
  import { drawingHasUnmodeledContent, parseDrawing } from './images.js';
19
18
  import { parsePivotTable } from './read-pivot.js';
20
19
  import { parseSharedStrings } from './read-shared-strings.js';
21
- import { parseIndexedColors, parseMruColors, parseStyleTable, parseTableStyles, } from './read-styles.js';
22
- import { parseWorksheet } from './read-worksheet.js';
20
+ import { parseStyleTable } from './read-styles.js';
21
+ import { worksheetPass } from './read-worksheet.js';
23
22
  import { parseTable } from './tables.js';
24
23
  import { parseThemeColorScheme, parseThemeFontScheme } from './theme-xml.js';
25
24
  import { buildCommentThreads, parsePersons, parseThreadedComments } from './threaded-comments.js';
26
- export { parseRelationships, resolveWorkbookPart } from '../opc/read-opc.js';
27
- export { DEFAULT_MAX_UNCOMPRESSED } from '../opc/read-options.js';
28
25
  export { parseStyleTable } from './read-styles.js';
29
26
  export function readXlsx(data, options = {}) {
30
- const cap = options.maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED;
31
- const files = inflateSpreadsheetPackage(data, cap);
32
- const pkg = packageAccessors(files);
27
+ const { files, pkg, workbookXml } = openSpreadsheetPackage(data, options.maxUncompressedBytes);
33
28
  const { partText } = pkg;
34
- const workbookXml = partText('xl/workbook.xml');
35
29
  if (workbookXml === undefined) {
36
30
  if (files[XLSB_WORKBOOK_PART] !== undefined)
37
31
  return readXlsbPackage(files);
38
32
  throw new UnsupportedFormatError('unknown');
39
33
  }
40
34
  const contentTypeOf = contentTypeResolver(partText('[Content_Types].xml') ?? '');
41
- const workbookRelsXml = partText('xl/_rels/workbook.xml.rels') ?? '';
42
- const rels = parseRelationships(workbookRelsXml);
35
+ const workbookRels = readPartRelationships('xl/workbook.xml', partText);
43
36
  const sharedStrings = parseSharedStrings(partText('xl/sharedStrings.xml') ?? '');
44
37
  const stylesXml = partText('xl/styles.xml') ?? '';
45
- const { cellXfs: xfStyles, namedStyles, defaultFont } = parseStyleTable(stylesXml);
38
+ const { cellXfs: xfStyles, namedStyles, defaultFont, preserved } = parseStyleTable(stylesXml);
46
39
  const workbook = new Workbook();
47
- workbook[INTERNAL].restoreDifferentialStyles(parseDxfs(stylesXml));
48
- workbook[INTERNAL].restoreIndexedColors(parseIndexedColors(stylesXml));
49
- workbook[INTERNAL].restoreMruColors(parseMruColors(stylesXml));
50
- workbook[INTERNAL].restoreTableStyles(parseTableStyles(stylesXml));
51
- readWorkbookTheme(workbookRelsXml, pkg, contentTypeOf, workbook);
40
+ workbook[INTERNAL].restoreDifferentialStyles([...preserved.dxfs]);
41
+ workbook[INTERNAL].restoreIndexedColors([...preserved.indexedColors]);
42
+ workbook[INTERNAL].restoreMruColors([...preserved.mruColors]);
43
+ workbook[INTERNAL].restoreTableStyles(preserved.tableStyles);
44
+ readWorkbookTheme(workbookRels, pkg, contentTypeOf, workbook);
52
45
  if (namedStyles.length > 1)
53
46
  workbook[INTERNAL].restoreNamedStyles(namedStyles);
54
47
  workbook[INTERNAL].restoreDefaultFont(defaultFont);
@@ -60,53 +53,72 @@ export function readXlsx(data, options = {}) {
60
53
  applyAppProperties(workbook, app);
61
54
  workbook.protection = parseWorkbookProtection(workbookXml);
62
55
  applyWorkbookView(workbook.view, workbookXml);
63
- readWorkbookPersons(workbookRelsXml, pkg, workbook);
64
- const imageIdByMediaPath = new Map();
56
+ readWorkbookPersons(workbookRels, pkg, workbook);
57
+ const context = {
58
+ pkg,
59
+ workbook,
60
+ contentTypeOf,
61
+ sharedStrings,
62
+ xfStyles,
63
+ imageIdByMediaPath: new Map(),
64
+ };
65
65
  const sheetOrder = [];
66
66
  for (const { name, relId, state } of parseWorkbookSheets(workbookXml)) {
67
- const target = rels.get(relId);
67
+ const target = workbookRels.byId(relId)?.target;
68
68
  const sheet = workbook.addWorksheet(name, state === undefined ? undefined : { state });
69
69
  sheetOrder.push(name);
70
- const path = target === undefined ? undefined : resolveWorkbookPart(target);
71
- const sheetXml = path === undefined ? undefined : partText(path);
72
- if (sheetXml !== undefined)
73
- parseWorksheet(sheetXml, sheet, sharedStrings, xfStyles);
74
- if (path !== undefined) {
75
- const sheetRels = readPartRelationships(path, partText);
76
- if (sheetXml !== undefined) {
77
- applyHyperlinks(sheet, parseSheetHyperlinks(sheetXml), (id) => sheetRels.byId(id)?.target);
78
- applyDataValidations(sheet, [
79
- ...parseDataValidations(sheetXml),
80
- ...parseExtendedDataValidations(sheetXml),
81
- ]);
82
- for (const cf of parseConditionalFormattings(sheetXml))
83
- sheet.addConditionalFormatting(cf);
84
- }
85
- const threads = readSheetCommentThreads(sheetRels, pkg, workbook);
86
- if (threads.length > 0)
87
- sheet[INTERNAL].restoreCommentThreads(threads);
88
- const comments = readSheetComments(sheetRels, pkg);
89
- if (comments !== undefined)
90
- applyNotes(sheet, comments);
91
- readSheetImages(sheetRels, pkg, workbook, sheet, imageIdByMediaPath);
92
- readSheetBackground(sheetRels, pkg, workbook, sheet, imageIdByMediaPath);
93
- if (sheetXml !== undefined) {
94
- readSheetPreservedReferences(sheetRels, sheetXml, pkg, contentTypeOf, sheet);
95
- }
96
- readSheetTables(sheetRels, pkg, sheet);
97
- readSheetPivotTables(sheetRels, pkg, sheet);
98
- const printerSettings = readSheetPrinterSettings(sheetRels, pkg);
99
- if (printerSettings !== undefined)
100
- sheet.pageSetup.printerSettings = printerSettings;
101
- }
70
+ readSheet(sheet, target === undefined ? undefined : workbookRels.pathOf(target), context);
102
71
  }
103
- readWorkbookPreservedReferences(workbookXml, pkg, contentTypeOf, workbook);
72
+ readWorkbookPreservedReferences(workbookXml, workbookRels, pkg, contentTypeOf, workbook);
104
73
  readRootPreservedReferences(pkg, contentTypeOf, workbook);
105
74
  for (const name of parseWorkbookDefinedNames(workbookXml, sheetOrder)) {
106
75
  workbook.defineName(name);
107
76
  }
108
77
  return workbook;
109
78
  }
79
+ function readSheet(sheet, path, context) {
80
+ const { pkg, workbook, contentTypeOf, sharedStrings, xfStyles, imageIdByMediaPath } = context;
81
+ const { partText } = pkg;
82
+ const sheetXml = path === undefined ? undefined : partText(path);
83
+ const hyperlinks = sheetHyperlinkPass();
84
+ const validations = dataValidationPass();
85
+ const extendedValidations = extendedDataValidationPass();
86
+ const formattings = conditionalFormattingPass();
87
+ if (sheetXml !== undefined) {
88
+ parseXmlPasses(sheetXml, [
89
+ worksheetPass(sheet, sharedStrings, xfStyles),
90
+ hyperlinks,
91
+ validations,
92
+ extendedValidations,
93
+ formattings,
94
+ ]);
95
+ }
96
+ if (path === undefined)
97
+ return;
98
+ const sheetRels = readPartRelationships(path, partText);
99
+ if (sheetXml !== undefined) {
100
+ applyHyperlinks(sheet, hyperlinks.result(), (id) => sheetRels.byId(id)?.target);
101
+ applyDataValidations(sheet, [...validations.result(), ...extendedValidations.result()]);
102
+ for (const cf of formattings.result())
103
+ sheet.addConditionalFormatting(cf);
104
+ }
105
+ const threads = readSheetCommentThreads(sheetRels, pkg, workbook);
106
+ if (threads.length > 0)
107
+ sheet[INTERNAL].restoreCommentThreads(threads);
108
+ const comments = readSheetComments(sheetRels, pkg);
109
+ if (comments !== undefined)
110
+ applyNotes(sheet, comments);
111
+ readSheetImages(sheetRels, pkg, workbook, sheet, imageIdByMediaPath);
112
+ readSheetBackground(sheetRels, pkg, workbook, sheet, imageIdByMediaPath);
113
+ if (sheetXml !== undefined) {
114
+ readSheetPreservedReferences(sheetRels, sheetXml, pkg, contentTypeOf, sheet);
115
+ }
116
+ readSheetTables(sheetRels, pkg, sheet);
117
+ readSheetPivotTables(sheetRels, pkg, sheet);
118
+ const printerSettings = readSheetPrinterSettings(sheetRels, pkg);
119
+ if (printerSettings !== undefined)
120
+ sheet.pageSetup.printerSettings = printerSettings;
121
+ }
110
122
  function readSheetComments(sheetRels, pkg) {
111
123
  const commentsPath = sheetRels.targetPath('comments');
112
124
  if (commentsPath === undefined)
@@ -116,17 +128,16 @@ function readSheetComments(sheetRels, pkg) {
116
128
  return undefined;
117
129
  return parseComments(commentsXml);
118
130
  }
119
- function readWorkbookPersons(workbookRelsXml, pkg, workbook) {
120
- const target = relationshipTargetByType(workbookRelsXml, 'person');
121
- const xml = target === undefined ? undefined : pkg.partText(resolveWorkbookPart(target));
131
+ function readWorkbookPersons(workbookRels, pkg, workbook) {
132
+ const path = workbookRels.targetPath('person');
133
+ const xml = path === undefined ? undefined : pkg.partText(path);
122
134
  if (xml !== undefined)
123
135
  workbook[INTERNAL].restorePersons(parsePersons(xml));
124
136
  }
125
- function readWorkbookTheme(workbookRelsXml, pkg, contentTypeOf, workbook) {
126
- const target = relationshipTargetByType(workbookRelsXml, 'theme');
127
- if (target === undefined)
137
+ function readWorkbookTheme(workbookRels, pkg, contentTypeOf, workbook) {
138
+ const entryPath = workbookRels.targetPath('theme');
139
+ if (entryPath === undefined)
128
140
  return;
129
- const entryPath = resolveWorkbookPart(target);
130
141
  const parts = capturePartClosure(entryPath, pkg.partText, pkg.partBytes, contentTypeOf);
131
142
  if (parts === undefined)
132
143
  return;
@@ -217,17 +228,14 @@ function readSheetPreservedReferences(sheetRels, sheetXml, pkg, contentTypeOf, s
217
228
  function isPreservedSheetRelType(type) {
218
229
  return type.endsWith('/pivotTable') || type.endsWith('/slicer');
219
230
  }
220
- function readWorkbookPreservedReferences(workbookXml, pkg, contentTypeOf, workbook) {
231
+ function readWorkbookPreservedReferences(workbookXml, workbookRels, pkg, contentTypeOf, workbook) {
221
232
  const { partText, partBytes } = pkg;
222
- const relsXml = partText('xl/_rels/workbook.xml.rels');
223
- if (relsXml === undefined)
224
- return;
225
233
  const cacheIdByRelId = parsePivotCacheRegistrations(workbookXml);
226
234
  const externalIndexByRelId = parseExternalReferenceRegistrations(workbookXml);
227
- for (const record of parseRelationshipRecords(relsXml)) {
235
+ for (const record of workbookRels.records) {
228
236
  if (record.external || !isPreservedWorkbookRelType(record.type))
229
237
  continue;
230
- const entryPath = resolveWorkbookPart(record.target);
238
+ const entryPath = workbookRels.pathOf(record.target);
231
239
  const parts = capturePartClosure(entryPath, partText, partBytes, contentTypeOf);
232
240
  if (parts === undefined)
233
241
  continue;
@@ -1,5 +1,5 @@
1
1
  import type { RichTextRun } from '../../core/value.ts';
2
- import { type XmlAttributes } from '../../xml/xml-read.ts';
2
+ import { type XmlAttributes } from '../../xml/xml-scan.ts';
3
3
  /**
4
4
  * What {@link RunAccumulator.close} did with an element: `'container'` means the `<si>`/`<is>` itself
5
5
  * closed and the caller should commit whatever it makes of the gathered runs, the one step the two
@@ -1,4 +1,5 @@
1
- import { decodeSpreadsheetText, TextCapture } from '../../xml/xml-read.js';
1
+ import { TextCapture } from '../../xml/xml-read.js';
2
+ import { decodeSpreadsheetText } from '../../xml/xml-scan.js';
2
3
  import { applyFontChild } from './read-styles.js';
3
4
  export class RunAccumulator {
4
5
  #container;
@@ -1,5 +1,5 @@
1
1
  import { encodeAddress } from '../../core/address.js';
2
- import { isPageOrder, isPageOrientation, } from '../../core/page-setup.js';
2
+ import { HEADER_FOOTER_ELEMENTS, MARGIN_SIDES, PAGE_SETUP_FACETS, PRINT_OPTION_FLAGS, } from '../../core/page-setup.js';
3
3
  import { SHEET_PROTECTION_FLAGS } from '../../core/protection.js';
4
4
  import { boolAttr, checkedToken, escapeAttr, escapeSpreadsheetText, numAttr, numberText, } from '../../xml/xml.js';
5
5
  import { colorAttrs } from './color-xml.js';
@@ -80,16 +80,8 @@ function filterCriteriaXml(criteria) {
80
80
  .join('');
81
81
  return `<customFilters${andAttr}>${predicates}</customFilters>`;
82
82
  }
83
- const HF_CHILDREN = [
84
- { tag: 'oddHeader', key: 'oddHeader' },
85
- { tag: 'oddFooter', key: 'oddFooter' },
86
- { tag: 'evenHeader', key: 'evenHeader' },
87
- { tag: 'evenFooter', key: 'evenFooter' },
88
- { tag: 'firstHeader', key: 'firstHeader' },
89
- { tag: 'firstFooter', key: 'firstFooter' },
90
- ];
91
83
  export function headerFooterXml(hf) {
92
- const children = HF_CHILDREN.filter(({ key }) => hf[key] !== undefined);
84
+ const children = HEADER_FOOTER_ELEMENTS.filter((key) => hf[key] !== undefined);
93
85
  if (children.length === 0)
94
86
  return '';
95
87
  const differentOddEven = hf.evenHeader !== undefined || hf.evenFooter !== undefined;
@@ -100,7 +92,7 @@ export function headerFooterXml(hf) {
100
92
  if (differentFirst)
101
93
  attrs += ' differentFirst="1"';
102
94
  const body = children
103
- .map(({ tag, key }) => `<${tag}>${escapeSpreadsheetText(hf[key])}</${tag}>`)
95
+ .map((key) => `<${key}>${escapeSpreadsheetText(hf[key] ?? '')}</${key}>`)
104
96
  .join('');
105
97
  return `<headerFooter${attrs}>${body}</headerFooter>`;
106
98
  }
@@ -112,13 +104,8 @@ const DEFAULT_MARGINS = {
112
104
  header: 0.3,
113
105
  footer: 0.3,
114
106
  };
115
- const MARGIN_SIDES = ['left', 'right', 'top', 'bottom', 'header', 'footer'];
116
107
  export function printOptionsXml(printOptions) {
117
- const attrs = boolAttr('horizontalCentered', printOptions.horizontalCentered) +
118
- boolAttr('verticalCentered', printOptions.verticalCentered) +
119
- boolAttr('headings', printOptions.headings) +
120
- boolAttr('gridLines', printOptions.gridLines) +
121
- boolAttr('gridLinesSet', printOptions.gridLinesSet);
108
+ const attrs = PRINT_OPTION_FLAGS.map((flag) => boolAttr(flag, printOptions[flag])).join('');
122
109
  return attrs === '' ? '' : `<printOptions${attrs}/>`;
123
110
  }
124
111
  export function pageMarginsXml(margins) {
@@ -128,17 +115,23 @@ export function pageMarginsXml(margins) {
128
115
  return `<pageMargins ${attrs}/>`;
129
116
  }
130
117
  export function pageSetupXml(pageSetup, printerSettingsRelId) {
131
- const attrs = numAttr('paperSize', pageSetup.paperSize) +
132
- numAttr('scale', pageSetup.scale) +
133
- numAttr('fitToWidth', pageSetup.fitToWidth) +
134
- numAttr('fitToHeight', pageSetup.fitToHeight) +
135
- (pageSetup.pageOrder === undefined
136
- ? ''
137
- : ` pageOrder="${checkedToken(pageSetup.pageOrder, isPageOrder, 'page order')}"`) +
138
- (pageSetup.orientation === undefined
139
- ? ''
140
- : ` orientation="${checkedToken(pageSetup.orientation, isPageOrientation, 'page orientation')}"`) +
141
- (printerSettingsRelId !== null ? ` r:id="${printerSettingsRelId}"` : '');
118
+ let attrs = '';
119
+ for (const facet of PAGE_SETUP_FACETS) {
120
+ switch (facet.kind) {
121
+ case 'count':
122
+ attrs += numAttr(facet.key, pageSetup[facet.key]);
123
+ break;
124
+ case 'token': {
125
+ const value = pageSetup[facet.key];
126
+ if (value !== undefined) {
127
+ attrs += ` ${facet.key}="${checkedToken(value, facet.isValid, facet.label)}"`;
128
+ }
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ if (printerSettingsRelId !== null)
134
+ attrs += ` r:id="${printerSettingsRelId}"`;
142
135
  return attrs === '' ? '' : `<pageSetup${attrs}/>`;
143
136
  }
144
137
  export function pageBreaksXml(breaks, element) {
@@ -1,6 +1,6 @@
1
- import { isBorderStyle, isFillPatternType, isFontScheme, isFontVerticalAlignment, isHorizontalAlignment, isNamedUnderlineStyle, isVerticalAlignment, } from '../../core/style.js';
1
+ import { ALIGNMENT_FACETS, isBorderStyle, isFillPatternType, isFontScheme, isFontVerticalAlignment, isNamedUnderlineStyle, } from '../../core/style.js';
2
2
  import { TABLE_STYLE_ELEMENT_TYPES } from '../../core/table-style.js';
3
- import { decodeEntities } from '../../xml/xml-read.js';
3
+ import { decodeEntities } from '../../xml/xml-scan.js';
4
4
  import { assertRepresentable, checkedToken, escapeAttr, numberText, XML_DECLARATION, } from '../../xml/xml.js';
5
5
  import { colorAttrs } from './color-xml.js';
6
6
  import { MARKUP_COMPATIBILITY_NS, SPREADSHEETML_NS } from './namespaces.js';
@@ -281,24 +281,27 @@ function cellStyleTag(entry) {
281
281
  }
282
282
  function alignmentAttrs(alignment) {
283
283
  const parts = [];
284
- if (alignment.horizontal !== undefined && alignment.horizontal !== 'general') {
285
- parts.push(`horizontal="${checkedToken(alignment.horizontal, isHorizontalAlignment, 'horizontal alignment')}"`);
286
- }
287
- if (alignment.vertical !== undefined) {
288
- parts.push(`vertical="${checkedToken(alignment.vertical, isVerticalAlignment, 'vertical alignment')}"`);
289
- }
290
- if (alignment.textRotation !== undefined && alignment.textRotation !== 0) {
291
- parts.push(`textRotation="${numberText(alignment.textRotation)}"`);
292
- }
293
- if (alignment.wrapText)
294
- parts.push('wrapText="1"');
295
- if (alignment.indent !== undefined && alignment.indent !== 0) {
296
- parts.push(`indent="${numberText(alignment.indent)}"`);
297
- }
298
- if (alignment.shrinkToFit)
299
- parts.push('shrinkToFit="1"');
300
- if (alignment.readingOrder !== undefined && alignment.readingOrder !== 0) {
301
- parts.push(`readingOrder="${numberText(alignment.readingOrder)}"`);
284
+ for (const facet of ALIGNMENT_FACETS) {
285
+ switch (facet.kind) {
286
+ case 'token': {
287
+ const value = alignment[facet.key];
288
+ if (value === undefined || value === facet.omit)
289
+ break;
290
+ parts.push(`${facet.key}="${checkedToken(value, facet.isValid, facet.label)}"`);
291
+ break;
292
+ }
293
+ case 'number': {
294
+ const value = alignment[facet.key];
295
+ if (value === undefined || value === 0)
296
+ break;
297
+ parts.push(`${facet.key}="${numberText(value)}"`);
298
+ break;
299
+ }
300
+ case 'flag':
301
+ if (alignment[facet.key])
302
+ parts.push(`${facet.key}="1"`);
303
+ break;
304
+ }
302
305
  }
303
306
  return parts.join(' ');
304
307
  }
@@ -1,6 +1,7 @@
1
1
  import { encodeAddress, tryDecodeRange } from '../../core/address.js';
2
2
  import { isTotalsRowFunction, } from '../../core/table.js';
3
- import { boolPresent, localName, numInteger, parseXml, TextCapture } from '../../xml/xml-read.js';
3
+ import { parseXml, TextCapture } from '../../xml/xml-read.js';
4
+ import { boolPresent, localName, numInteger } from '../../xml/xml-scan.js';
4
5
  import { boolAttr, escapeAttr, escapeText, XML_DECLARATION } from '../../xml/xml.js';
5
6
  import { NS } from './relationships.js';
6
7
  export function tableXml(table, id) {
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_THEME_COLOR_SCHEME, isThemeColorSlot, normalizeThemeColor, } from '../../core/theme.js';
2
- import { decodeEntities } from '../../xml/xml-read.js';
2
+ import { decodeEntities } from '../../xml/xml-scan.js';
3
3
  import { escapeAttr } from '../../xml/xml.js';
4
4
  const SCHEME_SLOT = new RegExp('<a:(dk1|lt1|dk2|lt2|accent[1-6]|hlink|folHlink)\\b[^>]*>' + '\\s*<a:(srgbClr|sysClr)\\b([^>]*)>', 'g');
5
5
  export function parseThemeColorScheme(themeXml) {
@@ -1,6 +1,7 @@
1
1
  import { encodeAddress, tryDecodeCellRef } from '../../core/address.js';
2
2
  import { MENTION_OFFSET_MAX, } from '../../core/comment-thread.js';
3
- import { boolStrict, decodeSpreadsheetText, localName, numInteger, parseXml, TextCapture, } from '../../xml/xml-read.js';
3
+ import { parseXml, TextCapture } from '../../xml/xml-read.js';
4
+ import { boolStrict, decodeSpreadsheetText, localName, numInteger, } from '../../xml/xml-scan.js';
4
5
  import { escapeAttr, escapeSpreadsheetText, textAttr, XML_DECLARATION } from '../../xml/xml.js';
5
6
  import { THREADED_COMMENTS_NS } from './namespaces.js';
6
7
  export function parsePersons(xml) {
@@ -2,10 +2,11 @@ import { mangleFormula, quoteSheetName } from '../../core/formula.js';
2
2
  import { WORKBOOK_PROTECTION_CREDENTIAL_ATTRS } from '../../core/workbook-protection.js';
3
3
  import { isVisibility } from '../../core/worksheet.js';
4
4
  import { checkedToken, escapeAttr, escapeText, numAttr, textAttr, XML_DECLARATION, } from '../../xml/xml.js';
5
- import { extensionOf, relativePartPath, THEME_PART_PATH } from '../opc/part-paths.js';
5
+ import { extensionOf, THEME_PART_PATH } from '../opc/part-paths.js';
6
6
  import { relationship, relationshipsPart } from '../opc/rels.js';
7
7
  import { imageContentType } from './images.js';
8
8
  import { SLICER_CACHES_EXT_URI } from './namespaces.js';
9
+ import { APP_PROPS_PART, commentsPart, CORE_PROPS_PART, drawingPart, PERSONS_PART, pivotCacheDefinitionPart, pivotCacheRecordsPart, pivotTablePart, SHARED_STRINGS_PART, STYLES_PART, tablePart, targetFromWorkbook, threadedCommentsPart, WORKBOOK_PART, worksheetPart, } from './part-names.js';
9
10
  import { NS, REL } from './relationships.js';
10
11
  import { x14Ext } from './x14-ext.js';
11
12
  const CT = {
@@ -72,37 +73,37 @@ function contentTypeOverrides(sheetCount, tables, drawingNumbers, commentNumbers
72
73
  .filter((part) => part.path !== THEME_PART_PATH)
73
74
  .filter((part) => extensionDefaults.get(extensionOf(part.path).toLowerCase())?.contentType !==
74
75
  part.contentType)
75
- .map((part) => override(`/${part.path}`, part.contentType));
76
+ .map((part) => override(part.path, part.contentType));
76
77
  return [
77
- override('/xl/workbook.xml', isMacroEnabled(preservedWorkbookRefs) ? CT.macroEnabledWorkbook : CT.workbook),
78
- ...Array.from({ length: sheetCount }, (_, i) => override(`/xl/worksheets/sheet${i + 1}.xml`, CT.worksheet)),
79
- ...tables.map(({ number }) => override(`/xl/tables/table${number}.xml`, CT.table)),
80
- ...drawingNumbers.map((number) => override(`/xl/drawings/drawing${number}.xml`, CT.drawing)),
81
- ...commentNumbers.map((number) => override(`/xl/comments${number}.xml`, CT.comments)),
82
- ...threadedCommentNumbers.map((number) => override(`/xl/threadedComments/threadedComment${number}.xml`, CT.threadedComments)),
83
- ...pivots.map(({ number }) => override(`/xl/pivotTables/pivotTable${number}.xml`, CT.pivotTable)),
84
- ...pivots.map(({ number }) => override(`/xl/pivotCache/pivotCacheDefinition${number}.xml`, CT.pivotCacheDefinition)),
85
- ...pivots.map(({ number }) => override(`/xl/pivotCache/pivotCacheRecords${number}.xml`, CT.pivotCacheRecords)),
86
- override(`/${THEME_PART_PATH}`, CT.theme),
87
- override('/xl/styles.xml', CT.styles),
88
- ...(hasSharedStrings ? [override('/xl/sharedStrings.xml', CT.sharedStrings)] : []),
89
- ...(hasPersons ? [override('/xl/persons/person.xml', CT.person)] : []),
90
- override('/docProps/core.xml', CT.core),
91
- override('/docProps/app.xml', CT.app),
78
+ override(WORKBOOK_PART, isMacroEnabled(preservedWorkbookRefs) ? CT.macroEnabledWorkbook : CT.workbook),
79
+ ...Array.from({ length: sheetCount }, (_, i) => override(worksheetPart(i + 1), CT.worksheet)),
80
+ ...tables.map(({ number }) => override(tablePart(number), CT.table)),
81
+ ...drawingNumbers.map((number) => override(drawingPart(number), CT.drawing)),
82
+ ...commentNumbers.map((number) => override(commentsPart(number), CT.comments)),
83
+ ...threadedCommentNumbers.map((number) => override(threadedCommentsPart(number), CT.threadedComments)),
84
+ ...pivots.map(({ number }) => override(pivotTablePart(number), CT.pivotTable)),
85
+ ...pivots.map(({ number }) => override(pivotCacheDefinitionPart(number), CT.pivotCacheDefinition)),
86
+ ...pivots.map(({ number }) => override(pivotCacheRecordsPart(number), CT.pivotCacheRecords)),
87
+ override(THEME_PART_PATH, CT.theme),
88
+ override(STYLES_PART, CT.styles),
89
+ ...(hasSharedStrings ? [override(SHARED_STRINGS_PART, CT.sharedStrings)] : []),
90
+ ...(hasPersons ? [override(PERSONS_PART, CT.person)] : []),
91
+ override(CORE_PROPS_PART, CT.core),
92
+ override(APP_PROPS_PART, CT.app),
92
93
  ...preservedOverrides,
93
94
  ].join('');
94
95
  }
95
- function override(partName, contentType) {
96
- return `<Override PartName="${partName}" ContentType="${contentType}"/>`;
96
+ function override(partPath, contentType) {
97
+ return `<Override PartName="/${partPath}" ContentType="${contentType}"/>`;
97
98
  }
98
99
  function defaultType(extension, contentType) {
99
100
  return `<Default Extension="${extension}" ContentType="${contentType}"/>`;
100
101
  }
101
102
  export function rootRelsXml(rootRefs) {
102
103
  return relationshipsPart([
103
- relationship('rId1', REL.officeDocument, 'xl/workbook.xml'),
104
- relationship('rId2', REL.coreProps, 'docProps/core.xml'),
105
- relationship('rId3', REL.extProps, 'docProps/app.xml'),
104
+ relationship('rId1', REL.officeDocument, WORKBOOK_PART),
105
+ relationship('rId2', REL.coreProps, CORE_PROPS_PART),
106
+ relationship('rId3', REL.extProps, APP_PROPS_PART),
106
107
  ...rootRefs.map((ref, i) => relationship(`rId${4 + i}`, ref.relType, ref.entryPath)),
107
108
  ]);
108
109
  }
@@ -221,19 +222,19 @@ function filterDatabaseRefersTo(sheetName, range) {
221
222
  export const FIXED_WORKBOOK_REL_COUNT = 2;
222
223
  export function workbookRelsXml(sheetCount, hasSharedStrings, personsRelId, preservedRels, pivots) {
223
224
  return relationshipsPart([
224
- ...Array.from({ length: sheetCount }, (_, i) => relationship(`rId${i + 1}`, REL.worksheet, `worksheets/sheet${i + 1}.xml`)),
225
+ ...Array.from({ length: sheetCount }, (_, i) => relationship(`rId${i + 1}`, REL.worksheet, targetFromWorkbook(worksheetPart(i + 1)))),
225
226
  relationship(`rId${sheetCount + 1}`, REL.styles, 'styles.xml'),
226
- relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT}`, REL.theme, relativePartPath('xl/workbook.xml', THEME_PART_PATH)),
227
+ relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT}`, REL.theme, targetFromWorkbook(THEME_PART_PATH)),
227
228
  ...(hasSharedStrings
228
229
  ? [
229
- relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT + 1}`, REL.sharedStrings, 'sharedStrings.xml'),
230
+ relationship(`rId${sheetCount + FIXED_WORKBOOK_REL_COUNT + 1}`, REL.sharedStrings, targetFromWorkbook(SHARED_STRINGS_PART)),
230
231
  ]
231
232
  : []),
232
233
  ...(personsRelId === null
233
234
  ? []
234
- : [relationship(personsRelId, REL.person, 'persons/person.xml')]),
235
- ...preservedRels.map((ref) => relationship(ref.relId, ref.relType, escapeAttr(relativePartPath('xl/workbook.xml', ref.entryPath)))),
236
- ...pivots.map((pivot) => relationship(pivot.workbookRelId, REL.pivotCacheDefinition, `pivotCache/pivotCacheDefinition${pivot.number}.xml`)),
235
+ : [relationship(personsRelId, REL.person, targetFromWorkbook(PERSONS_PART))]),
236
+ ...preservedRels.map((ref) => relationship(ref.relId, ref.relType, escapeAttr(targetFromWorkbook(ref.entryPath)))),
237
+ ...pivots.map((pivot) => relationship(pivot.workbookRelId, REL.pivotCacheDefinition, targetFromWorkbook(pivotCacheDefinitionPart(pivot.number)))),
237
238
  ]);
238
239
  }
239
240
  export function corePropsXml(properties) {
@@ -1,4 +1,4 @@
1
- import type { Cell } from '../../core/cell.ts';
1
+ import { type Cell } from '../../core/cell.ts';
2
2
  import type { ColumnProperties, RowProperties, Worksheet } from '../../core/worksheet.ts';
3
3
  import { type HyperlinkPlan } from './hyperlinks.ts';
4
4
  import type { BackgroundPlan, CommentPlan, DrawingPlan, PivotPlan, PreservedReferencePlan, PrinterSettingsPlan, TablePlan, ThreadedCommentPlan } from './package-plan.ts';
@@ -1,4 +1,5 @@
1
1
  import { decodeRange, encodeAddress } from '../../core/address.js';
2
+ import { cellHasOwnStyle } from '../../core/cell.js';
2
3
  import { DEFAULT_DATE_NUMFMT, dateToSerial } from '../../core/date.js';
3
4
  import { mangleFormula } from '../../core/formula.js';
4
5
  import { NAMED_STYLE_ID } from '../../core/internal.js';
@@ -6,12 +7,12 @@ import { CELL_STYLE_FACETS, pickStyleFacets } from '../../core/style.js';
6
7
  import { detectValueType, isDataTableFormulaValue, isErrorValue, isFormulaValue, isHyperlinkValue, isRichTextValue, isSharedFormulaValue, } from '../../core/value.js';
7
8
  import { AuthoringError, InternalError } from '../../errors.js';
8
9
  import { assertWritableNumber, escapeAttr, escapeSpreadsheetText, escapeText, numberText, textAttr, textElement, XML_DECLARATION, } from '../../xml/xml.js';
9
- import { relativePartPath } from '../opc/part-paths.js';
10
10
  import { relationship, relationshipsPart } from '../opc/rels.js';
11
11
  import { conditionalFormattingsExtXml, conditionalFormattingsXml } from './conditional-formatting.js';
12
12
  import { dataValidationsExtXml, dataValidationsXml } from './data-validation.js';
13
13
  import { hyperlinksXml } from './hyperlinks.js';
14
14
  import { SLICER_LIST_EXT_URI } from './namespaces.js';
15
+ import { commentsPart, drawingPart, mediaPart, pivotTablePart, printerSettingsPart, tablePart, targetFromWorksheet, threadedCommentsPart, vmlDrawingPart, } from './part-names.js';
15
16
  import { NS, REL } from './relationships.js';
16
17
  import { richTextRunsXml } from './rich-text.js';
17
18
  import { planSharedFormulas } from './shared-formulas.js';
@@ -115,7 +116,7 @@ export function buildColumnDefaults(sheet) {
115
116
  }
116
117
  export function renderRow(entry, ctx) {
117
118
  const { number, cells, properties } = entry;
118
- const rendered = cells.filter((cell) => cell.value !== null || hasOwnStyle(cell));
119
+ const rendered = cells.filter((cell) => cell.value !== null || cellHasOwnStyle(cell));
119
120
  const attrs = rowAttrs(properties, ctx.styles, ctx.collapsedSummaries.has(number));
120
121
  if (rendered.length === 0 && attrs === '')
121
122
  return { xml: '', minCol: Infinity, maxCol: -Infinity };
@@ -204,33 +205,35 @@ function tablePartsXml(tables) {
204
205
  }
205
206
  export function worksheetRelsXml(tables, drawing, comments, threadedComments, printerSettings, background, hyperlinks, preservedReferences, pivots) {
206
207
  const rels = [
207
- ...tables.map(({ relId, number }) => relationship(relId, REL.table, `../tables/table${number}.xml`)),
208
- ...pivots.map((pivot) => relationship(pivot.sheetRelId, REL.pivotTable, `../pivotTables/pivotTable${pivot.number}.xml`)),
208
+ ...tables.map(({ relId, number }) => relationship(relId, REL.table, targetFromWorksheet(tablePart(number)))),
209
+ ...pivots.map((pivot) => relationship(pivot.sheetRelId, REL.pivotTable, targetFromWorksheet(pivotTablePart(pivot.number)))),
209
210
  ...(drawing === null
210
211
  ? []
211
- : [relationship(drawing.relId, REL.drawing, `../drawings/drawing${drawing.number}.xml`)]),
212
+ : [
213
+ relationship(drawing.relId, REL.drawing, targetFromWorksheet(drawingPart(drawing.number))),
214
+ ]),
212
215
  ...(comments === null
213
216
  ? []
214
217
  : [
215
- relationship(comments.vmlRelId, REL.vmlDrawing, `../drawings/vmlDrawing${comments.number}.vml`),
216
- relationship(comments.commentsRelId, REL.comments, `../comments${comments.number}.xml`),
218
+ relationship(comments.vmlRelId, REL.vmlDrawing, targetFromWorksheet(vmlDrawingPart(comments.number))),
219
+ relationship(comments.commentsRelId, REL.comments, targetFromWorksheet(commentsPart(comments.number))),
217
220
  ]),
218
221
  ...(threadedComments === null
219
222
  ? []
220
223
  : [
221
- relationship(threadedComments.relId, REL.threadedComment, `../threadedComments/threadedComment${threadedComments.number}.xml`),
224
+ relationship(threadedComments.relId, REL.threadedComment, targetFromWorksheet(threadedCommentsPart(threadedComments.number))),
222
225
  ]),
223
226
  ...(printerSettings === null
224
227
  ? []
225
228
  : [
226
- relationship(printerSettings.relId, REL.printerSettings, `../printerSettings/printerSettings${printerSettings.number}.bin`),
229
+ relationship(printerSettings.relId, REL.printerSettings, targetFromWorksheet(printerSettingsPart(printerSettings.number))),
227
230
  ]),
228
231
  ...(background === null
229
232
  ? []
230
233
  : [
231
- relationship(background.relId, REL.image, `../media/image${background.mediaNumber}.${background.extension}`),
234
+ relationship(background.relId, REL.image, targetFromWorksheet(mediaPart(background.mediaNumber, background.extension))),
232
235
  ]),
233
- ...preservedReferences.map((reference) => relationship(reference.relId, reference.relType, escapeAttr(relativePartPath('xl/worksheets/sheet1.xml', reference.entryPath)))),
236
+ ...preservedReferences.map((reference) => relationship(reference.relId, reference.relType, escapeAttr(targetFromWorksheet(reference.entryPath)))),
234
237
  ...hyperlinks
235
238
  .filter((link) => link.relId !== undefined && link.target !== undefined)
236
239
  .map((link) => relationship(link.relId, REL.hyperlink, link.target, {
@@ -409,11 +412,6 @@ function cellXml(cell, style, shared, sharedStrings) {
409
412
  return `<c r="${ref}"${s}/>`;
410
413
  throw new InternalError(`writing a ${detectValueType(value)} cell value has no arm: every CellValue kind is handled above`);
411
414
  }
412
- function hasOwnStyle(cell) {
413
- return (CELL_STYLE_FACETS.some((facet) => cell[facet] !== undefined) ||
414
- cell.quotePrefix === true ||
415
- cell[NAMED_STYLE_ID] !== undefined);
416
- }
417
415
  function cellFormulaXml(ref, s, value, shared) {
418
416
  if (shared !== undefined) {
419
417
  if (shared.ref !== undefined && isFormulaValue(value)) {
@@ -172,6 +172,12 @@ export declare class WorkbookStreamWriter {
172
172
  * Assemble the workbook into its package, stream the bytes through {@link stream}, and resolve with
173
173
  * the same bytes. Every sheet is frozen first, so a row added after this rejects legibly. Idempotent
174
174
  * only in that a second call throws rather than re-emitting.
175
+ *
176
+ * If assembling or zipping the package fails, the returned promise rejects *and* every stream this
177
+ * writer was given or handed out is destroyed with that error. A caller piping {@link stream}, or
178
+ * awaiting its own sink, therefore sees an `error` rather than waiting on a stream that will never
179
+ * end; and it is an `error` rather than a clean `end` because the bytes written so far are a
180
+ * truncated package that must not be read as a whole one.
175
181
  */
176
182
  commit(): Promise<Uint8Array>;
177
183
  }