@rowsncolumns/toolkit 24.4.5 → 24.5.1

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 (113) hide show
  1. package/dist/cjs/autofilter-condition.d.ts +45 -1
  2. package/dist/cjs/autofilter-condition.d.ts.map +1 -1
  3. package/dist/cjs/autofilter-condition.js +135 -12
  4. package/dist/cjs/excel-parser/helpers.d.ts +15 -1
  5. package/dist/cjs/excel-parser/helpers.d.ts.map +1 -1
  6. package/dist/cjs/excel-parser/helpers.js +119 -5
  7. package/dist/cjs/excel-parser/interface.d.ts +3 -1
  8. package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
  9. package/dist/cjs/excel-parser/plugins/ods/index.d.ts.map +1 -1
  10. package/dist/cjs/excel-parser/plugins/ods/index.js +186 -97
  11. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts +15 -0
  12. package/dist/cjs/excel-parser/plugins/ods/styles.d.ts.map +1 -1
  13. package/dist/cjs/excel-parser/plugins/ods/styles.js +80 -6
  14. package/dist/cjs/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
  15. package/dist/cjs/excel-parser/plugins/xlsb/index.js +107 -103
  16. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts +77 -1
  17. package/dist/cjs/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
  18. package/dist/cjs/excel-parser/plugins/xlsx/charts.js +920 -122
  19. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts +7 -1
  20. package/dist/cjs/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
  21. package/dist/cjs/excel-parser/plugins/xlsx/drawing.js +24 -9
  22. package/dist/cjs/excel-parser/plugins/xlsx/form-controls.d.ts +24 -3
  23. package/dist/cjs/excel-parser/plugins/xlsx/form-controls.d.ts.map +1 -1
  24. package/dist/cjs/excel-parser/plugins/xlsx/form-controls.js +25 -3
  25. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +24 -1
  26. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  27. package/dist/cjs/excel-parser/plugins/xlsx/index.js +561 -394
  28. package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts +8 -2
  29. package/dist/cjs/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
  30. package/dist/cjs/excel-parser/plugins/xlsx/pivots.js +312 -21
  31. package/dist/cjs/excel-parser/plugins/xlsx/relationships.d.ts +6 -0
  32. package/dist/cjs/excel-parser/plugins/xlsx/relationships.d.ts.map +1 -1
  33. package/dist/cjs/excel-parser/plugins/xlsx/relationships.js +9 -0
  34. package/dist/cjs/excel-parser/plugins/xlsx/theme.d.ts +1 -1
  35. package/dist/cjs/excel-parser/types.d.ts +9 -1
  36. package/dist/cjs/excel-parser/types.d.ts.map +1 -1
  37. package/dist/cjs/excel-parser/workbook.d.ts +1 -0
  38. package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
  39. package/dist/cjs/excel-parser/workbook.js +5 -0
  40. package/dist/cjs/excel-parser/worker.js +2 -0
  41. package/dist/cjs/exporter/ods/ods-export.d.ts.map +1 -1
  42. package/dist/cjs/exporter/ods/ods-export.js +255 -80
  43. package/dist/cjs/exporter/xlsx/chart-builder.d.ts +7 -1
  44. package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -1
  45. package/dist/cjs/exporter/xlsx/chart-builder.js +902 -119
  46. package/dist/cjs/exporter/xlsx/form-controls-builder.d.ts +16 -3
  47. package/dist/cjs/exporter/xlsx/form-controls-builder.d.ts.map +1 -1
  48. package/dist/cjs/exporter/xlsx/form-controls-builder.js +30 -8
  49. package/dist/cjs/exporter/xlsx/pivot-builder.d.ts.map +1 -1
  50. package/dist/cjs/exporter/xlsx/pivot-builder.js +206 -162
  51. package/dist/cjs/exporter/xlsx/style-builder.d.ts +8 -2
  52. package/dist/cjs/exporter/xlsx/style-builder.d.ts.map +1 -1
  53. package/dist/cjs/exporter/xlsx/style-builder.js +25 -0
  54. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +2 -2
  55. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  56. package/dist/cjs/exporter/xlsx/xlsx-export.js +884 -470
  57. package/dist/esm/autofilter-condition.d.ts +45 -1
  58. package/dist/esm/autofilter-condition.d.ts.map +1 -1
  59. package/dist/esm/autofilter-condition.js +131 -11
  60. package/dist/esm/excel-parser/helpers.d.ts +15 -1
  61. package/dist/esm/excel-parser/helpers.d.ts.map +1 -1
  62. package/dist/esm/excel-parser/helpers.js +94 -4
  63. package/dist/esm/excel-parser/interface.d.ts +3 -1
  64. package/dist/esm/excel-parser/interface.d.ts.map +1 -1
  65. package/dist/esm/excel-parser/plugins/ods/index.d.ts.map +1 -1
  66. package/dist/esm/excel-parser/plugins/ods/index.js +105 -22
  67. package/dist/esm/excel-parser/plugins/ods/styles.d.ts +15 -0
  68. package/dist/esm/excel-parser/plugins/ods/styles.d.ts.map +1 -1
  69. package/dist/esm/excel-parser/plugins/ods/styles.js +79 -5
  70. package/dist/esm/excel-parser/plugins/xlsb/index.d.ts.map +1 -1
  71. package/dist/esm/excel-parser/plugins/xlsb/index.js +15 -7
  72. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts +77 -1
  73. package/dist/esm/excel-parser/plugins/xlsx/charts.d.ts.map +1 -1
  74. package/dist/esm/excel-parser/plugins/xlsx/charts.js +708 -16
  75. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts +7 -1
  76. package/dist/esm/excel-parser/plugins/xlsx/drawing.d.ts.map +1 -1
  77. package/dist/esm/excel-parser/plugins/xlsx/drawing.js +23 -0
  78. package/dist/esm/excel-parser/plugins/xlsx/form-controls.d.ts +24 -3
  79. package/dist/esm/excel-parser/plugins/xlsx/form-controls.d.ts.map +1 -1
  80. package/dist/esm/excel-parser/plugins/xlsx/form-controls.js +21 -3
  81. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +24 -1
  82. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  83. package/dist/esm/excel-parser/plugins/xlsx/index.js +192 -54
  84. package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts +8 -2
  85. package/dist/esm/excel-parser/plugins/xlsx/pivots.d.ts.map +1 -1
  86. package/dist/esm/excel-parser/plugins/xlsx/pivots.js +220 -4
  87. package/dist/esm/excel-parser/plugins/xlsx/relationships.d.ts +6 -0
  88. package/dist/esm/excel-parser/plugins/xlsx/relationships.d.ts.map +1 -1
  89. package/dist/esm/excel-parser/plugins/xlsx/relationships.js +9 -0
  90. package/dist/esm/excel-parser/plugins/xlsx/theme.d.ts +1 -1
  91. package/dist/esm/excel-parser/types.d.ts +9 -1
  92. package/dist/esm/excel-parser/types.d.ts.map +1 -1
  93. package/dist/esm/excel-parser/workbook.d.ts +1 -0
  94. package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
  95. package/dist/esm/excel-parser/workbook.js +4 -0
  96. package/dist/esm/excel-parser/worker.js +2 -0
  97. package/dist/esm/exporter/ods/ods-export.d.ts.map +1 -1
  98. package/dist/esm/exporter/ods/ods-export.js +238 -72
  99. package/dist/esm/exporter/xlsx/chart-builder.d.ts +7 -1
  100. package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
  101. package/dist/esm/exporter/xlsx/chart-builder.js +674 -58
  102. package/dist/esm/exporter/xlsx/form-controls-builder.d.ts +16 -3
  103. package/dist/esm/exporter/xlsx/form-controls-builder.d.ts.map +1 -1
  104. package/dist/esm/exporter/xlsx/form-controls-builder.js +29 -8
  105. package/dist/esm/exporter/xlsx/pivot-builder.d.ts.map +1 -1
  106. package/dist/esm/exporter/xlsx/pivot-builder.js +187 -139
  107. package/dist/esm/exporter/xlsx/style-builder.d.ts +8 -2
  108. package/dist/esm/exporter/xlsx/style-builder.d.ts.map +1 -1
  109. package/dist/esm/exporter/xlsx/style-builder.js +28 -0
  110. package/dist/esm/exporter/xlsx/xlsx-export.d.ts +2 -2
  111. package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  112. package/dist/esm/exporter/xlsx/xlsx-export.js +600 -206
  113. package/package.json +10 -5
@@ -2,19 +2,19 @@ import { addressToCell, addressToSelection, alpha2number, checkIfStringIsReferen
2
2
  import { XMLParser } from "fast-xml-parser";
3
3
  import { get } from "lodash-es";
4
4
  import { Parser as HtmlParser } from "htmlparser2";
5
- import { createFilterSpecsFromAutoFilter, excelColumnWidthToPixels, findMax, getRangeFromAddressRef, parseTableColumnTotals, } from "../../helpers";
5
+ import { createFilterSpecsFromAutoFilter, excelColumnWidthToPixels, findMax, createSortSpecsFromSortState, getRangeFromAddressRef, hiddenFilterButtonColumns, parseTableColumnTotals, } from "../../helpers";
6
6
  import { defaultSpreadsheetTheme } from "../../theme-default";
7
7
  import { Unescape } from "../../utils";
8
8
  import { compactPrintSettings, isBuiltinPrintName, isDefaultPageMargins, parsePrintAreaFormula, parsePrintTitlesFormula, PRINT_AREA_NAME, } from "../../../print-settings";
9
9
  import { StreamingZipParser } from "../../zip-parser-streaming";
10
10
  import { SheetDataValidationStamper } from "../../dv-cell-stamp";
11
11
  import { BaseWorkbookPlugin } from "../base/index";
12
- import { processChart } from "./charts";
13
- import { customFiltersToCondition, dynamicFilterToCondition, } from "../../../autofilter-condition";
12
+ import { bindPivotChartsToPivotTables, PIVOT_SOURCE_NAME_KEY, processChart, } from "./charts";
13
+ import { customFiltersToCriteria, dynamicFilterToCondition, top10FilterToCondition, } from "../../../autofilter-condition";
14
14
  import { getConditionalFormattingFromSheet } from "./conditional-formatting";
15
15
  import { getDataValidationsForSheet as parseDataValidationsFromSheetData } from "./data-validations";
16
- import { anchorsInDocumentOrder, processAnchors, } from "./drawing";
17
- import { getRelationsById } from "./relationships";
16
+ import { anchorPlacementFromEditAs, anchorsInDocumentOrder, processAnchors, } from "./drawing";
17
+ import { getRelationsById, getSheetRelsPath } from "./relationships";
18
18
  import { hasPowerQueryParts, } from "../../../power-query";
19
19
  import { extractSheetViewProperties } from "./sheet-views";
20
20
  import { EMPTY_SHEET_THREADED_COMMENTS, parsePersons, parseThreadedComments, } from "./threaded-comments";
@@ -422,10 +422,17 @@ export class WorkbookStyles {
422
422
  if (color)
423
423
  bandedDef.rowProperties.headerColor = color;
424
424
  break;
425
- case "totalRow":
425
+ case "totalRow": {
426
426
  if (color)
427
427
  bandedDef.rowProperties.totalRowColor = color;
428
+ // Excel's totals element bolds the row (`<font><b/></font>`), fill or not.
429
+ const font = dxf?.font || dxf?.["x:font"];
430
+ const b = font?.b ?? font?.["x:b"];
431
+ if (b !== undefined && (b?.a$?.val ?? b?.val) !== "0") {
432
+ bandedDef.rowProperties.totalRowBold = true;
433
+ }
428
434
  break;
435
+ }
429
436
  case "firstRowStripe":
430
437
  if (color)
431
438
  bandedDef.rowProperties.firstBandColor = color;
@@ -442,6 +449,14 @@ export class WorkbookStyles {
442
449
  if (color)
443
450
  bandedDef.columnProperties.secondBandColor = color;
444
451
  break;
452
+ case "firstColumn":
453
+ if (color)
454
+ bandedDef.rowProperties.firstColumnColor = color;
455
+ break;
456
+ case "lastColumn":
457
+ if (color)
458
+ bandedDef.rowProperties.lastColumnColor = color;
459
+ break;
445
460
  }
446
461
  }
447
462
  // Only return if we found any properties
@@ -717,6 +732,12 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
717
732
  * relevant elements. Absent entry ⇒ unknown ⇒ extractors parse as before.
718
733
  */
719
734
  sheetFeatures = new Map();
735
+ /**
736
+ * Pivot tables are parsed once: `getCharts()` needs them to bind
737
+ * PivotCharts before `getPivotTables()` runs, and both must see the
738
+ * same generated `pivotId`s.
739
+ */
740
+ pivotTablesPromise;
720
741
  /**
721
742
  * Single-entry memo for `getSheetData`: consecutive extractor calls for
722
743
  * the same sheet reuse one parsed DOM instead of re-decompressing and
@@ -724,6 +745,11 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
724
745
  * multi-sheet workbook never retains more than one worksheet DOM.
725
746
  */
726
747
  lastSheetDataCache = null;
748
+ /**
749
+ * Per-rid memo of a chartsheet's parsed chart, so `Sheet.chartId` (set by
750
+ * `getSheets()`) and `getCharts()` see the same generated chart id.
751
+ */
752
+ chartsheetChartsCache = new Map();
727
753
  /** workbook.xml sheet elements indexed by sheetId (sheet-scoped API). */
728
754
  worksheetBySheetId = new Map();
729
755
  /**
@@ -1120,6 +1146,15 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1120
1146
  entry.dynamicFilterType = attrs.type;
1121
1147
  }
1122
1148
  }
1149
+ // <top10 top="1" percent="0" val="10"/> (Excel's "Top 10…")
1150
+ if (name === "top10" && currentFilterColumn !== null) {
1151
+ const entry = getOrCreateFilterColumn(currentFilterColumn);
1152
+ entry.top10 = {
1153
+ top: typeof attrs.top === "string" ? attrs.top : undefined,
1154
+ percent: typeof attrs.percent === "string" ? attrs.percent : undefined,
1155
+ val: typeof attrs.val === "string" ? attrs.val : undefined,
1156
+ };
1157
+ }
1123
1158
  if (name === "tablePart") {
1124
1159
  const rid = attrs["r:id"];
1125
1160
  if (typeof rid === "string") {
@@ -1182,15 +1217,20 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1182
1217
  };
1183
1218
  }
1184
1219
  if (raw.customFilters?.length) {
1185
- const condition = customFiltersToCondition(raw.customFilters, raw.customFiltersAnd === true);
1186
- if (condition)
1187
- criteria.condition = condition;
1220
+ const conditions = customFiltersToCriteria(raw.customFilters, raw.customFiltersAnd === true);
1221
+ if (conditions)
1222
+ Object.assign(criteria, conditions);
1188
1223
  }
1189
1224
  else if (raw.dynamicFilterType) {
1190
1225
  const condition = dynamicFilterToCondition(raw.dynamicFilterType);
1191
1226
  if (condition)
1192
1227
  criteria.condition = condition;
1193
1228
  }
1229
+ else if (raw.top10) {
1230
+ const condition = top10FilterToCondition(raw.top10);
1231
+ if (condition)
1232
+ criteria.condition = condition;
1233
+ }
1194
1234
  return { columnIndex, filterCriteria: criteria };
1195
1235
  });
1196
1236
  const sheetViewData = sheetViewAttrs || paneAttrs
@@ -1286,7 +1326,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1286
1326
  return { maxColumnIndex, maxRowIndex };
1287
1327
  }
1288
1328
  const sheetPath = this.resolveSheetPath(relation.target);
1289
- const sheetRelPath = sheetPath.replace("worksheets", "worksheets/_rels") + ".rels";
1329
+ const sheetRelPath = getSheetRelsPath(sheetPath);
1290
1330
  const sheetRelContent = await this.zipParser.getText(sheetRelPath);
1291
1331
  if (!sheetRelContent) {
1292
1332
  return { maxColumnIndex, maxRowIndex };
@@ -1415,7 +1455,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1415
1455
  controls: false,
1416
1456
  };
1417
1457
  const sheetPath = this.resolveSheetPath(relation.target);
1418
- const sheetRelPath = sheetPath.replace("worksheets", "worksheets/_rels") + ".rels";
1458
+ const sheetRelPath = getSheetRelsPath(sheetPath);
1419
1459
  let sheetRelationships = {};
1420
1460
  try {
1421
1461
  const sheetRelContent = await this.zipParser.getText(sheetRelPath);
@@ -1937,6 +1977,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1937
1977
  this.personsPromise = null;
1938
1978
  this.sheetFeatures.clear();
1939
1979
  this.lastSheetDataCache = null;
1980
+ this.chartsheetChartsCache.clear();
1940
1981
  this.sheetMetadataCache.clear();
1941
1982
  this.dataValidationsBySheetCache.clear();
1942
1983
  this.conditionalFormattingBySheetCache.clear();
@@ -1957,6 +1998,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1957
1998
  this.personsPromise = null;
1958
1999
  this.sheetFeatures.clear();
1959
2000
  this.lastSheetDataCache = null;
2001
+ this.chartsheetChartsCache.clear();
1960
2002
  this.sheetMetadataCache.clear();
1961
2003
  this.dataValidationsBySheetCache.clear();
1962
2004
  this.conditionalFormattingBySheetCache.clear();
@@ -1994,7 +2036,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
1994
2036
  const sheetPath = target.startsWith("/xl")
1995
2037
  ? target.replace(/^\//gi, "")
1996
2038
  : `xl/${this.relations[rId].target}`;
1997
- const sheetRelPath = sheetPath.replace("worksheets", "worksheets/_rels") + ".rels";
2039
+ const sheetRelPath = getSheetRelsPath(sheetPath);
1998
2040
  try {
1999
2041
  const sheetRelContent = await this.zipParser.getText(sheetRelPath);
2000
2042
  const sheetRels = this.xmlParser.parse(sheetRelContent);
@@ -2035,10 +2077,17 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2035
2077
  // Parse table settings
2036
2078
  const headerRow = attrs.headerRowCount !== "0";
2037
2079
  const totalRow = attrs.totalsRowCount === "1";
2038
- const showFirstColumn = attrs.showFirstColumn === "1";
2039
- const showLastColumn = attrs.showLastColumn === "1";
2040
- const showRowStripes = attrs.showRowStripes !== "0";
2041
- const showColumnStripes = attrs.showColumnStripes === "1";
2080
+ // Parse table style information
2081
+ // Handle both with and without namespace prefix
2082
+ const tableStyle = get(tableDefinition, "tableStyleInfo") ||
2083
+ get(tableDefinition, "x:tableStyleInfo") ||
2084
+ {};
2085
+ const tableStyleAttrs = tableStyle.a$ || {};
2086
+ // The banding toggles live on `<tableStyleInfo>`, not on `<table>`.
2087
+ const showFirstColumn = tableStyleAttrs.showFirstColumn === "1";
2088
+ const showLastColumn = tableStyleAttrs.showLastColumn === "1";
2089
+ const showRowStripes = tableStyleAttrs.showRowStripes !== "0";
2090
+ const showColumnStripes = tableStyleAttrs.showColumnStripes === "1";
2042
2091
  // Parse table columns
2043
2092
  // Handle both with and without namespace prefix
2044
2093
  const columnDefinitions = get(tableDefinition, "tableColumns.tableColumn") ||
@@ -2066,19 +2115,20 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2066
2115
  }
2067
2116
  return columnObj;
2068
2117
  });
2069
- // Parse table style information
2070
2118
  // Handle both with and without namespace prefix
2071
- const tableStyle = get(tableDefinition, "tableStyleInfo") ||
2072
- get(tableDefinition, "x:tableStyleInfo") ||
2073
- {};
2074
- const tableStyleAttrs = tableStyle.a$ || {};
2075
- // Extract filter button settings
2076
- const filterButton = attrs.filterButton !== "0";
2119
+ const autoFilter = tableDefinition.autoFilter ?? tableDefinition["x:autoFilter"];
2120
+ // Excel shows a table's filter buttons iff the part carries an
2121
+ // `<autoFilter>`; per-column buttons hide via
2122
+ // `<filterColumn hiddenButton="1">`.
2123
+ const filterButton = attrs.filterButton !== "0" && (!headerRow || autoFilter !== undefined);
2124
+ for (const colId of hiddenFilterButtonColumns(autoFilter)) {
2125
+ if (columns[colId])
2126
+ columns[colId].filterButton = false;
2127
+ }
2077
2128
  const range = getRangeFromAddressRef(attrs.ref);
2078
2129
  if (range) {
2079
- // Handle both with and without namespace prefix
2080
- const autoFilter = tableDefinition.autoFilter ?? tableDefinition["x:autoFilter"];
2081
2130
  const filterSpecs = createFilterSpecsFromAutoFilter(autoFilter, (dxfId, cellColor) => this.resolveDxfColor(dxfId, cellColor));
2131
+ const sortSpecs = createSortSpecsFromSortState(tableDefinition.sortState ?? tableDefinition["x:sortState"], range, (dxfId, cellColor) => this.resolveDxfColor(dxfId, cellColor));
2082
2132
  // Create the table view object
2083
2133
  const tableView = {
2084
2134
  id: attrs.id,
@@ -2094,6 +2144,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2094
2144
  columns,
2095
2145
  range,
2096
2146
  filterSpecs,
2147
+ ...(sortSpecs.length ? { sortSpecs } : {}),
2097
2148
  };
2098
2149
  // Add table theme if available
2099
2150
  if (tableStyleAttrs.name) {
@@ -2111,16 +2162,22 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2111
2162
  // Only include row/column properties based on stripe settings
2112
2163
  if (!showRowStripes && bandedRange.rowProperties) {
2113
2164
  // Keep header and total row styling even if stripes are off
2114
- const { headerColor, headerBorder, totalRowColor, totalRowBorder, } = bandedRange.rowProperties;
2165
+ const { headerColor, headerBorder, totalRowColor, totalRowBorder, firstColumnColor, lastColumnColor, totalRowBold, } = bandedRange.rowProperties;
2115
2166
  if (headerColor ||
2116
2167
  headerBorder ||
2117
2168
  totalRowColor ||
2118
- totalRowBorder) {
2169
+ totalRowBorder ||
2170
+ firstColumnColor ||
2171
+ lastColumnColor ||
2172
+ totalRowBold) {
2119
2173
  bandedRange.rowProperties = {
2120
2174
  headerColor,
2121
2175
  headerBorder,
2122
2176
  totalRowColor,
2123
2177
  totalRowBorder,
2178
+ firstColumnColor,
2179
+ lastColumnColor,
2180
+ totalRowBold,
2124
2181
  };
2125
2182
  }
2126
2183
  else {
@@ -2173,6 +2230,25 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2173
2230
  }
2174
2231
  return this.resolveSheetPath(relation.target);
2175
2232
  }
2233
+ /**
2234
+ * A chartsheet (`xl/chartsheets/sheetN.xml`) is listed in `<sheets>` like a
2235
+ * worksheet; only its workbook relationship type tells it apart.
2236
+ */
2237
+ isChartsheet(rid) {
2238
+ const relation = this.relations[rid];
2239
+ if (!relation)
2240
+ return false;
2241
+ return ((relation.type ?? "").endsWith("/chartsheet") ||
2242
+ this.resolveSheetPath(relation.target).includes("chartsheets/"));
2243
+ }
2244
+ getChartsheetCharts(rid, sheetId) {
2245
+ let cached = this.chartsheetChartsCache.get(rid);
2246
+ if (!cached) {
2247
+ cached = this.getSheetData(rid).then((sheetData) => this._getCharts(rid, sheetId, sheetData));
2248
+ this.chartsheetChartsCache.set(rid, cached);
2249
+ }
2250
+ return cached;
2251
+ }
2176
2252
  async *streamSheetRows(rid) {
2177
2253
  const path = this.getSheetPathByRid(rid);
2178
2254
  let inSheetData = false;
@@ -2564,7 +2640,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2564
2640
  return comments;
2565
2641
  }
2566
2642
  const sheetPath = this.resolveSheetPath(relation.target);
2567
- const sheetRelPath = sheetPath.replace("worksheets", "worksheets/_rels") + ".rels";
2643
+ const sheetRelPath = getSheetRelsPath(sheetPath);
2568
2644
  let sheetRelContent;
2569
2645
  try {
2570
2646
  sheetRelContent = await this.zipParser.getText(sheetRelPath);
@@ -2656,7 +2732,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2656
2732
  return result;
2657
2733
  }
2658
2734
  const sheetPath = this.resolveSheetPath(relation.target);
2659
- const sheetRelPath = sheetPath.replace("worksheets", "worksheets/_rels") + ".rels";
2735
+ const sheetRelPath = getSheetRelsPath(sheetPath);
2660
2736
  try {
2661
2737
  const sheetRelContent = await this.zipParser.getText(sheetRelPath);
2662
2738
  const sheetRels = this.xmlParser.parse(sheetRelContent);
@@ -2853,14 +2929,16 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2853
2929
  // Handle both with and without namespace prefix
2854
2930
  const drawing = get(sheetData, "worksheet.drawing") ||
2855
2931
  get(sheetData, "drawing") ||
2856
- get(sheetData, "x:worksheet.x:drawing");
2932
+ get(sheetData, "x:worksheet.x:drawing") ||
2933
+ get(sheetData, "chartsheet.drawing") ||
2934
+ get(sheetData, "x:chartsheet.x:drawing");
2857
2935
  if (!drawing) {
2858
2936
  return [];
2859
2937
  }
2860
2938
  const drawings = Array.isArray(drawing) ? drawing : [drawing];
2861
2939
  try {
2862
2940
  // Get the sheet's relationships to find drawing relationship
2863
- const sheetRelPath = path.replace("worksheets", "worksheets/_rels") + ".rels";
2941
+ const sheetRelPath = getSheetRelsPath(path);
2864
2942
  const sheetRelContent = await this.zipParser.getText(sheetRelPath);
2865
2943
  const sheetRels = this.xmlParser.parse(sheetRelContent);
2866
2944
  const drawingRelations = get(sheetRels, "Relationships.Relationship");
@@ -2909,22 +2987,11 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2909
2987
  const drawingRelsArray = Array.isArray(drawingRelsList)
2910
2988
  ? drawingRelsList
2911
2989
  : [drawingRelsList];
2912
- // Extract anchors
2913
- const oneCellAnchors = get(drawingXml, "xdr:wsDr.xdr:oneCellAnchor") ||
2914
- get(drawingXml, "wsDr.oneCellAnchor") ||
2915
- [];
2916
- const twoCellAnchors = get(drawingXml, "xdr:wsDr.xdr:twoCellAnchor") ||
2917
- get(drawingXml, "wsDr.twoCellAnchor") ||
2918
- [];
2919
- const anchors = [
2920
- ...(Array.isArray(oneCellAnchors)
2921
- ? oneCellAnchors
2922
- : [oneCellAnchors]),
2923
- ...(Array.isArray(twoCellAnchors)
2924
- ? twoCellAnchors
2925
- : [twoCellAnchors]),
2926
- ].filter(Boolean);
2927
- for (const anchor of anchors) {
2990
+ // Anchors in document order — Excel's z-order, shared with the
2991
+ // sheet's pictures / shapes (`_getDrawings` numbers the same
2992
+ // anchors), kept on `position.zOrder`.
2993
+ const orderedAnchors = anchorsInDocumentOrder(drawingXml, drawingContent);
2994
+ for (const [zOrder, { node: anchor }] of orderedAnchors.entries()) {
2928
2995
  const chartRef = getChartReferenceFromAnchor(anchor);
2929
2996
  if (!chartRef) {
2930
2997
  // Excel 2016+ chartex (treemap / sunburst / histogram …) anchors
@@ -2937,8 +3004,10 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2937
3004
  : undefined;
2938
3005
  if (chartExRel) {
2939
3006
  const chartEx = await processChartEx(chartExRel.a$.Target, drawingPath, anchor, sheetId, this.zipParser, this.xmlParser, sheetDimensions, resolveSheetId);
2940
- if (chartEx)
3007
+ if (chartEx) {
3008
+ chartEx.position = { ...chartEx.position, zOrder };
2941
3009
  charts.push(chartEx);
3010
+ }
2942
3011
  }
2943
3012
  continue;
2944
3013
  }
@@ -2954,6 +3023,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2954
3023
  if (chartRel) {
2955
3024
  const chart = await processChart(chartRel.a$.Target, drawingPath, anchor, sheetId, this.zipParser, this.xmlParser, sheetDimensions, resolveSheetId);
2956
3025
  if (chart) {
3026
+ chart.position = { ...chart.position, zOrder };
2957
3027
  charts.push(chart);
2958
3028
  }
2959
3029
  }
@@ -2980,14 +3050,16 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
2980
3050
  // Handle both with and without namespace prefix
2981
3051
  const drawing = get(sheetData, "worksheet.drawing") ||
2982
3052
  get(sheetData, "drawing") ||
2983
- get(sheetData, "x:worksheet.x:drawing");
3053
+ get(sheetData, "x:worksheet.x:drawing") ||
3054
+ get(sheetData, "chartsheet.drawing") ||
3055
+ get(sheetData, "x:chartsheet.x:drawing");
2984
3056
  if (!drawing) {
2985
3057
  return [];
2986
3058
  }
2987
3059
  const drawings = Array.isArray(drawing) ? drawing : [drawing];
2988
3060
  // Get the sheet's relationships to find drawing relationship
2989
3061
  // Modified to handle both patterns of relationship files
2990
- const sheetRelPath = path.replace("worksheets", "worksheets/_rels") + ".rels";
3062
+ const sheetRelPath = getSheetRelsPath(path);
2991
3063
  const sheetRelContent = await this.zipParser.getText(sheetRelPath);
2992
3064
  const sheetRels = this.xmlParser.parse(sheetRelContent);
2993
3065
  const drawingRelations = get(sheetRels, "Relationships.Relationship");
@@ -3027,8 +3099,10 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
3027
3099
  // The parse groups anchors by element name, so the document order
3028
3100
  // across `oneCellAnchor` / `twoCellAnchor` / `absoluteAnchor` is
3029
3101
  // recovered from the raw part and each anchor processed in that order.
3030
- for (const anchor of anchorsInDocumentOrder(drawing, drawingContent)) {
3102
+ const orderedAnchors = anchorsInDocumentOrder(drawing, drawingContent);
3103
+ for (const [zOrder, anchor] of orderedAnchors.entries()) {
3031
3104
  for (const image of await processAnchors(anchor.node, anchor.kind, drawingPath, sheetId, this.zipParser, this.xmlParser)) {
3105
+ image.position = { ...image.position, zOrder };
3032
3106
  images.push(image);
3033
3107
  }
3034
3108
  }
@@ -3059,6 +3133,22 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
3059
3133
  getNamedCellStyles() {
3060
3134
  return resolveNamedCellStyles(this.workbookStyles);
3061
3135
  }
3136
+ /**
3137
+ * The workbook's custom `<tableStyle>`s with their per-element dxf fills resolved into banding —
3138
+ * the `customTableStyles` document facet (table style gallery + re-export), #668 T-103.
3139
+ */
3140
+ getCustomTableStyles() {
3141
+ const styles = this.workbookStyles?.tableStyles;
3142
+ if (!styles)
3143
+ return [];
3144
+ const out = [];
3145
+ for (const name of styles.keys()) {
3146
+ const bandedRange = this.workbookStyles?.getCustomTableStyleBanding(name);
3147
+ if (bandedRange)
3148
+ out.push({ name, bandedRange });
3149
+ }
3150
+ return out;
3151
+ }
3062
3152
  /**
3063
3153
  * The workbook default font — styles.xml fonts[0], the font every cell
3064
3154
  * without an explicit font inherits. Exporters pass this through so the
@@ -3294,12 +3384,14 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
3294
3384
  : state === "hidden"
3295
3385
  ? "hidden"
3296
3386
  : "visible";
3387
+ const rid = element.a$["r:id"];
3297
3388
  catalog.push({
3298
3389
  sheetId: Number(element.a$.sheetId),
3299
3390
  title: name,
3300
3391
  visibility,
3301
3392
  hidden: visibility !== "visible",
3302
3393
  index,
3394
+ type: rid && this.isChartsheet(rid) ? "chartsheet" : "worksheet",
3303
3395
  });
3304
3396
  });
3305
3397
  return catalog;
@@ -3340,6 +3432,13 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
3340
3432
  if (visibility !== "visible") {
3341
3433
  sheet.visibility = visibility;
3342
3434
  }
3435
+ if (this.isChartsheet(rid)) {
3436
+ sheet.type = "chartsheet";
3437
+ const [chart] = await this.getChartsheetCharts(rid, sheetId);
3438
+ if (chart) {
3439
+ sheet.chartId = chart.chartId;
3440
+ }
3441
+ }
3343
3442
  const metadata = await this.getSheetMetadata(rid);
3344
3443
  const { merges, rowEntries, columnMetadata, maxRowIndex, maxColumnIndex, autoFilterRange, filterSpecs, sheetViewData, tabColor, sheetHasFilterMode, zeroHeight, defaultRowHeightPx, defaultColumnWidthPx, tablePartIds, outlineSummaryBelow, outlineSummaryRight, sheetProtection, protectedRangeEntries, printSettings, } = metadata;
3345
3444
  if (sheetProtection) {
@@ -4059,7 +4158,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4059
4158
  const sheetPath = target.startsWith("/xl")
4060
4159
  ? target.replace(/^\//gi, "")
4061
4160
  : `xl/${target}`;
4062
- const sheetRelPath = sheetPath.replace("worksheets", "worksheets/_rels") + ".rels";
4161
+ const sheetRelPath = getSheetRelsPath(sheetPath);
4063
4162
  const relTargets = new Map();
4064
4163
  try {
4065
4164
  const sheetRels = this.xmlParser.parse(await this.zipParser.getText(sheetRelPath));
@@ -4110,6 +4209,10 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4110
4209
  for (const element of workSheets) {
4111
4210
  const sheetId = Number(element.a$.sheetId);
4112
4211
  const rid = element.a$["r:id"];
4212
+ if (this.isChartsheet(rid)) {
4213
+ allCharts.push(...(await this.getChartsheetCharts(rid, sheetId)));
4214
+ continue;
4215
+ }
4113
4216
  // Charts are anchored inside drawing parts, which the worksheet
4114
4217
  // references via a <drawing> element; skip when the sheet has none.
4115
4218
  const features = this.sheetFeatures.get(rid);
@@ -4120,6 +4223,23 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4120
4223
  const charts = await this._getCharts(rid, sheetId, sheet_data);
4121
4224
  allCharts.push(...charts);
4122
4225
  }
4226
+ if (allCharts.some((chart) => chart.spec[PIVOT_SOURCE_NAME_KEY] !==
4227
+ undefined)) {
4228
+ const sheetTitleById = new Map();
4229
+ for (const ws of workSheets) {
4230
+ const id = Number(ws.a$?.sheetId);
4231
+ if (ws.a$?.name && Number.isFinite(id))
4232
+ sheetTitleById.set(id, ws.a$.name);
4233
+ }
4234
+ let pivotTables = [];
4235
+ try {
4236
+ pivotTables = await this.getPivotTables();
4237
+ }
4238
+ catch {
4239
+ // Pivot import is best-effort; the charts fall back to plain charts.
4240
+ }
4241
+ bindPivotChartsToPivotTables(allCharts, pivotTables, sheetTitleById);
4242
+ }
4123
4243
  return allCharts;
4124
4244
  }
4125
4245
  /**
@@ -4129,6 +4249,10 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4129
4249
  * suitable for `onChangePivotTables`.
4130
4250
  */
4131
4251
  async getPivotTables() {
4252
+ this.pivotTablesPromise ??= this.parsePivotTables();
4253
+ return this.pivotTablesPromise;
4254
+ }
4255
+ async parsePivotTables() {
4132
4256
  const { parsePivotTablesForSheet } = await import("./pivots");
4133
4257
  const all = [];
4134
4258
  const workSheets = this._getSheets();
@@ -4154,7 +4278,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4154
4278
  const path = target.startsWith("/xl")
4155
4279
  ? target.replace(/^\//gi, "")
4156
4280
  : `xl/${target}`;
4157
- const sheetRelPath = path.replace("worksheets", "worksheets/_rels") + ".rels";
4281
+ const sheetRelPath = getSheetRelsPath(path);
4158
4282
  // Sheets without a rels part (common for plain data sheets) must not
4159
4283
  // abort pivot extraction for the remaining sheets.
4160
4284
  let sheetRelContent;
@@ -4518,7 +4642,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4518
4642
  if (!relation?.target)
4519
4643
  continue;
4520
4644
  const sheetPath = this.resolveSheetPath(relation.target);
4521
- const sheetRelPath = sheetPath.replace("worksheets", "worksheets/_rels") + ".rels";
4645
+ const sheetRelPath = getSheetRelsPath(sheetPath);
4522
4646
  try {
4523
4647
  const sheetRelContent = await this.zipParser.getText(sheetRelPath);
4524
4648
  if (!sheetRelContent)
@@ -4773,6 +4897,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4773
4897
  const height = extAttributes.cy
4774
4898
  ? Math.round(parseInt(String(extAttributes.cy), 10) / 9525)
4775
4899
  : 300;
4900
+ // A `oneCellAnchor` is "Move but don't size with cells" (#668 C-46)
4776
4901
  return {
4777
4902
  sheetId,
4778
4903
  overlayPosition: {
@@ -4784,6 +4909,7 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4784
4909
  offsetYPixels: Math.round(rowOff / 9525),
4785
4910
  widthPixels: width,
4786
4911
  heightPixels: height,
4912
+ placement: "moveOnly",
4787
4913
  },
4788
4914
  };
4789
4915
  }
@@ -4797,9 +4923,14 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4797
4923
  const rowOff = this.parseIntSafe(get(fromCell, "xdr:rowOff") || get(fromCell, "rowOff"));
4798
4924
  const toCol = this.parseIntSafe(get(toCell, "xdr:col") || get(toCell, "col"));
4799
4925
  const toRow = this.parseIntSafe(get(toCell, "xdr:row") || get(toCell, "row"));
4926
+ const toColOff = this.parseIntSafe(get(toCell, "xdr:colOff") || get(toCell, "colOff"));
4927
+ const toRowOff = this.parseIntSafe(get(toCell, "xdr:rowOff") || get(toCell, "rowOff"));
4800
4928
  // Estimate size based on cell span
4801
4929
  const width = Math.max((toCol - col) * 64, 100);
4802
4930
  const height = Math.max((toRow - row) * 20, 100);
4931
+ // `editAs` is the slicer's placement mode; a plain twoCellAnchor
4932
+ // keeps `to` as the second anchor like a chart (#668 C-46)
4933
+ const placement = anchorPlacementFromEditAs(get(anchor, "a$.editAs"));
4803
4934
  return {
4804
4935
  sheetId,
4805
4936
  overlayPosition: {
@@ -4811,6 +4942,13 @@ export class XlsxPlugin extends BaseWorkbookPlugin {
4811
4942
  offsetYPixels: Math.round(rowOff / 9525),
4812
4943
  widthPixels: width,
4813
4944
  heightPixels: height,
4945
+ ...(placement === "moveAndSize"
4946
+ ? {
4947
+ toCell: { rowIndex: toRow + 1, columnIndex: toCol + 1 },
4948
+ toOffsetXPixels: Math.round(toColOff / 9525),
4949
+ toOffsetYPixels: Math.round(toRowOff / 9525),
4950
+ }
4951
+ : { placement }),
4814
4952
  },
4815
4953
  };
4816
4954
  }
@@ -14,9 +14,15 @@
14
14
  * Output shape matches @rowsncolumns/spreadsheet `PivotTable` so
15
15
  * imported pivots drop straight into `onChangePivotTables`.
16
16
  */
17
- import type { PivotTable } from "@rowsncolumns/spreadsheet";
18
- import type { XMLParser } from "fast-xml-parser";
17
+ import type { PivotSharedItems, PivotTable } from "@rowsncolumns/spreadsheet";
18
+ import { XMLParser } from "fast-xml-parser";
19
19
  import type { StreamingZipParser } from "../../zip-parser-streaming";
20
+ /**
21
+ * Each `<cacheField>`'s `<sharedItems>` verbatim, in cache-field order (`undefined`
22
+ * where a field has none): attributes minus `count`, items in document order with
23
+ * their type, `v` and any further attributes (rowsncolumns/spreadsheet#668 C-84).
24
+ */
25
+ export declare const readSharedItemsVerbatim: (cacheText: string) => (PivotSharedItems | undefined)[];
20
26
  type SheetPivotRel = {
21
27
  /** Relationship target → xl/pivotTables/pivotTable<N>.xml */
22
28
  target: string;
@@ -1 +1 @@
1
- {"version":3,"file":"pivots.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsx/pivots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAEV,UAAU,EAGX,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAoErE,KAAK,aAAa,GAAG;IACnB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAkQF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS;IACP,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,mEAAmE;IACnE,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,kDAAkD;IAClD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,cAAc,EAAE,MAAM,CAAC;CACxB,KACA,OAAO,CAAC,UAAU,EAAE,CAsEtB,CAAC"}
1
+ {"version":3,"file":"pivots.d.ts","sourceRoot":"","sources":["../../../../../excel-parser/plugins/xlsx/pivots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAOV,gBAAgB,EAChB,UAAU,EAIX,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAwJrE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,WAAW,MAAM,KAChB,CAAC,gBAAgB,GAAG,SAAS,CAAC,EAqEhC,CAAC;AA4BF,KAAK,aAAa,GAAG;IACnB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAyRF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS;IACP,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,mEAAmE;IACnE,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,kDAAkD;IAClD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,cAAc,EAAE,MAAM,CAAC;CACxB,KACA,OAAO,CAAC,UAAU,EAAE,CA4EtB,CAAC"}