@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.
- package/dist/browser/index.browser.d.ts +1 -0
- package/dist/browser/index.browser.js +1 -0
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/modules/excel/cell.d.ts +1 -0
- package/dist/browser/modules/excel/note.d.ts +3 -23
- package/dist/browser/modules/excel/note.js +8 -2
- package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +17 -5
- package/dist/browser/modules/excel/utils/ooxml-paths.js +46 -19
- package/dist/browser/modules/excel/xlsx/xform/comment/comment-xform.js +3 -2
- package/dist/browser/modules/excel/xlsx/xform/comment/comments-xform.d.ts +8 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/comments-xform.js +52 -5
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +6 -0
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-client-data-xform.js +27 -1
- package/dist/browser/modules/excel/xlsx/xform/comment/vml-shape-xform.js +4 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +54 -14
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +3 -3
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +16 -15
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/modules/excel/note.js +8 -2
- package/dist/cjs/modules/excel/utils/ooxml-paths.js +49 -24
- package/dist/cjs/modules/excel/xlsx/xform/comment/comment-xform.js +3 -2
- package/dist/cjs/modules/excel/xlsx/xform/comment/comments-xform.js +52 -5
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-client-data-xform.js +27 -1
- package/dist/cjs/modules/excel/xlsx/xform/comment/vml-shape-xform.js +4 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +53 -13
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +15 -14
- package/dist/esm/index.browser.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/modules/excel/note.js +8 -2
- package/dist/esm/modules/excel/utils/ooxml-paths.js +46 -19
- package/dist/esm/modules/excel/xlsx/xform/comment/comment-xform.js +3 -2
- package/dist/esm/modules/excel/xlsx/xform/comment/comments-xform.js +52 -5
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-client-data-xform.js +27 -1
- package/dist/esm/modules/excel/xlsx/xform/comment/vml-shape-xform.js +4 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +54 -14
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +16 -15
- package/dist/iife/excelts.iife.js +149 -56
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +30 -30
- package/dist/types/index.browser.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/modules/excel/cell.d.ts +1 -0
- package/dist/types/modules/excel/note.d.ts +3 -23
- package/dist/types/modules/excel/utils/ooxml-paths.d.ts +17 -5
- package/dist/types/modules/excel/xlsx/xform/comment/comments-xform.d.ts +8 -0
- package/dist/types/modules/excel/xlsx/xform/comment/vml-client-data-xform.d.ts +6 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +3 -3
- 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,
|
|
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
|
|
1011
|
-
|
|
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,
|
|
1071
|
+
async _processCommentEntry(stream, model, zipPath) {
|
|
1073
1072
|
const xform = new CommentsXform();
|
|
1074
1073
|
const comments = await xform.parseStream(stream);
|
|
1075
|
-
|
|
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,
|
|
1077
|
+
async _processTableEntry(stream, model, zipPath) {
|
|
1078
1078
|
const xform = new TableXform();
|
|
1079
1079
|
const table = await xform.parseStream(stream);
|
|
1080
|
-
|
|
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,
|
|
1150
|
+
async _processVmlDrawingEntry(entry, model, zipPath) {
|
|
1150
1151
|
const xform = new VmlDrawingXform();
|
|
1151
1152
|
const vmlDrawing = await xform.parseStream(entry);
|
|
1152
|
-
|
|
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,
|
|
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
|
-
|
|
1310
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1014
|
-
|
|
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,
|
|
1074
|
+
async _processCommentEntry(stream, model, zipPath) {
|
|
1076
1075
|
const xform = new comments_xform_1.CommentsXform();
|
|
1077
1076
|
const comments = await xform.parseStream(stream);
|
|
1078
|
-
|
|
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,
|
|
1080
|
+
async _processTableEntry(stream, model, zipPath) {
|
|
1081
1081
|
const xform = new table_xform_1.TableXform();
|
|
1082
1082
|
const table = await xform.parseStream(stream);
|
|
1083
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1313
|
-
|
|
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,
|
|
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
|