@cj-tech-master/excelts 2.0.0-canary.20251228024848.8822305 → 2.0.1-canary.20251228093548.379d895

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 (53) hide show
  1. package/dist/browser/excelts.esm.js +146 -116
  2. package/dist/browser/excelts.esm.js.map +1 -1
  3. package/dist/browser/excelts.esm.min.js +22 -22
  4. package/dist/browser/excelts.iife.js +146 -116
  5. package/dist/browser/excelts.iife.js.map +1 -1
  6. package/dist/browser/excelts.iife.min.js +22 -22
  7. package/dist/cjs/doc/pivot-table.js +37 -3
  8. package/dist/cjs/doc/worksheet.js +0 -1
  9. package/dist/cjs/stream/xlsx/worksheet-writer.js +0 -1
  10. package/dist/cjs/xlsx/xform/book/sheet-xform.js +3 -2
  11. package/dist/cjs/xlsx/xform/book/workbook-properties-xform.js +1 -1
  12. package/dist/cjs/xlsx/xform/core/content-types-xform.js +12 -6
  13. package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +11 -4
  14. package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +1 -10
  15. package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +39 -17
  16. package/dist/cjs/xlsx/xform/sheet/page-setup-xform.js +4 -3
  17. package/dist/cjs/xlsx/xform/sheet/row-xform.js +1 -1
  18. package/dist/cjs/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
  19. package/dist/cjs/xlsx/xform/sheet/sheet-view-xform.js +8 -4
  20. package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +2 -2
  21. package/dist/cjs/xlsx/xform/style/font-xform.js +36 -23
  22. package/dist/cjs/xlsx/xform/table/auto-filter-xform.js +3 -1
  23. package/dist/cjs/xlsx/xform/table/table-column-xform.js +2 -1
  24. package/dist/cjs/xlsx/xform/table/table-xform.js +5 -9
  25. package/dist/esm/doc/pivot-table.js +37 -3
  26. package/dist/esm/doc/worksheet.js +0 -1
  27. package/dist/esm/stream/xlsx/worksheet-writer.js +0 -1
  28. package/dist/esm/xlsx/xform/book/sheet-xform.js +3 -2
  29. package/dist/esm/xlsx/xform/book/workbook-properties-xform.js +1 -1
  30. package/dist/esm/xlsx/xform/core/content-types-xform.js +12 -6
  31. package/dist/esm/xlsx/xform/pivot-table/cache-field.js +11 -4
  32. package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +1 -10
  33. package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +39 -17
  34. package/dist/esm/xlsx/xform/sheet/page-setup-xform.js +4 -3
  35. package/dist/esm/xlsx/xform/sheet/row-xform.js +1 -1
  36. package/dist/esm/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
  37. package/dist/esm/xlsx/xform/sheet/sheet-view-xform.js +8 -4
  38. package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +2 -2
  39. package/dist/esm/xlsx/xform/style/font-xform.js +36 -23
  40. package/dist/esm/xlsx/xform/table/auto-filter-xform.js +3 -1
  41. package/dist/esm/xlsx/xform/table/table-column-xform.js +2 -1
  42. package/dist/esm/xlsx/xform/table/table-xform.js +5 -9
  43. package/dist/types/doc/pivot-table.d.ts +5 -1
  44. package/dist/types/stream/xlsx/worksheet-writer.d.ts +1 -1
  45. package/dist/types/types.d.ts +1 -1
  46. package/dist/types/xlsx/xform/pivot-table/cache-field.d.ts +5 -1
  47. package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +0 -5
  48. package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +0 -3
  49. package/dist/types/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -1
  50. package/dist/types/xlsx/xform/sheet/worksheet-xform.d.ts +1 -1
  51. package/dist/types/xlsx/xform/style/font-xform.d.ts +1 -0
  52. package/dist/types/xlsx/xform/table/table-xform.d.ts +0 -4
  53. package/package.json +1 -1
@@ -37,8 +37,11 @@ function createTableSourceAdapter(table) {
37
37
  const endRow = startRow + tableModel.rows.length; // header row + data rows
38
38
  const endCol = startCol + columnNames.length - 1;
39
39
  const shortRange = col_cache_1.colCache.encode(startRow, startCol, endRow, endCol);
40
+ // Use the worksheet name (not table name) for pivotCacheDefinition's worksheetSource
41
+ // The sheet attribute in worksheetSource must reference the actual worksheet name
42
+ const worksheetName = table.worksheet.name;
40
43
  return {
41
- name: tableModel.name,
44
+ name: worksheetName,
42
45
  getRow(rowNumber) {
43
46
  if (rowNumber === 1) {
44
47
  return { values: headerRow };
@@ -187,12 +190,43 @@ function makeCacheFields(source, fieldNamesWithSharedItems) {
187
190
  }
188
191
  return (0, utils_1.toSortedArray)(uniqueValues);
189
192
  };
193
+ // Calculate min/max for numeric fields
194
+ const getMinMax = (columnIndex) => {
195
+ const columnValues = source.getColumn(columnIndex).values;
196
+ let min = Infinity;
197
+ let max = -Infinity;
198
+ let hasNumeric = false;
199
+ for (let i = 2; i < columnValues.length; i++) {
200
+ const v = columnValues[i];
201
+ if (typeof v === "number" && !isNaN(v)) {
202
+ hasNumeric = true;
203
+ if (v < min) {
204
+ min = v;
205
+ }
206
+ if (v > max) {
207
+ max = v;
208
+ }
209
+ }
210
+ }
211
+ return hasNumeric ? { minValue: min, maxValue: max } : null;
212
+ };
190
213
  // Build result array
191
214
  const result = [];
192
215
  for (const columnIndex of (0, utils_1.range)(1, names.length)) {
193
216
  const name = names[columnIndex];
194
- const sharedItems = sharedItemsFields.has(name) ? aggregate(columnIndex) : null;
195
- result.push({ name, sharedItems });
217
+ if (sharedItemsFields.has(name)) {
218
+ result.push({ name, sharedItems: aggregate(columnIndex) });
219
+ }
220
+ else {
221
+ // Numeric field - calculate min/max
222
+ const minMax = getMinMax(columnIndex);
223
+ result.push({
224
+ name,
225
+ sharedItems: null,
226
+ minValue: minMax?.minValue,
227
+ maxValue: minMax?.maxValue
228
+ });
229
+ }
196
230
  }
197
231
  return result;
198
232
  }
@@ -43,7 +43,6 @@ class Worksheet {
43
43
  // for tabColor, default row height, outline levels, etc
44
44
  this.properties = Object.assign({}, {
45
45
  defaultRowHeight: 15,
46
- dyDescent: 55,
47
46
  outlineLevelCol: 0,
48
47
  outlineLevelRow: 0
49
48
  }, options.properties);
@@ -98,7 +98,6 @@ class WorksheetWriter {
98
98
  // for default row height, outline levels, etc
99
99
  this.properties = Object.assign({}, {
100
100
  defaultRowHeight: 15,
101
- dyDescent: 55,
102
101
  outlineLevelCol: 0,
103
102
  outlineLevelRow: 0
104
103
  }, options.properties);
@@ -6,9 +6,10 @@ const base_xform_1 = require("../base-xform");
6
6
  class WorksheetXform extends base_xform_1.BaseXform {
7
7
  render(xmlStream, model) {
8
8
  xmlStream.leafNode("sheet", {
9
- sheetId: model.id,
10
9
  name: model.name,
11
- state: model.state,
10
+ sheetId: model.id,
11
+ // Excel doesn't output state when it's 'visible' (default)
12
+ state: model.state === "visible" ? undefined : model.state,
12
13
  "r:id": model.rId
13
14
  });
14
15
  }
@@ -6,7 +6,7 @@ class WorkbookPropertiesXform extends base_xform_1.BaseXform {
6
6
  render(xmlStream, model) {
7
7
  xmlStream.leafNode("workbookPr", {
8
8
  date1904: model.date1904 ? 1 : undefined,
9
- defaultThemeVersion: 164011,
9
+ // Excel doesn't output defaultThemeVersion
10
10
  filterPrivacy: 1
11
11
  });
12
12
  }
@@ -41,7 +41,7 @@ class ContentTypesXform extends base_xform_1.BaseXform {
41
41
  });
42
42
  });
43
43
  if ((model.pivotTables || []).length) {
44
- // Add content types for each pivot table
44
+ // Add content types for pivot cache (definition and records)
45
45
  (model.pivotTables || []).forEach((pivotTable) => {
46
46
  const n = pivotTable.tableNumber;
47
47
  xmlStream.leafNode("Override", {
@@ -52,10 +52,6 @@ class ContentTypesXform extends base_xform_1.BaseXform {
52
52
  PartName: `/xl/pivotCache/pivotCacheRecords${n}.xml`,
53
53
  ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
54
54
  });
55
- xmlStream.leafNode("Override", {
56
- PartName: `/xl/pivotTables/pivotTable${n}.xml`,
57
- ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
58
- });
59
55
  });
60
56
  }
61
57
  xmlStream.leafNode("Override", {
@@ -81,6 +77,16 @@ class ContentTypesXform extends base_xform_1.BaseXform {
81
77
  });
82
78
  });
83
79
  }
80
+ // Add pivot table overrides after tables (matches Excel order)
81
+ if ((model.pivotTables || []).length) {
82
+ (model.pivotTables || []).forEach((pivotTable) => {
83
+ const n = pivotTable.tableNumber;
84
+ xmlStream.leafNode("Override", {
85
+ PartName: `/xl/pivotTables/pivotTable${n}.xml`,
86
+ ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
87
+ });
88
+ });
89
+ }
84
90
  if (model.drawings) {
85
91
  model.drawings.forEach((drawing) => {
86
92
  xmlStream.leafNode("Override", {
@@ -89,7 +95,7 @@ class ContentTypesXform extends base_xform_1.BaseXform {
89
95
  });
90
96
  });
91
97
  }
92
- if (model.commentRefs) {
98
+ if (model.commentRefs && model.commentRefs.length) {
93
99
  xmlStream.leafNode("Default", {
94
100
  Extension: "vml",
95
101
  ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CacheField = void 0;
4
4
  const utils_1 = require("../../../utils/utils");
5
5
  class CacheField {
6
- constructor({ name, sharedItems }) {
6
+ constructor({ name, sharedItems, minValue, maxValue }) {
7
7
  // string type
8
8
  //
9
9
  // {
@@ -17,10 +17,14 @@ class CacheField {
17
17
  //
18
18
  // {
19
19
  // 'name': 'D',
20
- // 'sharedItems': null
20
+ // 'sharedItems': null,
21
+ // 'minValue': 5,
22
+ // 'maxValue': 45
21
23
  // }
22
24
  this.name = name;
23
25
  this.sharedItems = sharedItems;
26
+ this.minValue = minValue;
27
+ this.maxValue = maxValue;
24
28
  }
25
29
  render() {
26
30
  // PivotCache Field: http://www.datypic.com/sc/ooxml/e-ssml_cacheField-1.html
@@ -29,9 +33,12 @@ class CacheField {
29
33
  const escapedName = (0, utils_1.xmlEncode)(this.name);
30
34
  // integer types
31
35
  if (this.sharedItems === null) {
32
- // TK(2023-07-18): left out attributes... minValue="5" maxValue="45"
36
+ // Build minValue/maxValue attributes if available
37
+ const minMaxAttrs = this.minValue !== undefined && this.maxValue !== undefined
38
+ ? ` minValue="${this.minValue}" maxValue="${this.maxValue}"`
39
+ : "";
33
40
  return `<cacheField name="${escapedName}" numFmtId="0">
34
- <sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1" />
41
+ <sharedItems containsSemiMixedTypes="0" containsString="0" containsNumber="1" containsInteger="1"${minMaxAttrs} />
35
42
  </cacheField>`;
36
43
  }
37
44
  // string types - escape XML special characters in each shared item value
@@ -53,8 +53,6 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
53
53
  ...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
54
54
  "r:id": "rId1",
55
55
  refreshOnLoad: "1", // important for our implementation to work
56
- refreshedBy: "Author",
57
- refreshedDate: "45125.026046874998",
58
56
  createdVersion: "8",
59
57
  refreshedVersion: "8",
60
58
  minRefreshableVersion: "3",
@@ -82,8 +80,6 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
82
80
  ...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
83
81
  "r:id": model.rId || "rId1",
84
82
  refreshOnLoad: model.refreshOnLoad || "1",
85
- refreshedBy: model.refreshedBy || "Author",
86
- refreshedDate: model.refreshedDate || "45125.026046874998",
87
83
  createdVersion: model.createdVersion || "8",
88
84
  refreshedVersion: model.refreshedVersion || "8",
89
85
  minRefreshableVersion: model.minRefreshableVersion || "3",
@@ -117,8 +113,6 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
117
113
  cacheFields: [],
118
114
  rId: attributes["r:id"],
119
115
  refreshOnLoad: attributes.refreshOnLoad,
120
- refreshedBy: attributes.refreshedBy,
121
- refreshedDate: attributes.refreshedDate,
122
116
  createdVersion: attributes.createdVersion,
123
117
  refreshedVersion: attributes.refreshedVersion,
124
118
  minRefreshableVersion: attributes.minRefreshableVersion,
@@ -184,8 +178,5 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
184
178
  exports.PivotCacheDefinitionXform = PivotCacheDefinitionXform;
185
179
  PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES = {
186
180
  xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
187
- "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
188
- "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
189
- "mc:Ignorable": "xr",
190
- "xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
181
+ "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
191
182
  };
@@ -58,8 +58,6 @@ class PivotTableXform extends base_xform_1.BaseXform {
58
58
  */
59
59
  renderNew(xmlStream, model) {
60
60
  const { rows, columns, values, cacheFields, cacheId, applyWidthHeightFormats } = model;
61
- // Generate unique UID for each pivot table to prevent Excel treating them as identical
62
- const uniqueUid = `{${crypto.randomUUID().toUpperCase()}}`;
63
61
  // Build rowItems - need one <i> for each unique value in row fields, plus grand total
64
62
  const rowItems = buildRowItems(rows, cacheFields);
65
63
  // Build colItems - need one <i> for each unique value in col fields, plus grand total
@@ -71,15 +69,20 @@ class PivotTableXform extends base_xform_1.BaseXform {
71
69
  // - firstHeaderRow: 1 (column headers are in first row of pivot table)
72
70
  // - firstDataRow: 2 (data starts in second row)
73
71
  // - firstDataCol: 1 (data starts in second column, after row labels)
74
- // Calculate ref based on actual data size
75
- const endRow = 3 + rowFieldItemCount + 1; // start row + data rows + grand total
72
+ // Calculate ref based on actual data size:
73
+ // - Start row: 3
74
+ // - Header rows: 2 (column label row + subheader row)
75
+ // - Data rows: rowFieldItemCount
76
+ // - Grand total row: 1
77
+ // endRow = 3 + 2 + rowFieldItemCount + 1 - 1 = 5 + rowFieldItemCount
78
+ // Or simplified: startRow (3) + 1 (column labels) + rowFieldItemCount (data) + 1 (grand total)
79
+ const endRow = 3 + 1 + rowFieldItemCount + 1; // = 5 + rowFieldItemCount
76
80
  const endCol = 1 + colFieldItemCount + 1; // start col + data cols + grand total
77
81
  const endColLetter = String.fromCharCode(64 + endCol);
78
82
  const locationRef = `A3:${endColLetter}${endRow}`;
79
83
  xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
80
84
  xmlStream.openNode(this.tag, {
81
85
  ...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
82
- "xr:uid": uniqueUid,
83
86
  name: "PivotTable2",
84
87
  cacheId,
85
88
  applyNumberFormats: "0",
@@ -156,11 +159,9 @@ class PivotTableXform extends base_xform_1.BaseXform {
156
159
  * Render loaded pivot table (preserving original structure)
157
160
  */
158
161
  renderLoaded(xmlStream, model) {
159
- const uniqueUid = model.uid || `{${crypto.randomUUID().toUpperCase()}}`;
160
162
  xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
161
163
  xmlStream.openNode(this.tag, {
162
164
  ...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
163
- "xr:uid": uniqueUid,
164
165
  name: model.name || "PivotTable1",
165
166
  cacheId: model.cacheId,
166
167
  applyNumberFormats: model.applyNumberFormats || "0",
@@ -474,17 +475,15 @@ class PivotTableXform extends base_xform_1.BaseXform {
474
475
  }
475
476
  exports.PivotTableXform = PivotTableXform;
476
477
  PivotTableXform.PIVOT_TABLE_ATTRIBUTES = {
477
- xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
478
- "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
479
- "mc:Ignorable": "xr",
480
- "xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
478
+ xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
481
479
  };
482
480
  // Helpers
483
481
  /**
484
482
  * Build rowItems XML - one item for each unique value in row fields, plus grand total.
485
483
  * Each <i> represents a row in the pivot table.
486
- * - Regular items: <i><x v="index"/></i> where index is the position in sharedItems
484
+ * - Regular items: <i><x/></i> for index 0, <i><x v="index"/></i> for index > 0
487
485
  * - Grand total: <i t="grand"><x/></i>
486
+ * Note: When v=0, the v attribute should be omitted (Excel convention)
488
487
  */
489
488
  function buildRowItems(rows, cacheFields) {
490
489
  if (rows.length === 0) {
@@ -498,8 +497,14 @@ function buildRowItems(rows, cacheFields) {
498
497
  // Build items: one for each unique value + grand total
499
498
  const items = [];
500
499
  // Regular items - reference each unique value by index
500
+ // Note: v="0" should be omitted (Excel uses <x/> instead of <x v="0"/>)
501
501
  for (let i = 0; i < itemCount; i++) {
502
- items.push(`<i><x v="${i}" /></i>`);
502
+ if (i === 0) {
503
+ items.push("<i><x /></i>");
504
+ }
505
+ else {
506
+ items.push(`<i><x v="${i}" /></i>`);
507
+ }
503
508
  }
504
509
  // Grand total row
505
510
  items.push('<i t="grand"><x /></i>');
@@ -511,6 +516,7 @@ function buildRowItems(rows, cacheFields) {
511
516
  /**
512
517
  * Build colItems XML - one item for each unique value in column fields, plus grand total.
513
518
  * When there are multiple data fields (values), each column value may have sub-columns.
519
+ * Note: When v=0, the v attribute should be omitted (Excel convention)
514
520
  */
515
521
  function buildColItems(columns, cacheFields, valueCount) {
516
522
  if (columns.length === 0) {
@@ -519,7 +525,12 @@ function buildColItems(columns, cacheFields, valueCount) {
519
525
  // Multiple values: one column per value + grand total
520
526
  const items = [];
521
527
  for (let i = 0; i < valueCount; i++) {
522
- items.push(`<i><x v="${i}" /></i>`);
528
+ if (i === 0) {
529
+ items.push("<i><x /></i>");
530
+ }
531
+ else {
532
+ items.push(`<i><x v="${i}" /></i>`);
533
+ }
523
534
  }
524
535
  items.push('<i t="grand"><x /></i>');
525
536
  return { count: items.length, xml: items.join("\n ") };
@@ -534,8 +545,14 @@ function buildColItems(columns, cacheFields, valueCount) {
534
545
  // Build items: one for each unique value + grand total
535
546
  const items = [];
536
547
  // Regular items - reference each unique value by index
548
+ // Note: v="0" should be omitted (Excel uses <x/> instead of <x v="0"/>)
537
549
  for (let i = 0; i < itemCount; i++) {
538
- items.push(`<i><x v="${i}" /></i>`);
550
+ if (i === 0) {
551
+ items.push("<i><x /></i>");
552
+ }
553
+ else {
554
+ items.push(`<i><x v="${i}" /></i>`);
555
+ }
539
556
  }
540
557
  // Grand total column
541
558
  items.push('<i t="grand"><x /></i>');
@@ -581,22 +598,27 @@ function renderPivotFields(pivotTable) {
581
598
  }
582
599
  function renderPivotField(fieldType, sharedItems) {
583
600
  // fieldType: 'row', 'column', 'value', null
584
- const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
601
+ // Note: defaultSubtotal="0" should only be on value fields and non-axis fields,
602
+ // NOT on row/column axis fields (Excel will auto-calculate subtotals for them)
585
603
  if (fieldType === "row" || fieldType === "column") {
586
604
  const axis = fieldType === "row" ? "axisRow" : "axisCol";
605
+ // Row and column fields should NOT have defaultSubtotal="0"
606
+ const axisAttributes = 'compact="0" outline="0" showAll="0"';
587
607
  // items = one for each shared item + one default item
588
608
  const itemsXml = [
589
609
  ...sharedItems.map((_item, index) => `<item x="${index}" />`),
590
610
  '<item t="default" />' // Required default item for subtotals/grand totals
591
611
  ].join("\n ");
592
612
  return `
593
- <pivotField axis="${axis}" ${defaultAttributes}>
613
+ <pivotField axis="${axis}" ${axisAttributes}>
594
614
  <items count="${sharedItems.length + 1}">
595
615
  ${itemsXml}
596
616
  </items>
597
617
  </pivotField>
598
618
  `;
599
619
  }
620
+ // Value fields and non-axis fields should have defaultSubtotal="0"
621
+ const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
600
622
  return `
601
623
  <pivotField
602
624
  ${fieldType === "value" ? 'dataField="1"' : ""}
@@ -51,9 +51,10 @@ class PageSetupXform extends base_xform_1.BaseXform {
51
51
  draft: booleanToXml(model.draft),
52
52
  cellComments: cellCommentsToXml(model.cellComments),
53
53
  errors: errorsToXml(model.errors),
54
- scale: model.scale,
55
- fitToWidth: model.fitToWidth,
56
- fitToHeight: model.fitToHeight,
54
+ // Only output non-default values (matches Excel behavior)
55
+ scale: model.scale !== 100 ? model.scale : undefined,
56
+ fitToWidth: model.fitToWidth !== 1 ? model.fitToWidth : undefined,
57
+ fitToHeight: model.fitToHeight !== 1 ? model.fitToHeight : undefined,
57
58
  firstPageNumber: model.firstPageNumber,
58
59
  useFirstPageNumber: booleanToXml(!!model.firstPageNumber),
59
60
  usePrinterDefaults: booleanToXml(model.usePrinterDefaults),
@@ -51,7 +51,7 @@ class RowXform extends base_xform_1.BaseXform {
51
51
  xmlStream.addAttribute("s", model.styleId);
52
52
  xmlStream.addAttribute("customFormat", "1");
53
53
  }
54
- xmlStream.addAttribute("x14ac:dyDescent", "0.25");
54
+ // Note: dyDescent is MS extension, not output by default (Excel auto-calculates)
55
55
  if (model.outlineLevel) {
56
56
  xmlStream.addAttribute("outlineLevel", model.outlineLevel);
57
57
  }
@@ -10,10 +10,13 @@ class SheetFormatPropertiesXform extends base_xform_1.BaseXform {
10
10
  if (model) {
11
11
  const attributes = {
12
12
  defaultRowHeight: model.defaultRowHeight,
13
- outlineLevelRow: model.outlineLevelRow,
14
- outlineLevelCol: model.outlineLevelCol,
15
- "x14ac:dyDescent": model.dyDescent
13
+ // Only output outlineLevelRow/Col when non-zero (matches Excel behavior)
14
+ outlineLevelRow: model.outlineLevelRow || undefined,
15
+ outlineLevelCol: model.outlineLevelCol || undefined,
16
+ // Only output dyDescent if explicitly set (MS extension, not ECMA-376 standard)
17
+ "x14ac:dyDescent": model.dyDescent || undefined
16
18
  };
19
+ // Only output defaultColWidth if explicitly set
17
20
  if (model.defaultColWidth) {
18
21
  attributes.defaultColWidth = model.defaultColWidth;
19
22
  }
@@ -23,16 +23,20 @@ class SheetViewXform extends base_xform_1.BaseXform {
23
23
  }
24
24
  }
25
25
  render(xmlStream, model) {
26
- xmlStream.openNode("sheetView", {
27
- workbookViewId: model.workbookViewId || 0
28
- });
26
+ // Build initial attributes with correct order to match Excel output
27
+ const initialAttrs = {};
28
+ if (model.tabSelected) {
29
+ initialAttrs.tabSelected = "1";
30
+ }
31
+ initialAttrs.workbookViewId = model.workbookViewId || 0;
32
+ xmlStream.openNode("sheetView", initialAttrs);
29
33
  const add = function (name, value, included) {
30
34
  if (included) {
31
35
  xmlStream.addAttribute(name, value);
32
36
  }
33
37
  };
34
38
  add("rightToLeft", "1", model.rightToLeft === true);
35
- add("tabSelected", "1", model.tabSelected);
39
+ // tabSelected is now in initialAttrs
36
40
  add("showRuler", "0", model.showRuler === false);
37
41
  add("showRowColHeaders", "0", model.showRowColHeaders === false);
38
42
  add("showGridLines", "0", model.showGridLines === false);
@@ -515,6 +515,6 @@ WorkSheetXform.WORKSHEET_ATTRIBUTES = {
515
515
  xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
516
516
  "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
517
517
  "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
518
- "mc:Ignorable": "x14ac",
519
- "xmlns:x14ac": "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"
518
+ "xmlns:x14ac": "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac",
519
+ "mc:Ignorable": "x14ac"
520
520
  };
@@ -12,35 +12,48 @@ class FontXform extends base_xform_1.BaseXform {
12
12
  constructor(options) {
13
13
  super();
14
14
  this.options = options || FontXform.OPTIONS;
15
- this.map = {
16
- b: { prop: "bold", xform: new boolean_xform_1.BooleanXform({ tag: "b", attr: "val" }) },
17
- i: { prop: "italic", xform: new boolean_xform_1.BooleanXform({ tag: "i", attr: "val" }) },
18
- u: { prop: "underline", xform: new underline_xform_1.UnderlineXform() },
19
- charset: { prop: "charset", xform: new integer_xform_1.IntegerXform({ tag: "charset", attr: "val" }) },
20
- color: { prop: "color", xform: new color_xform_1.ColorXform() },
21
- condense: { prop: "condense", xform: new boolean_xform_1.BooleanXform({ tag: "condense", attr: "val" }) },
22
- extend: { prop: "extend", xform: new boolean_xform_1.BooleanXform({ tag: "extend", attr: "val" }) },
23
- family: { prop: "family", xform: new integer_xform_1.IntegerXform({ tag: "family", attr: "val" }) },
24
- outline: { prop: "outline", xform: new boolean_xform_1.BooleanXform({ tag: "outline", attr: "val" }) },
25
- vertAlign: { prop: "vertAlign", xform: new string_xform_1.StringXform({ tag: "vertAlign", attr: "val" }) },
26
- scheme: { prop: "scheme", xform: new string_xform_1.StringXform({ tag: "scheme", attr: "val" }) },
27
- shadow: { prop: "shadow", xform: new boolean_xform_1.BooleanXform({ tag: "shadow", attr: "val" }) },
28
- strike: { prop: "strike", xform: new boolean_xform_1.BooleanXform({ tag: "strike", attr: "val" }) },
29
- sz: { prop: "size", xform: new integer_xform_1.IntegerXform({ tag: "sz", attr: "val" }) }
30
- };
31
- this.map[this.options.fontNameTag] = {
32
- prop: "name",
33
- xform: new string_xform_1.StringXform({ tag: this.options.fontNameTag, attr: "val" })
34
- };
15
+ // Define properties in render order (Excel's expected order)
16
+ const fontProperties = [
17
+ { tag: "b", prop: "bold", xform: new boolean_xform_1.BooleanXform({ tag: "b", attr: "val" }) },
18
+ { tag: "i", prop: "italic", xform: new boolean_xform_1.BooleanXform({ tag: "i", attr: "val" }) },
19
+ { tag: "u", prop: "underline", xform: new underline_xform_1.UnderlineXform() },
20
+ { tag: "strike", prop: "strike", xform: new boolean_xform_1.BooleanXform({ tag: "strike", attr: "val" }) },
21
+ {
22
+ tag: "condense",
23
+ prop: "condense",
24
+ xform: new boolean_xform_1.BooleanXform({ tag: "condense", attr: "val" })
25
+ },
26
+ { tag: "extend", prop: "extend", xform: new boolean_xform_1.BooleanXform({ tag: "extend", attr: "val" }) },
27
+ { tag: "outline", prop: "outline", xform: new boolean_xform_1.BooleanXform({ tag: "outline", attr: "val" }) },
28
+ { tag: "shadow", prop: "shadow", xform: new boolean_xform_1.BooleanXform({ tag: "shadow", attr: "val" }) },
29
+ { tag: "sz", prop: "size", xform: new integer_xform_1.IntegerXform({ tag: "sz", attr: "val" }) },
30
+ { tag: "color", prop: "color", xform: new color_xform_1.ColorXform() },
31
+ {
32
+ tag: this.options.fontNameTag,
33
+ prop: "name",
34
+ xform: new string_xform_1.StringXform({ tag: this.options.fontNameTag, attr: "val" })
35
+ },
36
+ { tag: "family", prop: "family", xform: new integer_xform_1.IntegerXform({ tag: "family", attr: "val" }) },
37
+ { tag: "scheme", prop: "scheme", xform: new string_xform_1.StringXform({ tag: "scheme", attr: "val" }) },
38
+ { tag: "charset", prop: "charset", xform: new integer_xform_1.IntegerXform({ tag: "charset", attr: "val" }) },
39
+ {
40
+ tag: "vertAlign",
41
+ prop: "vertAlign",
42
+ xform: new string_xform_1.StringXform({ tag: "vertAlign", attr: "val" })
43
+ }
44
+ ];
45
+ // Build map and renderOrder from single source of truth
46
+ this.map = Object.fromEntries(fontProperties.map(p => [p.tag, { prop: p.prop, xform: p.xform }]));
47
+ this.renderOrder = fontProperties.map(p => p.tag);
35
48
  }
36
49
  get tag() {
37
50
  return this.options.tagName;
38
51
  }
39
52
  render(xmlStream, model) {
40
- const { map } = this;
53
+ const { map, renderOrder } = this;
41
54
  xmlStream.openNode(this.options.tagName);
42
- Object.entries(this.map).forEach(([tag, defn]) => {
43
- map[tag].xform.render(xmlStream, model[defn.prop]);
55
+ renderOrder.forEach(tag => {
56
+ map[tag].xform.render(xmlStream, model[map[tag].prop]);
44
57
  });
45
58
  xmlStream.closeNode();
46
59
  }
@@ -20,7 +20,9 @@ class AutoFilterXform extends base_xform_1.BaseXform {
20
20
  });
21
21
  }
22
22
  render(xmlStream, model) {
23
- xmlStream.openNode(this.tag, { ref: model.autoFilterRef });
23
+ xmlStream.openNode(this.tag, {
24
+ ref: model.autoFilterRef
25
+ });
24
26
  model.columns.forEach((column) => {
25
27
  this.map.filterColumn.render(xmlStream, column);
26
28
  });
@@ -18,7 +18,8 @@ class TableColumnXform extends base_xform_1.BaseXform {
18
18
  id: model.id.toString(),
19
19
  name: model.name,
20
20
  totalsRowLabel: model.totalsRowLabel,
21
- totalsRowFunction: model.totalsRowFunction,
21
+ // Excel doesn't output totalsRowFunction when value is 'none'
22
+ totalsRowFunction: model.totalsRowFunction === "none" ? undefined : model.totalsRowFunction,
22
23
  dxfId: model.dxfId
23
24
  });
24
25
  }
@@ -43,8 +43,8 @@ class TableXform extends base_xform_1.BaseXform {
43
43
  displayName: model.displayName || model.name,
44
44
  ref: model.tableRef,
45
45
  totalsRowCount: model.totalsRow ? "1" : undefined,
46
- totalsRowShown: model.totalsRow ? undefined : "1",
47
- headerRowCount: model.headerRow ? "1" : "0"
46
+ // Excel doesn't output headerRowCount when it's 1 (default) or when there's a header row
47
+ headerRowCount: model.headerRow ? undefined : "0"
48
48
  });
49
49
  this.map.autoFilter.render(xmlStream, model);
50
50
  this.map.tableColumns.render(xmlStream, model.columns);
@@ -65,7 +65,8 @@ class TableXform extends base_xform_1.BaseXform {
65
65
  displayName: attributes.displayName || attributes.name,
66
66
  tableRef: attributes.ref,
67
67
  totalsRow: attributes.totalsRowCount === "1",
68
- headerRow: attributes.headerRowCount === "1"
68
+ // ECMA-376: headerRowCount defaults to 1, so missing attribute means has header
69
+ headerRow: attributes.headerRowCount !== "0"
69
70
  };
70
71
  break;
71
72
  default:
@@ -124,10 +125,5 @@ class TableXform extends base_xform_1.BaseXform {
124
125
  }
125
126
  exports.TableXform = TableXform;
126
127
  TableXform.TABLE_ATTRIBUTES = {
127
- xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
128
- "xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
129
- "mc:Ignorable": "xr xr3",
130
- "xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision",
131
- "xmlns:xr3": "http://schemas.microsoft.com/office/spreadsheetml/2016/revision3"
132
- // 'xr:uid': '{00000000-000C-0000-FFFF-FFFF00000000}',
128
+ xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
133
129
  };