UNPKG

@protobi/exceljs

Version:

Excel Workbook Manager - Temporary fork with pivot table enhancements and bug fixes pending upstream merge

198 lines (184 loc) 8.68 kB
"use strict"; function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var _require = require('../utils/utils'), objectFromProps = _require.objectFromProps, range = _require.range, toSortedArray = _require.toSortedArray; // TK(2023-10-10): turn this into a class constructor. // IMPORTANT: Pivot tables are NOT supported with streaming API (WorkbookWriter) // // Pivot tables require reading ALL source data to generate the pivot cache, // which conflicts with streaming's one-pass write model. Excel requires complete // pivot cache data (all unique values and all data rows) at file creation time. // // For large datasets, use the standard (non-streaming) Workbook API with pivot tables. function makePivotTable(worksheet, model) { // Example `model`: // { // // Source of data: the entire sheet range is taken, // // akin to `worksheet1.getSheetValues()`. // sourceSheet: worksheet1, // // // Pivot table fields: values indicate field names; // // they come from the first row in `worksheet1`. // rows: ['A', 'B'], // columns: ['C'], // values: ['E'], // only 1 item possible for now // metric: 'sum', 'count' // only 'sum' and 'count' are possible for now // } validate(worksheet, model); var sourceSheet = model.sourceSheet; var rows = model.rows, columns = model.columns, values = model.values; var metric = model.metric; // Generate sharedItems for ALL fields in the source, not just the ones used by this pivot table // This ensures Excel can properly display any field configuration var allHeaderNames = sourceSheet.getRow(1).values.slice(1); var cacheFields = makeCacheFields(sourceSheet, allHeaderNames); // let {rows, columns, values} use indices instead of names; // names can then be accessed via `pivotTable.cacheFields[index].name`. // *Note*: Using `reduce` as `Object.fromEntries` requires Node 12+; // ExcelJS is >=8.3.0 (as of 2023-10-08). var nameToIndex = cacheFields.reduce(function (result, cacheField, index) { result[cacheField.name] = index; return result; }, {}); rows = rows.map(function (row) { return nameToIndex[row]; }); columns = columns.map(function (column) { return nameToIndex[column]; }); values = values.map(function (value) { return nameToIndex[value]; }); // Generate unique cache ID based on the number of existing pivot tables // Each pivot table gets its own cache ID (starting from 10) var cacheId = String(10 + worksheet.workbook.pivotTables.length); // form pivot table object return { sourceSheet: sourceSheet, rows: rows, columns: columns, values: values, metric: metric, cacheFields: cacheFields, // defined in <pivotTableDefinition> of xl/pivotTables/pivotTableN.xml; // also used in xl/workbook.xml cacheId: cacheId, // Control whether pivot table style overrides worksheet column widths // '0' = preserve worksheet column widths (useful for custom sizing) // '1' = apply pivot table style width/height (default Excel behavior) applyWidthHeightFormats: model.applyWidthHeightFormats !== undefined ? model.applyWidthHeightFormats : '1' }; } function validate(worksheet, model) { // Note: Multiple pivot tables are now supported if (model.metric && model.metric !== 'sum' && model.metric !== 'count') { throw new Error('Only the "sum" and "count" metric is supported at this time.'); } var headerNames = model.sourceSheet.getRow(1).values.slice(1); var isInHeaderNames = objectFromProps(headerNames, true); for (var _i = 0, _arr = [].concat(_toConsumableArray(model.rows), _toConsumableArray(model.columns), _toConsumableArray(model.values)); _i < _arr.length; _i++) { var name = _arr[_i]; if (!isInHeaderNames[name]) { throw new Error("The header name \"".concat(name, "\" was not found in ").concat(model.sourceSheet.name, ".")); } } if (!model.rows.length) { throw new Error('No pivot table rows specified.'); } if (!model.columns.length) { throw new Error('No pivot table columns specified.'); } if (model.values.length !== 1) { throw new Error('Exactly 1 value needs to be specified at this time.'); } } function makeCacheFields(worksheet, fieldNamesWithSharedItems) { // Cache fields are used in pivot tables to reference source data. // // Example // ------- // Turn // // `worksheet` sheet values [ // ['A', 'B', 'C', 'D', 'E'], // ['a1', 'b1', 'c1', 4, 5], // ['a1', 'b2', 'c1', 4, 5], // ['a2', 'b1', 'c2', 14, 24], // ['a2', 'b2', 'c2', 24, 35], // ['a3', 'b1', 'c3', 34, 45], // ['a3', 'b2', 'c3', 44, 45] // ]; // fieldNamesWithSharedItems = ['A', 'B', 'C']; // // into // // [ // { name: 'A', sharedItems: ['a1', 'a2', 'a3'] }, // { name: 'B', sharedItems: ['b1', 'b2'] }, // { name: 'C', sharedItems: ['c1', 'c2', 'c3'] }, // { name: 'D', sharedItems: null }, // { name: 'E', sharedItems: null } // ] var names = worksheet.getRow(1).values; var nameToHasSharedItems = objectFromProps(fieldNamesWithSharedItems, true); var aggregate = function aggregate(columnIndex) { var columnValues = worksheet.getColumn(columnIndex).values.slice(2); // Deduplicate case-insensitively for Excel compatibility // Excel treats pivot table values as case-insensitive, so "Apple" and "apple" // are considered the same value. We keep the first occurrence of each case-insensitive variant. var seen = new Map(); // lowercase -> first occurrence var uniqueValues = []; var _iterator = _createForOfIteratorHelper(columnValues), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var value = _step.value; if (value === null || value === undefined) continue; var key = typeof value === 'string' ? value.toLowerCase() : value; if (!seen.has(key)) { seen.set(key, value); uniqueValues.push(value); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return toSortedArray(uniqueValues); }; // make result var result = []; var _iterator2 = _createForOfIteratorHelper(range(1, names.length)), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var columnIndex = _step2.value; var name = names[columnIndex]; var sharedItems = nameToHasSharedItems[name] ? aggregate(columnIndex) : null; result.push({ name: name, sharedItems: sharedItems }); } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return result; } module.exports = { makePivotTable: makePivotTable }; //# sourceMappingURL=pivot-table.js.map