@justybase/spreadsheet-tasks 3.0.0 → 3.1.0

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/cjs/index.js CHANGED
@@ -57,6 +57,7 @@ __export(index_exports, {
57
57
  convertXlsbToXlsx: () => convertXlsbToXlsx,
58
58
  convertXlsxToXlsb: () => convertXlsxToXlsb,
59
59
  defaultColWidth: () => defaultColWidth,
60
+ escapeXlsxText: () => escapeXlsxText,
60
61
  escapeXmlText: () => escapeXmlText,
61
62
  getFormat: () => getFormat,
62
63
  initColWidths: () => initColWidths,
@@ -67,6 +68,7 @@ __export(index_exports, {
67
68
  sanitizeSheetName: () => sanitizeSheetName,
68
69
  trimTrailingEmptyRows: () => trimTrailingEmptyRows,
69
70
  unescapeXml: () => unescapeXml,
71
+ unescapeXmlText: () => unescapeXmlText,
70
72
  unwrapCell: () => unwrapCell,
71
73
  updateColWidthsFromRows: () => updateColWidthsFromRows
72
74
  });
@@ -322,6 +324,16 @@ function defaultColWidth(width, floor = false) {
322
324
  }
323
325
  return 10;
324
326
  }
327
+ function cellRefAttr(col, prevCol, colRef, rowNum) {
328
+ return col === prevCol + 1 ? "" : ` r="${colRef}${rowNum}"`;
329
+ }
330
+ var DEFAULT_COMPRESSION_LEVEL = 6;
331
+ function assertCompressionLevel(level) {
332
+ if (!Number.isInteger(level) || level < 0 || level > 9) {
333
+ throw new RangeError(`compressionLevel must be an integer from 0 to 9, got ${level}`);
334
+ }
335
+ return level;
336
+ }
325
337
 
326
338
  // src/ExcelReaderAbstract.ts
327
339
  var ExcelReaderAbstract = class {
@@ -619,10 +631,10 @@ function stripInvalidControlChars(str) {
619
631
  return out;
620
632
  }
621
633
  function escapeXmlText(str) {
622
- if (!/&|<|>|"|'/.test(str) && !hasInvalidControlChars(str)) {
634
+ if (!/[&<>"'\r]/.test(str) && !hasInvalidControlChars(str)) {
623
635
  return str;
624
636
  }
625
- let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
637
+ let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\r/g, "&#13;");
626
638
  if (hasInvalidControlChars(out)) {
627
639
  out = stripInvalidControlChars(out);
628
640
  }
@@ -659,6 +671,14 @@ function unescapeXml(str) {
659
671
  }
660
672
  });
661
673
  }
674
+ function escapeXlsxText(str) {
675
+ return escapeXmlText(str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_"));
676
+ }
677
+ function unescapeXmlText(str) {
678
+ if (!str) return str;
679
+ const text = unescapeXml(str.indexOf("\r") === -1 ? str : str.replace(/\r\n?/g, "\n"));
680
+ return text.indexOf("_x") === -1 ? text : text.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(parseInt(hex, 16)));
681
+ }
662
682
  function parseTagAttributes(tag) {
663
683
  const attrs = {};
664
684
  const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
@@ -725,7 +745,7 @@ function parseSharedStringsXml(xml) {
725
745
  while ((textMatch = textRegex.exec(content)) !== null) {
726
746
  value += textMatch[1];
727
747
  }
728
- result.push(unescapeXml(value));
748
+ result.push(unescapeXmlText(value));
729
749
  }
730
750
  return result;
731
751
  }
@@ -1410,7 +1430,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
1410
1430
  }
1411
1431
  let finalVal = null;
1412
1432
  if (inlineVals !== null) {
1413
- finalVal = unescapeXml(inlineVals.join(""));
1433
+ finalVal = unescapeXmlText(inlineVals.join(""));
1414
1434
  } else if (val !== null) {
1415
1435
  if (type === "s") {
1416
1436
  const idx = parseInt(val, 10);
@@ -1418,13 +1438,13 @@ var XlsxReader = class extends ExcelReaderAbstract {
1418
1438
  } else if (type === "b") {
1419
1439
  finalVal = val === "1" || val === "true";
1420
1440
  } else if (type === "inlineStr") {
1421
- finalVal = unescapeXml(val);
1441
+ finalVal = unescapeXmlText(val);
1422
1442
  } else if (type === "str") {
1423
- finalVal = unescapeXml(val);
1443
+ finalVal = unescapeXmlText(val);
1424
1444
  } else if (type === "e") {
1425
1445
  finalVal = unescapeXml(val);
1426
1446
  } else if (type === "d") {
1427
- finalVal = parseIsoDateUtc(val) ?? unescapeXml(val);
1447
+ finalVal = parseIsoDateUtc(val) ?? unescapeXmlText(val);
1428
1448
  } else if (type === "n" || type === "") {
1429
1449
  finalVal = parseFloat(val);
1430
1450
  if (styleIndex > 0 && this.xfIdToNumFmtId) {
@@ -1531,7 +1551,11 @@ var XlsbWriter = class _XlsbWriter {
1531
1551
  /**
1532
1552
  * @param filePath Destination `.xlsb` path on disk.
1533
1553
  */
1534
- constructor(filePath) {
1554
+ /**
1555
+ * @param filePath Destination `.xlsb` path on disk.
1556
+ * @param options.compressionLevel Deflate level 0-9 (default 6).
1557
+ */
1558
+ constructor(filePath, options = {}) {
1535
1559
  this._outputError = null;
1536
1560
  this._finalized = false;
1537
1561
  this.sheetCount = 0;
@@ -1544,6 +1568,7 @@ var XlsbWriter = class _XlsbWriter {
1544
1568
  this._nextXfIdx = 4;
1545
1569
  this._stFmtRecords = /* @__PURE__ */ new Map();
1546
1570
  this.stream = new StreamingSheetState();
1571
+ const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
1547
1572
  this._targetPath = path2.resolve(filePath);
1548
1573
  fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
1549
1574
  this._tempPath = path2.join(
@@ -1554,7 +1579,7 @@ var XlsbWriter = class _XlsbWriter {
1554
1579
  this.output.on("error", (err) => {
1555
1580
  this._outputError = err;
1556
1581
  });
1557
- this.archive = (0, import_archiver.default)("zip");
1582
+ this.archive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
1558
1583
  this.archive.on("error", (err) => {
1559
1584
  this._outputError = err;
1560
1585
  });
@@ -4004,7 +4029,11 @@ var XlsxWriter = class {
4004
4029
  /**
4005
4030
  * @param filePath Destination `.xlsx` path on disk.
4006
4031
  */
4007
- constructor(filePath) {
4032
+ /**
4033
+ * @param filePath Destination `.xlsx` path on disk.
4034
+ * @param options.compressionLevel Deflate level 0-9 (default 6).
4035
+ */
4036
+ constructor(filePath, options = {}) {
4008
4037
  this._outputError = null;
4009
4038
  this._finalized = false;
4010
4039
  this.sheetCount = 0;
@@ -4020,6 +4049,7 @@ var XlsxWriter = class {
4020
4049
  this._nextXfIndex = 4;
4021
4050
  this.stream = new StreamingSheetState();
4022
4051
  this.currentColLetters = [];
4052
+ const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
4023
4053
  this._targetPath = path3.resolve(filePath);
4024
4054
  fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
4025
4055
  this._tempPath = path3.join(
@@ -4030,7 +4060,7 @@ var XlsxWriter = class {
4030
4060
  this.output.on("error", (err) => {
4031
4061
  this._outputError = err;
4032
4062
  });
4033
- this.archive = (0, import_archiver2.default)("zip");
4063
+ this.archive = (0, import_archiver2.default)("zip", { zlib: { level: compressionLevel } });
4034
4064
  this.archive.on("error", (err) => {
4035
4065
  this._outputError = err;
4036
4066
  });
@@ -4075,7 +4105,7 @@ var XlsxWriter = class {
4075
4105
  _needsEscape(str) {
4076
4106
  for (let i = 0; i < str.length; i++) {
4077
4107
  const c = str.charCodeAt(i);
4078
- if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
4108
+ if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c === 13 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
4079
4109
  return true;
4080
4110
  }
4081
4111
  }
@@ -4083,8 +4113,9 @@ var XlsxWriter = class {
4083
4113
  }
4084
4114
  _escape(str) {
4085
4115
  if (typeof str !== "string") return str;
4116
+ str = str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_");
4086
4117
  if (!this._needsEscape(str)) return str;
4087
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
4118
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\r/g, "&#13;").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
4088
4119
  }
4089
4120
  /**
4090
4121
  * Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
@@ -4137,7 +4168,7 @@ var XlsxWriter = class {
4137
4168
  this.stream.rowNum++;
4138
4169
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
4139
4170
  for (let c = 0; c < headers.length; c++) {
4140
- this._writeStringCell(bigBuf, headers[c], colLetters[c], this.stream.rowNum, 3);
4171
+ this._writeStringCell(bigBuf, headers[c], "", 3);
4141
4172
  }
4142
4173
  bigBuf.writeString("</row>");
4143
4174
  }
@@ -4153,6 +4184,7 @@ var XlsxWriter = class {
4153
4184
  this.stream.rowNum++;
4154
4185
  assertRowCount(this.stream.rowNum);
4155
4186
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
4187
+ let prevCol = -1;
4156
4188
  for (let c = 0; c < row.length; c++) {
4157
4189
  const raw = row[c];
4158
4190
  if (raw === null || raw === void 0) continue;
@@ -4160,27 +4192,28 @@ var XlsxWriter = class {
4160
4192
  const val = fmtString !== null ? unwrapCell(raw) : raw;
4161
4193
  const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
4162
4194
  const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
4163
- const colRef = this.currentColLetters[c];
4195
+ const ref = cellRefAttr(c, prevCol, this.currentColLetters[c], this.stream.rowNum);
4196
+ prevCol = c;
4164
4197
  if (typeof val === "number") {
4165
4198
  if (Number.isFinite(val)) {
4166
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}"${styleAttr}><v>${val}</v></c>`);
4199
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
4167
4200
  } else {
4168
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4201
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4169
4202
  }
4170
4203
  } else if (typeof val === "bigint") {
4171
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4204
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4172
4205
  } else if (typeof val === "boolean") {
4173
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4206
+ bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4174
4207
  } else if (val instanceof Date) {
4175
4208
  const oaDate = this._toOADate(val);
4176
4209
  if (Number.isFinite(oaDate)) {
4177
4210
  const finalStyle = xfIdx >= 0 ? xfIdx : 1;
4178
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4211
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
4179
4212
  } else {
4180
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4213
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4181
4214
  }
4182
4215
  } else if (val !== null && val !== void 0) {
4183
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4216
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4184
4217
  }
4185
4218
  }
4186
4219
  bigBuf.writeString("</row>");
@@ -4266,7 +4299,7 @@ var XlsxWriter = class {
4266
4299
  rowNum++;
4267
4300
  bigBuf.writeString(`<row r="${rowNum}">`);
4268
4301
  for (let c = 0; c < headers.length; c++) {
4269
- this._writeStringCell(bigBuf, headers[c], colLetters[c], rowNum, 3);
4302
+ this._writeStringCell(bigBuf, headers[c], "", 3);
4270
4303
  }
4271
4304
  bigBuf.writeString("</row>");
4272
4305
  }
@@ -4274,6 +4307,7 @@ var XlsxWriter = class {
4274
4307
  rowNum++;
4275
4308
  bigBuf.writeString(`<row r="${rowNum}">`);
4276
4309
  const row = rows[r];
4310
+ let prevCol = -1;
4277
4311
  for (let c = 0; c < row.length; c++) {
4278
4312
  const raw = row[c];
4279
4313
  if (raw === null || raw === void 0) continue;
@@ -4281,27 +4315,28 @@ var XlsxWriter = class {
4281
4315
  const val = fmtString !== null ? unwrapCell(raw) : raw;
4282
4316
  const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
4283
4317
  const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
4284
- const colRef = colLetters[c];
4318
+ const ref = cellRefAttr(c, prevCol, colLetters[c], rowNum);
4319
+ prevCol = c;
4285
4320
  if (typeof val === "number") {
4286
4321
  if (Number.isFinite(val)) {
4287
- bigBuf.writeString(`<c r="${colRef}${rowNum}"${styleAttr}><v>${val}</v></c>`);
4322
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
4288
4323
  } else {
4289
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4324
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4290
4325
  }
4291
4326
  } else if (typeof val === "bigint") {
4292
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4327
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4293
4328
  } else if (typeof val === "boolean") {
4294
- bigBuf.writeString(`<c r="${colRef}${rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4329
+ bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4295
4330
  } else if (val instanceof Date) {
4296
4331
  const oaDate = this._toOADate(val);
4297
4332
  if (Number.isFinite(oaDate)) {
4298
4333
  const finalStyle = xfIdx >= 0 ? xfIdx : 1;
4299
- bigBuf.writeString(`<c r="${colRef}${rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4334
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
4300
4335
  } else {
4301
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4336
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4302
4337
  }
4303
4338
  } else if (val !== null && val !== void 0) {
4304
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4339
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4305
4340
  }
4306
4341
  }
4307
4342
  bigBuf.writeString("</row>");
@@ -4320,7 +4355,8 @@ var XlsxWriter = class {
4320
4355
  name: this.sheetList[this.sheetCount - 1].pathInArchive
4321
4356
  });
4322
4357
  }
4323
- _writeStringCell(bigBuf, val, colRef, rowNum, styleOverride) {
4358
+ /** `ref` is the cell's `r` attribute from `cellRefAttr`, or '' when omitted. */
4359
+ _writeStringCell(bigBuf, val, ref, styleOverride) {
4324
4360
  assertCellTextLength(val);
4325
4361
  let index = this.sstMap.get(val);
4326
4362
  if (index === void 0) {
@@ -4330,7 +4366,7 @@ var XlsxWriter = class {
4330
4366
  }
4331
4367
  this.sstCntAll++;
4332
4368
  const styleAttr = styleOverride !== void 0 ? ` s="${styleOverride}"` : "";
4333
- bigBuf.writeString(`<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`);
4369
+ bigBuf.writeString(`<c${ref} t="s"${styleAttr}><v>${index}</v></c>`);
4334
4370
  }
4335
4371
  _registerFormat(fmtString) {
4336
4372
  if (this._formatXfMap.has(fmtString)) {
@@ -4738,7 +4774,7 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4738
4774
  } catch {
4739
4775
  }
4740
4776
  });
4741
- archive = (0, import_archiver3.default)("zip");
4777
+ archive = (0, import_archiver3.default)("zip", { zlib: { level: DEFAULT_COMPRESSION_LEVEL } });
4742
4778
  archive.on("error", (err) => {
4743
4779
  outputError = err;
4744
4780
  });
@@ -5247,7 +5283,7 @@ var XlsxUpdater = class _XlsxUpdater {
5247
5283
  throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
5248
5284
  }
5249
5285
  const appended = this.sharedStringsPending.map((txt) => {
5250
- const clean = escapeXmlText(txt);
5286
+ const clean = escapeXlsxText(txt);
5251
5287
  const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
5252
5288
  const tAttr = preserve ? ' xml:space="preserve"' : "";
5253
5289
  return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
@@ -5270,14 +5306,25 @@ var XlsxUpdater = class _XlsxUpdater {
5270
5306
  const sdEnd = sheetXml.indexOf("</sheetData>", sdStart);
5271
5307
  if (sdEnd === -1) return { dataStyles: /* @__PURE__ */ new Map(), headerStyles };
5272
5308
  const region = sheetXml.substring(sdStart, sdEnd);
5273
- const cellRe = /<c\b[^>]*>/g;
5309
+ const cellRe = /<row\b[^>]*>|<c\b[^>]*>/g;
5274
5310
  let m;
5311
+ let row = 0;
5312
+ let nextCol = 0;
5275
5313
  while ((m = cellRe.exec(region)) !== null) {
5276
5314
  const tag = m[0];
5315
+ if (tag.startsWith("<row")) {
5316
+ const rowAttr = /\br="(\d+)"/.exec(tag);
5317
+ row = rowAttr ? parseInt(rowAttr[1], 10) : row + 1;
5318
+ nextCol = 0;
5319
+ continue;
5320
+ }
5277
5321
  const rMatch = /r="([A-Z]+)(\d+)"/.exec(tag);
5278
- if (!rMatch) continue;
5279
- const col = columnLetterToIndex(rMatch[1]);
5280
- const row = parseInt(rMatch[2], 10);
5322
+ let col = nextCol;
5323
+ if (rMatch) {
5324
+ col = columnLetterToIndex(rMatch[1]);
5325
+ row = parseInt(rMatch[2], 10);
5326
+ }
5327
+ nextCol = col + 1;
5281
5328
  const sMatch = /s="(\d+)"/.exec(tag);
5282
5329
  if (!sMatch) continue;
5283
5330
  const s = parseInt(sMatch[1], 10);
@@ -5398,7 +5445,7 @@ var XlsxUpdater = class _XlsxUpdater {
5398
5445
  const colRef = columnIndexToLetter(col);
5399
5446
  const styleAttr = style > 0 ? ` s="${style}"` : "";
5400
5447
  if (this.sharedStringsXml === null) {
5401
- const clean = escapeXmlText(text);
5448
+ const clean = escapeXlsxText(text);
5402
5449
  const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
5403
5450
  const tAttr = preserve ? ' xml:space="preserve"' : "";
5404
5451
  return `<c r="${colRef}${rowNum}" t="inlineStr"${styleAttr}><is><t${tAttr}>${clean}</t></is></c>`;
@@ -6938,6 +6985,7 @@ function convertXlsxToXlsb(inputPath, outputPath, options) {
6938
6985
  convertXlsbToXlsx,
6939
6986
  convertXlsxToXlsb,
6940
6987
  defaultColWidth,
6988
+ escapeXlsxText,
6941
6989
  escapeXmlText,
6942
6990
  getFormat,
6943
6991
  initColWidths,
@@ -6948,6 +6996,7 @@ function convertXlsxToXlsb(inputPath, outputPath, options) {
6948
6996
  sanitizeSheetName,
6949
6997
  trimTrailingEmptyRows,
6950
6998
  unescapeXml,
6999
+ unescapeXmlText,
6951
7000
  unwrapCell,
6952
7001
  updateColWidthsFromRows
6953
7002
  });