@cj-tech-master/excelts 2.0.0 → 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.
- package/dist/browser/excelts.esm.js +217 -126
- package/dist/browser/excelts.esm.js.map +1 -1
- package/dist/browser/excelts.esm.min.js +37 -34
- package/dist/browser/excelts.iife.js +217 -126
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +37 -34
- package/dist/cjs/doc/pivot-table.js +37 -3
- package/dist/cjs/doc/worksheet.js +0 -1
- package/dist/cjs/stream/xlsx/worksheet-writer.js +0 -1
- package/dist/cjs/utils/datetime.js +7 -156
- package/dist/cjs/xlsx/xform/book/sheet-xform.js +3 -2
- package/dist/cjs/xlsx/xform/book/workbook-properties-xform.js +1 -1
- package/dist/cjs/xlsx/xform/core/content-types-xform.js +12 -6
- package/dist/cjs/xlsx/xform/pivot-table/cache-field.js +11 -4
- package/dist/cjs/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +4 -11
- package/dist/cjs/xlsx/xform/pivot-table/pivot-table-xform.js +124 -17
- package/dist/cjs/xlsx/xform/sheet/page-setup-xform.js +4 -3
- package/dist/cjs/xlsx/xform/sheet/row-xform.js +1 -1
- package/dist/cjs/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
- package/dist/cjs/xlsx/xform/sheet/sheet-view-xform.js +8 -4
- package/dist/cjs/xlsx/xform/sheet/worksheet-xform.js +2 -2
- package/dist/cjs/xlsx/xform/style/font-xform.js +36 -23
- package/dist/cjs/xlsx/xform/table/auto-filter-xform.js +3 -1
- package/dist/cjs/xlsx/xform/table/table-column-xform.js +2 -1
- package/dist/cjs/xlsx/xform/table/table-xform.js +5 -9
- package/dist/esm/doc/pivot-table.js +37 -3
- package/dist/esm/doc/worksheet.js +0 -1
- package/dist/esm/stream/xlsx/worksheet-writer.js +0 -1
- package/dist/esm/utils/datetime.js +7 -153
- package/dist/esm/xlsx/xform/book/sheet-xform.js +3 -2
- package/dist/esm/xlsx/xform/book/workbook-properties-xform.js +1 -1
- package/dist/esm/xlsx/xform/core/content-types-xform.js +12 -6
- package/dist/esm/xlsx/xform/pivot-table/cache-field.js +11 -4
- package/dist/esm/xlsx/xform/pivot-table/pivot-cache-definition-xform.js +4 -11
- package/dist/esm/xlsx/xform/pivot-table/pivot-table-xform.js +124 -17
- package/dist/esm/xlsx/xform/sheet/page-setup-xform.js +4 -3
- package/dist/esm/xlsx/xform/sheet/row-xform.js +1 -1
- package/dist/esm/xlsx/xform/sheet/sheet-format-properties-xform.js +6 -3
- package/dist/esm/xlsx/xform/sheet/sheet-view-xform.js +8 -4
- package/dist/esm/xlsx/xform/sheet/worksheet-xform.js +2 -2
- package/dist/esm/xlsx/xform/style/font-xform.js +36 -23
- package/dist/esm/xlsx/xform/table/auto-filter-xform.js +3 -1
- package/dist/esm/xlsx/xform/table/table-column-xform.js +2 -1
- package/dist/esm/xlsx/xform/table/table-xform.js +5 -9
- package/dist/types/csv/csv-core.d.ts +0 -6
- package/dist/types/doc/pivot-table.d.ts +5 -1
- package/dist/types/stream/xlsx/workbook-reader.d.ts +1 -1
- package/dist/types/stream/xlsx/worksheet-reader.d.ts +1 -1
- package/dist/types/stream/xlsx/worksheet-writer.d.ts +1 -1
- package/dist/types/types.d.ts +1 -1
- package/dist/types/utils/datetime.d.ts +0 -29
- package/dist/types/xlsx/xform/pivot-table/cache-field.d.ts +5 -1
- package/dist/types/xlsx/xform/pivot-table/pivot-cache-definition-xform.d.ts +0 -5
- package/dist/types/xlsx/xform/pivot-table/pivot-table-xform.d.ts +0 -3
- package/dist/types/xlsx/xform/sheet/sheet-format-properties-xform.d.ts +1 -1
- package/dist/types/xlsx/xform/sheet/worksheet-xform.d.ts +1 -1
- package/dist/types/xlsx/xform/style/font-xform.d.ts +1 -0
- package/dist/types/xlsx/xform/table/table-xform.d.ts +0 -4
- 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:
|
|
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
|
-
|
|
195
|
-
|
|
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
|
}
|
|
@@ -7,9 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.DateFormatter = exports.DateParser = void 0;
|
|
10
|
-
exports.mightBeDate = mightBeDate;
|
|
11
|
-
exports.parseDate = parseDate;
|
|
12
|
-
exports.formatDate = formatDate;
|
|
13
10
|
exports.getSupportedFormats = getSupportedFormats;
|
|
14
11
|
// ============================================================================
|
|
15
12
|
// Constants - Pre-computed lookup tables for zero-allocation operations
|
|
@@ -18,7 +15,6 @@ exports.getSupportedFormats = getSupportedFormats;
|
|
|
18
15
|
const PAD2 = Array.from({ length: 60 }, (_, i) => (i < 10 ? `0${i}` : `${i}`));
|
|
19
16
|
// Character codes for fast comparison
|
|
20
17
|
const C_0 = 48;
|
|
21
|
-
const C_9 = 57;
|
|
22
18
|
const C_DASH = 45;
|
|
23
19
|
const C_SLASH = 47;
|
|
24
20
|
const C_COLON = 58;
|
|
@@ -194,158 +190,6 @@ const AUTO_DETECT = [
|
|
|
194
190
|
[29, [parseISOMsOffset]]
|
|
195
191
|
];
|
|
196
192
|
// ============================================================================
|
|
197
|
-
// Public API
|
|
198
|
-
// ============================================================================
|
|
199
|
-
/**
|
|
200
|
-
* Quick pre-filter to reject non-date strings
|
|
201
|
-
*/
|
|
202
|
-
function mightBeDate(s) {
|
|
203
|
-
const len = s.length;
|
|
204
|
-
if (len < 10 || len > 30) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
const c0 = s.charCodeAt(0);
|
|
208
|
-
const c1 = s.charCodeAt(1);
|
|
209
|
-
// Must start with digits
|
|
210
|
-
if (c0 < C_0 || c0 > C_9 || c1 < C_0 || c1 > C_9) {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
const c2 = s.charCodeAt(2);
|
|
214
|
-
// Either YYYY format (4 digits) or MM/DD format (separator at pos 2)
|
|
215
|
-
return (c2 >= C_0 && c2 <= C_9) || c2 === C_DASH || c2 === C_SLASH;
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Parse a date string
|
|
219
|
-
*
|
|
220
|
-
* @param value - String to parse
|
|
221
|
-
* @param formats - Specific formats to try (optional, auto-detects ISO if omitted)
|
|
222
|
-
* @returns Date object or null
|
|
223
|
-
*
|
|
224
|
-
* @example
|
|
225
|
-
* parseDate("2024-12-26") // auto-detect ISO
|
|
226
|
-
* parseDate("12-26-2024", ["MM-DD-YYYY"]) // explicit US format
|
|
227
|
-
*/
|
|
228
|
-
function parseDate(value, formats) {
|
|
229
|
-
if (!value || typeof value !== "string") {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
const s = value.trim();
|
|
233
|
-
if (!s) {
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
// Explicit formats
|
|
237
|
-
if (formats?.length) {
|
|
238
|
-
for (const fmt of formats) {
|
|
239
|
-
const result = PARSERS[fmt]?.(s);
|
|
240
|
-
if (result) {
|
|
241
|
-
return result;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
// Auto-detect by length
|
|
247
|
-
const len = s.length;
|
|
248
|
-
for (const [l, parsers] of AUTO_DETECT) {
|
|
249
|
-
if (len === l) {
|
|
250
|
-
for (const p of parsers) {
|
|
251
|
-
const result = p(s);
|
|
252
|
-
if (result) {
|
|
253
|
-
return result;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Format a Date to string
|
|
262
|
-
*
|
|
263
|
-
* @param date - Date to format
|
|
264
|
-
* @param format - Format template (optional, defaults to ISO)
|
|
265
|
-
* @param utc - Use UTC time (default: false)
|
|
266
|
-
* @returns Formatted string or empty string if invalid
|
|
267
|
-
*
|
|
268
|
-
* @example
|
|
269
|
-
* formatDate(date) // "2024-12-26T10:30:00.000+08:00"
|
|
270
|
-
* formatDate(date, "YYYY-MM-DD", true) // "2024-12-26"
|
|
271
|
-
*/
|
|
272
|
-
function formatDate(date, format, utc = false) {
|
|
273
|
-
if (!(date instanceof Date)) {
|
|
274
|
-
return "";
|
|
275
|
-
}
|
|
276
|
-
const t = date.getTime();
|
|
277
|
-
if (t !== t) {
|
|
278
|
-
return "";
|
|
279
|
-
} // NaN check (faster than isNaN)
|
|
280
|
-
// Default ISO format - direct string building (faster than toISOString)
|
|
281
|
-
if (!format) {
|
|
282
|
-
if (utc) {
|
|
283
|
-
const y = date.getUTCFullYear();
|
|
284
|
-
const M = date.getUTCMonth() + 1;
|
|
285
|
-
const D = date.getUTCDate();
|
|
286
|
-
const H = date.getUTCHours();
|
|
287
|
-
const m = date.getUTCMinutes();
|
|
288
|
-
const s = date.getUTCSeconds();
|
|
289
|
-
const ms = date.getUTCMilliseconds();
|
|
290
|
-
return `${y}-${PAD2[M]}-${PAD2[D]}T${PAD2[H]}:${PAD2[m]}:${PAD2[s]}.${ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms}Z`;
|
|
291
|
-
}
|
|
292
|
-
const y = date.getFullYear();
|
|
293
|
-
const M = date.getMonth() + 1;
|
|
294
|
-
const D = date.getDate();
|
|
295
|
-
const H = date.getHours();
|
|
296
|
-
const m = date.getMinutes();
|
|
297
|
-
const s = date.getSeconds();
|
|
298
|
-
const ms = date.getMilliseconds();
|
|
299
|
-
return `${y}-${PAD2[M]}-${PAD2[D]}T${PAD2[H]}:${PAD2[m]}:${PAD2[s]}.${ms < 10 ? "00" + ms : ms < 100 ? "0" + ms : ms}${tzOffset(date)}`;
|
|
300
|
-
}
|
|
301
|
-
// Fast paths for common formats
|
|
302
|
-
if (format === "YYYY-MM-DD") {
|
|
303
|
-
return utc
|
|
304
|
-
? `${date.getUTCFullYear()}-${PAD2[date.getUTCMonth() + 1]}-${PAD2[date.getUTCDate()]}`
|
|
305
|
-
: `${date.getFullYear()}-${PAD2[date.getMonth() + 1]}-${PAD2[date.getDate()]}`;
|
|
306
|
-
}
|
|
307
|
-
return renderFormat(date, format, utc);
|
|
308
|
-
}
|
|
309
|
-
function tzOffset(d) {
|
|
310
|
-
const off = -d.getTimezoneOffset();
|
|
311
|
-
const sign = off >= 0 ? "+" : "-";
|
|
312
|
-
const h = (Math.abs(off) / 60) | 0; // Bitwise OR faster than Math.floor
|
|
313
|
-
const m = Math.abs(off) % 60;
|
|
314
|
-
return `${sign}${PAD2[h]}:${PAD2[m]}`;
|
|
315
|
-
}
|
|
316
|
-
function renderFormat(d, fmt, utc) {
|
|
317
|
-
// Handle escaped sections [...]
|
|
318
|
-
const esc = [];
|
|
319
|
-
let out = fmt.replace(/\[([^\]]*)\]/g, (_, c) => {
|
|
320
|
-
esc.push(c);
|
|
321
|
-
return `\x00${esc.length - 1}\x00`;
|
|
322
|
-
});
|
|
323
|
-
// Get components once
|
|
324
|
-
const y = utc ? d.getUTCFullYear() : d.getFullYear();
|
|
325
|
-
const M = utc ? d.getUTCMonth() + 1 : d.getMonth() + 1;
|
|
326
|
-
const D = utc ? d.getUTCDate() : d.getDate();
|
|
327
|
-
const H = utc ? d.getUTCHours() : d.getHours();
|
|
328
|
-
const m = utc ? d.getUTCMinutes() : d.getMinutes();
|
|
329
|
-
const s = utc ? d.getUTCSeconds() : d.getSeconds();
|
|
330
|
-
const ms = utc ? d.getUTCMilliseconds() : d.getMilliseconds();
|
|
331
|
-
// Replace tokens
|
|
332
|
-
out = out
|
|
333
|
-
.replace(/YYYY/g, String(y))
|
|
334
|
-
.replace(/SSS/g, ms < 10 ? `00${ms}` : ms < 100 ? `0${ms}` : String(ms))
|
|
335
|
-
.replace(/MM/g, PAD2[M])
|
|
336
|
-
.replace(/DD/g, PAD2[D])
|
|
337
|
-
.replace(/HH/g, PAD2[H])
|
|
338
|
-
.replace(/mm/g, PAD2[m])
|
|
339
|
-
.replace(/ss/g, PAD2[s])
|
|
340
|
-
.replace(/Z/g, utc ? "Z" : tzOffset(d));
|
|
341
|
-
// Restore escaped
|
|
342
|
-
if (esc.length) {
|
|
343
|
-
// oxlint-disable-next-line no-control-regex
|
|
344
|
-
out = out.replace(/\x00(\d+)\x00/g, (_, i) => esc[+i]);
|
|
345
|
-
}
|
|
346
|
-
return out;
|
|
347
|
-
}
|
|
348
|
-
// ============================================================================
|
|
349
193
|
// High-performance batch processors (class-based for state encapsulation)
|
|
350
194
|
// ============================================================================
|
|
351
195
|
/**
|
|
@@ -419,6 +263,13 @@ class DateParser {
|
|
|
419
263
|
}
|
|
420
264
|
}
|
|
421
265
|
exports.DateParser = DateParser;
|
|
266
|
+
function tzOffset(d) {
|
|
267
|
+
const off = -d.getTimezoneOffset();
|
|
268
|
+
const sign = off >= 0 ? "+" : "-";
|
|
269
|
+
const h = (Math.abs(off) / 60) | 0; // Bitwise OR faster than Math.floor
|
|
270
|
+
const m = Math.abs(off) % 60;
|
|
271
|
+
return `${sign}${PAD2[h]}:${PAD2[m]}`;
|
|
272
|
+
}
|
|
422
273
|
/**
|
|
423
274
|
* Optimized date formatter for batch processing
|
|
424
275
|
*
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
@@ -46,17 +46,17 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
|
|
|
46
46
|
*/
|
|
47
47
|
renderNew(xmlStream, model) {
|
|
48
48
|
const { source, cacheFields } = model;
|
|
49
|
+
// Record count = number of data rows (excluding header row)
|
|
50
|
+
const recordCount = source.getSheetValues().slice(2).length;
|
|
49
51
|
xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
|
|
50
52
|
xmlStream.openNode(this.tag, {
|
|
51
53
|
...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
|
|
52
54
|
"r:id": "rId1",
|
|
53
55
|
refreshOnLoad: "1", // important for our implementation to work
|
|
54
|
-
refreshedBy: "Author",
|
|
55
|
-
refreshedDate: "45125.026046874998",
|
|
56
56
|
createdVersion: "8",
|
|
57
57
|
refreshedVersion: "8",
|
|
58
58
|
minRefreshableVersion: "3",
|
|
59
|
-
recordCount
|
|
59
|
+
recordCount
|
|
60
60
|
});
|
|
61
61
|
xmlStream.openNode("cacheSource", { type: "worksheet" });
|
|
62
62
|
xmlStream.leafNode("worksheetSource", {
|
|
@@ -80,8 +80,6 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
|
|
|
80
80
|
...PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES,
|
|
81
81
|
"r:id": model.rId || "rId1",
|
|
82
82
|
refreshOnLoad: model.refreshOnLoad || "1",
|
|
83
|
-
refreshedBy: model.refreshedBy || "Author",
|
|
84
|
-
refreshedDate: model.refreshedDate || "45125.026046874998",
|
|
85
83
|
createdVersion: model.createdVersion || "8",
|
|
86
84
|
refreshedVersion: model.refreshedVersion || "8",
|
|
87
85
|
minRefreshableVersion: model.minRefreshableVersion || "3",
|
|
@@ -115,8 +113,6 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
|
|
|
115
113
|
cacheFields: [],
|
|
116
114
|
rId: attributes["r:id"],
|
|
117
115
|
refreshOnLoad: attributes.refreshOnLoad,
|
|
118
|
-
refreshedBy: attributes.refreshedBy,
|
|
119
|
-
refreshedDate: attributes.refreshedDate,
|
|
120
116
|
createdVersion: attributes.createdVersion,
|
|
121
117
|
refreshedVersion: attributes.refreshedVersion,
|
|
122
118
|
minRefreshableVersion: attributes.minRefreshableVersion,
|
|
@@ -182,8 +178,5 @@ class PivotCacheDefinitionXform extends base_xform_1.BaseXform {
|
|
|
182
178
|
exports.PivotCacheDefinitionXform = PivotCacheDefinitionXform;
|
|
183
179
|
PivotCacheDefinitionXform.PIVOT_CACHE_DEFINITION_ATTRIBUTES = {
|
|
184
180
|
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
185
|
-
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
186
|
-
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
187
|
-
"mc:Ignorable": "xr",
|
|
188
|
-
"xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
|
|
181
|
+
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
189
182
|
};
|
|
@@ -58,12 +58,31 @@ 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
|
-
//
|
|
62
|
-
const
|
|
61
|
+
// Build rowItems - need one <i> for each unique value in row fields, plus grand total
|
|
62
|
+
const rowItems = buildRowItems(rows, cacheFields);
|
|
63
|
+
// Build colItems - need one <i> for each unique value in col fields, plus grand total
|
|
64
|
+
const colItems = buildColItems(columns, cacheFields, values.length);
|
|
65
|
+
// Calculate pivot table dimensions
|
|
66
|
+
const rowFieldItemCount = rows.length > 0 ? cacheFields[rows[0]]?.sharedItems?.length || 0 : 0;
|
|
67
|
+
const colFieldItemCount = columns.length > 0 ? cacheFields[columns[0]]?.sharedItems?.length || 0 : 0;
|
|
68
|
+
// Location: A3 is where pivot table starts
|
|
69
|
+
// - firstHeaderRow: 1 (column headers are in first row of pivot table)
|
|
70
|
+
// - firstDataRow: 2 (data starts in second row)
|
|
71
|
+
// - firstDataCol: 1 (data starts in second column, after row labels)
|
|
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
|
|
80
|
+
const endCol = 1 + colFieldItemCount + 1; // start col + data cols + grand total
|
|
81
|
+
const endColLetter = String.fromCharCode(64 + endCol);
|
|
82
|
+
const locationRef = `A3:${endColLetter}${endRow}`;
|
|
63
83
|
xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
|
|
64
84
|
xmlStream.openNode(this.tag, {
|
|
65
85
|
...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
|
|
66
|
-
"xr:uid": uniqueUid,
|
|
67
86
|
name: "PivotTable2",
|
|
68
87
|
cacheId,
|
|
69
88
|
applyNumberFormats: "0",
|
|
@@ -84,23 +103,23 @@ class PivotTableXform extends base_xform_1.BaseXform {
|
|
|
84
103
|
multipleFieldFilters: "0"
|
|
85
104
|
});
|
|
86
105
|
xmlStream.writeXml(`
|
|
87
|
-
<location ref="
|
|
106
|
+
<location ref="${locationRef}" firstHeaderRow="1" firstDataRow="2" firstDataCol="1" />
|
|
88
107
|
<pivotFields count="${cacheFields.length}">
|
|
89
108
|
${renderPivotFields(model)}
|
|
90
109
|
</pivotFields>
|
|
91
110
|
<rowFields count="${rows.length}">
|
|
92
111
|
${rows.map(rowIndex => `<field x="${rowIndex}" />`).join("\n ")}
|
|
93
112
|
</rowFields>
|
|
94
|
-
<rowItems count="
|
|
95
|
-
|
|
113
|
+
<rowItems count="${rowItems.count}">
|
|
114
|
+
${rowItems.xml}
|
|
96
115
|
</rowItems>
|
|
97
116
|
<colFields count="${columns.length === 0 ? 1 : columns.length}">
|
|
98
117
|
${columns.length === 0
|
|
99
118
|
? '<field x="-2" />'
|
|
100
119
|
: columns.map(columnIndex => `<field x="${columnIndex}" />`).join("\n ")}
|
|
101
120
|
</colFields>
|
|
102
|
-
<colItems count="
|
|
103
|
-
|
|
121
|
+
<colItems count="${colItems.count}">
|
|
122
|
+
${colItems.xml}
|
|
104
123
|
</colItems>
|
|
105
124
|
<dataFields count="${values.length}">
|
|
106
125
|
${buildDataFields(cacheFields, values, model.metric)}
|
|
@@ -140,11 +159,9 @@ class PivotTableXform extends base_xform_1.BaseXform {
|
|
|
140
159
|
* Render loaded pivot table (preserving original structure)
|
|
141
160
|
*/
|
|
142
161
|
renderLoaded(xmlStream, model) {
|
|
143
|
-
const uniqueUid = model.uid || `{${crypto.randomUUID().toUpperCase()}}`;
|
|
144
162
|
xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
|
|
145
163
|
xmlStream.openNode(this.tag, {
|
|
146
164
|
...PivotTableXform.PIVOT_TABLE_ATTRIBUTES,
|
|
147
|
-
"xr:uid": uniqueUid,
|
|
148
165
|
name: model.name || "PivotTable1",
|
|
149
166
|
cacheId: model.cacheId,
|
|
150
167
|
applyNumberFormats: model.applyNumberFormats || "0",
|
|
@@ -458,12 +475,92 @@ class PivotTableXform extends base_xform_1.BaseXform {
|
|
|
458
475
|
}
|
|
459
476
|
exports.PivotTableXform = PivotTableXform;
|
|
460
477
|
PivotTableXform.PIVOT_TABLE_ATTRIBUTES = {
|
|
461
|
-
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
462
|
-
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
463
|
-
"mc:Ignorable": "xr",
|
|
464
|
-
"xmlns:xr": "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"
|
|
478
|
+
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
|
465
479
|
};
|
|
466
480
|
// Helpers
|
|
481
|
+
/**
|
|
482
|
+
* Build rowItems XML - one item for each unique value in row fields, plus grand total.
|
|
483
|
+
* Each <i> represents a row in the pivot table.
|
|
484
|
+
* - Regular items: <i><x/></i> for index 0, <i><x v="index"/></i> for index > 0
|
|
485
|
+
* - Grand total: <i t="grand"><x/></i>
|
|
486
|
+
* Note: When v=0, the v attribute should be omitted (Excel convention)
|
|
487
|
+
*/
|
|
488
|
+
function buildRowItems(rows, cacheFields) {
|
|
489
|
+
if (rows.length === 0) {
|
|
490
|
+
// No row fields - just grand total
|
|
491
|
+
return { count: 1, xml: '<i t="grand"><x /></i>' };
|
|
492
|
+
}
|
|
493
|
+
// Get unique values count from the first row field
|
|
494
|
+
const rowFieldIndex = rows[0];
|
|
495
|
+
const sharedItems = cacheFields[rowFieldIndex]?.sharedItems || [];
|
|
496
|
+
const itemCount = sharedItems.length;
|
|
497
|
+
// Build items: one for each unique value + grand total
|
|
498
|
+
const items = [];
|
|
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
|
+
for (let i = 0; i < itemCount; i++) {
|
|
502
|
+
if (i === 0) {
|
|
503
|
+
items.push("<i><x /></i>");
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
items.push(`<i><x v="${i}" /></i>`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Grand total row
|
|
510
|
+
items.push('<i t="grand"><x /></i>');
|
|
511
|
+
return {
|
|
512
|
+
count: items.length,
|
|
513
|
+
xml: items.join("\n ")
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Build colItems XML - one item for each unique value in column fields, plus grand total.
|
|
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)
|
|
520
|
+
*/
|
|
521
|
+
function buildColItems(columns, cacheFields, valueCount) {
|
|
522
|
+
if (columns.length === 0) {
|
|
523
|
+
// No column fields - columns are based on data fields (values)
|
|
524
|
+
if (valueCount > 1) {
|
|
525
|
+
// Multiple values: one column per value + grand total
|
|
526
|
+
const items = [];
|
|
527
|
+
for (let i = 0; i < valueCount; i++) {
|
|
528
|
+
if (i === 0) {
|
|
529
|
+
items.push("<i><x /></i>");
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
items.push(`<i><x v="${i}" /></i>`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
items.push('<i t="grand"><x /></i>');
|
|
536
|
+
return { count: items.length, xml: items.join("\n ") };
|
|
537
|
+
}
|
|
538
|
+
// Single value: just grand total
|
|
539
|
+
return { count: 1, xml: '<i t="grand"><x /></i>' };
|
|
540
|
+
}
|
|
541
|
+
// Get unique values count from the first column field
|
|
542
|
+
const colFieldIndex = columns[0];
|
|
543
|
+
const sharedItems = cacheFields[colFieldIndex]?.sharedItems || [];
|
|
544
|
+
const itemCount = sharedItems.length;
|
|
545
|
+
// Build items: one for each unique value + grand total
|
|
546
|
+
const items = [];
|
|
547
|
+
// Regular items - reference each unique value by index
|
|
548
|
+
// Note: v="0" should be omitted (Excel uses <x/> instead of <x v="0"/>)
|
|
549
|
+
for (let i = 0; i < itemCount; i++) {
|
|
550
|
+
if (i === 0) {
|
|
551
|
+
items.push("<i><x /></i>");
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
items.push(`<i><x v="${i}" /></i>`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// Grand total column
|
|
558
|
+
items.push('<i t="grand"><x /></i>');
|
|
559
|
+
return {
|
|
560
|
+
count: items.length,
|
|
561
|
+
xml: items.join("\n ")
|
|
562
|
+
};
|
|
563
|
+
}
|
|
467
564
|
/**
|
|
468
565
|
* Build dataField XML elements for all values in the pivot table.
|
|
469
566
|
* Supports multiple values when columns is empty.
|
|
@@ -501,17 +598,27 @@ function renderPivotFields(pivotTable) {
|
|
|
501
598
|
}
|
|
502
599
|
function renderPivotField(fieldType, sharedItems) {
|
|
503
600
|
// fieldType: 'row', 'column', 'value', null
|
|
504
|
-
|
|
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)
|
|
505
603
|
if (fieldType === "row" || fieldType === "column") {
|
|
506
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"';
|
|
607
|
+
// items = one for each shared item + one default item
|
|
608
|
+
const itemsXml = [
|
|
609
|
+
...sharedItems.map((_item, index) => `<item x="${index}" />`),
|
|
610
|
+
'<item t="default" />' // Required default item for subtotals/grand totals
|
|
611
|
+
].join("\n ");
|
|
507
612
|
return `
|
|
508
|
-
<pivotField axis="${axis}" ${
|
|
613
|
+
<pivotField axis="${axis}" ${axisAttributes}>
|
|
509
614
|
<items count="${sharedItems.length + 1}">
|
|
510
|
-
${
|
|
615
|
+
${itemsXml}
|
|
511
616
|
</items>
|
|
512
617
|
</pivotField>
|
|
513
618
|
`;
|
|
514
619
|
}
|
|
620
|
+
// Value fields and non-axis fields should have defaultSubtotal="0"
|
|
621
|
+
const defaultAttributes = 'compact="0" outline="0" showAll="0" defaultSubtotal="0"';
|
|
515
622
|
return `
|
|
516
623
|
<pivotField
|
|
517
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
}
|