@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/esm/index.js CHANGED
@@ -248,6 +248,16 @@ function defaultColWidth(width, floor = false) {
248
248
  }
249
249
  return 10;
250
250
  }
251
+ function cellRefAttr(col, prevCol, colRef, rowNum) {
252
+ return col === prevCol + 1 ? "" : ` r="${colRef}${rowNum}"`;
253
+ }
254
+ var DEFAULT_COMPRESSION_LEVEL = 6;
255
+ function assertCompressionLevel(level) {
256
+ if (!Number.isInteger(level) || level < 0 || level > 9) {
257
+ throw new RangeError(`compressionLevel must be an integer from 0 to 9, got ${level}`);
258
+ }
259
+ return level;
260
+ }
251
261
 
252
262
  // src/ExcelReaderAbstract.ts
253
263
  var ExcelReaderAbstract = class {
@@ -545,10 +555,10 @@ function stripInvalidControlChars(str) {
545
555
  return out;
546
556
  }
547
557
  function escapeXmlText(str) {
548
- if (!/&|<|>|"|'/.test(str) && !hasInvalidControlChars(str)) {
558
+ if (!/[&<>"'\r]/.test(str) && !hasInvalidControlChars(str)) {
549
559
  return str;
550
560
  }
551
- let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
561
+ let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\r/g, "&#13;");
552
562
  if (hasInvalidControlChars(out)) {
553
563
  out = stripInvalidControlChars(out);
554
564
  }
@@ -585,6 +595,14 @@ function unescapeXml(str) {
585
595
  }
586
596
  });
587
597
  }
598
+ function escapeXlsxText(str) {
599
+ return escapeXmlText(str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_"));
600
+ }
601
+ function unescapeXmlText(str) {
602
+ if (!str) return str;
603
+ const text = unescapeXml(str.indexOf("\r") === -1 ? str : str.replace(/\r\n?/g, "\n"));
604
+ return text.indexOf("_x") === -1 ? text : text.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(parseInt(hex, 16)));
605
+ }
588
606
  function parseTagAttributes(tag) {
589
607
  const attrs = {};
590
608
  const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
@@ -651,7 +669,7 @@ function parseSharedStringsXml(xml) {
651
669
  while ((textMatch = textRegex.exec(content)) !== null) {
652
670
  value += textMatch[1];
653
671
  }
654
- result.push(unescapeXml(value));
672
+ result.push(unescapeXmlText(value));
655
673
  }
656
674
  return result;
657
675
  }
@@ -1336,7 +1354,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
1336
1354
  }
1337
1355
  let finalVal = null;
1338
1356
  if (inlineVals !== null) {
1339
- finalVal = unescapeXml(inlineVals.join(""));
1357
+ finalVal = unescapeXmlText(inlineVals.join(""));
1340
1358
  } else if (val !== null) {
1341
1359
  if (type === "s") {
1342
1360
  const idx = parseInt(val, 10);
@@ -1344,13 +1362,13 @@ var XlsxReader = class extends ExcelReaderAbstract {
1344
1362
  } else if (type === "b") {
1345
1363
  finalVal = val === "1" || val === "true";
1346
1364
  } else if (type === "inlineStr") {
1347
- finalVal = unescapeXml(val);
1365
+ finalVal = unescapeXmlText(val);
1348
1366
  } else if (type === "str") {
1349
- finalVal = unescapeXml(val);
1367
+ finalVal = unescapeXmlText(val);
1350
1368
  } else if (type === "e") {
1351
1369
  finalVal = unescapeXml(val);
1352
1370
  } else if (type === "d") {
1353
- finalVal = parseIsoDateUtc(val) ?? unescapeXml(val);
1371
+ finalVal = parseIsoDateUtc(val) ?? unescapeXmlText(val);
1354
1372
  } else if (type === "n" || type === "") {
1355
1373
  finalVal = parseFloat(val);
1356
1374
  if (styleIndex > 0 && this.xfIdToNumFmtId) {
@@ -1457,7 +1475,11 @@ var XlsbWriter = class _XlsbWriter {
1457
1475
  /**
1458
1476
  * @param filePath Destination `.xlsb` path on disk.
1459
1477
  */
1460
- constructor(filePath) {
1478
+ /**
1479
+ * @param filePath Destination `.xlsb` path on disk.
1480
+ * @param options.compressionLevel Deflate level 0-9 (default 6).
1481
+ */
1482
+ constructor(filePath, options = {}) {
1461
1483
  this._outputError = null;
1462
1484
  this._finalized = false;
1463
1485
  this.sheetCount = 0;
@@ -1470,6 +1492,7 @@ var XlsbWriter = class _XlsbWriter {
1470
1492
  this._nextXfIdx = 4;
1471
1493
  this._stFmtRecords = /* @__PURE__ */ new Map();
1472
1494
  this.stream = new StreamingSheetState();
1495
+ const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
1473
1496
  this._targetPath = path2.resolve(filePath);
1474
1497
  fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
1475
1498
  this._tempPath = path2.join(
@@ -1480,7 +1503,7 @@ var XlsbWriter = class _XlsbWriter {
1480
1503
  this.output.on("error", (err) => {
1481
1504
  this._outputError = err;
1482
1505
  });
1483
- this.archive = archiver("zip");
1506
+ this.archive = archiver("zip", { zlib: { level: compressionLevel } });
1484
1507
  this.archive.on("error", (err) => {
1485
1508
  this._outputError = err;
1486
1509
  });
@@ -3930,7 +3953,11 @@ var XlsxWriter = class {
3930
3953
  /**
3931
3954
  * @param filePath Destination `.xlsx` path on disk.
3932
3955
  */
3933
- constructor(filePath) {
3956
+ /**
3957
+ * @param filePath Destination `.xlsx` path on disk.
3958
+ * @param options.compressionLevel Deflate level 0-9 (default 6).
3959
+ */
3960
+ constructor(filePath, options = {}) {
3934
3961
  this._outputError = null;
3935
3962
  this._finalized = false;
3936
3963
  this.sheetCount = 0;
@@ -3946,6 +3973,7 @@ var XlsxWriter = class {
3946
3973
  this._nextXfIndex = 4;
3947
3974
  this.stream = new StreamingSheetState();
3948
3975
  this.currentColLetters = [];
3976
+ const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
3949
3977
  this._targetPath = path3.resolve(filePath);
3950
3978
  fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
3951
3979
  this._tempPath = path3.join(
@@ -3956,7 +3984,7 @@ var XlsxWriter = class {
3956
3984
  this.output.on("error", (err) => {
3957
3985
  this._outputError = err;
3958
3986
  });
3959
- this.archive = archiver2("zip");
3987
+ this.archive = archiver2("zip", { zlib: { level: compressionLevel } });
3960
3988
  this.archive.on("error", (err) => {
3961
3989
  this._outputError = err;
3962
3990
  });
@@ -4001,7 +4029,7 @@ var XlsxWriter = class {
4001
4029
  _needsEscape(str) {
4002
4030
  for (let i = 0; i < str.length; i++) {
4003
4031
  const c = str.charCodeAt(i);
4004
- if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
4032
+ 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) {
4005
4033
  return true;
4006
4034
  }
4007
4035
  }
@@ -4009,8 +4037,9 @@ var XlsxWriter = class {
4009
4037
  }
4010
4038
  _escape(str) {
4011
4039
  if (typeof str !== "string") return str;
4040
+ str = str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_");
4012
4041
  if (!this._needsEscape(str)) return str;
4013
- 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, "");
4042
+ 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, "");
4014
4043
  }
4015
4044
  /**
4016
4045
  * Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
@@ -4063,7 +4092,7 @@ var XlsxWriter = class {
4063
4092
  this.stream.rowNum++;
4064
4093
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
4065
4094
  for (let c = 0; c < headers.length; c++) {
4066
- this._writeStringCell(bigBuf, headers[c], colLetters[c], this.stream.rowNum, 3);
4095
+ this._writeStringCell(bigBuf, headers[c], "", 3);
4067
4096
  }
4068
4097
  bigBuf.writeString("</row>");
4069
4098
  }
@@ -4079,6 +4108,7 @@ var XlsxWriter = class {
4079
4108
  this.stream.rowNum++;
4080
4109
  assertRowCount(this.stream.rowNum);
4081
4110
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
4111
+ let prevCol = -1;
4082
4112
  for (let c = 0; c < row.length; c++) {
4083
4113
  const raw = row[c];
4084
4114
  if (raw === null || raw === void 0) continue;
@@ -4086,27 +4116,28 @@ var XlsxWriter = class {
4086
4116
  const val = fmtString !== null ? unwrapCell(raw) : raw;
4087
4117
  const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
4088
4118
  const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
4089
- const colRef = this.currentColLetters[c];
4119
+ const ref = cellRefAttr(c, prevCol, this.currentColLetters[c], this.stream.rowNum);
4120
+ prevCol = c;
4090
4121
  if (typeof val === "number") {
4091
4122
  if (Number.isFinite(val)) {
4092
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}"${styleAttr}><v>${val}</v></c>`);
4123
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
4093
4124
  } else {
4094
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4125
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4095
4126
  }
4096
4127
  } else if (typeof val === "bigint") {
4097
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4128
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4098
4129
  } else if (typeof val === "boolean") {
4099
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4130
+ bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4100
4131
  } else if (val instanceof Date) {
4101
4132
  const oaDate = this._toOADate(val);
4102
4133
  if (Number.isFinite(oaDate)) {
4103
4134
  const finalStyle = xfIdx >= 0 ? xfIdx : 1;
4104
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4135
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
4105
4136
  } else {
4106
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4137
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4107
4138
  }
4108
4139
  } else if (val !== null && val !== void 0) {
4109
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4140
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4110
4141
  }
4111
4142
  }
4112
4143
  bigBuf.writeString("</row>");
@@ -4192,7 +4223,7 @@ var XlsxWriter = class {
4192
4223
  rowNum++;
4193
4224
  bigBuf.writeString(`<row r="${rowNum}">`);
4194
4225
  for (let c = 0; c < headers.length; c++) {
4195
- this._writeStringCell(bigBuf, headers[c], colLetters[c], rowNum, 3);
4226
+ this._writeStringCell(bigBuf, headers[c], "", 3);
4196
4227
  }
4197
4228
  bigBuf.writeString("</row>");
4198
4229
  }
@@ -4200,6 +4231,7 @@ var XlsxWriter = class {
4200
4231
  rowNum++;
4201
4232
  bigBuf.writeString(`<row r="${rowNum}">`);
4202
4233
  const row = rows[r];
4234
+ let prevCol = -1;
4203
4235
  for (let c = 0; c < row.length; c++) {
4204
4236
  const raw = row[c];
4205
4237
  if (raw === null || raw === void 0) continue;
@@ -4207,27 +4239,28 @@ var XlsxWriter = class {
4207
4239
  const val = fmtString !== null ? unwrapCell(raw) : raw;
4208
4240
  const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
4209
4241
  const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
4210
- const colRef = colLetters[c];
4242
+ const ref = cellRefAttr(c, prevCol, colLetters[c], rowNum);
4243
+ prevCol = c;
4211
4244
  if (typeof val === "number") {
4212
4245
  if (Number.isFinite(val)) {
4213
- bigBuf.writeString(`<c r="${colRef}${rowNum}"${styleAttr}><v>${val}</v></c>`);
4246
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
4214
4247
  } else {
4215
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4248
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4216
4249
  }
4217
4250
  } else if (typeof val === "bigint") {
4218
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4251
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4219
4252
  } else if (typeof val === "boolean") {
4220
- bigBuf.writeString(`<c r="${colRef}${rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4253
+ bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4221
4254
  } else if (val instanceof Date) {
4222
4255
  const oaDate = this._toOADate(val);
4223
4256
  if (Number.isFinite(oaDate)) {
4224
4257
  const finalStyle = xfIdx >= 0 ? xfIdx : 1;
4225
- bigBuf.writeString(`<c r="${colRef}${rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4258
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
4226
4259
  } else {
4227
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4260
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4228
4261
  }
4229
4262
  } else if (val !== null && val !== void 0) {
4230
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4263
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
4231
4264
  }
4232
4265
  }
4233
4266
  bigBuf.writeString("</row>");
@@ -4246,7 +4279,8 @@ var XlsxWriter = class {
4246
4279
  name: this.sheetList[this.sheetCount - 1].pathInArchive
4247
4280
  });
4248
4281
  }
4249
- _writeStringCell(bigBuf, val, colRef, rowNum, styleOverride) {
4282
+ /** `ref` is the cell's `r` attribute from `cellRefAttr`, or '' when omitted. */
4283
+ _writeStringCell(bigBuf, val, ref, styleOverride) {
4250
4284
  assertCellTextLength(val);
4251
4285
  let index = this.sstMap.get(val);
4252
4286
  if (index === void 0) {
@@ -4256,7 +4290,7 @@ var XlsxWriter = class {
4256
4290
  }
4257
4291
  this.sstCntAll++;
4258
4292
  const styleAttr = styleOverride !== void 0 ? ` s="${styleOverride}"` : "";
4259
- bigBuf.writeString(`<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`);
4293
+ bigBuf.writeString(`<c${ref} t="s"${styleAttr}><v>${index}</v></c>`);
4260
4294
  }
4261
4295
  _registerFormat(fmtString) {
4262
4296
  if (this._formatXfMap.has(fmtString)) {
@@ -4664,7 +4698,7 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4664
4698
  } catch {
4665
4699
  }
4666
4700
  });
4667
- archive = archiver3("zip");
4701
+ archive = archiver3("zip", { zlib: { level: DEFAULT_COMPRESSION_LEVEL } });
4668
4702
  archive.on("error", (err) => {
4669
4703
  outputError = err;
4670
4704
  });
@@ -5173,7 +5207,7 @@ var XlsxUpdater = class _XlsxUpdater {
5173
5207
  throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
5174
5208
  }
5175
5209
  const appended = this.sharedStringsPending.map((txt) => {
5176
- const clean = escapeXmlText(txt);
5210
+ const clean = escapeXlsxText(txt);
5177
5211
  const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
5178
5212
  const tAttr = preserve ? ' xml:space="preserve"' : "";
5179
5213
  return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
@@ -5196,14 +5230,25 @@ var XlsxUpdater = class _XlsxUpdater {
5196
5230
  const sdEnd = sheetXml.indexOf("</sheetData>", sdStart);
5197
5231
  if (sdEnd === -1) return { dataStyles: /* @__PURE__ */ new Map(), headerStyles };
5198
5232
  const region = sheetXml.substring(sdStart, sdEnd);
5199
- const cellRe = /<c\b[^>]*>/g;
5233
+ const cellRe = /<row\b[^>]*>|<c\b[^>]*>/g;
5200
5234
  let m;
5235
+ let row = 0;
5236
+ let nextCol = 0;
5201
5237
  while ((m = cellRe.exec(region)) !== null) {
5202
5238
  const tag = m[0];
5239
+ if (tag.startsWith("<row")) {
5240
+ const rowAttr = /\br="(\d+)"/.exec(tag);
5241
+ row = rowAttr ? parseInt(rowAttr[1], 10) : row + 1;
5242
+ nextCol = 0;
5243
+ continue;
5244
+ }
5203
5245
  const rMatch = /r="([A-Z]+)(\d+)"/.exec(tag);
5204
- if (!rMatch) continue;
5205
- const col = columnLetterToIndex(rMatch[1]);
5206
- const row = parseInt(rMatch[2], 10);
5246
+ let col = nextCol;
5247
+ if (rMatch) {
5248
+ col = columnLetterToIndex(rMatch[1]);
5249
+ row = parseInt(rMatch[2], 10);
5250
+ }
5251
+ nextCol = col + 1;
5207
5252
  const sMatch = /s="(\d+)"/.exec(tag);
5208
5253
  if (!sMatch) continue;
5209
5254
  const s = parseInt(sMatch[1], 10);
@@ -5324,7 +5369,7 @@ var XlsxUpdater = class _XlsxUpdater {
5324
5369
  const colRef = columnIndexToLetter(col);
5325
5370
  const styleAttr = style > 0 ? ` s="${style}"` : "";
5326
5371
  if (this.sharedStringsXml === null) {
5327
- const clean = escapeXmlText(text);
5372
+ const clean = escapeXlsxText(text);
5328
5373
  const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
5329
5374
  const tAttr = preserve ? ' xml:space="preserve"' : "";
5330
5375
  return `<c r="${colRef}${rowNum}" t="inlineStr"${styleAttr}><is><t${tAttr}>${clean}</t></is></c>`;
@@ -6863,6 +6908,7 @@ export {
6863
6908
  convertXlsbToXlsx,
6864
6909
  convertXlsxToXlsb,
6865
6910
  defaultColWidth,
6911
+ escapeXlsxText,
6866
6912
  escapeXmlText,
6867
6913
  getFormat,
6868
6914
  initColWidths,
@@ -6873,6 +6919,7 @@ export {
6873
6919
  sanitizeSheetName,
6874
6920
  trimTrailingEmptyRows,
6875
6921
  unescapeXml,
6922
+ unescapeXmlText,
6876
6923
  unwrapCell,
6877
6924
  updateColWidthsFromRows
6878
6925
  };