@rowsncolumns/toolkit 24.4.1 → 24.4.3

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 (131) hide show
  1. package/dist/cjs/excel-parser/cfb-decryptor.d.ts +6 -0
  2. package/dist/cjs/excel-parser/cfb-decryptor.d.ts.map +1 -1
  3. package/dist/cjs/excel-parser/cfb-decryptor.js +6 -0
  4. package/dist/cjs/excel-parser/helpers.d.ts +9 -1
  5. package/dist/cjs/excel-parser/helpers.d.ts.map +1 -1
  6. package/dist/cjs/excel-parser/helpers.js +37 -0
  7. package/dist/cjs/excel-parser/interface.d.ts +10 -1
  8. package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
  9. package/dist/cjs/excel-parser/plugins/ods/index.d.ts +19 -0
  10. package/dist/cjs/excel-parser/plugins/ods/index.d.ts.map +1 -1
  11. package/dist/cjs/excel-parser/plugins/ods/index.js +676 -304
  12. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts +43 -1
  13. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts.map +1 -1
  14. package/dist/cjs/excel-parser/plugins/ods/styles.js +343 -45
  15. package/dist/cjs/excel-parser/plugins/xls/index.d.ts +11 -3
  16. package/dist/cjs/excel-parser/plugins/xls/index.d.ts.map +1 -1
  17. package/dist/cjs/excel-parser/plugins/xls/index.js +55 -6
  18. package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts +4 -0
  19. package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
  20. package/dist/cjs/excel-parser/plugins/xlsb/index.js +21 -9
  21. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts +10 -0
  22. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
  23. package/dist/cjs/excel-parser/plugins/xlsx/charts.js +68 -9
  24. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts +28 -1
  25. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
  26. package/dist/cjs/excel-parser/plugins/xlsx/drawing.js +155 -20
  27. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +26 -1
  28. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  29. package/dist/cjs/excel-parser/plugins/xlsx/index.js +567 -234
  30. package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
  31. package/dist/cjs/excel-parser/plugins/xlsx/pivots.js +125 -37
  32. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts +3 -0
  33. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -1
  34. package/dist/cjs/excel-parser/plugins/xlsx/sheet-views.js +4 -1
  35. package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
  36. package/dist/cjs/excel-parser/plugins/xlsx/styles.js +12 -2
  37. package/dist/cjs/excel-parser/registry.d.ts.map +1 -1
  38. package/dist/cjs/excel-parser/registry.js +2 -0
  39. package/dist/cjs/excel-parser/types.d.ts +7 -1
  40. package/dist/cjs/excel-parser/types.d.ts.map +1 -1
  41. package/dist/cjs/excel-parser/workbook.d.ts +1 -0
  42. package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
  43. package/dist/cjs/excel-parser/workbook.js +4 -0
  44. package/dist/cjs/excel-parser/worker.js +3 -1
  45. package/dist/cjs/exporter/ods/ods-export.d.ts.map +1 -1
  46. package/dist/cjs/exporter/ods/ods-export.js +432 -175
  47. package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -1
  48. package/dist/cjs/exporter/xlsx/chart-builder.js +210 -6
  49. package/dist/cjs/exporter/xlsx/pivot-builder.d.ts +8 -0
  50. package/dist/cjs/exporter/xlsx/pivot-builder.d.ts.map +1 -1
  51. package/dist/cjs/exporter/xlsx/pivot-builder.js +141 -42
  52. package/dist/cjs/exporter/xlsx/style-builder.js +9 -3
  53. package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts +7 -1
  54. package/dist/cjs/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -1
  55. package/dist/cjs/exporter/xlsx/threaded-comments-builder.js +15 -3
  56. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +11 -2
  57. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  58. package/dist/cjs/exporter/xlsx/xlsx-export.js +678 -222
  59. package/dist/cjs/helpers/implicit-intersection.d.ts +16 -0
  60. package/dist/cjs/helpers/implicit-intersection.d.ts.map +1 -0
  61. package/dist/cjs/helpers/implicit-intersection.js +197 -0
  62. package/dist/cjs/print-settings.d.ts +105 -0
  63. package/dist/cjs/print-settings.d.ts.map +1 -0
  64. package/dist/cjs/print-settings.js +494 -0
  65. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  66. package/dist/esm/excel-parser/cfb-decryptor.d.ts +6 -0
  67. package/dist/esm/excel-parser/cfb-decryptor.d.ts.map +1 -1
  68. package/dist/esm/excel-parser/cfb-decryptor.js +6 -0
  69. package/dist/esm/excel-parser/helpers.d.ts +9 -1
  70. package/dist/esm/excel-parser/helpers.d.ts.map +1 -1
  71. package/dist/esm/excel-parser/helpers.js +34 -0
  72. package/dist/esm/excel-parser/interface.d.ts +10 -1
  73. package/dist/esm/excel-parser/interface.d.ts.map +1 -1
  74. package/dist/esm/excel-parser/plugins/ods/index.d.ts +19 -0
  75. package/dist/esm/excel-parser/plugins/ods/index.d.ts.map +1 -1
  76. package/dist/esm/excel-parser/plugins/ods/index.js +347 -33
  77. package/dist/esm/excel-parser/plugins/ods/styles.d.ts +43 -1
  78. package/dist/esm/excel-parser/plugins/ods/styles.d.ts.map +1 -1
  79. package/dist/esm/excel-parser/plugins/ods/styles.js +276 -2
  80. package/dist/esm/excel-parser/plugins/xls/index.d.ts +11 -3
  81. package/dist/esm/excel-parser/plugins/xls/index.d.ts.map +1 -1
  82. package/dist/esm/excel-parser/plugins/xls/index.js +37 -5
  83. package/dist/esm/excel-parser/plugins/xlsb/index.d.ts +4 -0
  84. package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
  85. package/dist/esm/excel-parser/plugins/xlsb/index.js +20 -6
  86. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts +10 -0
  87. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
  88. package/dist/esm/excel-parser/plugins/xlsx/charts.js +69 -6
  89. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts +28 -1
  90. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
  91. package/dist/esm/excel-parser/plugins/xlsx/drawing.js +99 -0
  92. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +26 -1
  93. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  94. package/dist/esm/excel-parser/plugins/xlsx/index.js +362 -26
  95. package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
  96. package/dist/esm/excel-parser/plugins/xlsx/pivots.js +86 -6
  97. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts +3 -0
  98. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.d.ts.map +1 -1
  99. package/dist/esm/excel-parser/plugins/xlsx/sheet-views.js +2 -0
  100. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
  101. package/dist/esm/excel-parser/plugins/xlsx/styles.js +12 -2
  102. package/dist/esm/excel-parser/registry.d.ts.map +1 -1
  103. package/dist/esm/excel-parser/registry.js +2 -0
  104. package/dist/esm/excel-parser/types.d.ts +7 -1
  105. package/dist/esm/excel-parser/types.d.ts.map +1 -1
  106. package/dist/esm/excel-parser/workbook.d.ts +1 -0
  107. package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
  108. package/dist/esm/excel-parser/workbook.js +3 -0
  109. package/dist/esm/excel-parser/worker.js +2 -0
  110. package/dist/esm/exporter/ods/ods-export.d.ts.map +1 -1
  111. package/dist/esm/exporter/ods/ods-export.js +306 -35
  112. package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
  113. package/dist/esm/exporter/xlsx/chart-builder.js +161 -0
  114. package/dist/esm/exporter/xlsx/pivot-builder.d.ts +8 -0
  115. package/dist/esm/exporter/xlsx/pivot-builder.d.ts.map +1 -1
  116. package/dist/esm/exporter/xlsx/pivot-builder.js +104 -19
  117. package/dist/esm/exporter/xlsx/style-builder.js +9 -3
  118. package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts +7 -1
  119. package/dist/esm/exporter/xlsx/threaded-comments-builder.d.ts.map +1 -1
  120. package/dist/esm/exporter/xlsx/threaded-comments-builder.js +15 -3
  121. package/dist/esm/exporter/xlsx/xlsx-export.d.ts +11 -2
  122. package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  123. package/dist/esm/exporter/xlsx/xlsx-export.js +449 -47
  124. package/dist/esm/helpers/implicit-intersection.d.ts +16 -0
  125. package/dist/esm/helpers/implicit-intersection.d.ts.map +1 -0
  126. package/dist/esm/helpers/implicit-intersection.js +184 -0
  127. package/dist/esm/print-settings.d.ts +105 -0
  128. package/dist/esm/print-settings.d.ts.map +1 -0
  129. package/dist/esm/print-settings.js +422 -0
  130. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  131. package/package.json +3 -3
@@ -3,6 +3,7 @@ import { CellXfsRegistry, deriveEffectiveNumberFormatFromFormula, } from "@rowsn
3
3
  import { cellRangeToAddress, cellToAddress, getCellEffectiveFormat, getCellEffectiveValue, getCellFormattedValue, getCellUserEnteredFormat, getCellUserEnteredValue, getExtendedValueBool, getExtendedValueError, getExtendedValueFormula, getExtendedValueNumber, getExtendedValueString, escapeCharacters, isNil, sortSheetsByIndex, addressToSelection, } from "@rowsncolumns/utils";
4
4
  import { defaultSpreadsheetTheme } from "../../excel-parser/theme-default";
5
5
  import { imageUrlToBase64 } from "../../helpers";
6
+ import { buildOdsPrintTokens, DEFAULT_PAGE_MARGINS_IN, inchesToOdsCm, isHeaderFooterEmpty, odsTableCentering, paperSizeToCm, splitHeaderFooterZones, tokenizeHeaderFooterZone, } from "../../print-settings";
6
7
  import { omitInvalidDataValidationError } from "../invalid-data-validation";
7
8
  import { buildOdsSparklineGroupsXml } from "./sparkline-builder";
8
9
  const BLANK_LABEL = "(Blanks)";
@@ -80,6 +81,24 @@ const outlineGroupOpenTag = (span, tag) => {
80
81
  };
81
82
  const XML_DECLARATION = `<?xml version="1.0" encoding="UTF-8"?>`;
82
83
  const ODS_MIMETYPE = "application/vnd.oasis.opendocument.spreadsheet";
84
+ /**
85
+ * Emit the wrappers that end at `rowIndex`: the header-rows band (when this
86
+ * is its last row) and any outline groups. A group may not close inside
87
+ * `<table:table-header-rows>`, so an open band is closed first — the caller
88
+ * re-opens it on the next band row. Returns the band's open state.
89
+ */
90
+ const closeRowWrappers = (chunks, rowIndex, closesAt, headerRowsCloseAt, headerRowsOpen) => {
91
+ let open = headerRowsOpen;
92
+ const closes = closesAt.get(rowIndex) ?? [];
93
+ if (open && (rowIndex === headerRowsCloseAt || closes.length)) {
94
+ chunks.push("</table:table-header-rows>");
95
+ open = false;
96
+ }
97
+ for (const _ of closes) {
98
+ chunks.push("</table:table-row-group>");
99
+ }
100
+ return open;
101
+ };
83
102
  const CREATOR = "Rows n Columns";
84
103
  const PIXELS_PER_INCH = 96;
85
104
  const CM_PER_INCH = 2.54;
@@ -127,14 +146,18 @@ class OdsStyleBuilder {
127
146
  usedStyleNames = new Set();
128
147
  rowStyles = new Map();
129
148
  columnStyles = new Map();
149
+ rtlTableStyleName;
130
150
  textStyles = new Map();
131
151
  numberStyles = new Map();
132
152
  styleXmlChunks = [];
133
153
  numberXmlChunks = [];
134
154
  // NEW: separate storage for conditional format styles (go to styles.xml)
135
155
  conditionalStyleXmlChunks = [];
156
+ pageLayoutXmlChunks = [];
157
+ masterPageXmlChunks = [];
136
158
  styleIndex = 1;
137
159
  numberIndex = 1;
160
+ pageLayoutIndex = 0;
138
161
  constructor(theme) {
139
162
  this.theme = theme;
140
163
  }
@@ -147,32 +170,73 @@ class OdsStyleBuilder {
147
170
  return existing;
148
171
  return this.registerCellWithName(format, key);
149
172
  }
150
- registerRow(heightPx, hidden) {
151
- if (!heightPx && !hidden)
173
+ registerRow(heightPx, hidden, breakBefore = false) {
174
+ if (!heightPx && !hidden && !breakBefore)
152
175
  return undefined;
153
- const key = stableStringify({ heightPx, hidden });
176
+ const key = stableStringify({ heightPx, hidden, breakBefore });
154
177
  const existing = this.rowStyles.get(key);
155
178
  if (existing)
156
179
  return existing;
157
180
  const styleName = `ro${this.styleIndex++}`;
158
- const styleXml = buildRowStyleXml(styleName, heightPx, hidden);
181
+ const styleXml = buildRowStyleXml(styleName, heightPx, hidden, breakBefore);
159
182
  this.rowStyles.set(key, styleName);
160
183
  this.styleXmlChunks.push(styleXml);
161
184
  return styleName;
162
185
  }
163
- registerColumn(widthPx, hidden) {
164
- if (!widthPx && !hidden)
186
+ registerColumn(widthPx, hidden, breakBefore = false) {
187
+ if (!widthPx && !hidden && !breakBefore)
165
188
  return undefined;
166
- const key = stableStringify({ widthPx, hidden });
189
+ const key = stableStringify({ widthPx, hidden, breakBefore });
167
190
  const existing = this.columnStyles.get(key);
168
191
  if (existing)
169
192
  return existing;
170
193
  const styleName = `co${this.styleIndex++}`;
171
- const styleXml = buildColumnStyleXml(styleName, widthPx, hidden);
194
+ const styleXml = buildColumnStyleXml(styleName, widthPx, hidden, breakBefore);
172
195
  this.columnStyles.set(key, styleName);
173
196
  this.styleXmlChunks.push(styleXml);
174
197
  return styleName;
175
198
  }
199
+ /**
200
+ * A sheet's print settings → a `table` automatic style (content.xml)
201
+ * pointing at a master page whose page layout + header/footer live in
202
+ * styles.xml. Returns the table style name, or undefined when the sheet
203
+ * has no page-level print settings to carry.
204
+ */
205
+ registerTable(print, rightToLeft) {
206
+ const pageLayoutXml = buildOdsPageLayoutXml(print);
207
+ const headerFooter = print?.headerFooter;
208
+ const hasPrintSettings = !!pageLayoutXml || !isHeaderFooterEmpty(headerFooter);
209
+ // Right-to-left is the table style's `style:writing-mode="rl-tb"`, the
210
+ // ODF counterpart of `<sheetView rightToLeft="1">`. A sheet with only
211
+ // that flag shares one RTL style on the default master page.
212
+ const writingMode = rightToLeft ? "rl-tb" : "lr-tb";
213
+ if (!hasPrintSettings) {
214
+ if (!rightToLeft)
215
+ return undefined;
216
+ if (this.rtlTableStyleName)
217
+ return this.rtlTableStyleName;
218
+ const styleName = `ta${this.styleIndex++}`;
219
+ this.rtlTableStyleName = styleName;
220
+ this.styleXmlChunks.push(`<style:style style:name="${styleName}" style:family="table" style:master-page-name="Default"><style:table-properties table:display="true" style:writing-mode="${writingMode}"/></style:style>`);
221
+ return styleName;
222
+ }
223
+ const index = ++this.pageLayoutIndex;
224
+ const layoutName = `Mpm${index}`;
225
+ const masterName = `Mp${index}`;
226
+ const tableStyleName = `ta${this.styleIndex++}`;
227
+ this.pageLayoutXmlChunks.push(`<style:page-layout style:name="${layoutName}">${pageLayoutXml ?? "<style:page-layout-properties/>"}</style:page-layout>`);
228
+ this.masterPageXmlChunks.push(`<style:master-page style:name="${masterName}" style:page-layout-name="${layoutName}">${buildOdsHeaderFooterXml("style:header", headerFooter?.oddHeader)}${buildOdsHeaderFooterXml("style:footer", headerFooter?.oddFooter)}</style:master-page>`);
229
+ this.styleXmlChunks.push(`<style:style style:name="${tableStyleName}" style:family="table" style:master-page-name="${masterName}"><style:table-properties table:display="true" style:writing-mode="${writingMode}"/></style:style>`);
230
+ return tableStyleName;
231
+ }
232
+ /** `<style:page-layout>` entries for styles.xml `<office:automatic-styles>`. */
233
+ toPageLayoutStylesXml() {
234
+ return this.pageLayoutXmlChunks.join("");
235
+ }
236
+ /** `<style:master-page>` entries for styles.xml `<office:master-styles>`. */
237
+ toMasterPagesXml() {
238
+ return this.masterPageXmlChunks.join("");
239
+ }
176
240
  registerText(format) {
177
241
  if (!format || Object.keys(format).length === 0)
178
242
  return undefined;
@@ -353,7 +417,7 @@ export const createODSFile = async ({ conditionalFormats: _conditionalFormats, d
353
417
  automaticStylesXml: styleBuilder.toAutomaticStylesXml(),
354
418
  calculationSettingsXml,
355
419
  });
356
- const stylesXml = buildStylesXml(styleBuilder.toConditionalStylesXml());
420
+ const stylesXml = buildStylesXml(styleBuilder.toConditionalStylesXml(), styleBuilder.toPageLayoutStylesXml(), styleBuilder.toMasterPagesXml());
357
421
  const metaXml = buildMetaXml();
358
422
  const settingsXml = buildSettingsXml(sortedSheets, _theme ?? defaultSpreadsheetTheme);
359
423
  const chartObjects = sheetXmlEntries.flatMap((entry) => entry.chartObjects);
@@ -393,7 +457,18 @@ const buildWorksheetXml = async ({ sheet, sheetName, rows, sourceSharedStrings,
393
457
  maxColumn: 0,
394
458
  };
395
459
  const rowMetadata = sheet.rowMetadata ?? [];
396
- const maxRows = Math.max(rows.length, rowMetadata.length);
460
+ const print = sheet.printSettings;
461
+ // Manual breaks ride on the row AFTER the break (`fo:break-before`), and
462
+ // print-title rows need a real `<table:table-row>` to wrap — so both
463
+ // extend the emitted row range past the data.
464
+ const rowBreakSet = new Set((print?.rowBreaks ?? []).filter((n) => Number.isInteger(n) && n >= 1));
465
+ const titleRows = print?.printTitles?.rowStart != null && print.printTitles.rowEnd != null
466
+ ? {
467
+ start: Math.max(1, print.printTitles.rowStart),
468
+ end: Math.max(print.printTitles.rowStart, print.printTitles.rowEnd),
469
+ }
470
+ : undefined;
471
+ const maxRows = Math.max(rows.length, rowMetadata.length, rowBreakSet.size ? Math.max(...rowBreakSet) + 2 : 0, titleRows ? titleRows.end + 1 : 0);
397
472
  const rowXmlEntries = [];
398
473
  const mergeState = buildMergeState(sheet.merges ?? []);
399
474
  // Build annotations map from citations
@@ -431,6 +506,7 @@ const buildWorksheetXml = async ({ sheet, sheetName, rows, sourceSharedStrings,
431
506
  rowNumber: rowIndex,
432
507
  row,
433
508
  rowMetadata: metadata,
509
+ breakBefore: rowBreakSet.has(rowIndex - 1),
434
510
  sourceSharedStrings,
435
511
  cellXfs,
436
512
  styleBuilder,
@@ -478,19 +554,41 @@ const buildWorksheetXml = async ({ sheet, sheetName, rows, sourceSharedStrings,
478
554
  const rowSpans = findOutlineGroupSpans(rowMetadata, maxRowIndex, summaryBelow);
479
555
  const { opensAt: rowOpensAt, closesAt: rowClosesAt } = buildOutlineGroupIndex(rowSpans);
480
556
  const rowXmlChunks = [];
557
+ // Print titles: `<table:table-header-rows>` wraps the repeated band. ODF
558
+ // only allows plain rows inside it, so — like LibreOffice's writer — the
559
+ // wrapper is closed before any `<table:table-row-group>` opens or closes
560
+ // inside the band and re-opened on the next band row; the importer unions
561
+ // the segments back into one band.
562
+ const headerRowsOpenAt = titleRows?.start;
563
+ const headerRowsCloseAt = titleRows?.end;
564
+ let headerRowsOpen = false;
481
565
  for (let index = 0; index < rowXmlEntries.length; index++) {
482
566
  const rowIndex = index + 1;
483
- for (const span of rowOpensAt.get(rowIndex) ?? []) {
567
+ const opens = rowOpensAt.get(rowIndex) ?? [];
568
+ if (opens.length && headerRowsOpen) {
569
+ rowXmlChunks.push("</table:table-header-rows>");
570
+ headerRowsOpen = false;
571
+ }
572
+ for (const span of opens) {
484
573
  rowXmlChunks.push(outlineGroupOpenTag(span, "table:table-row-group"));
485
574
  }
575
+ if (!headerRowsOpen &&
576
+ headerRowsOpenAt != null &&
577
+ headerRowsCloseAt != null &&
578
+ rowIndex >= headerRowsOpenAt &&
579
+ rowIndex <= headerRowsCloseAt) {
580
+ rowXmlChunks.push("<table:table-header-rows>");
581
+ headerRowsOpen = true;
582
+ }
486
583
  const entry = rowXmlEntries[index];
487
584
  if (entry) {
488
585
  rowXmlChunks.push(entry.xml);
489
586
  }
490
587
  else {
491
588
  // Aggregate consecutive empty rows into `number-rows-repeated`, but
492
- // never cross a group boundary or include a row that any group
493
- // opens/closes at — that would put a row inside the wrong wrapper.
589
+ // never cross a group / header-rows boundary or include a row that
590
+ // any group opens/closes at — that would put a row inside the wrong
591
+ // wrapper.
494
592
  let repeat = 1;
495
593
  while (index + repeat < rowXmlEntries.length &&
496
594
  !rowXmlEntries[index + repeat]) {
@@ -498,6 +596,10 @@ const buildWorksheetXml = async ({ sheet, sheetName, rows, sourceSharedStrings,
498
596
  if (rowOpensAt.has(nextRowIndex) || rowClosesAt.has(nextRowIndex - 1)) {
499
597
  break;
500
598
  }
599
+ if (nextRowIndex === headerRowsOpenAt ||
600
+ nextRowIndex - 1 === headerRowsCloseAt) {
601
+ break;
602
+ }
501
603
  repeat += 1;
502
604
  }
503
605
  rowXmlChunks.push(repeat > 1
@@ -506,15 +608,11 @@ const buildWorksheetXml = async ({ sheet, sheetName, rows, sourceSharedStrings,
506
608
  // The break condition keeps closes from landing on any row except
507
609
  // the last in the repetition, so we only need to emit them once.
508
610
  const lastInRepeat = rowIndex + repeat - 1;
509
- for (const _ of rowClosesAt.get(lastInRepeat) ?? []) {
510
- rowXmlChunks.push(`</table:table-row-group>`);
511
- }
611
+ headerRowsOpen = closeRowWrappers(rowXmlChunks, lastInRepeat, rowClosesAt, headerRowsCloseAt, headerRowsOpen);
512
612
  index += repeat - 1;
513
613
  continue;
514
614
  }
515
- for (const _ of rowClosesAt.get(rowIndex) ?? []) {
516
- rowXmlChunks.push(`</table:table-row-group>`);
517
- }
615
+ headerRowsOpen = closeRowWrappers(rowXmlChunks, rowIndex, rowClosesAt, headerRowsCloseAt, headerRowsOpen);
518
616
  }
519
617
  const embedFrames = embeds.length
520
618
  ? await buildEmbedFramesXml({
@@ -536,10 +634,28 @@ const buildWorksheetXml = async ({ sheet, sheetName, rows, sourceSharedStrings,
536
634
  const shapesXml = embedFrames.length || chartFramesResult.frames.length
537
635
  ? `<table:shapes>${[...embedFrames, ...chartFramesResult.frames].join("")}</table:shapes>`
538
636
  : "";
539
- const columnsXml = buildColumnsXml(sheet, styleBuilder, bounds);
637
+ const columnsXml = buildColumnsXml(sheet, styleBuilder, bounds, print);
540
638
  // Build sparkline XML from SPARKLINE formulas
541
639
  const sparklineXml = buildOdsSparklineGroupsXml(sheet, sheetName, rowDataMap) ?? "";
542
- const sheetXml = `<table:table table:name="${escapeXml(sheetName)}">${shapesXml}${columnsXml}${rowXmlChunks.join("")}${conditionalFormattingXml}${sparklineXml}</table:table>`;
640
+ // ODF has no allow-edit `<protectedRange>` equivalent, so only the sheet-level
641
+ // switch is written; protected ranges degrade to unprotected on ODS export.
642
+ const protectedAttr = sheet.sheetProtection?.sheet
643
+ ? ' table:protected="true"'
644
+ : "";
645
+ // Page setup / margins / header-footer ride on the table style's master
646
+ // page (right-to-left is the same style's writing mode); the print area
647
+ // is `table:print-ranges` (space-separated).
648
+ const tableStyleName = styleBuilder.registerTable(print, sheet.rightToLeft ?? undefined);
649
+ const tableStyleAttr = tableStyleName
650
+ ? ` table:style-name="${tableStyleName}"`
651
+ : "";
652
+ const printRanges = (print?.printArea ?? [])
653
+ .map((range) => buildOdsRangeAddress(range, sheetName))
654
+ .filter((address) => Boolean(address));
655
+ const printRangesAttr = printRanges.length
656
+ ? ` table:print-ranges="${escapeXml(printRanges.join(" "))}"`
657
+ : "";
658
+ const sheetXml = `<table:table table:name="${escapeXml(sheetName)}"${tableStyleAttr}${protectedAttr}${printRangesAttr}>${shapesXml}${columnsXml}${rowXmlChunks.join("")}${conditionalFormattingXml}${sparklineXml}</table:table>`;
543
659
  return {
544
660
  xml: sheetXml,
545
661
  databaseRanges: buildDatabaseRangeEntries(tables, sheetName, sheet, rows),
@@ -547,7 +663,7 @@ const buildWorksheetXml = async ({ sheet, sheetName, rows, sourceSharedStrings,
547
663
  nextChartObjectIndex: chartFramesResult.nextIndex,
548
664
  };
549
665
  };
550
- const buildWorksheetRow = async ({ rowNumber, row, rowMetadata, sourceSharedStrings, cellXfs, styleBuilder, mergeState, dataValidationsById, mediaEntries, sheet, namedRangeNames, tables, ignoreFormulaPatterns, annotationsMap, sheetData, sheets, locale, }) => {
666
+ const buildWorksheetRow = async ({ rowNumber, row, rowMetadata, breakBefore = false, sourceSharedStrings, cellXfs, styleBuilder, mergeState, dataValidationsById, mediaEntries, sheet, namedRangeNames, tables, ignoreFormulaPatterns, annotationsMap, sheetData, sheets, locale, }) => {
551
667
  const values = row?.values ?? [];
552
668
  const cellXmlChunks = [];
553
669
  let minColumn = Number.POSITIVE_INFINITY;
@@ -557,7 +673,7 @@ const buildWorksheetRow = async ({ rowNumber, row, rowMetadata, sourceSharedStri
557
673
  const rowSize = rowMetadata?.size && rowMetadata.size > 0 ? rowMetadata.size : undefined;
558
674
  const hasExplicitHeight = Boolean(rowSize && Math.abs(rowSize - defaultRowHeightPx) > 0.01);
559
675
  const hasCustomHeight = Boolean(rowSize && rowMetadata?.resizedByUser);
560
- const rowStyle = styleBuilder.registerRow(rowSize && (hasExplicitHeight || hasCustomHeight) ? rowSize : undefined, hasHiddenFlag);
676
+ const rowStyle = styleBuilder.registerRow(rowSize && (hasExplicitHeight || hasCustomHeight) ? rowSize : undefined, hasHiddenFlag, breakBefore);
561
677
  const maxColumnIndex = Math.max(values.length - 1, mergeState.maxColumnByRow.get(rowNumber) ?? 0);
562
678
  for (let columnIndex = 1; columnIndex <= maxColumnIndex; columnIndex++) {
563
679
  if (mergeState.coveredCells.has(`${rowNumber}:${columnIndex}`)) {
@@ -594,7 +710,7 @@ const buildWorksheetRow = async ({ rowNumber, row, rowMetadata, sourceSharedStri
594
710
  maxColumn = Math.max(maxColumn, columnIndex);
595
711
  }
596
712
  const hasCells = cellXmlChunks.length > 0;
597
- const needsMetadata = hasHiddenFlag || hasExplicitHeight || hasCustomHeight;
713
+ const needsMetadata = hasHiddenFlag || hasExplicitHeight || hasCustomHeight || breakBefore;
598
714
  if (!hasCells && !needsMetadata) {
599
715
  return null;
600
716
  }
@@ -962,8 +1078,110 @@ const buildCalculationSettingsXml = (iterativeCalculationOptions) => {
962
1078
  const buildContentXml = ({ sheetsXml, databaseRangesXml, namedRangesXml, contentValidationsXml, automaticStylesXml, calculationSettingsXml, }) => {
963
1079
  return `${XML_DECLARATION}<office:document-content xmlns:office="${ODS_NS.office}" xmlns:style="${ODS_NS.style}" xmlns:text="${ODS_NS.text}" xmlns:table="${ODS_NS.table}" xmlns:draw="${ODS_NS.draw}" xmlns:fo="${ODS_NS.fo}" xmlns:xlink="${ODS_NS.xlink}" xmlns:svg="${ODS_NS.svg}" xmlns:number="${ODS_NS.number}" xmlns:of="${ODS_NS.of}" xmlns:calcext="${ODS_NS.calcext}" xmlns:dc="${ODS_NS.dc}" office:version="1.2"><office:scripts/><office:automatic-styles>${automaticStylesXml}</office:automatic-styles><office:body><office:spreadsheet>${calculationSettingsXml}${contentValidationsXml}${sheetsXml}${namedRangesXml}${databaseRangesXml}</office:spreadsheet></office:body></office:document-content>`;
964
1080
  };
965
- const buildStylesXml = (conditionalStylesXml = "") => {
966
- return `${XML_DECLARATION}<office:document-styles xmlns:office="${ODS_NS.office}" xmlns:style="${ODS_NS.style}" xmlns:text="${ODS_NS.text}" xmlns:table="${ODS_NS.table}" xmlns:draw="${ODS_NS.draw}" xmlns:fo="${ODS_NS.fo}" xmlns:xlink="${ODS_NS.xlink}" xmlns:svg="${ODS_NS.svg}" xmlns:number="${ODS_NS.number}" office:version="1.2"><office:styles><style:style style:name="Default" style:family="table-cell"><style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm"/></style:style>${conditionalStylesXml}</office:styles><office:automatic-styles/><office:master-styles/></office:document-styles>`;
1081
+ const buildStylesXml = (conditionalStylesXml = "", pageLayoutsXml = "", masterPagesXml = "") => {
1082
+ const automaticStyles = pageLayoutsXml
1083
+ ? `<office:automatic-styles>${pageLayoutsXml}</office:automatic-styles>`
1084
+ : "<office:automatic-styles/>";
1085
+ const masterStyles = masterPagesXml
1086
+ ? `<office:master-styles>${masterPagesXml}</office:master-styles>`
1087
+ : "<office:master-styles/>";
1088
+ return `${XML_DECLARATION}<office:document-styles xmlns:office="${ODS_NS.office}" xmlns:style="${ODS_NS.style}" xmlns:text="${ODS_NS.text}" xmlns:table="${ODS_NS.table}" xmlns:draw="${ODS_NS.draw}" xmlns:fo="${ODS_NS.fo}" xmlns:xlink="${ODS_NS.xlink}" xmlns:svg="${ODS_NS.svg}" xmlns:number="${ODS_NS.number}" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.2"><office:styles><style:style style:name="Default" style:family="table-cell"><style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm"/></style:style>${conditionalStylesXml}</office:styles>${automaticStyles}${masterStyles}</office:document-styles>`;
1089
+ };
1090
+ /**
1091
+ * `<style:page-layout-properties>` (+ header/footer styles) for a sheet's
1092
+ * page setup, margins and print options. ODF keeps the header INSIDE the
1093
+ * top margin, so Excel's `header` (edge → header) becomes `fo:margin-top`
1094
+ * and `top - header` the header's `fo:min-height`; footer likewise.
1095
+ */
1096
+ const buildOdsPageLayoutXml = (print) => {
1097
+ const setup = print?.pageSetup;
1098
+ const margins = print?.pageMargins;
1099
+ const options = print?.printOptions;
1100
+ if (!setup && !margins && !options)
1101
+ return undefined;
1102
+ const attrs = [];
1103
+ const landscape = setup?.orientation === "landscape";
1104
+ const paper = paperSizeToCm(setup?.paperSize);
1105
+ if (paper) {
1106
+ const width = landscape ? paper.height : paper.width;
1107
+ const height = landscape ? paper.width : paper.height;
1108
+ attrs.push(`fo:page-width="${width.toFixed(3)}cm"`, `fo:page-height="${height.toFixed(3)}cm"`);
1109
+ }
1110
+ if (setup?.orientation) {
1111
+ attrs.push(`style:print-orientation="${setup.orientation}"`);
1112
+ }
1113
+ const left = margins?.left ?? DEFAULT_PAGE_MARGINS_IN.left;
1114
+ const right = margins?.right ?? DEFAULT_PAGE_MARGINS_IN.right;
1115
+ const top = margins?.top ?? DEFAULT_PAGE_MARGINS_IN.top;
1116
+ const bottom = margins?.bottom ?? DEFAULT_PAGE_MARGINS_IN.bottom;
1117
+ const header = Math.min(margins?.header ?? DEFAULT_PAGE_MARGINS_IN.header, top);
1118
+ const footer = Math.min(margins?.footer ?? DEFAULT_PAGE_MARGINS_IN.footer, bottom);
1119
+ attrs.push(`fo:margin-top="${inchesToOdsCm(header)}"`, `fo:margin-bottom="${inchesToOdsCm(footer)}"`, `fo:margin-left="${inchesToOdsCm(left)}"`, `fo:margin-right="${inchesToOdsCm(right)}"`);
1120
+ if (setup?.fitToPage) {
1121
+ // LibreOffice's per-axis fit (`loext:scale-to-X/Y`); 0 = automatic.
1122
+ attrs.push(`loext:scale-to-X="${setup.fitToWidth ?? 1}"`, `loext:scale-to-Y="${setup.fitToHeight ?? 1}"`);
1123
+ }
1124
+ else if (typeof setup?.scale === "number" && setup.scale > 0) {
1125
+ attrs.push(`style:scale-to="${setup.scale}%"`);
1126
+ }
1127
+ const centering = odsTableCentering(options);
1128
+ if (centering)
1129
+ attrs.push(`style:table-centering="${centering}"`);
1130
+ attrs.push(`style:print="${buildOdsPrintTokens(options)}"`);
1131
+ if (setup?.pageOrder === "overThenDown") {
1132
+ attrs.push(`style:print-page-order="ltr"`);
1133
+ }
1134
+ if (typeof setup?.firstPageNumber === "number" &&
1135
+ setup.useFirstPageNumber !== false) {
1136
+ attrs.push(`style:first-page-number="${setup.firstPageNumber}"`);
1137
+ }
1138
+ const headerStyle = `<style:header-style><style:header-footer-properties fo:min-height="${inchesToOdsCm(Math.max(0, top - header))}" fo:margin-bottom="0cm"/></style:header-style>`;
1139
+ const footerStyle = `<style:footer-style><style:header-footer-properties fo:min-height="${inchesToOdsCm(Math.max(0, bottom - footer))}" fo:margin-top="0cm"/></style:footer-style>`;
1140
+ return `<style:page-layout-properties ${attrs.join(" ")}/>${headerStyle}${footerStyle}`;
1141
+ };
1142
+ const odsFieldXml = (token) => {
1143
+ if (token.type === "text") {
1144
+ return token.text
1145
+ .split("\n")
1146
+ .map((line) => escapeXml(line))
1147
+ .join("<text:line-break/>");
1148
+ }
1149
+ switch (token.field) {
1150
+ case "page":
1151
+ return "<text:page-number>1</text:page-number>";
1152
+ case "pages":
1153
+ return "<text:page-count>1</text:page-count>";
1154
+ case "date":
1155
+ return "<text:date/>";
1156
+ case "time":
1157
+ return "<text:time/>";
1158
+ case "sheet":
1159
+ return "<text:sheet-name/>";
1160
+ case "file":
1161
+ return '<text:file-name text:display="name"/>';
1162
+ case "path":
1163
+ return '<text:file-name text:display="path"/>';
1164
+ default:
1165
+ return "";
1166
+ }
1167
+ };
1168
+ const odsHeaderZoneXml = (code) => code
1169
+ ? `<text:p>${tokenizeHeaderFooterZone(code).map(odsFieldXml).join("")}</text:p>`
1170
+ : "";
1171
+ /**
1172
+ * Excel header/footer code → `<style:header>` / `<style:footer>` with
1173
+ * `&L` / `&C` / `&R` zones as ODF regions and field macros as `<text:*>`
1174
+ * fields. Formatting codes have no ODF counterpart and are dropped; an
1175
+ * absent code yields a hidden (`style:display="false"`) element.
1176
+ */
1177
+ const buildOdsHeaderFooterXml = (tag, code) => {
1178
+ if (!code)
1179
+ return `<${tag} style:display="false"/>`;
1180
+ const zones = splitHeaderFooterZones(code);
1181
+ if (!zones.left && !zones.right) {
1182
+ return `<${tag}>${odsHeaderZoneXml(zones.center)}</${tag}>`;
1183
+ }
1184
+ return `<${tag}><style:region-left>${odsHeaderZoneXml(zones.left)}</style:region-left><style:region-center>${odsHeaderZoneXml(zones.center)}</style:region-center><style:region-right>${odsHeaderZoneXml(zones.right)}</style:region-right></${tag}>`;
967
1185
  };
968
1186
  const buildMetaXml = () => {
969
1187
  const now = new Date().toISOString();
@@ -1024,29 +1242,57 @@ const buildFreezeCell = (rows, columns) => {
1024
1242
  const row = Math.max(1, rows + 1);
1025
1243
  return `${col}${row}`;
1026
1244
  };
1027
- const buildColumnsXml = (sheet, styleBuilder, bounds) => {
1245
+ const buildColumnsXml = (sheet, styleBuilder, bounds, print) => {
1028
1246
  const metadata = sheet.columnMetadata ?? [];
1029
- const maxColumns = Math.max(metadata.length - 1, bounds.maxColumn || 0);
1247
+ const columnBreakSet = new Set((print?.columnBreaks ?? []).filter((n) => Number.isInteger(n) && n >= 1));
1248
+ const titles = print?.printTitles;
1249
+ const titleColumns = titles?.columnStart != null && titles.columnEnd != null
1250
+ ? {
1251
+ start: Math.max(1, titles.columnStart),
1252
+ end: Math.max(titles.columnStart, titles.columnEnd),
1253
+ }
1254
+ : undefined;
1255
+ const maxColumns = Math.max(metadata.length - 1, bounds.maxColumn || 0, columnBreakSet.size ? Math.max(...columnBreakSet) + 1 : 0, titleColumns?.end ?? 0);
1030
1256
  if (!maxColumns)
1031
1257
  return "";
1032
1258
  const summaryRight = sheet.outlinePr?.summaryRight !== false;
1033
1259
  const spans = findOutlineGroupSpans(metadata, maxColumns, summaryRight);
1034
1260
  const { opensAt, closesAt } = buildOutlineGroupIndex(spans);
1035
1261
  const parts = [];
1262
+ // Same rule as the row band: `<table:table-header-columns>` holds plain
1263
+ // columns only, so it closes around a column-group edge and re-opens.
1264
+ let headerColumnsOpen = false;
1036
1265
  for (let index = 1; index <= maxColumns; index++) {
1037
- for (const span of opensAt.get(index) ?? []) {
1266
+ const opens = opensAt.get(index) ?? [];
1267
+ if (opens.length && headerColumnsOpen) {
1268
+ parts.push("</table:table-header-columns>");
1269
+ headerColumnsOpen = false;
1270
+ }
1271
+ for (const span of opens) {
1038
1272
  parts.push(outlineGroupOpenTag(span, "table:table-column-group"));
1039
1273
  }
1274
+ if (!headerColumnsOpen &&
1275
+ titleColumns &&
1276
+ index >= titleColumns.start &&
1277
+ index <= titleColumns.end) {
1278
+ parts.push("<table:table-header-columns>");
1279
+ headerColumnsOpen = true;
1280
+ }
1040
1281
  const props = metadata[index];
1041
1282
  const widthPx = getColumnWidthPx(sheet, index);
1042
1283
  const hidden = Boolean(props?.hiddenByUser || props?.hiddenByFilter);
1043
- const styleName = styleBuilder.registerColumn(widthPx, hidden);
1284
+ const styleName = styleBuilder.registerColumn(widthPx, hidden, columnBreakSet.has(index - 1));
1044
1285
  const attrs = [];
1045
1286
  if (styleName) {
1046
1287
  attrs.push(`table:style-name="${styleName}"`);
1047
1288
  }
1048
1289
  parts.push(`<table:table-column${attrs.length ? " " + attrs.join(" ") : ""}/>`);
1049
- for (const _ of closesAt.get(index) ?? []) {
1290
+ const closes = closesAt.get(index) ?? [];
1291
+ if (headerColumnsOpen && (index === titleColumns?.end || closes.length)) {
1292
+ parts.push("</table:table-header-columns>");
1293
+ headerColumnsOpen = false;
1294
+ }
1295
+ for (const _ of closes) {
1050
1296
  parts.push(`</table:table-column-group>`);
1051
1297
  }
1052
1298
  }
@@ -1208,7 +1454,7 @@ const buildRichTextParagraph = (text, runs, styleBuilder) => {
1208
1454
  }
1209
1455
  return `<text:p>${parts.join("")}</text:p>`;
1210
1456
  };
1211
- const buildRowStyleXml = (name, heightPx, hidden) => {
1457
+ const buildRowStyleXml = (name, heightPx, hidden, breakBefore = false) => {
1212
1458
  const props = [];
1213
1459
  if (heightPx) {
1214
1460
  props.push(`style:row-height="${pxToCm(heightPx).toFixed(2)}cm"`);
@@ -1216,9 +1462,12 @@ const buildRowStyleXml = (name, heightPx, hidden) => {
1216
1462
  if (hidden) {
1217
1463
  props.push(`style:visibility="collapse"`);
1218
1464
  }
1465
+ if (breakBefore) {
1466
+ props.push(`fo:break-before="page"`);
1467
+ }
1219
1468
  return `<style:style style:name="${name}" style:family="table-row"><style:table-row-properties${props.length ? " " + props.join(" ") : ""}/></style:style>`;
1220
1469
  };
1221
- const buildColumnStyleXml = (name, widthPx, hidden) => {
1470
+ const buildColumnStyleXml = (name, widthPx, hidden, breakBefore = false) => {
1222
1471
  const props = [];
1223
1472
  if (widthPx) {
1224
1473
  props.push(`style:column-width="${pxToCm(widthPx).toFixed(2)}cm"`);
@@ -1226,6 +1475,9 @@ const buildColumnStyleXml = (name, widthPx, hidden) => {
1226
1475
  if (hidden) {
1227
1476
  props.push(`style:visibility="collapse"`);
1228
1477
  }
1478
+ if (breakBefore) {
1479
+ props.push(`fo:break-before="page"`);
1480
+ }
1229
1481
  return `<style:style style:name="${name}" style:family="table-column"><style:table-column-properties${props.length ? " " + props.join(" ") : ""}/></style:style>`;
1230
1482
  };
1231
1483
  const buildNumberStyleXml = (name, format) => {
@@ -2727,7 +2979,26 @@ const buildChartObjectXml = (chart, sheetNameById) => {
2727
2979
  scatter: "chart:scatter",
2728
2980
  area: "chart:area",
2729
2981
  };
2730
- const chartClass = typeMap[spec.chartType] ?? "chart:bar";
2982
+ // 3-D / surface classes degrade to a 2-D `chartType` with the authored
2983
+ // class kept as `sourceChartType` (BAR_3D, LINE_3D, PIE_3D, AREA_3D,
2984
+ // SURFACE, SURFACE_3D). ODF expresses the same thing as
2985
+ // `chart:three-dimensional="true"` on the plot-area style (plus the
2986
+ // `chart:surface` class for surfaces), so the flag round-trips.
2987
+ const sourceChartType = String(spec.sourceChartType ?? "").toUpperCase();
2988
+ const isSurface = sourceChartType === "SURFACE" || sourceChartType === "SURFACE_3D";
2989
+ // A plain SURFACE is Excel's contour (top-down) view, so only SURFACE_3D
2990
+ // carries the three-dimensional flag.
2991
+ const threeDimensional = sourceChartType === "SURFACE_3D" ||
2992
+ sourceChartType === "BAR_3D" ||
2993
+ sourceChartType === "LINE_3D" ||
2994
+ sourceChartType === "PIE_3D" ||
2995
+ sourceChartType === "AREA_3D";
2996
+ const chartClass = isSurface
2997
+ ? "chart:surface"
2998
+ : (typeMap[spec.chartType] ?? "chart:bar");
2999
+ const plotStyleProps = threeDimensional
3000
+ ? `<style:chart-properties chart:three-dimensional="true"/>`
3001
+ : "";
2731
3002
  const title = spec.title
2732
3003
  ? `<chart:title><text:p>${escapeXml(String(spec.title))}</text:p></chart:title>`
2733
3004
  : "";
@@ -2810,7 +3081,7 @@ const buildChartObjectXml = (chart, sheetNameById) => {
2810
3081
  const legendXml = chartClass === "chart:circle"
2811
3082
  ? ""
2812
3083
  : `<chart:legend chart:legend-position="end" chart:legend-align="center" chart:style-name="legend1"/>`;
2813
- return `${XML_DECLARATION}<office:document-content xmlns:office="${ODS_NS.office}" xmlns:chart="${ODS_NS.chart}" xmlns:table="${ODS_NS.table}" xmlns:text="${ODS_NS.text}" xmlns:style="${ODS_NS.style}" xmlns:draw="${ODS_NS.draw}" xmlns:svg="${ODS_NS.svg}" xmlns:fo="${ODS_NS.fo}" office:version="1.2"><office:automatic-styles>${gradientDefsXml}<style:style style:name="ch1" style:family="chart"><style:graphic-properties draw:fill="solid" draw:fill-color="#ffffff"/></style:style><style:style style:name="plot1" style:family="chart"><style:graphic-properties draw:fill="solid" draw:fill-color="#ffffff" svg:stroke-color="#b3b3b3"/></style:style><style:style style:name="wall1" style:family="chart"><style:graphic-properties draw:fill="solid" draw:fill-color="#ffffff" svg:stroke-color="#b3b3b3" svg:stroke-width="0.01cm"/></style:style><style:style style:name="floor1" style:family="chart"><style:graphic-properties draw:fill="none" svg:stroke-color="#b3b3b3"/></style:style><style:style style:name="legend1" style:family="chart"><style:graphic-properties draw:fill="none" svg:stroke-color="#b3b3b3"/><style:text-properties fo:font-size="10pt"/></style:style><style:style style:name="axisx" style:family="chart"><style:chart-properties chart:display-label="true" chart:tick-marks-major-inner="false" chart:tick-marks-major-outer="true"/><style:graphic-properties svg:stroke-color="#b3b3b3"/><style:text-properties fo:font-size="9pt"/></style:style><style:style style:name="axisy" style:family="chart"><style:chart-properties chart:display-label="true" chart:tick-marks-major-inner="false" chart:tick-marks-major-outer="true"/><style:graphic-properties svg:stroke-color="#b3b3b3"/><style:text-properties fo:font-size="9pt"/></style:style>${seriesStyles}</office:automatic-styles><office:body><office:chart><chart:chart chart:class="${chartClass}" chart:style-name="ch1">${title}${legendXml}<chart:plot-area chart:style-name="plot1"${plotAreaAttrs}>${axisXml}${seriesXml}${wallFloor}</chart:plot-area></chart:chart></office:chart></office:body></office:document-content>`;
3084
+ return `${XML_DECLARATION}<office:document-content xmlns:office="${ODS_NS.office}" xmlns:chart="${ODS_NS.chart}" xmlns:table="${ODS_NS.table}" xmlns:text="${ODS_NS.text}" xmlns:style="${ODS_NS.style}" xmlns:draw="${ODS_NS.draw}" xmlns:svg="${ODS_NS.svg}" xmlns:fo="${ODS_NS.fo}" office:version="1.2"><office:automatic-styles>${gradientDefsXml}<style:style style:name="ch1" style:family="chart"><style:graphic-properties draw:fill="solid" draw:fill-color="#ffffff"/></style:style><style:style style:name="plot1" style:family="chart">${plotStyleProps}<style:graphic-properties draw:fill="solid" draw:fill-color="#ffffff" svg:stroke-color="#b3b3b3"/></style:style><style:style style:name="wall1" style:family="chart"><style:graphic-properties draw:fill="solid" draw:fill-color="#ffffff" svg:stroke-color="#b3b3b3" svg:stroke-width="0.01cm"/></style:style><style:style style:name="floor1" style:family="chart"><style:graphic-properties draw:fill="none" svg:stroke-color="#b3b3b3"/></style:style><style:style style:name="legend1" style:family="chart"><style:graphic-properties draw:fill="none" svg:stroke-color="#b3b3b3"/><style:text-properties fo:font-size="10pt"/></style:style><style:style style:name="axisx" style:family="chart"><style:chart-properties chart:display-label="true" chart:tick-marks-major-inner="false" chart:tick-marks-major-outer="true"/><style:graphic-properties svg:stroke-color="#b3b3b3"/><style:text-properties fo:font-size="9pt"/></style:style><style:style style:name="axisy" style:family="chart"><style:chart-properties chart:display-label="true" chart:tick-marks-major-inner="false" chart:tick-marks-major-outer="true"/><style:graphic-properties svg:stroke-color="#b3b3b3"/><style:text-properties fo:font-size="9pt"/></style:style>${seriesStyles}</office:automatic-styles><office:body><office:chart><chart:chart chart:class="${chartClass}" chart:style-name="ch1">${title}${legendXml}<chart:plot-area chart:style-name="plot1"${plotAreaAttrs}>${axisXml}${seriesXml}${wallFloor}</chart:plot-area></chart:chart></office:chart></office:body></office:document-content>`;
2814
3085
  };
2815
3086
  const buildChartRangeAddress = (range, sheetNameById) => {
2816
3087
  const sheetName = sheetNameById.get(range.sheetId);
@@ -1 +1 @@
1
- {"version":3,"file":"chart-builder.d.ts","sourceRoot":"","sources":["../../../../exporter/xlsx/chart-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,aAAa,EAEd,MAAM,2BAA2B,CAAC;AAovCnC,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,MAAM,GAAG,SAsnBX,CAAC"}
1
+ {"version":3,"file":"chart-builder.d.ts","sourceRoot":"","sources":["../../../../exporter/xlsx/chart-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,aAAa,EAEd,MAAM,2BAA2B,CAAC;AAovCnC,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,MAAM,GAAG,SAgoBX,CAAC"}