@cj-tech-master/excelts 9.4.0 → 9.4.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 (49) hide show
  1. package/dist/browser/index.browser.d.ts +1 -0
  2. package/dist/browser/index.browser.js +1 -0
  3. package/dist/browser/index.d.ts +1 -0
  4. package/dist/browser/index.js +1 -0
  5. package/dist/browser/modules/excel/cell.d.ts +1 -0
  6. package/dist/browser/modules/excel/note.d.ts +3 -23
  7. package/dist/browser/modules/excel/note.js +8 -2
  8. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +17 -5
  9. package/dist/browser/modules/excel/utils/ooxml-paths.js +46 -19
  10. package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.js +3 -2
  11. package/dist/browser/modules/excel/xlsx/xform/comment/comments-xform.d.ts +8 -0
  12. package/dist/browser/modules/excel/xlsx/xform/comment/comments-xform.js +52 -5
  13. package/dist/browser/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +6 -0
  14. package/dist/browser/modules/excel/xlsx/xform/comment/vml-client-data-xform.js +27 -1
  15. package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +4 -0
  16. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +54 -14
  17. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +3 -3
  18. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +16 -15
  19. package/dist/cjs/index.js +4 -2
  20. package/dist/cjs/modules/excel/note.js +8 -2
  21. package/dist/cjs/modules/excel/utils/ooxml-paths.js +49 -24
  22. package/dist/cjs/modules/excel/xlsx/xform/comment/comment-xform.js +3 -2
  23. package/dist/cjs/modules/excel/xlsx/xform/comment/comments-xform.js +52 -5
  24. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-client-data-xform.js +27 -1
  25. package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +4 -0
  26. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +53 -13
  27. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +15 -14
  28. package/dist/esm/index.browser.js +1 -0
  29. package/dist/esm/index.js +1 -0
  30. package/dist/esm/modules/excel/note.js +8 -2
  31. package/dist/esm/modules/excel/utils/ooxml-paths.js +46 -19
  32. package/dist/esm/modules/excel/xlsx/xform/comment/comment-xform.js +3 -2
  33. package/dist/esm/modules/excel/xlsx/xform/comment/comments-xform.js +52 -5
  34. package/dist/esm/modules/excel/xlsx/xform/comment/vml-client-data-xform.js +27 -1
  35. package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +4 -0
  36. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +54 -14
  37. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +16 -15
  38. package/dist/iife/excelts.iife.js +149 -56
  39. package/dist/iife/excelts.iife.js.map +1 -1
  40. package/dist/iife/excelts.iife.min.js +30 -30
  41. package/dist/types/index.browser.d.ts +1 -0
  42. package/dist/types/index.d.ts +1 -0
  43. package/dist/types/modules/excel/cell.d.ts +1 -0
  44. package/dist/types/modules/excel/note.d.ts +3 -23
  45. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +17 -5
  46. package/dist/types/modules/excel/xlsx/xform/comment/comments-xform.d.ts +8 -0
  47. package/dist/types/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +6 -0
  48. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +3 -3
  49. package/package.json +4 -4
@@ -13,7 +13,7 @@ import { StreamingZip, ZipDeflateFile } from "../../archive/zip/stream.js";
13
13
  import { ExcelStreamStateError, ExcelFileError, ImageError, ExcelNotSupportedError, XmlParseError, TableError } from "../errors.js";
14
14
  import { filterDrawingAnchors } from "../utils/drawing-utils.js";
15
15
  import { rewriteExternalRefs } from "../utils/external-link-formula.js";
16
- import { commentsPath, commentsRelTargetFromWorksheetName, ctrlPropPath, drawingPath, drawingRelsPath, externalLinkPath, externalLinkRelsPath, externalLinkRelTargetFromWorkbook, OOXML_REL_TARGETS, pivotTableRelTargetFromWorksheetName, pivotCacheDefinitionRelTargetFromWorkbook, getCommentsIndexFromPath, getDrawingNameFromPath, getDrawingNameFromRelsPath, getExternalLinkIndexFromPath, getExternalLinkIndexFromRelsPath, getMediaFilenameFromPath, mediaPath, getPivotCacheDefinitionNameFromPath, getPivotCacheDefinitionNameFromRelsPath, getPivotCacheRecordsNameFromPath, getPivotTableNameFromPath, getPivotTableNameFromRelsPath, pivotCacheDefinitionPath, pivotCacheDefinitionRelsPath, pivotCacheDefinitionRelTargetFromPivotTable, pivotCacheRecordsPath, pivotCacheRecordsRelTarget, pivotTablePath, pivotTableRelsPath, getTableNameFromPath, tablePath, tableRelTargetFromWorksheetName, themePath, getThemeNameFromPath, getVmlDrawingNameFromPath, getVmlDrawingHFNameFromPath, getWorksheetNoFromWorksheetPath, getWorksheetNoFromWorksheetRelsPath, isBinaryEntryPath, normalizeZipPath, OOXML_PATHS, vmlDrawingRelTargetFromWorksheetName, vmlDrawingPath, vmlDrawingHFPath, vmlDrawingHFRelsPath, worksheetPath, worksheetRelsPath, worksheetRelTarget } from "../utils/ooxml-paths.js";
16
+ import { commentsPath, ctrlPropPath, drawingPath, drawingRelsPath, externalLinkPath, externalLinkRelsPath, externalLinkRelTargetFromWorkbook, OOXML_REL_TARGETS, pivotCacheDefinitionRelTargetFromWorkbook, pivotTablePathFromName, isCommentsPath, getDrawingNameFromPath, getDrawingNameFromRelsPath, getExternalLinkIndexFromPath, getExternalLinkIndexFromRelsPath, getMediaFilenameFromPath, mediaPath, getPivotCacheDefinitionNameFromPath, getPivotCacheDefinitionNameFromRelsPath, getPivotCacheRecordsNameFromPath, getPivotTableNameFromPath, getPivotTableNameFromRelsPath, pivotCacheDefinitionPath, pivotCacheDefinitionRelsPath, pivotCacheDefinitionRelTargetFromPivotTable, pivotCacheRecordsPath, pivotCacheRecordsRelTarget, pivotTablePath, pivotTableRelsPath, getTableNameFromPath, tablePath, themePath, getThemeNameFromPath, getVmlDrawingNameFromPath, getVmlDrawingHFNameFromPath, getWorksheetNoFromWorksheetPath, getWorksheetNoFromWorksheetRelsPath, isBinaryEntryPath, normalizeZipPath, OOXML_PATHS, vmlDrawingPath, vmlDrawingHFPath, vmlDrawingHFRelsPath, worksheetPath, worksheetRelsPath, worksheetRelTarget } from "../utils/ooxml-paths.js";
17
17
  import { PassthroughManager } from "../utils/passthrough-manager.js";
18
18
  import { StreamBuf } from "../utils/stream-buf.js";
19
19
  import { RelType } from "./rel-type.js";
@@ -1007,9 +1007,8 @@ class XLSX {
1007
1007
  applyWidthHeightFormats: pt.applyWidthHeightFormats === "1" ? "1" : "0"
1008
1008
  };
1009
1009
  loadedPivotTables.push(completePivotTable);
1010
- // Key format (e.g., "../pivotTables/pivotTable1.xml") matches worksheet .rels Target values,
1011
- // allowing worksheet reconciliation to look up pivot tables by their relationship target path.
1012
- pivotTablesIndexed[pivotTableRelTargetFromWorksheetName(pivotName)] = completePivotTable;
1010
+ // Key by absolute zip path so reconcile can match any rel target layout.
1011
+ pivotTablesIndexed[pivotTablePathFromName(pivotName)] = completePivotTable;
1013
1012
  });
1014
1013
  loadedPivotTables.sort((a, b) => a.tableNumber - b.tableNumber);
1015
1014
  model.pivotTables = loadedPivotTables;
@@ -1069,15 +1068,17 @@ class XLSX {
1069
1068
  model.worksheetHash[path] = worksheet;
1070
1069
  model.worksheets.push(worksheet);
1071
1070
  }
1072
- async _processCommentEntry(stream, model, name) {
1071
+ async _processCommentEntry(stream, model, zipPath) {
1073
1072
  const xform = new CommentsXform();
1074
1073
  const comments = await xform.parseStream(stream);
1075
- model.comments[commentsRelTargetFromWorksheetName(name)] = comments;
1074
+ // Key by absolute zip path so reconcile can match any rel target layout.
1075
+ model.comments[zipPath] = comments;
1076
1076
  }
1077
- async _processTableEntry(stream, model, name) {
1077
+ async _processTableEntry(stream, model, zipPath) {
1078
1078
  const xform = new TableXform();
1079
1079
  const table = await xform.parseStream(stream);
1080
- model.tables[tableRelTargetFromWorksheetName(name)] = table;
1080
+ // Key by absolute zip path so reconcile can match any rel target layout.
1081
+ model.tables[zipPath] = table;
1081
1082
  }
1082
1083
  async _processWorksheetRelsEntry(stream, model, sheetNo) {
1083
1084
  const xform = new RelationshipsXform();
@@ -1146,10 +1147,11 @@ class XLSX {
1146
1147
  const relationships = await xform.parseStream(entry);
1147
1148
  model.drawingRels[name] = relationships;
1148
1149
  }
1149
- async _processVmlDrawingEntry(entry, model, name) {
1150
+ async _processVmlDrawingEntry(entry, model, zipPath) {
1150
1151
  const xform = new VmlDrawingXform();
1151
1152
  const vmlDrawing = await xform.parseStream(entry);
1152
- model.vmlDrawings[vmlDrawingRelTargetFromWorksheetName(name)] = vmlDrawing;
1153
+ // Key by absolute zip path so reconcile can match any rel target layout.
1154
+ model.vmlDrawings[zipPath] = vmlDrawing;
1153
1155
  }
1154
1156
  async _processVmlDrawingHFEntry(entry, model, _name) {
1155
1157
  const xform = new VmlDrawingXform();
@@ -1296,7 +1298,7 @@ class XLSX {
1296
1298
  }
1297
1299
  const vmlDrawingName = getVmlDrawingNameFromPath(entryName);
1298
1300
  if (vmlDrawingName) {
1299
- await this._processVmlDrawingEntry(stream, model, vmlDrawingName);
1301
+ await this._processVmlDrawingEntry(stream, model, entryName);
1300
1302
  return true;
1301
1303
  }
1302
1304
  // VML header/footer drawings (watermark in header mode).
@@ -1306,14 +1308,13 @@ class XLSX {
1306
1308
  await this._processVmlDrawingHFEntry(stream, model, vmlHFName);
1307
1309
  return true;
1308
1310
  }
1309
- const commentsIndex = getCommentsIndexFromPath(entryName);
1310
- if (commentsIndex) {
1311
- await this._processCommentEntry(stream, model, `comments${commentsIndex}`);
1311
+ if (isCommentsPath(entryName)) {
1312
+ await this._processCommentEntry(stream, model, entryName);
1312
1313
  return true;
1313
1314
  }
1314
1315
  const tableName = getTableNameFromPath(entryName);
1315
1316
  if (tableName) {
1316
- await this._processTableEntry(stream, model, tableName);
1317
+ await this._processTableEntry(stream, model, entryName);
1317
1318
  return true;
1318
1319
  }
1319
1320
  const themeName = getThemeNameFromPath(entryName);
package/dist/cjs/index.js CHANGED
@@ -17,8 +17,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
17
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.pdf = exports.uint8ArrayToString = exports.stringToUint8Array = exports.toUint8Array = exports.concatUint8Arrays = exports.getRootCause = exports.getErrorChain = exports.errorToJSON = exports.toError = exports.BaseError = exports.getSupportedFormats = exports.DateFormatter = exports.DateParser = exports.xmlDecode = exports.xmlEncode = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.excelToDate = exports.dateToExcel = exports.isDateDisplayFormat = exports.formatCellValue = exports.getCellDisplayText = exports.encodeRange = exports.decodeRange = exports.encodeCell = exports.decodeCell = exports.encodeRow = exports.decodeRow = exports.encodeCol = exports.decodeCol = exports.DefinedNames = exports.createCsvFormatterStream = exports.createCsvParserStream = exports.CsvFormatterStream = exports.CsvParserStream = exports.createTextWatermarkImage = exports.WorksheetReader = exports.WorksheetWriter = exports.WorkbookReader = exports.WorkbookWriter = exports.FormCheckbox = exports.DataValidations = exports.Table = exports.Image = exports.Range = exports.Cell = exports.Column = exports.Row = exports.Worksheet = exports.Workbook = void 0;
21
- exports.MarkdownParseError = exports.MarkdownError = exports.MaxItemsExceededError = exports.ImageError = exports.TableError = exports.PivotTableError = exports.WorksheetNameError = exports.XmlParseError = exports.InvalidValueTypeError = exports.MergeConflictError = exports.RowOutOfBoundsError = exports.ColumnOutOfBoundsError = exports.InvalidAddressError = exports.ExcelStreamStateError = exports.ExcelNotSupportedError = exports.ExcelDownloadError = exports.ExcelFileError = exports.isExcelError = exports.ExcelError = exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = exports.PdfRenderError = exports.PdfError = exports.PageSizes = exports.excelToPdf = void 0;
20
+ exports.uint8ArrayToString = exports.stringToUint8Array = exports.toUint8Array = exports.concatUint8Arrays = exports.getRootCause = exports.getErrorChain = exports.errorToJSON = exports.toError = exports.BaseError = exports.getSupportedFormats = exports.DateFormatter = exports.DateParser = exports.xmlDecode = exports.xmlEncode = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.excelToDate = exports.dateToExcel = exports.isDateDisplayFormat = exports.formatCellValue = exports.getCellDisplayText = exports.encodeRange = exports.decodeRange = exports.encodeCell = exports.decodeCell = exports.encodeRow = exports.decodeRow = exports.encodeCol = exports.decodeCol = exports.DefinedNames = exports.createCsvFormatterStream = exports.createCsvParserStream = exports.CsvFormatterStream = exports.CsvParserStream = exports.createTextWatermarkImage = exports.WorksheetReader = exports.WorksheetWriter = exports.WorkbookReader = exports.WorkbookWriter = exports.FormCheckbox = exports.DataValidations = exports.Note = exports.Table = exports.Image = exports.Range = exports.Cell = exports.Column = exports.Row = exports.Worksheet = exports.Workbook = void 0;
21
+ exports.MarkdownParseError = exports.MarkdownError = exports.MaxItemsExceededError = exports.ImageError = exports.TableError = exports.PivotTableError = exports.WorksheetNameError = exports.XmlParseError = exports.InvalidValueTypeError = exports.MergeConflictError = exports.RowOutOfBoundsError = exports.ColumnOutOfBoundsError = exports.InvalidAddressError = exports.ExcelStreamStateError = exports.ExcelNotSupportedError = exports.ExcelDownloadError = exports.ExcelFileError = exports.isExcelError = exports.ExcelError = exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = exports.PdfRenderError = exports.PdfError = exports.PageSizes = exports.excelToPdf = exports.pdf = void 0;
22
22
  var workbook_1 = require("./modules/excel/workbook.js");
23
23
  Object.defineProperty(exports, "Workbook", { enumerable: true, get: function () { return workbook_1.Workbook; } });
24
24
  var worksheet_1 = require("./modules/excel/worksheet.js");
@@ -36,6 +36,8 @@ Object.defineProperty(exports, "Image", { enumerable: true, get: function () { r
36
36
  __exportStar(require("./modules/excel/anchor.js"), exports);
37
37
  var table_1 = require("./modules/excel/table.js");
38
38
  Object.defineProperty(exports, "Table", { enumerable: true, get: function () { return table_1.Table; } });
39
+ var note_1 = require("./modules/excel/note.js");
40
+ Object.defineProperty(exports, "Note", { enumerable: true, get: function () { return note_1.Note; } });
39
41
  var data_validations_1 = require("./modules/excel/data-validations.js");
40
42
  Object.defineProperty(exports, "DataValidations", { enumerable: true, get: function () { return data_validations_1.DataValidations; } });
41
43
  var form_control_1 = require("./modules/excel/form-control.js");
@@ -3,8 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Note = void 0;
4
4
  const under_dash_1 = require("./utils/under-dash.js");
5
5
  class Note {
6
- constructor(note) {
6
+ constructor(note, author) {
7
7
  this.note = note;
8
+ this.author = author;
8
9
  }
9
10
  get model() {
10
11
  let value;
@@ -29,7 +30,11 @@ class Note {
29
30
  break;
30
31
  }
31
32
  // Suitable for all cell comments
32
- return (0, under_dash_1.deepMerge)({}, Note.DEFAULT_CONFIGS, value);
33
+ const result = (0, under_dash_1.deepMerge)({}, Note.DEFAULT_CONFIGS, value);
34
+ if (this.author !== undefined) {
35
+ result.author = this.author;
36
+ }
37
+ return result;
33
38
  }
34
39
  set model(value) {
35
40
  const { note } = value;
@@ -40,6 +45,7 @@ class Note {
40
45
  else {
41
46
  this.note = note;
42
47
  }
48
+ this.author = value.author;
43
49
  }
44
50
  static fromModel(model) {
45
51
  const note = new Note();
@@ -13,7 +13,7 @@ exports.getDrawingNameFromPath = getDrawingNameFromPath;
13
13
  exports.getDrawingNameFromRelsPath = getDrawingNameFromRelsPath;
14
14
  exports.getVmlDrawingNameFromPath = getVmlDrawingNameFromPath;
15
15
  exports.getVmlDrawingHFNameFromPath = getVmlDrawingHFNameFromPath;
16
- exports.getCommentsIndexFromPath = getCommentsIndexFromPath;
16
+ exports.isCommentsPath = isCommentsPath;
17
17
  exports.getTableNameFromPath = getTableNameFromPath;
18
18
  exports.getPivotTableNameFromPath = getPivotTableNameFromPath;
19
19
  exports.getPivotTableNameFromRelsPath = getPivotTableNameFromRelsPath;
@@ -41,6 +41,7 @@ exports.pivotCacheDefinitionRelsPath = pivotCacheDefinitionRelsPath;
41
41
  exports.pivotCacheRecordsPath = pivotCacheRecordsPath;
42
42
  exports.pivotCacheRecordsRelTarget = pivotCacheRecordsRelTarget;
43
43
  exports.pivotTablePath = pivotTablePath;
44
+ exports.pivotTablePathFromName = pivotTablePathFromName;
44
45
  exports.pivotTableRelsPath = pivotTableRelsPath;
45
46
  exports.externalLinkPath = externalLinkPath;
46
47
  exports.externalLinkRelsPath = externalLinkRelsPath;
@@ -51,15 +52,12 @@ exports.commentsRelTargetFromWorksheet = commentsRelTargetFromWorksheet;
51
52
  exports.vmlDrawingRelTargetFromWorksheet = vmlDrawingRelTargetFromWorksheet;
52
53
  exports.vmlDrawingHFRelTargetFromWorksheet = vmlDrawingHFRelTargetFromWorksheet;
53
54
  exports.drawingRelTargetFromWorksheet = drawingRelTargetFromWorksheet;
54
- exports.vmlDrawingRelTargetFromWorksheetName = vmlDrawingRelTargetFromWorksheetName;
55
- exports.commentsRelTargetFromWorksheetName = commentsRelTargetFromWorksheetName;
56
55
  exports.pivotTableRelTargetFromWorksheet = pivotTableRelTargetFromWorksheet;
57
- exports.pivotTableRelTargetFromWorksheetName = pivotTableRelTargetFromWorksheetName;
58
56
  exports.tableRelTargetFromWorksheet = tableRelTargetFromWorksheet;
59
- exports.tableRelTargetFromWorksheetName = tableRelTargetFromWorksheetName;
60
57
  exports.mediaRelTargetFromRels = mediaRelTargetFromRels;
61
58
  exports.ctrlPropPath = ctrlPropPath;
62
59
  exports.ctrlPropRelTargetFromWorksheet = ctrlPropRelTargetFromWorksheet;
60
+ exports.resolveRelTarget = resolveRelTarget;
63
61
  exports.OOXML_PATHS = {
64
62
  contentTypes: "[Content_Types].xml",
65
63
  rootRels: "_rels/.rels",
@@ -81,7 +79,9 @@ const drawingXmlRegex = /^xl\/drawings\/(drawing\d+)[.]xml$/;
81
79
  const drawingRelsXmlRegex = /^xl\/drawings\/_rels\/(drawing\d+)[.]xml[.]rels$/;
82
80
  const vmlDrawingRegex = /^xl\/drawings\/(vmlDrawing\d+)[.]vml$/;
83
81
  const vmlDrawingHFRegex = /^xl\/drawings\/(vmlDrawingHF\d+)[.]vml$/;
84
- const commentsXmlRegex = /^xl\/comments(\d+)[.]xml$/;
82
+ // Matches both flat layout (xl/comments1.xml) and subdirectory layout (xl/comments/comment1.xml).
83
+ // Both are valid OOXML — the actual path is determined by .rels, not by convention.
84
+ const commentsXmlRegex = /^xl\/(?:comments(\d+)|comments\/comment(\d+))[.]xml$/;
85
85
  const tableXmlRegex = /^xl\/tables\/(table\d+)[.]xml$/;
86
86
  const pivotTableXmlRegex = /^xl\/pivotTables\/(pivotTable\d+)[.]xml$/;
87
87
  const pivotTableRelsXmlRegex = /^xl\/pivotTables\/_rels\/(pivotTable\d+)[.]xml[.]rels$/;
@@ -143,9 +143,12 @@ function getVmlDrawingHFNameFromPath(path) {
143
143
  const match = vmlDrawingHFRegex.exec(path);
144
144
  return match ? match[1] : undefined;
145
145
  }
146
- function getCommentsIndexFromPath(path) {
147
- const match = commentsXmlRegex.exec(path);
148
- return match ? match[1] : undefined;
146
+ /**
147
+ * Check if a zip entry path is a comments XML file.
148
+ * Works for both `xl/comments1.xml` and `xl/comments/comment1.xml`.
149
+ */
150
+ function isCommentsPath(path) {
151
+ return commentsXmlRegex.test(path);
149
152
  }
150
153
  function getTableNameFromPath(path) {
151
154
  const match = tableXmlRegex.exec(path);
@@ -246,6 +249,9 @@ function pivotCacheRecordsRelTarget(n) {
246
249
  function pivotTablePath(n) {
247
250
  return `xl/pivotTables/pivotTable${n}.xml`;
248
251
  }
252
+ function pivotTablePathFromName(name) {
253
+ return `xl/pivotTables/${name}.xml`;
254
+ }
249
255
  function pivotTableRelsPath(n) {
250
256
  return `xl/pivotTables/_rels/pivotTable${n}.xml.rels`;
251
257
  }
@@ -294,29 +300,14 @@ function drawingRelTargetFromWorksheet(drawingName) {
294
300
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
295
301
  return `../drawings/${drawingName}.xml`;
296
302
  }
297
- function vmlDrawingRelTargetFromWorksheetName(vmlName) {
298
- // For VML drawings when the caller already has the logical name (e.g. "vmlDrawing1").
299
- return `../drawings/${vmlName}.vml`;
300
- }
301
- function commentsRelTargetFromWorksheetName(commentName) {
302
- // For comments when the caller already has the logical name (e.g. "comments1").
303
- return `../${commentName}.xml`;
304
- }
305
303
  function pivotTableRelTargetFromWorksheet(n) {
306
304
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
307
305
  return `../pivotTables/pivotTable${n}.xml`;
308
306
  }
309
- function pivotTableRelTargetFromWorksheetName(pivotName) {
310
- // For pivot tables when the caller already has the logical name (e.g. "pivotTable1").
311
- return `../pivotTables/${pivotName}.xml`;
312
- }
313
307
  function tableRelTargetFromWorksheet(target) {
314
308
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
315
309
  return `../tables/${target}`;
316
310
  }
317
- function tableRelTargetFromWorksheetName(name) {
318
- return `../tables/${name}.xml`;
319
- }
320
311
  function mediaRelTargetFromRels(filename) {
321
312
  // Target from a rels file located under xl/*/_rels (base is one level deeper than xl/)
322
313
  return `../media/${filename}`;
@@ -329,3 +320,37 @@ function ctrlPropRelTargetFromWorksheet(id) {
329
320
  // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
330
321
  return `../ctrlProps/ctrlProp${id}.xml`;
331
322
  }
323
+ /**
324
+ * Resolve a relationship Target (relative or absolute) to a normalized zip path.
325
+ *
326
+ * OOXML relationship targets may be:
327
+ * - Relative: `../comments1.xml` (resolved against `baseDir`)
328
+ * - Absolute: `/xl/comments/comment1.xml` (leading slash stripped)
329
+ *
330
+ * @param baseDir The directory containing the source part (e.g. `xl/worksheets/`)
331
+ * @param target The raw Target value from the .rels file
332
+ */
333
+ function resolveRelTarget(baseDir, target) {
334
+ // Absolute target — strip leading slash to get the zip path.
335
+ if (target.startsWith("/")) {
336
+ return target.slice(1);
337
+ }
338
+ // Ensure baseDir ends with "/" so the join works correctly.
339
+ const base = baseDir.endsWith("/") ? baseDir : baseDir + "/";
340
+ // Relative target — resolve against baseDir.
341
+ // Simple resolution: join base + target, then resolve `.` and `..` segments.
342
+ const parts = (base + target).split("/");
343
+ const resolved = [];
344
+ for (const part of parts) {
345
+ if (part === "." || part === "") {
346
+ continue;
347
+ }
348
+ else if (part === "..") {
349
+ resolved.pop();
350
+ }
351
+ else {
352
+ resolved.push(part);
353
+ }
354
+ }
355
+ return resolved.join("/");
356
+ }
@@ -21,7 +21,7 @@ class CommentXform extends base_xform_1.BaseXform {
21
21
  const renderModel = model || this.model;
22
22
  xmlStream.openNode("comment", {
23
23
  ref: renderModel.ref,
24
- authorId: 0
24
+ authorId: renderModel.authorId ?? 0
25
25
  });
26
26
  xmlStream.openNode("text");
27
27
  if (renderModel && renderModel.note && renderModel.note.texts) {
@@ -44,7 +44,8 @@ class CommentXform extends base_xform_1.BaseXform {
44
44
  note: {
45
45
  texts: []
46
46
  },
47
- ...node.attributes
47
+ ref: node.attributes.ref,
48
+ authorId: node.attributes.authorId != null ? Number(node.attributes.authorId) : undefined
48
49
  };
49
50
  return true;
50
51
  case "r":
@@ -4,9 +4,18 @@ exports.CommentsXform = void 0;
4
4
  const base_xform_1 = require("../base-xform.js");
5
5
  const comment_xform_1 = require("./comment-xform.js");
6
6
  const writer_1 = require("../../../../xml/writer.js");
7
+ const DEFAULT_AUTHOR = "Author";
7
8
  class CommentsXform extends base_xform_1.BaseXform {
8
9
  constructor() {
9
10
  super();
11
+ /** Authors collected while parsing the <authors> element. */
12
+ this._authors = [];
13
+ /** Whether we are currently inside the <authors> element. */
14
+ this._inAuthors = false;
15
+ /** Whether we are currently inside an <author> element (collecting text). */
16
+ this._inAuthor = false;
17
+ /** Accumulator for the current <author> text content. */
18
+ this._currentAuthor = "";
10
19
  this.map = {
11
20
  comment: new comment_xform_1.CommentXform()
12
21
  };
@@ -16,15 +25,23 @@ class CommentsXform extends base_xform_1.BaseXform {
16
25
  const renderModel = model || this.model;
17
26
  xmlStream.openXml(writer_1.StdDocAttributes);
18
27
  xmlStream.openNode("comments", CommentsXform.COMMENTS_ATTRIBUTES);
19
- // authors
20
- // TODO: support authors properly
28
+ // Collect unique authors from comments
29
+ const authorSet = new Set();
30
+ for (const comment of renderModel.comments) {
31
+ authorSet.add(comment.author ?? DEFAULT_AUTHOR);
32
+ }
33
+ const authors = [...authorSet];
21
34
  xmlStream.openNode("authors");
22
- xmlStream.leafNode("author", null, "Author");
35
+ for (const author of authors) {
36
+ xmlStream.leafNode("author", null, author);
37
+ }
23
38
  xmlStream.closeNode();
24
39
  // comments
25
40
  xmlStream.openNode("commentList");
26
41
  renderModel.comments.forEach(comment => {
27
- this.map.comment.render(xmlStream, comment);
42
+ // Set the authorId based on the authors list for rendering
43
+ const authorId = authors.indexOf(comment.author ?? DEFAULT_AUTHOR);
44
+ this.map.comment.render(xmlStream, { ...comment, authorId: authorId >= 0 ? authorId : 0 });
28
45
  });
29
46
  xmlStream.closeNode();
30
47
  xmlStream.closeNode();
@@ -35,6 +52,16 @@ class CommentsXform extends base_xform_1.BaseXform {
35
52
  return true;
36
53
  }
37
54
  switch (node.name) {
55
+ case "authors":
56
+ this._inAuthors = true;
57
+ this._authors = [];
58
+ return true;
59
+ case "author":
60
+ if (this._inAuthors) {
61
+ this._inAuthor = true;
62
+ this._currentAuthor = "";
63
+ }
64
+ return true;
38
65
  case "commentList":
39
66
  this.model = {
40
67
  comments: []
@@ -49,13 +76,33 @@ class CommentsXform extends base_xform_1.BaseXform {
49
76
  }
50
77
  }
51
78
  parseText(text) {
52
- if (this.parser) {
79
+ if (this._inAuthor) {
80
+ this._currentAuthor += text;
81
+ }
82
+ else if (this.parser) {
53
83
  this.parser.parseText(text);
54
84
  }
55
85
  }
56
86
  parseClose(name) {
57
87
  switch (name) {
88
+ case "authors":
89
+ this._inAuthors = false;
90
+ return true;
91
+ case "author":
92
+ if (this._inAuthors) {
93
+ this._authors.push(this._currentAuthor);
94
+ this._inAuthor = false;
95
+ this._currentAuthor = "";
96
+ }
97
+ return true;
58
98
  case "commentList":
99
+ // Resolve authorId → author name on each comment
100
+ for (const comment of this.model.comments) {
101
+ const { authorId } = comment;
102
+ if (authorId != null && authorId >= 0 && authorId < this._authors.length) {
103
+ comment.author = this._authors[authorId];
104
+ }
105
+ }
59
106
  return false;
60
107
  case "comment":
61
108
  this.model.comments.push(this.parser.model);
@@ -9,6 +9,8 @@ const POSITION_TYPE = ["twoCells", "oneCells", "absolute"];
9
9
  class VmlClientDataXform extends base_xform_1.BaseXform {
10
10
  constructor() {
11
11
  super();
12
+ /** Accumulated text for the current leaf element. */
13
+ this._leafText = "";
12
14
  this.map = {
13
15
  "x:Anchor": new vml_anchor_xform_1.VmlAnchorXform(),
14
16
  "x:Locked": new vml_protection_xform_1.VmlProtectionXform({ tag: "x:Locked" }),
@@ -44,6 +46,11 @@ class VmlClientDataXform extends base_xform_1.BaseXform {
44
46
  editAs: ""
45
47
  };
46
48
  break;
49
+ case "x:Row":
50
+ case "x:Column":
51
+ this._leafName = node.name;
52
+ this._leafText = "";
53
+ break;
47
54
  default:
48
55
  this.parser = this.map[node.name];
49
56
  if (this.parser) {
@@ -54,11 +61,30 @@ class VmlClientDataXform extends base_xform_1.BaseXform {
54
61
  return true;
55
62
  }
56
63
  parseText(text) {
57
- if (this.parser) {
64
+ if (this._leafName) {
65
+ this._leafText += text;
66
+ }
67
+ else if (this.parser) {
58
68
  this.parser.parseText(text);
59
69
  }
60
70
  }
61
71
  parseClose(name) {
72
+ if (this._leafName) {
73
+ if (name === this._leafName) {
74
+ const value = parseInt(this._leafText, 10);
75
+ if (name === "x:Row") {
76
+ // VML uses 0-based row; convert to 1-based to match comment ref
77
+ this.model.row = value + 1;
78
+ }
79
+ else if (name === "x:Column") {
80
+ // VML uses 0-based col; convert to 1-based to match comment ref
81
+ this.model.col = value + 1;
82
+ }
83
+ this._leafName = undefined;
84
+ this._leafText = "";
85
+ }
86
+ return true;
87
+ }
62
88
  if (this.parser) {
63
89
  if (!this.parser.parseClose(name)) {
64
90
  this.parser = undefined;
@@ -69,6 +69,10 @@ class VmlShapeXform extends base_xform_1.BaseXform {
69
69
  this.map["x:ClientData"].model && this.map["x:ClientData"].model.protection;
70
70
  this.model.anchor = this.map["x:ClientData"].model && this.map["x:ClientData"].model.anchor;
71
71
  this.model.editAs = this.map["x:ClientData"].model && this.map["x:ClientData"].model.editAs;
72
+ if (this.map["x:ClientData"].model) {
73
+ this.model.row = this.map["x:ClientData"].model.row;
74
+ this.model.col = this.map["x:ClientData"].model.col;
75
+ }
72
76
  return false;
73
77
  default:
74
78
  return true;
@@ -703,25 +703,63 @@ class WorkSheetXform extends base_xform_1.BaseXform {
703
703
  reconcile(model, options) {
704
704
  // options.merges = new Merges();
705
705
  // options.merges.reconcile(model.mergeCells, model.rows);
706
- const rels = (model.relationships ?? []).reduce((h, rel) => {
706
+ // Build rel index first, then process comments and VML in two passes so
707
+ // that the result is independent of the order rels appear in the file.
708
+ const relList = model.relationships ?? [];
709
+ const rels = relList.reduce((h, rel) => {
707
710
  h[rel.Id] = rel;
711
+ return h;
712
+ }, {});
713
+ // Pass 1: resolve comments
714
+ for (const rel of relList) {
708
715
  if (rel.Type === rel_type_1.RelType.Comments) {
709
- const commentEntry = options.comments?.[rel.Target];
716
+ const resolvedPath = (0, ooxml_paths_1.resolveRelTarget)("xl/worksheets/", rel.Target);
717
+ const commentEntry = options.comments?.[resolvedPath];
710
718
  if (commentEntry) {
711
719
  model.comments = commentEntry.comments;
712
720
  }
713
721
  }
714
- if (rel.Type === rel_type_1.RelType.VmlDrawing && model.comments && model.comments.length) {
715
- const vmlEntry = options.vmlDrawings?.[rel.Target];
716
- if (vmlEntry) {
717
- const vmlComment = vmlEntry.comments;
718
- model.comments.forEach((comment, index) => {
719
- comment.note = Object.assign({}, comment.note, vmlComment[index]);
720
- });
722
+ }
723
+ // Pass 2: merge VML note metadata (requires model.comments from pass 1)
724
+ if (model.comments && model.comments.length) {
725
+ for (const rel of relList) {
726
+ if (rel.Type === rel_type_1.RelType.VmlDrawing) {
727
+ const resolvedVmlPath = (0, ooxml_paths_1.resolveRelTarget)("xl/worksheets/", rel.Target);
728
+ const vmlEntry = options.vmlDrawings?.[resolvedVmlPath];
729
+ if (vmlEntry) {
730
+ // Build a ref-keyed map from VML comments for order-independent merge.
731
+ // Fall back to index-based merge if VML entries lack row/col.
732
+ const vmlComments = vmlEntry.comments;
733
+ const vmlByRef = {};
734
+ let hasRefInfo = false;
735
+ for (const vc of vmlComments) {
736
+ if (vc.row != null && vc.col != null) {
737
+ const ref = col_cache_1.colCache.encodeAddress(vc.row, vc.col);
738
+ vmlByRef[ref] = vc;
739
+ hasRefInfo = true;
740
+ }
741
+ }
742
+ if (hasRefInfo) {
743
+ // Merge by cell reference (robust against order differences)
744
+ for (const comment of model.comments) {
745
+ const vml = vmlByRef[comment.ref];
746
+ if (vml) {
747
+ comment.note = Object.assign({}, comment.note, vml);
748
+ }
749
+ }
750
+ }
751
+ else {
752
+ // Fallback: index-based merge for VML files without row/col
753
+ model.comments.forEach((comment, index) => {
754
+ if (index < vmlComments.length) {
755
+ comment.note = Object.assign({}, comment.note, vmlComments[index]);
756
+ }
757
+ });
758
+ }
759
+ }
721
760
  }
722
761
  }
723
- return h;
724
- }, {});
762
+ }
725
763
  options.commentsMap = (model.comments ?? []).reduce((h, comment) => {
726
764
  if (comment.ref) {
727
765
  h[comment.ref] = comment;
@@ -833,7 +871,8 @@ class WorkSheetXform extends base_xform_1.BaseXform {
833
871
  model.tables = (model.tables ?? []).reduce((acc, tablePart) => {
834
872
  const rel = rels[tablePart.rId];
835
873
  if (rel) {
836
- const table = options.tables[rel.Target];
874
+ const resolvedPath = (0, ooxml_paths_1.resolveRelTarget)("xl/worksheets/", rel.Target);
875
+ const table = options.tables[resolvedPath];
837
876
  if (table) {
838
877
  acc.push(table);
839
878
  }
@@ -845,7 +884,8 @@ class WorkSheetXform extends base_xform_1.BaseXform {
845
884
  model.pivotTables = [];
846
885
  (model.relationships ?? []).forEach(rel => {
847
886
  if (rel.Type === rel_type_1.RelType.PivotTable && options.pivotTables) {
848
- const pivotTable = options.pivotTables[rel.Target];
887
+ const resolvedPath = (0, ooxml_paths_1.resolveRelTarget)("xl/worksheets/", rel.Target);
888
+ const pivotTable = options.pivotTables[resolvedPath];
849
889
  if (pivotTable) {
850
890
  model.pivotTables.push(pivotTable);
851
891
  }
@@ -1010,9 +1010,8 @@ class XLSX {
1010
1010
  applyWidthHeightFormats: pt.applyWidthHeightFormats === "1" ? "1" : "0"
1011
1011
  };
1012
1012
  loadedPivotTables.push(completePivotTable);
1013
- // Key format (e.g., "../pivotTables/pivotTable1.xml") matches worksheet .rels Target values,
1014
- // allowing worksheet reconciliation to look up pivot tables by their relationship target path.
1015
- pivotTablesIndexed[(0, ooxml_paths_1.pivotTableRelTargetFromWorksheetName)(pivotName)] = completePivotTable;
1013
+ // Key by absolute zip path so reconcile can match any rel target layout.
1014
+ pivotTablesIndexed[(0, ooxml_paths_1.pivotTablePathFromName)(pivotName)] = completePivotTable;
1016
1015
  });
1017
1016
  loadedPivotTables.sort((a, b) => a.tableNumber - b.tableNumber);
1018
1017
  model.pivotTables = loadedPivotTables;
@@ -1072,15 +1071,17 @@ class XLSX {
1072
1071
  model.worksheetHash[path] = worksheet;
1073
1072
  model.worksheets.push(worksheet);
1074
1073
  }
1075
- async _processCommentEntry(stream, model, name) {
1074
+ async _processCommentEntry(stream, model, zipPath) {
1076
1075
  const xform = new comments_xform_1.CommentsXform();
1077
1076
  const comments = await xform.parseStream(stream);
1078
- model.comments[(0, ooxml_paths_1.commentsRelTargetFromWorksheetName)(name)] = comments;
1077
+ // Key by absolute zip path so reconcile can match any rel target layout.
1078
+ model.comments[zipPath] = comments;
1079
1079
  }
1080
- async _processTableEntry(stream, model, name) {
1080
+ async _processTableEntry(stream, model, zipPath) {
1081
1081
  const xform = new table_xform_1.TableXform();
1082
1082
  const table = await xform.parseStream(stream);
1083
- model.tables[(0, ooxml_paths_1.tableRelTargetFromWorksheetName)(name)] = table;
1083
+ // Key by absolute zip path so reconcile can match any rel target layout.
1084
+ model.tables[zipPath] = table;
1084
1085
  }
1085
1086
  async _processWorksheetRelsEntry(stream, model, sheetNo) {
1086
1087
  const xform = new relationships_xform_1.RelationshipsXform();
@@ -1149,10 +1150,11 @@ class XLSX {
1149
1150
  const relationships = await xform.parseStream(entry);
1150
1151
  model.drawingRels[name] = relationships;
1151
1152
  }
1152
- async _processVmlDrawingEntry(entry, model, name) {
1153
+ async _processVmlDrawingEntry(entry, model, zipPath) {
1153
1154
  const xform = new vml_drawing_xform_1.VmlDrawingXform();
1154
1155
  const vmlDrawing = await xform.parseStream(entry);
1155
- model.vmlDrawings[(0, ooxml_paths_1.vmlDrawingRelTargetFromWorksheetName)(name)] = vmlDrawing;
1156
+ // Key by absolute zip path so reconcile can match any rel target layout.
1157
+ model.vmlDrawings[zipPath] = vmlDrawing;
1156
1158
  }
1157
1159
  async _processVmlDrawingHFEntry(entry, model, _name) {
1158
1160
  const xform = new vml_drawing_xform_1.VmlDrawingXform();
@@ -1299,7 +1301,7 @@ class XLSX {
1299
1301
  }
1300
1302
  const vmlDrawingName = (0, ooxml_paths_1.getVmlDrawingNameFromPath)(entryName);
1301
1303
  if (vmlDrawingName) {
1302
- await this._processVmlDrawingEntry(stream, model, vmlDrawingName);
1304
+ await this._processVmlDrawingEntry(stream, model, entryName);
1303
1305
  return true;
1304
1306
  }
1305
1307
  // VML header/footer drawings (watermark in header mode).
@@ -1309,14 +1311,13 @@ class XLSX {
1309
1311
  await this._processVmlDrawingHFEntry(stream, model, vmlHFName);
1310
1312
  return true;
1311
1313
  }
1312
- const commentsIndex = (0, ooxml_paths_1.getCommentsIndexFromPath)(entryName);
1313
- if (commentsIndex) {
1314
- await this._processCommentEntry(stream, model, `comments${commentsIndex}`);
1314
+ if ((0, ooxml_paths_1.isCommentsPath)(entryName)) {
1315
+ await this._processCommentEntry(stream, model, entryName);
1315
1316
  return true;
1316
1317
  }
1317
1318
  const tableName = (0, ooxml_paths_1.getTableNameFromPath)(entryName);
1318
1319
  if (tableName) {
1319
- await this._processTableEntry(stream, model, tableName);
1320
+ await this._processTableEntry(stream, model, entryName);
1320
1321
  return true;
1321
1322
  }
1322
1323
  const themeName = (0, ooxml_paths_1.getThemeNameFromPath)(entryName);
@@ -14,6 +14,7 @@ export { Range } from "./modules/excel/range.js";
14
14
  export { Image } from "./modules/excel/image.js";
15
15
  export * from "./modules/excel/anchor.js";
16
16
  export { Table } from "./modules/excel/table.js";
17
+ export { Note } from "./modules/excel/note.js";
17
18
  export { DataValidations } from "./modules/excel/data-validations.js";
18
19
  export { FormCheckbox } from "./modules/excel/form-control.js";
19
20
  // Note: the formula engine lives at the `./formula` subpath so it stays