@shbernal/ts-xlsx 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/core/autofilter.d.ts +1 -1
  2. package/dist/core/autofilter.js +3 -5
  3. package/dist/core/axis-handle.d.ts +8 -0
  4. package/dist/core/axis-handle.js +14 -0
  5. package/dist/core/cell.d.ts +24 -0
  6. package/dist/core/cell.js +9 -1
  7. package/dist/core/column.d.ts +4 -1
  8. package/dist/core/column.js +31 -35
  9. package/dist/core/conditional-formatting.d.ts +5 -5
  10. package/dist/core/conditional-formatting.js +11 -25
  11. package/dist/core/data-validation.d.ts +3 -3
  12. package/dist/core/data-validation.js +7 -15
  13. package/dist/core/image.d.ts +1 -1
  14. package/dist/core/image.js +2 -4
  15. package/dist/core/merge-index.d.ts +31 -0
  16. package/dist/core/merge-index.js +60 -0
  17. package/dist/core/merge.d.ts +0 -10
  18. package/dist/core/merge.js +0 -8
  19. package/dist/core/page-setup.d.ts +72 -2
  20. package/dist/core/page-setup.js +34 -8
  21. package/dist/core/pivot-table.d.ts +1 -1
  22. package/dist/core/pivot-table.js +4 -8
  23. package/dist/core/row.d.ts +4 -1
  24. package/dist/core/row.js +19 -23
  25. package/dist/core/style.d.ts +51 -7
  26. package/dist/core/style.js +29 -34
  27. package/dist/core/table.d.ts +1 -1
  28. package/dist/core/table.js +3 -5
  29. package/dist/core/used-extent.d.ts +44 -0
  30. package/dist/core/used-extent.js +102 -0
  31. package/dist/core/worksheet.d.ts +17 -6
  32. package/dist/core/worksheet.js +48 -42
  33. package/dist/customui/ribbon.js +4 -6
  34. package/dist/io/opc/inflate.js +1 -1
  35. package/dist/io/opc/part-paths.d.ts +1 -0
  36. package/dist/io/opc/part-paths.js +15 -0
  37. package/dist/io/opc/read-opc.d.ts +22 -3
  38. package/dist/io/opc/read-opc.js +11 -39
  39. package/dist/io/opc/rels.d.ts +1 -6
  40. package/dist/io/opc/rels.js +1 -4
  41. package/dist/io/style/xf-style.d.ts +26 -2
  42. package/dist/io/style/xf-style.js +6 -0
  43. package/dist/io/xlsb/read-styles.js +6 -3
  44. package/dist/io/xlsb/read.d.ts +1 -1
  45. package/dist/io/xlsb/read.js +5 -8
  46. package/dist/io/xlsx/cell-accumulator.d.ts +1 -1
  47. package/dist/io/xlsx/cell-accumulator.js +1 -1
  48. package/dist/io/xlsx/cell-value.js +1 -1
  49. package/dist/io/xlsx/color-xml.js +1 -1
  50. package/dist/io/xlsx/comments.js +2 -1
  51. package/dist/io/xlsx/conditional-formatting.d.ts +3 -2
  52. package/dist/io/xlsx/conditional-formatting.js +22 -21
  53. package/dist/io/xlsx/data-validation.d.ts +9 -7
  54. package/dist/io/xlsx/data-validation.js +10 -9
  55. package/dist/io/xlsx/edit-vba.js +4 -11
  56. package/dist/io/xlsx/hyperlinks.d.ts +4 -2
  57. package/dist/io/xlsx/hyperlinks.js +20 -17
  58. package/dist/io/xlsx/images.js +15 -14
  59. package/dist/io/xlsx/package-plan.d.ts +9 -1
  60. package/dist/io/xlsx/package-plan.js +29 -16
  61. package/dist/io/xlsx/part-names.d.ts +41 -0
  62. package/dist/io/xlsx/part-names.js +46 -0
  63. package/dist/io/xlsx/read-pivot.js +2 -1
  64. package/dist/io/xlsx/read-rows.js +12 -13
  65. package/dist/io/xlsx/read-shared-strings.js +2 -1
  66. package/dist/io/xlsx/read-styles.d.ts +1 -1
  67. package/dist/io/xlsx/read-styles.js +63 -42
  68. package/dist/io/xlsx/read-worksheet.d.ts +10 -1
  69. package/dist/io/xlsx/read-worksheet.js +43 -56
  70. package/dist/io/xlsx/read.d.ts +2 -3
  71. package/dist/io/xlsx/read.js +81 -73
  72. package/dist/io/xlsx/rich-runs.d.ts +1 -1
  73. package/dist/io/xlsx/rich-runs.js +2 -1
  74. package/dist/io/xlsx/sheet-properties.js +21 -28
  75. package/dist/io/xlsx/styles.js +23 -20
  76. package/dist/io/xlsx/tables.js +2 -1
  77. package/dist/io/xlsx/theme-xml.js +1 -1
  78. package/dist/io/xlsx/threaded-comments.js +2 -1
  79. package/dist/io/xlsx/workbook-xml.js +29 -28
  80. package/dist/io/xlsx/worksheet-xml.d.ts +1 -1
  81. package/dist/io/xlsx/worksheet-xml.js +14 -16
  82. package/dist/io/xlsx/write-stream.d.ts +6 -0
  83. package/dist/io/xlsx/write-stream.js +30 -22
  84. package/dist/io/xlsx/write.d.ts +5 -0
  85. package/dist/io/xlsx/write.js +155 -131
  86. package/dist/token-set.d.ts +15 -0
  87. package/dist/token-set.js +4 -0
  88. package/dist/vba/cfb-format.d.ts +24 -0
  89. package/dist/vba/cfb-format.js +12 -0
  90. package/dist/vba/cfb-writer.js +17 -17
  91. package/dist/vba/cfb.js +1 -7
  92. package/dist/vba/ms-ovba.js +32 -11
  93. package/dist/vba/project.js +13 -13
  94. package/dist/vba/vba-encoding.js +1 -1
  95. package/dist/xml/xml-read.d.ts +59 -74
  96. package/dist/xml/xml-read.js +83 -176
  97. package/dist/xml/xml-scan.d.ts +100 -0
  98. package/dist/xml/xml-scan.js +208 -0
  99. package/dist/xml/xml.d.ts +1 -1
  100. package/package.json +2 -3
@@ -0,0 +1,46 @@
1
+ import { relativePartPath } from '../opc/part-paths.js';
2
+ export const WORKBOOK_PART = 'xl/workbook.xml';
3
+ export const STYLES_PART = 'xl/styles.xml';
4
+ export const SHARED_STRINGS_PART = 'xl/sharedStrings.xml';
5
+ export const PERSONS_PART = 'xl/persons/person.xml';
6
+ export const CORE_PROPS_PART = 'docProps/core.xml';
7
+ export const APP_PROPS_PART = 'docProps/app.xml';
8
+ export function worksheetPart(number) {
9
+ return `xl/worksheets/sheet${number}.xml`;
10
+ }
11
+ export function tablePart(number) {
12
+ return `xl/tables/table${number}.xml`;
13
+ }
14
+ export function mediaPart(number, extension) {
15
+ return `xl/media/image${number}.${extension}`;
16
+ }
17
+ export function drawingPart(number) {
18
+ return `xl/drawings/drawing${number}.xml`;
19
+ }
20
+ export function vmlDrawingPart(number) {
21
+ return `xl/drawings/vmlDrawing${number}.vml`;
22
+ }
23
+ export function commentsPart(number) {
24
+ return `xl/comments${number}.xml`;
25
+ }
26
+ export function threadedCommentsPart(number) {
27
+ return `xl/threadedComments/threadedComment${number}.xml`;
28
+ }
29
+ export function printerSettingsPart(number) {
30
+ return `xl/printerSettings/printerSettings${number}.bin`;
31
+ }
32
+ export function pivotTablePart(number) {
33
+ return `xl/pivotTables/pivotTable${number}.xml`;
34
+ }
35
+ export function pivotCacheDefinitionPart(number) {
36
+ return `xl/pivotCache/pivotCacheDefinition${number}.xml`;
37
+ }
38
+ export function pivotCacheRecordsPart(number) {
39
+ return `xl/pivotCache/pivotCacheRecords${number}.xml`;
40
+ }
41
+ export function targetFromWorksheet(partPath) {
42
+ return relativePartPath(worksheetPart(1), partPath);
43
+ }
44
+ export function targetFromWorkbook(partPath) {
45
+ return relativePartPath(WORKBOOK_PART, partPath);
46
+ }
@@ -1,5 +1,6 @@
1
1
  import { isDeclarablePivotSourceKind, pivotMetricFromSubtotal, } from '../../core/pivot-table.js';
2
- import { localName, numInteger, parseXml } from '../../xml/xml-read.js';
2
+ import { parseXml } from '../../xml/xml-read.js';
3
+ import { localName, numInteger } from '../../xml/xml-scan.js';
3
4
  export function parsePivotTable(tableXml, cacheXml) {
4
5
  const { fields, source } = parsePivotCacheDefinition(cacheXml);
5
6
  const def = parsePivotTableDefinition(tableXml);
@@ -1,12 +1,13 @@
1
1
  import { MAX_COLUMN, MAX_ROW } from '../../core/address.js';
2
2
  import { AuthoringError } from '../../errors.js';
3
- import { boolStrict, closeEmptyElements, localName, numInteger, xmlEvents, } from '../../xml/xml-read.js';
4
- import { packageAccessors } from '../opc/read-opc.js';
5
- import { inflateSpreadsheetPackage, unsupportedWorkbookPart } from '../opc/sniff-format.js';
3
+ import { closeEmptyElements } from '../../xml/xml-read.js';
4
+ import { boolStrict, localName, numInteger, xmlEvents } from '../../xml/xml-scan.js';
5
+ import { openSpreadsheetPackage, readPartRelationships } from '../opc/read-opc.js';
6
+ import { unsupportedWorkbookPart } from '../opc/sniff-format.js';
6
7
  import { CellAccumulator } from './cell-accumulator.js';
7
8
  import { XlsxParseError } from './errors.js';
8
9
  import { parseSharedStrings } from './read-shared-strings.js';
9
- import { DEFAULT_MAX_UNCOMPRESSED, parseRelationships, parseStyleTable, parseWorkbookSheets, resolveWorkbookPart, } from './read.js';
10
+ import { parseStyleTable, parseWorkbookSheets, } from './read.js';
10
11
  export function* readSheetRows(data, options = {}) {
11
12
  const pkg = openPackage(data, options.maxUncompressedBytes);
12
13
  const chosen = pickSheet(pkg.sheets, options.sheet);
@@ -23,14 +24,13 @@ export function* readWorkbookStream(data, options = {}) {
23
24
  }
24
25
  }
25
26
  function openPackage(data, maxUncompressedBytes) {
26
- const cap = maxUncompressedBytes ?? DEFAULT_MAX_UNCOMPRESSED;
27
- const { partText: text } = packageAccessors(inflateSpreadsheetPackage(data, cap));
28
- const workbookXml = text('xl/workbook.xml');
27
+ const { pkg, workbookXml } = openSpreadsheetPackage(data, maxUncompressedBytes);
28
+ const { partText: text } = pkg;
29
29
  if (workbookXml === undefined) {
30
30
  throw unsupportedWorkbookPart(text, 'the binary .xlsb format (BIFF12) cannot be row-streamed yet; read it with readXlsx or readXlsb');
31
31
  }
32
32
  const sheets = parseWorkbookSheets(workbookXml);
33
- const rels = parseRelationships(text('xl/_rels/workbook.xml.rels') ?? '');
33
+ const rels = readPartRelationships('xl/workbook.xml', text);
34
34
  const sharedStrings = parseSharedStrings(text('xl/sharedStrings.xml') ?? '');
35
35
  const { cellXfs: xfStyles } = parseStyleTable(text('xl/styles.xml') ?? '');
36
36
  return {
@@ -38,9 +38,8 @@ function openPackage(data, maxUncompressedBytes) {
38
38
  sharedStrings,
39
39
  xfStyles,
40
40
  sheetXml(relId) {
41
- const target = rels.get(relId);
42
- const path = target === undefined ? undefined : resolveWorkbookPart(target);
43
- return path === undefined ? undefined : text(path);
41
+ const target = rels.byId(relId)?.target;
42
+ return target === undefined ? undefined : text(rels.pathOf(target));
44
43
  },
45
44
  };
46
45
  }
@@ -88,7 +87,7 @@ class StreamedSheetReader {
88
87
  }
89
88
  get merges() {
90
89
  this.#ensureScanned();
91
- return this.#merges;
90
+ return [...this.#merges];
92
91
  }
93
92
  #ensureScanned() {
94
93
  if (this.#scanned)
@@ -156,7 +155,7 @@ function* scanSheet(xml, sharedStrings, xfStyles, hiddenColumns, merges) {
156
155
  }
157
156
  }
158
157
  function collectHiddenColumn(attrs, hiddenColumns) {
159
- if (attrs.hidden !== '1' && attrs.hidden !== 'true')
158
+ if (!boolStrict(attrs.hidden))
160
159
  return;
161
160
  const min = numInteger(attrs.min, 1);
162
161
  const max = numInteger(attrs.max, 1);
@@ -1,4 +1,5 @@
1
- import { localName, parseXml } from '../../xml/xml-read.js';
1
+ import { parseXml } from '../../xml/xml-read.js';
2
+ import { localName } from '../../xml/xml-scan.js';
2
3
  import { RunAccumulator } from './rich-runs.js';
3
4
  export function parseSharedStrings(xml) {
4
5
  if (xml === '')
@@ -1,5 +1,5 @@
1
1
  import { type Font, type TableStyleTable } from '../../core/style.ts';
2
- import { type XmlAttributes } from '../../xml/xml-read.ts';
2
+ import { type XmlAttributes } from '../../xml/xml-scan.ts';
3
3
  import { type StyleTable } from '../style/xf-style.ts';
4
4
  export type FontDraft = {
5
5
  -readonly [K in keyof Font]?: Font[K];
@@ -1,6 +1,7 @@
1
- import { isBorderStyle, isFillPatternType, isFontScheme, isFontVerticalAlignment, isHorizontalAlignment, isNamedUnderlineStyle, isVerticalAlignment, } from '../../core/style.js';
2
- import { boolPresent, boolStrict, closeEmptyElements, localName, numFinite, numInteger, openElements, xmlEvents, } from '../../xml/xml-read.js';
3
- import { numFmtCodeFor, resolveStyleTable, } from '../style/xf-style.js';
1
+ import { ALIGNMENT_FACETS, isBorderStyle, isFillPatternType, isFontScheme, isFontVerticalAlignment, isNamedUnderlineStyle, } from '../../core/style.js';
2
+ import { closeEmptyElements, elementSubtrees, openElements, } from '../../xml/xml-read.js';
3
+ import { boolPresent, boolStrict, boolTristate, localName, numFinite, numInteger, xmlEvents, } from '../../xml/xml-scan.js';
4
+ import { NO_PRESERVED_STYLE_TABLES, numFmtCodeFor, resolveStyleTable, } from '../style/xf-style.js';
4
5
  import { parseColor } from './color-xml.js';
5
6
  const BORDER_EDGE_NAMES = ['left', 'right', 'top', 'bottom', 'diagonal'];
6
7
  const BORDER_EDGES = new Set(BORDER_EDGE_NAMES);
@@ -15,7 +16,7 @@ const STYLE_EMPTY_CLOSES = new Set([
15
16
  ]);
16
17
  export function parseStyleTable(xml) {
17
18
  if (xml === '')
18
- return { cellXfs: [], namedStyles: [] };
19
+ return { cellXfs: [], namedStyles: [], preserved: NO_PRESERVED_STYLE_TABLES };
19
20
  let fills = [];
20
21
  let fonts = [];
21
22
  let borders = [];
@@ -54,7 +55,17 @@ export function parseStyleTable(xml) {
54
55
  }
55
56
  next = events.next();
56
57
  }
57
- return resolveStyleTable({ directXfs: xfStyles, namedXfs, labels: cellStyleNames, fonts });
58
+ const { fragments, attributes } = elementSubtrees(xml, PRESERVED_SUBTREES);
59
+ const preserved = {
60
+ dxfs: fragments.get('dxfs') ?? [],
61
+ indexedColors: fragments.get('indexedColors') ?? [],
62
+ mruColors: fragments.get('mruColors') ?? [],
63
+ tableStyles: buildTableStyleTable(xml, fragments.get('tableStyles') ?? [], attributes.get('tableStyles')),
64
+ };
65
+ return {
66
+ ...resolveStyleTable({ directXfs: xfStyles, namedXfs, labels: cellStyleNames, fonts }),
67
+ preserved,
68
+ };
58
69
  }
59
70
  function* until(events, container) {
60
71
  let next = events.next();
@@ -389,53 +400,58 @@ function borderToStyle(draft) {
389
400
  }
390
401
  function parseAlignment(attrs) {
391
402
  const out = {};
392
- if (attrs.horizontal !== undefined &&
393
- attrs.horizontal !== 'general' &&
394
- isHorizontalAlignment(attrs.horizontal)) {
395
- out.horizontal = attrs.horizontal;
403
+ for (const facet of ALIGNMENT_FACETS) {
404
+ const raw = attrs[facet.key];
405
+ switch (facet.kind) {
406
+ case 'token':
407
+ if (raw !== undefined && raw !== facet.omit && facet.isValid(raw)) {
408
+ assignAlignmentToken(out, facet.key, raw);
409
+ }
410
+ break;
411
+ case 'number': {
412
+ const value = numFinite(raw);
413
+ if (value !== undefined && value !== 0)
414
+ out[facet.key] = value;
415
+ break;
416
+ }
417
+ case 'flag':
418
+ if (boolStrict(raw))
419
+ out[facet.key] = true;
420
+ break;
421
+ }
396
422
  }
397
- if (attrs.vertical !== undefined && isVerticalAlignment(attrs.vertical))
398
- out.vertical = attrs.vertical;
399
- const rotation = numFinite(attrs.textRotation);
400
- if (rotation !== undefined && rotation !== 0)
401
- out.textRotation = rotation;
402
- if (boolStrict(attrs.wrapText))
403
- out.wrapText = true;
404
- const indent = numInteger(attrs.indent);
405
- if (indent !== undefined && indent !== 0)
406
- out.indent = indent;
407
- if (boolStrict(attrs.shrinkToFit))
408
- out.shrinkToFit = true;
409
- const order = numInteger(attrs.readingOrder);
410
- if (order !== undefined && order !== 0)
411
- out.readingOrder = order;
412
423
  return Object.keys(out).length > 0 ? out : undefined;
413
424
  }
425
+ function assignAlignmentToken(out, key, raw) {
426
+ out[key] = raw;
427
+ }
414
428
  function parseProtection(attrs) {
415
429
  const out = {};
416
- if (attrs.locked === '0' || attrs.locked === 'false')
430
+ if (boolTristate(attrs.locked) === false)
417
431
  out.locked = false;
418
432
  if (boolStrict(attrs.hidden))
419
433
  out.hidden = true;
420
434
  return Object.keys(out).length > 0 ? out : undefined;
421
435
  }
422
436
  export function parseIndexedColors(stylesXml) {
423
- return elementFragments(stylesXml, 'indexedColors', 'rgbColor');
437
+ return capturedFragments(stylesXml, 'indexedColors');
424
438
  }
425
439
  export function parseMruColors(stylesXml) {
426
- return elementFragments(stylesXml, 'mruColors', 'color');
440
+ return capturedFragments(stylesXml, 'mruColors');
427
441
  }
428
442
  export function parseTableStyles(stylesXml) {
429
- const styles = elementFragments(stylesXml, 'tableStyles', 'tableStyle');
430
- const table = { styles };
431
- for (const { attrs } of openElements(stylesXml, 'tableStyles')) {
432
- if (attrs.defaultTableStyle !== undefined)
433
- table.defaultTableStyle = attrs.defaultTableStyle;
434
- if (attrs.defaultPivotStyle !== undefined)
435
- table.defaultPivotStyle = attrs.defaultPivotStyle;
436
- break;
443
+ const { fragments, attributes } = elementSubtrees(stylesXml, new Map([['tableStyles', 'tableStyle']]));
444
+ return buildTableStyleTable(stylesXml, fragments.get('tableStyles') ?? [], attributes.get('tableStyles'));
445
+ }
446
+ function buildTableStyleTable(stylesXml, styles, containerAttrs) {
447
+ const table = { styles: [...styles] };
448
+ if (containerAttrs?.defaultTableStyle !== undefined) {
449
+ table.defaultTableStyle = containerAttrs.defaultTableStyle;
437
450
  }
438
- const namespaces = fragmentNamespaces(stylesXml, styles);
451
+ if (containerAttrs?.defaultPivotStyle !== undefined) {
452
+ table.defaultPivotStyle = containerAttrs.defaultPivotStyle;
453
+ }
454
+ const namespaces = fragmentNamespaces(stylesXml, table.styles);
439
455
  if (namespaces.length > 0)
440
456
  table.namespaces = namespaces;
441
457
  return table;
@@ -470,11 +486,16 @@ function fragmentNamespaces(stylesXml, fragments) {
470
486
  ignorable: ignorable.has(prefix),
471
487
  }));
472
488
  }
473
- function elementFragments(xml, container, child) {
474
- const block = new RegExp(`<${container}\\b[^>]*>([\\s\\S]*?)</${container}>`).exec(xml);
475
- if (block === null)
489
+ const PRESERVED_SUBTREES = new Map([
490
+ ['dxfs', 'dxf'],
491
+ ['indexedColors', 'rgbColor'],
492
+ ['mruColors', 'color'],
493
+ ['tableStyles', 'tableStyle'],
494
+ ]);
495
+ function capturedFragments(xml, container) {
496
+ const child = PRESERVED_SUBTREES.get(container);
497
+ if (child === undefined)
476
498
  return [];
477
- const inner = block[1] ?? '';
478
- const pattern = new RegExp(`<${child}\\b[^>]*/>|<${child}\\b[^>]*>[\\s\\S]*?</${child}>`, 'g');
479
- return [...inner.matchAll(pattern)].map((m) => m[0] ?? '');
499
+ const { fragments } = elementSubtrees(xml, new Map([[container, child]]));
500
+ return [...(fragments.get(container) ?? [])];
480
501
  }
@@ -1,4 +1,13 @@
1
1
  import type { Worksheet } from '../../core/worksheet.ts';
2
+ import { type SaxPass } from '../../xml/xml-read.ts';
2
3
  import type { XfStyle } from '../style/xf-style.ts';
3
4
  import type { SharedString } from './cell-value.ts';
4
- export declare function parseWorksheet(xml: string, sheet: Worksheet, sharedStrings: readonly SharedString[], xfStyles: ReadonlyArray<XfStyle>): void;
5
+ /**
6
+ * A pass reading a worksheet part into `sheet`: cells and their styles, the column and row metadata,
7
+ * merges, the autofilter, page breaks, and the view and print layout. It commits as it goes rather
8
+ * than gathering, so it has nothing to hand back once the parse ends.
9
+ *
10
+ * Offered as a pass because the worksheet is the largest part in a package and four other readers
11
+ * want the same events, so the five share one parse of it rather than scanning it once each.
12
+ */
13
+ export declare function worksheetPass(sheet: Worksheet, sharedStrings: readonly SharedString[], xfStyles: ReadonlyArray<XfStyle>): SaxPass;
@@ -1,13 +1,17 @@
1
1
  import { MAX_COLUMN, MAX_ROW, tryDecodeRange } from '../../core/address.js';
2
2
  import { isCustomFilterOperator, } from '../../core/autofilter.js';
3
3
  import { INTERNAL } from '../../core/internal.js';
4
- import { isPageOrder, isPageOrientation, } from '../../core/page-setup.js';
4
+ import { HEADER_FOOTER_ELEMENTS, MARGIN_SIDES, PAGE_SETUP_FACETS, PRINT_OPTION_FLAGS, } from '../../core/page-setup.js';
5
5
  import { SHEET_PROTECTION_FLAGS, } from '../../core/protection.js';
6
6
  import { assignStyleFacets } from '../../core/style.js';
7
- import { boolPresent, boolStrict, boolTristate, decodeSpreadsheetText, enumToken, localName, numFinite, numInteger, parseXml, } from '../../xml/xml-read.js';
7
+ import {} from '../../xml/xml-read.js';
8
+ import { boolPresent, boolStrict, boolTristate, decodeSpreadsheetText, localName, numFinite, numInteger, } from '../../xml/xml-scan.js';
8
9
  import { CellAccumulator } from './cell-accumulator.js';
9
10
  import { parseColor } from './color-xml.js';
10
- const MARGIN_SIDES = ['left', 'right', 'top', 'bottom', 'header', 'footer'];
11
+ const HEADER_FOOTER_CHILDREN = new Set(HEADER_FOOTER_ELEMENTS);
12
+ function isHeaderFooterElement(local) {
13
+ return HEADER_FOOTER_CHILDREN.has(local);
14
+ }
11
15
  const WORKSHEET_EMPTY_CLOSES = new Set(['c', 'autoFilter']);
12
16
  function pendingFilterCriteria(values, blank, predicates, and) {
13
17
  if (values !== null && (values.length > 0 || blank)) {
@@ -19,7 +23,7 @@ function pendingFilterCriteria(values, blank, predicates, and) {
19
23
  return null;
20
24
  }
21
25
  function parseSheetProtection(attrs) {
22
- if (attrs.sheet === '0' || attrs.sheet === 'false')
26
+ if (boolTristate(attrs.sheet) === false)
23
27
  return undefined;
24
28
  const flags = {};
25
29
  for (const { key } of SHEET_PROTECTION_FLAGS) {
@@ -125,7 +129,7 @@ class PageBreakAccumulator {
125
129
  this.#target.push(brk);
126
130
  }
127
131
  }
128
- export function parseWorksheet(xml, sheet, sharedStrings, xfStyles) {
132
+ export function worksheetPass(sheet, sharedStrings, xfStyles) {
129
133
  const cell = new CellAccumulator({ richRuns: true });
130
134
  let rowStyle = -1;
131
135
  let rowCustomFormat = false;
@@ -141,11 +145,15 @@ export function parseWorksheet(xml, sheet, sharedStrings, xfStyles) {
141
145
  const style = styleIndex >= 0 ? xfStyles[styleIndex] : xfStyles[0];
142
146
  cell.finalize(sheet, sharedStrings, style);
143
147
  };
144
- parseXml(xml, {
148
+ const handlers = {
145
149
  onOpen(name, attrs, selfClosing) {
146
150
  const local = localName(name);
147
151
  if (cell.openElement(local, attrs, selfClosing))
148
152
  return;
153
+ if (HEADER_FOOTER_CHILDREN.has(local)) {
154
+ cell.capture();
155
+ return;
156
+ }
149
157
  switch (local) {
150
158
  case 'col':
151
159
  applyColumn(sheet, attrs, xfStyles, columnStyle);
@@ -155,14 +163,6 @@ export function parseWorksheet(xml, sheet, sharedStrings, xfStyles) {
155
163
  rowStyle = numInteger(attrs.s, 0) ?? -1;
156
164
  rowCustomFormat = boolStrict(attrs.customFormat);
157
165
  break;
158
- case 'oddHeader':
159
- case 'oddFooter':
160
- case 'evenHeader':
161
- case 'evenFooter':
162
- case 'firstHeader':
163
- case 'firstFooter':
164
- cell.capture();
165
- break;
166
166
  case 'mergeCell':
167
167
  if (attrs.ref !== undefined && attrs.ref !== '') {
168
168
  try {
@@ -229,15 +229,11 @@ export function parseWorksheet(xml, sheet, sharedStrings, xfStyles) {
229
229
  }
230
230
  if (claimed === 'claimed')
231
231
  return;
232
+ if (isHeaderFooterElement(local)) {
233
+ sheet.headerFooter[local] = decodeSpreadsheetText(cell.capturedText);
234
+ return;
235
+ }
232
236
  switch (local) {
233
- case 'oddHeader':
234
- case 'oddFooter':
235
- case 'evenHeader':
236
- case 'evenFooter':
237
- case 'firstHeader':
238
- case 'firstFooter':
239
- sheet.headerFooter[local] = decodeSpreadsheetText(cell.capturedText);
240
- break;
241
237
  case 'row':
242
238
  rowStyle = -1;
243
239
  rowCustomFormat = false;
@@ -256,7 +252,8 @@ export function parseWorksheet(xml, sheet, sharedStrings, xfStyles) {
256
252
  break;
257
253
  }
258
254
  },
259
- }, { closeEmptyElements: WORKSHEET_EMPTY_CLOSES });
255
+ };
256
+ return { handlers, closeEmptyElements: WORKSHEET_EMPTY_CLOSES };
260
257
  }
261
258
  function applySheetProperties(local, attrs, sheet) {
262
259
  switch (local) {
@@ -346,21 +343,11 @@ function applyRow(sheet, attrs) {
346
343
  row.collapsed = true;
347
344
  }
348
345
  function applyPrintOptions(printOptions, attrs) {
349
- const horizontalCentered = boolTristate(attrs.horizontalCentered);
350
- if (horizontalCentered !== undefined)
351
- printOptions.horizontalCentered = horizontalCentered;
352
- const verticalCentered = boolTristate(attrs.verticalCentered);
353
- if (verticalCentered !== undefined)
354
- printOptions.verticalCentered = verticalCentered;
355
- const headings = boolTristate(attrs.headings);
356
- if (headings !== undefined)
357
- printOptions.headings = headings;
358
- const gridLines = boolTristate(attrs.gridLines);
359
- if (gridLines !== undefined)
360
- printOptions.gridLines = gridLines;
361
- const gridLinesSet = boolTristate(attrs.gridLinesSet);
362
- if (gridLinesSet !== undefined)
363
- printOptions.gridLinesSet = gridLinesSet;
346
+ for (const flag of PRINT_OPTION_FLAGS) {
347
+ const value = boolTristate(attrs[flag]);
348
+ if (value !== undefined)
349
+ printOptions[flag] = value;
350
+ }
364
351
  }
365
352
  function applyMargins(margins, attrs) {
366
353
  for (const side of MARGIN_SIDES) {
@@ -370,22 +357,22 @@ function applyMargins(margins, attrs) {
370
357
  }
371
358
  }
372
359
  function applyPageSetup(pageSetup, attrs) {
373
- const paperSize = numInteger(attrs.paperSize, 0);
374
- if (paperSize !== undefined)
375
- pageSetup.paperSize = paperSize;
376
- const scale = numInteger(attrs.scale, 0);
377
- if (scale !== undefined)
378
- pageSetup.scale = scale;
379
- const fitToWidth = numInteger(attrs.fitToWidth, 0);
380
- if (fitToWidth !== undefined)
381
- pageSetup.fitToWidth = fitToWidth;
382
- const fitToHeight = numInteger(attrs.fitToHeight, 0);
383
- if (fitToHeight !== undefined)
384
- pageSetup.fitToHeight = fitToHeight;
385
- const pageOrder = enumToken(attrs.pageOrder, isPageOrder);
386
- if (pageOrder !== undefined)
387
- pageSetup.pageOrder = pageOrder;
388
- const orientation = enumToken(attrs.orientation, isPageOrientation);
389
- if (orientation !== undefined)
390
- pageSetup.orientation = orientation;
360
+ for (const facet of PAGE_SETUP_FACETS) {
361
+ const raw = attrs[facet.key];
362
+ switch (facet.kind) {
363
+ case 'count': {
364
+ const value = numInteger(raw, 0);
365
+ if (value !== undefined)
366
+ pageSetup[facet.key] = value;
367
+ break;
368
+ }
369
+ case 'token':
370
+ if (raw !== undefined && facet.isValid(raw))
371
+ assignPageSetupToken(pageSetup, facet.key, raw);
372
+ break;
373
+ }
374
+ }
375
+ }
376
+ function assignPageSetupToken(pageSetup, key, raw) {
377
+ pageSetup[key] = raw;
391
378
  }
@@ -1,8 +1,7 @@
1
1
  import { Workbook, type WorkbookView } from '../../core/workbook.ts';
2
2
  import { type WorksheetState } from '../../core/worksheet.ts';
3
- import { type ReadXlsxOptions } from '../opc/read-options.ts';
4
- export { parseRelationships, resolveWorkbookPart } from '../opc/read-opc.ts';
5
- export { DEFAULT_MAX_UNCOMPRESSED, type ReadXlsxOptions } from '../opc/read-options.ts';
3
+ import type { ReadXlsxOptions } from '../opc/read-options.ts';
4
+ export type { ReadXlsxOptions } from '../opc/read-options.ts';
6
5
  export type { StyleTable, XfStyle } from '../style/xf-style.ts';
7
6
  export { parseStyleTable } from './read-styles.ts';
8
7
  /**