@microsoft/connected-workbooks 3.3.1-beta → 3.4.0-beta

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 (40) hide show
  1. package/README.md +3 -3
  2. package/dist/types.d.ts +2 -1
  3. package/dist/utils/constants.js +94 -47
  4. package/dist/utils/documentUtils.js +2 -2
  5. package/dist/utils/gridUtils.js +4 -4
  6. package/dist/utils/mashupDocumentParser.js +1 -1
  7. package/dist/utils/pqUtils.js +3 -3
  8. package/dist/utils/tableUtils.js +11 -4
  9. package/dist/utils/xmlInnerPartsUtils.js +294 -31
  10. package/dist/utils/xmlPartsUtils.js +93 -19
  11. package/dist/workbookManager.js +3 -3
  12. package/dist/workbookTemplate.js +2 -1
  13. package/package.json +5 -3
  14. package/dist/src/generators.js +0 -15
  15. package/dist/src/types.js +0 -28
  16. package/dist/src/utils/arrayUtils.js +0 -51
  17. package/dist/src/utils/constants.js +0 -166
  18. package/dist/src/utils/documentUtils.js +0 -167
  19. package/dist/src/utils/gridUtils.js +0 -103
  20. package/dist/src/utils/htmlUtils.js +0 -19
  21. package/dist/src/utils/index.js +0 -24
  22. package/dist/src/utils/mashupDocumentParser.js +0 -188
  23. package/dist/src/utils/pqUtils.js +0 -194
  24. package/dist/src/utils/tableUtils.js +0 -233
  25. package/dist/src/utils/xmlInnerPartsUtils.js +0 -400
  26. package/dist/src/utils/xmlPartsUtils.js +0 -174
  27. package/dist/src/workbookTemplate.js +0 -8
  28. package/dist/tests/arrayUtils.test.js +0 -66
  29. package/dist/tests/documentUtils.test.js +0 -70
  30. package/dist/tests/gridUtils.test.js +0 -214
  31. package/dist/tests/htmlUtils.test.js +0 -111
  32. package/dist/tests/mashupDocumentParser.test.js +0 -113
  33. package/dist/tests/mocks/PqMock.js +0 -7
  34. package/dist/tests/mocks/index.js +0 -24
  35. package/dist/tests/mocks/section1mSimpleQueryMock.js +0 -8
  36. package/dist/tests/mocks/xmlMocks.js +0 -14
  37. package/dist/tests/tableUtils.test.js +0 -70
  38. package/dist/tests/workbookQueryTemplate.test.js +0 -218
  39. package/dist/tests/workbookTableTemplate.test.js +0 -126
  40. package/dist/tests/xmlInnerPartsUtils.test.js +0 -133
@@ -45,6 +45,23 @@ var types_1 = require("../types");
45
45
  var constants_1 = require("./constants");
46
46
  var documentUtils_1 = __importDefault(require("./documentUtils"));
47
47
  var xmldom_qsa_1 = require("xmldom-qsa");
48
+ /**
49
+ * Helper function to check for XML parser errors without using querySelector
50
+ * @param doc - The parsed XML document
51
+ * @param context - Context string for error message
52
+ * @throws {Error} If parser error is detected
53
+ */
54
+ var checkParserError = function (doc, context) {
55
+ if (!doc || !doc.documentElement) {
56
+ throw new Error("".concat(context, ": ").concat(constants_1.Errors.xmlParse));
57
+ }
58
+ // Check for parsererror elements using getElementsByTagName
59
+ var errorElements = doc.getElementsByTagName("parsererror");
60
+ if (errorElements && errorElements.length > 0) {
61
+ var errorText = errorElements[0].textContent || "Unknown parser error";
62
+ throw new Error("".concat(context, ": ").concat(errorText));
63
+ }
64
+ };
48
65
  var updateDocProps = function (zip, docProps) {
49
66
  if (docProps === void 0) { docProps = {}; }
50
67
  return __awaiter(void 0, void 0, void 0, function () {
@@ -76,33 +93,61 @@ var updateDocProps = function (zip, docProps) {
76
93
  });
77
94
  });
78
95
  };
96
+ var removeLabelInfoRelationship = function (doc, relationships) {
97
+ // Find and remove LabelInfo.xml relationship
98
+ var relationshipElements = doc.getElementsByTagName(constants_1.element.relationship);
99
+ for (var i = 0; i < relationshipElements.length; i++) {
100
+ var rel = relationshipElements[i];
101
+ if (rel.getAttribute(constants_1.elementAttributes.target) === constants_1.labelInfoXmlPath) {
102
+ relationships.removeChild(rel);
103
+ break;
104
+ }
105
+ }
106
+ };
107
+ var updateRelationshipIds = function (doc) {
108
+ // Update relationship IDs
109
+ var relationshipElements = doc.getElementsByTagName(constants_1.element.relationship);
110
+ for (var i = 0; i < relationshipElements.length; i++) {
111
+ var rel = relationshipElements[i];
112
+ var target = rel.getAttribute(constants_1.elementAttributes.target);
113
+ if (target === constants_1.workbookXmlPath) {
114
+ rel.setAttribute(constants_1.elementAttributes.Id, constants_1.elementAttributes.relationId1);
115
+ }
116
+ else if (target === constants_1.docPropsCoreXmlPath) {
117
+ rel.setAttribute(constants_1.elementAttributes.Id, constants_1.elementAttributes.relationId2);
118
+ }
119
+ else if (target === constants_1.docPropsAppXmlPath) {
120
+ rel.setAttribute(constants_1.elementAttributes.Id, constants_1.elementAttributes.relationId3);
121
+ }
122
+ }
123
+ };
79
124
  var clearLabelInfo = function (zip) { return __awaiter(void 0, void 0, void 0, function () {
80
- var relsString, parser, doc, relationships, element, serializer, newDoc;
81
- var _a, _b, _c, _d;
82
- return __generator(this, function (_e) {
83
- switch (_e.label) {
125
+ var relsString, parser, doc, relationshipsList, relationships, serializer, newDoc;
126
+ var _a;
127
+ return __generator(this, function (_b) {
128
+ switch (_b.label) {
84
129
  case 0:
85
130
  // remove docMetadata folder that contains only LabelInfo.xml in template file.
86
131
  zip.remove(constants_1.docMetadataXmlPath);
87
132
  return [4 /*yield*/, ((_a = zip.file(constants_1.relsXmlPath)) === null || _a === void 0 ? void 0 : _a.async(constants_1.textResultType))];
88
133
  case 1:
89
- relsString = _e.sent();
134
+ relsString = _b.sent();
90
135
  if (relsString === undefined) {
91
- throw new Error(constants_1.relsNotFoundErr);
136
+ throw new Error(constants_1.Errors.relsNotFound);
92
137
  }
93
138
  parser = new xmldom_qsa_1.DOMParser();
94
139
  doc = parser.parseFromString(relsString, constants_1.xmlTextResultType);
95
- relationships = doc.querySelector("Relationships");
96
- if (relationships === null) {
97
- throw new Error(constants_1.unexpectedErr);
140
+ checkParserError(doc, constants_1.Errors.relsParse);
141
+ relationshipsList = doc.getElementsByTagName(constants_1.element.relationships);
142
+ if (!relationshipsList || relationshipsList.length === 0) {
143
+ throw new Error(constants_1.Errors.relationship);
98
144
  }
99
- element = relationships.querySelector('Relationship[Target="docMetadata/LabelInfo.xml"]');
100
- if (element) {
101
- relationships.removeChild(element);
145
+ relationships = relationshipsList[0];
146
+ if (!relationships) {
147
+ throw new Error(constants_1.Errors.relationship);
102
148
  }
103
- (_b = relationships.querySelector('Relationship[Target="xl/workbook.xml"]')) === null || _b === void 0 ? void 0 : _b.setAttribute("Id", "rId1");
104
- (_c = relationships.querySelector('Relationship[Target="docProps/core.xml"]')) === null || _c === void 0 ? void 0 : _c.setAttribute("Id", "rId2");
105
- (_d = relationships.querySelector('Relationship[Target="docProps/app.xml"]')) === null || _d === void 0 ? void 0 : _d.setAttribute("Id", "rId3");
149
+ removeLabelInfoRelationship(doc, relationships);
150
+ updateRelationshipIds(doc);
106
151
  serializer = new xmldom_qsa_1.XMLSerializer();
107
152
  newDoc = serializer.serializeToString(doc);
108
153
  zip.file(constants_1.relsXmlPath, newDoc);
@@ -116,6 +161,7 @@ var updateConnections = function (connectionsXmlString, queryName, refreshOnOpen
116
161
  var serializer = new xmldom_qsa_1.XMLSerializer();
117
162
  var refreshOnLoadValue = refreshOnOpen ? constants_1.trueValue : constants_1.falseValue;
118
163
  var connectionsDoc = parser.parseFromString(connectionsXmlString, constants_1.xmlTextResultType);
164
+ checkParserError(connectionsDoc, constants_1.Errors.connectionsParse);
119
165
  var connectionsProperties = connectionsDoc.getElementsByTagName(constants_1.element.databaseProperties);
120
166
  var dbPr = connectionsProperties[0];
121
167
  dbPr.setAttribute(constants_1.elementAttributes.refreshOnLoad, refreshOnLoadValue);
@@ -127,7 +173,7 @@ var updateConnections = function (connectionsXmlString, queryName, refreshOnOpen
127
173
  var connectionId = (_c = dbPr.parentNode) === null || _c === void 0 ? void 0 : _c.getAttribute(constants_1.elementAttributes.id);
128
174
  var connectionXmlFileString = serializer.serializeToString(connectionsDoc);
129
175
  if (connectionId === null) {
130
- throw new Error(constants_1.connectionsNotFoundErr);
176
+ throw new Error(constants_1.Errors.connectionsNotFound);
131
177
  }
132
178
  return { connectionId: connectionId, connectionXmlFileString: connectionXmlFileString };
133
179
  };
@@ -135,9 +181,10 @@ var updateSharedStrings = function (sharedStringsXmlString, queryName) {
135
181
  var parser = new xmldom_qsa_1.DOMParser();
136
182
  var serializer = new xmldom_qsa_1.XMLSerializer();
137
183
  var sharedStringsDoc = parser.parseFromString(sharedStringsXmlString, constants_1.xmlTextResultType);
184
+ checkParserError(sharedStringsDoc, constants_1.Errors.sharedStringsParse);
138
185
  var sharedStringsTable = sharedStringsDoc.getElementsByTagName(constants_1.element.sharedStringTable)[0];
139
186
  if (!sharedStringsTable) {
140
- throw new Error(constants_1.sharedStringsNotFoundErr);
187
+ throw new Error(constants_1.Errors.sharedStringsNotFound);
141
188
  }
142
189
  var textElementCollection = sharedStringsDoc.getElementsByTagName(constants_1.element.text);
143
190
  var textElement = null;
@@ -175,6 +222,7 @@ var updateWorksheet = function (sheetsXmlString, sharedStringIndex) {
175
222
  var parser = new xmldom_qsa_1.DOMParser();
176
223
  var serializer = new xmldom_qsa_1.XMLSerializer();
177
224
  var sheetsDoc = parser.parseFromString(sheetsXmlString, constants_1.xmlTextResultType);
225
+ checkParserError(sheetsDoc, constants_1.Errors.worksheetParse);
178
226
  sheetsDoc.getElementsByTagName(constants_1.element.cellValue)[0].innerHTML = sharedStringIndex.toString();
179
227
  var newSheet = serializer.serializeToString(sheetsDoc);
180
228
  return newSheet;
@@ -240,7 +288,7 @@ var updatePivotTablesandQueryTables = function (zip, queryName, refreshOnOpen, c
240
288
  }
241
289
  });
242
290
  if (!found) {
243
- throw new Error(constants_1.queryAndPivotTableNotFoundErr);
291
+ throw new Error(constants_1.Errors.queryAndPivotTableNotFound);
244
292
  }
245
293
  return [2 /*return*/];
246
294
  }
@@ -252,6 +300,7 @@ var updateQueryTable = function (tableXmlString, connectionId, refreshOnOpen) {
252
300
  var parser = new xmldom_qsa_1.DOMParser();
253
301
  var serializer = new xmldom_qsa_1.XMLSerializer();
254
302
  var queryTableDoc = parser.parseFromString(tableXmlString, constants_1.xmlTextResultType);
303
+ checkParserError(queryTableDoc, constants_1.Errors.queryTableParse);
255
304
  var queryTable = queryTableDoc.getElementsByTagName(constants_1.element.queryTable)[0];
256
305
  var newQueryTable = constants_1.emptyValue;
257
306
  if (queryTable.getAttribute(constants_1.elementAttributes.connectionId) == connectionId) {
@@ -267,6 +316,7 @@ var updatePivotTable = function (tableXmlString, connectionId, refreshOnOpen) {
267
316
  var parser = new xmldom_qsa_1.DOMParser();
268
317
  var serializer = new xmldom_qsa_1.XMLSerializer();
269
318
  var pivotCacheDoc = parser.parseFromString(tableXmlString, constants_1.xmlTextResultType);
319
+ checkParserError(pivotCacheDoc, constants_1.Errors.pivotTableParse);
270
320
  var cacheSource = pivotCacheDoc.getElementsByTagName(constants_1.element.cacheSource)[0];
271
321
  var newPivotTable = constants_1.emptyValue;
272
322
  if (cacheSource.getAttribute(constants_1.elementAttributes.connectionId) == connectionId) {
@@ -282,25 +332,30 @@ var updatePivotTable = function (tableXmlString, connectionId, refreshOnOpen) {
282
332
  */
283
333
  function getSheetPathFromXlRelId(zip, rId) {
284
334
  return __awaiter(this, void 0, void 0, function () {
285
- var relsFile, relsString, relsDoc, relationship, target;
335
+ var relsFile, relsString, relsDoc, relationships, target, i, el;
286
336
  return __generator(this, function (_a) {
287
337
  switch (_a.label) {
288
338
  case 0:
289
339
  relsFile = zip.file(constants_1.workbookRelsXmlPath);
290
340
  if (!relsFile) {
291
- throw new Error(constants_1.xlRelsNotFoundErr);
341
+ throw new Error(constants_1.Errors.xlRelsNotFound);
292
342
  }
293
343
  return [4 /*yield*/, relsFile.async(constants_1.textResultType)];
294
344
  case 1:
295
345
  relsString = _a.sent();
296
346
  relsDoc = new xmldom_qsa_1.DOMParser().parseFromString(relsString, constants_1.xmlTextResultType);
297
- relationship = relsDoc.querySelector("Relationship[Id=\"".concat(rId, "\"]"));
298
- if (!relationship) {
299
- throw new Error("Relationship not found for Id: ".concat(rId));
347
+ checkParserError(relsDoc, constants_1.Errors.workbookRelsParse);
348
+ relationships = relsDoc.getElementsByTagName("Relationship");
349
+ target = null;
350
+ for (i = 0; i < relationships.length; i++) {
351
+ el = relationships[i];
352
+ if (el && el.getAttribute && el.getAttribute("Id") === rId) {
353
+ target = el.getAttribute(constants_1.elementAttributes.target);
354
+ break;
355
+ }
300
356
  }
301
- target = relationship.getAttribute(constants_1.elementAttributes.target);
302
357
  if (!target) {
303
- throw new Error("Target not found for Relationship Id: ".concat(rId));
358
+ throw new Error("Relationship not found or missing Target for Id: ".concat(rId));
304
359
  }
305
360
  return [2 /*return*/, target];
306
361
  }
@@ -317,10 +372,11 @@ var getSheetPathByNameFromZip = function (zip, sheetName) { return __awaiter(voi
317
372
  case 1:
318
373
  workbookXmlString = _b.sent();
319
374
  if (!workbookXmlString) {
320
- throw new Error(constants_1.WorkbookNotFoundERR);
375
+ throw new Error(constants_1.Errors.workbookNotFound);
321
376
  }
322
377
  parser = new xmldom_qsa_1.DOMParser();
323
378
  doc = parser.parseFromString(workbookXmlString, constants_1.xmlTextResultType);
379
+ checkParserError(doc, constants_1.Errors.workbookParse);
324
380
  sheetElements = doc.getElementsByTagName(constants_1.element.sheet);
325
381
  for (i = 0; i < sheetElements.length; i++) {
326
382
  if (sheetElements[i].getAttribute(constants_1.elementAttributes.name) === sheetName) {
@@ -344,14 +400,15 @@ var getReferenceFromTable = function (zip, tablePath) { return __awaiter(void 0,
344
400
  case 1:
345
401
  tableXmlString = _c.sent();
346
402
  if (!tableXmlString) {
347
- throw new Error(constants_1.WorkbookNotFoundERR);
403
+ throw new Error(constants_1.Errors.workbookNotFound);
348
404
  }
349
405
  parser = new xmldom_qsa_1.DOMParser();
350
406
  doc = parser.parseFromString(tableXmlString, constants_1.xmlTextResultType);
407
+ checkParserError(doc, constants_1.Errors.tableParse);
351
408
  tableElements = doc.getElementsByTagName(constants_1.element.table);
352
409
  reference = (_b = tableElements[0]) === null || _b === void 0 ? void 0 : _b.getAttribute(constants_1.elementAttributes.reference);
353
410
  if (!reference) {
354
- throw new Error(constants_1.tableReferenceNotFoundErr);
411
+ throw new Error(constants_1.Errors.tableReferenceNotFound);
355
412
  }
356
413
  return [2 /*return*/, reference.split(":")[0]]; // Return the start cell reference (e.g., "A1" from "A1:B10")
357
414
  }
@@ -362,9 +419,9 @@ var findTablePathFromZip = function (zip, targetTableName) { return __awaiter(vo
362
419
  return __generator(this, function (_b) {
363
420
  switch (_b.label) {
364
421
  case 0:
365
- tablesFolder = zip.folder("xl/tables");
422
+ tablesFolder = zip.folder(constants_1.tablesFolderPath);
366
423
  if (!tablesFolder)
367
- return [2 /*return*/, ""];
424
+ return [2 /*return*/, constants_1.emptyValue];
368
425
  tableFilePromises = [];
369
426
  tablesFolder.forEach(function (relativePath, file) {
370
427
  tableFilePromises.push(file.async(constants_1.textResultType).then(function (content) { return ({ path: relativePath, content: content }); }));
@@ -376,15 +433,216 @@ var findTablePathFromZip = function (zip, targetTableName) { return __awaiter(vo
376
433
  for (_i = 0, tableFiles_1 = tableFiles; _i < tableFiles_1.length; _i++) {
377
434
  _a = tableFiles_1[_i], path = _a.path, content = _a.content;
378
435
  doc = parser.parseFromString(content, constants_1.xmlTextResultType);
436
+ checkParserError(doc, "".concat(constants_1.Errors.tablePathParse, " ").concat(path));
379
437
  tableElem = doc.getElementsByTagName(constants_1.element.table)[0];
380
438
  if (tableElem && tableElem.getAttribute(constants_1.elementAttributes.name) === targetTableName) {
381
439
  return [2 /*return*/, path];
382
440
  }
383
441
  }
384
- throw new Error(constants_1.tableNotFoundErr);
442
+ throw new Error(constants_1.Errors.tableNotFound);
443
+ }
444
+ });
445
+ }); };
446
+ /**
447
+ * Determines the next available item number for a custom XML item in the Excel workbook.
448
+ * Scans the customXml folder to find existing item files and returns the next sequential number.
449
+ *
450
+ * @param zip - The JSZip instance containing the Excel workbook structure
451
+ * @returns Promise resolving to the next available item number (starting from 1 if no items exist)
452
+ *
453
+ * @example
454
+ * // If customXml folder contains item1.xml, item2.xml, returns 3
455
+ * const nextNumber = await getCustomXmlItemNumber(zip);
456
+ */
457
+ var getCustomXmlItemNumber = function (zip) { return __awaiter(void 0, void 0, void 0, function () {
458
+ var customXmlFolder, re, matches, max, _i, matches_1, f, m, n;
459
+ return __generator(this, function (_a) {
460
+ customXmlFolder = zip.folder(constants_1.customXmlXmlPath);
461
+ if (!customXmlFolder) {
462
+ return [2 /*return*/, 1]; // start from 1 if folder doesn't exist
463
+ }
464
+ re = new RegExp("^".concat(constants_1.customXmlXmlPath, "/").concat(constants_1.customXML.itemNumberPattern.source, "$"));
465
+ matches = zip.file(re);
466
+ max = 0;
467
+ // Iterate through all matching files to find the highest item number
468
+ for (_i = 0, matches_1 = matches; _i < matches_1.length; _i++) {
469
+ f = matches_1[_i];
470
+ m = f.name.match(constants_1.customXML.itemNumberPattern);
471
+ if (m) {
472
+ n = parseInt(m[1], 10);
473
+ if (!Number.isNaN(n) && n > max) {
474
+ max = n;
475
+ }
476
+ }
477
+ }
478
+ return [2 /*return*/, max + 1]; // Return next available number
479
+ });
480
+ }); };
481
+ /**
482
+ * Checks if a custom XML item with connected-workbooks already exists in the Excel workbook.
483
+ * Searches through all custom XML files in the customXml folder to find a match with the expected content.
484
+ *
485
+ * @param zip - The JSZip instance containing the Excel workbook structure
486
+ * @returns Promise resolving to true if the custom XML item exists, false otherwise
487
+ *
488
+ * @example
489
+ * const exists = await isCustomXmlExists(zip);
490
+ * if (!exists) {
491
+ * // Add new custom XML item
492
+ * }
493
+ */
494
+ var isCustomXmlExists = function (zip) { return __awaiter(void 0, void 0, void 0, function () {
495
+ var customXmlFolder, customXmlFiles, _i, customXmlFiles_1, file, content, error_1;
496
+ return __generator(this, function (_a) {
497
+ switch (_a.label) {
498
+ case 0:
499
+ customXmlFolder = zip.folder(constants_1.customXmlXmlPath);
500
+ if (!customXmlFolder) {
501
+ return [2 /*return*/, false]; // customXml folder does not exist
502
+ }
503
+ customXmlFiles = customXmlFolder.file(constants_1.customXML.itemFilePattern);
504
+ _i = 0, customXmlFiles_1 = customXmlFiles;
505
+ _a.label = 1;
506
+ case 1:
507
+ if (!(_i < customXmlFiles_1.length)) return [3 /*break*/, 6];
508
+ file = customXmlFiles_1[_i];
509
+ _a.label = 2;
510
+ case 2:
511
+ _a.trys.push([2, 4, , 5]);
512
+ return [4 /*yield*/, file.async(constants_1.textResultType)];
513
+ case 3:
514
+ content = _a.sent();
515
+ if (content.includes(constants_1.customXML.connectedWorkbookTag)) {
516
+ return [2 /*return*/, true]; // Found matching custom XML item
517
+ }
518
+ return [3 /*break*/, 5];
519
+ case 4:
520
+ error_1 = _a.sent();
521
+ // Skip files that can't be read and continue with the next file
522
+ return [3 /*break*/, 5];
523
+ case 5:
524
+ _i++;
525
+ return [3 /*break*/, 1];
526
+ case 6: return [2 /*return*/, false]; // No matching custom XML item found
385
527
  }
386
528
  });
387
529
  }); };
530
+ /**
531
+ * Adds a content type override entry to the [Content_Types].xml file for a custom XML item.
532
+ * This registration is required for Excel to recognize and process the custom XML item.
533
+ *
534
+ * @param zip - The JSZip instance containing the Excel workbook structure
535
+ * @param itemIndex - The index/number of the custom XML item to register
536
+ * @throws {Error} When the [Content_Types].xml file is not found or cannot be parsed
537
+ *
538
+ * @example
539
+ * await addToContentType(zip, "1"); // Registers customXml/item1.xml in content types
540
+ */
541
+ var addToContentType = function (zip, itemIndex) { return __awaiter(void 0, void 0, void 0, function () {
542
+ var contentTypesXmlString, parser, doc, partName, contentTypeValue, typesElement, ns, overrideEl, serializer, newDoc;
543
+ var _a;
544
+ return __generator(this, function (_b) {
545
+ switch (_b.label) {
546
+ case 0: return [4 /*yield*/, ((_a = zip.file(constants_1.contentTypesXmlPath)) === null || _a === void 0 ? void 0 : _a.async(constants_1.textResultType))];
547
+ case 1:
548
+ contentTypesXmlString = _b.sent();
549
+ if (!contentTypesXmlString) {
550
+ throw new Error(constants_1.Errors.contentTypesNotFound);
551
+ }
552
+ parser = new xmldom_qsa_1.DOMParser();
553
+ doc = parser.parseFromString(contentTypesXmlString, constants_1.xmlTextResultType);
554
+ checkParserError(doc, constants_1.Errors.contentTypesParse);
555
+ partName = constants_1.customXML.itemPropsPartNameTemplate(itemIndex);
556
+ contentTypeValue = constants_1.customXML.contentType;
557
+ typesElement = doc.documentElement;
558
+ if (!typesElement) {
559
+ throw new Error(constants_1.Errors.contentTypesElementNotFound);
560
+ }
561
+ ns = doc.documentElement.namespaceURI;
562
+ overrideEl = ns ? doc.createElementNS(ns, constants_1.element.override) : doc.createElement(constants_1.element.override);
563
+ overrideEl.setAttribute(constants_1.elementAttributes.partName, partName);
564
+ overrideEl.setAttribute(constants_1.elementAttributes.contentType, contentTypeValue);
565
+ typesElement.appendChild(overrideEl);
566
+ serializer = new xmldom_qsa_1.XMLSerializer();
567
+ newDoc = serializer.serializeToString(doc);
568
+ zip.file(constants_1.contentTypesXmlPath, newDoc);
569
+ return [2 /*return*/];
570
+ }
571
+ });
572
+ }); };
573
+ /**
574
+ * Adds a relationship entry to the workbook relationships file for a custom XML item.
575
+ * Creates a new relationship that links the workbook to the custom XML item.
576
+ *
577
+ * @param zip - The JSZip instance containing the Excel workbook structure
578
+ * @param itemIndex - The index/number of the custom XML item to create a relationship for
579
+ * @throws {Error} When the workbook relationships file is not found or cannot be parsed
580
+ *
581
+ * @example
582
+ * await addCustomXmlToRels(zip, "1"); // Creates relationship to customXml/item1.xml
583
+ */
584
+ var addCustomXmlToRels = function (zip, itemIndex) { return __awaiter(void 0, void 0, void 0, function () {
585
+ var relsXmlString, parser, doc, relationshipsElements, relationshipsElement, highestRid, newRid, target, type, ns, relationshipEl, serializer, newDoc;
586
+ var _a;
587
+ return __generator(this, function (_b) {
588
+ switch (_b.label) {
589
+ case 0: return [4 /*yield*/, ((_a = zip.file(constants_1.workbookRelsXmlPath)) === null || _a === void 0 ? void 0 : _a.async(constants_1.textResultType))];
590
+ case 1:
591
+ relsXmlString = _b.sent();
592
+ if (!relsXmlString) {
593
+ throw new Error(constants_1.Errors.relsNotFound);
594
+ }
595
+ parser = new xmldom_qsa_1.DOMParser();
596
+ doc = parser.parseFromString(relsXmlString, constants_1.xmlTextResultType);
597
+ checkParserError(doc, constants_1.Errors.workbookRelsParse);
598
+ relationshipsElements = doc.getElementsByTagName(constants_1.element.relationships);
599
+ if (!relationshipsElements || relationshipsElements.length === 0) {
600
+ throw new Error(constants_1.Errors.relationship);
601
+ }
602
+ relationshipsElement = relationshipsElements[0];
603
+ highestRid = getHighestRelationshipId(relationshipsElement);
604
+ newRid = "".concat(constants_1.elementAttributes.relationshipIdPrefix).concat(highestRid + 1);
605
+ target = constants_1.customXML.relativeItemPathTemplate(itemIndex);
606
+ type = constants_1.customXML.relationshipType;
607
+ ns = doc.documentElement.namespaceURI;
608
+ relationshipEl = ns ? doc.createElementNS(ns, constants_1.element.relationship) : doc.createElement(constants_1.element.relationship);
609
+ relationshipEl.setAttribute(constants_1.elementAttributes.Id, newRid);
610
+ relationshipEl.setAttribute(constants_1.elementAttributes.type, type);
611
+ relationshipEl.setAttribute(constants_1.elementAttributes.target, target);
612
+ relationshipsElement.appendChild(relationshipEl);
613
+ serializer = new xmldom_qsa_1.XMLSerializer();
614
+ newDoc = serializer.serializeToString(doc);
615
+ zip.file(constants_1.workbookRelsXmlPath, newDoc);
616
+ return [2 /*return*/];
617
+ }
618
+ });
619
+ }); };
620
+ /**
621
+ * Finds the highest relationship ID number from existing relationships in a relationships element.
622
+ * Scans all relationship elements and extracts the numeric part from rId attributes.
623
+ *
624
+ * @param relationshipsElement - The relationships XML element containing relationship elements
625
+ * @returns The highest relationship ID number found, or 0 if none exist
626
+ *
627
+ * @example
628
+ * // If relationships contain rId1, rId3, rId7, returns 7
629
+ * const highestRid = getHighestRelationshipId(relationshipsElement);
630
+ */
631
+ var getHighestRelationshipId = function (relationshipsElement) {
632
+ var relationships = relationshipsElement.getElementsByTagName(constants_1.element.relationship);
633
+ var highestRid = 0;
634
+ for (var i = 0; i < relationships.length; i++) {
635
+ var idAttr = relationships[i].getAttribute(constants_1.elementAttributes.Id);
636
+ if (idAttr && idAttr.startsWith(constants_1.elementAttributes.relationshipIdPrefix)) {
637
+ // Extract numeric part from rId (e.g., "rId5" -> 5, "rId123" -> 123)
638
+ var ridNumber = parseInt(idAttr.substring(constants_1.elementAttributes.relationshipIdPrefix.length), 10);
639
+ if (!isNaN(ridNumber) && ridNumber > highestRid) {
640
+ highestRid = ridNumber;
641
+ }
642
+ }
643
+ }
644
+ return highestRid;
645
+ };
388
646
  exports.default = {
389
647
  updateDocProps: updateDocProps,
390
648
  clearLabelInfo: clearLabelInfo,
@@ -397,4 +655,9 @@ exports.default = {
397
655
  getSheetPathByNameFromZip: getSheetPathByNameFromZip,
398
656
  getReferenceFromTable: getReferenceFromTable,
399
657
  findTablePathFromZip: findTablePathFromZip,
658
+ getCustomXmlItemNumber: getCustomXmlItemNumber,
659
+ isCustomXmlExists: isCustomXmlExists,
660
+ addToContentType: addToContentType,
661
+ addCustomXmlToRels: addCustomXmlToRels,
662
+ checkParserError: checkParserError,
400
663
  };
@@ -47,12 +47,78 @@ var pqUtils_1 = __importDefault(require("./pqUtils"));
47
47
  var tableUtils_1 = __importDefault(require("./tableUtils"));
48
48
  var xmlInnerPartsUtils_1 = __importDefault(require("./xmlInnerPartsUtils"));
49
49
  var documentUtils_1 = __importDefault(require("./documentUtils"));
50
+ var addCustomXMLToWorkbook = function (zip) { return __awaiter(void 0, void 0, void 0, function () {
51
+ var customXmlItemNumber, backupData, contentTypesPath, workbookRelsPath, _a, _b, _c, _d, error_1, _i, _e, _f, filePath, content;
52
+ var _g, _h;
53
+ return __generator(this, function (_j) {
54
+ switch (_j.label) {
55
+ case 0: return [4 /*yield*/, xmlInnerPartsUtils_1.default.isCustomXmlExists(zip)];
56
+ case 1:
57
+ if (_j.sent()) {
58
+ return [2 /*return*/, true];
59
+ }
60
+ return [4 /*yield*/, xmlInnerPartsUtils_1.default.getCustomXmlItemNumber(zip)];
61
+ case 2:
62
+ customXmlItemNumber = _j.sent();
63
+ backupData = {};
64
+ contentTypesPath = "[Content_Types].xml";
65
+ workbookRelsPath = "xl/_rels/workbook.xml.rels";
66
+ _j.label = 3;
67
+ case 3:
68
+ _j.trys.push([3, 8, , 9]);
69
+ // Backup existing files
70
+ _a = backupData;
71
+ _b = contentTypesPath;
72
+ return [4 /*yield*/, ((_g = zip.file(contentTypesPath)) === null || _g === void 0 ? void 0 : _g.async("text"))];
73
+ case 4:
74
+ // Backup existing files
75
+ _a[_b] = (_j.sent()) || null;
76
+ _c = backupData;
77
+ _d = workbookRelsPath;
78
+ return [4 /*yield*/, ((_h = zip.file(workbookRelsPath)) === null || _h === void 0 ? void 0 : _h.async("text"))];
79
+ case 5:
80
+ _c[_d] = (_j.sent()) || null;
81
+ // Perform modifications
82
+ return [4 /*yield*/, xmlInnerPartsUtils_1.default.addToContentType(zip, customXmlItemNumber.toString())];
83
+ case 6:
84
+ // Perform modifications
85
+ _j.sent();
86
+ return [4 /*yield*/, xmlInnerPartsUtils_1.default.addCustomXmlToRels(zip, customXmlItemNumber.toString())];
87
+ case 7:
88
+ _j.sent();
89
+ // Adding the custom XML files
90
+ zip.file(constants_1.customXML.itemPathTemplate(customXmlItemNumber), constants_1.customXML.customXMLItemContent);
91
+ zip.file(constants_1.customXML.itemPropsPathTemplate(customXmlItemNumber), constants_1.customXML.customXMLItemPropsContent);
92
+ zip.file(constants_1.customXML.itemRelsPathTemplate(customXmlItemNumber), constants_1.customXML.customXMLRelationships(customXmlItemNumber));
93
+ return [3 /*break*/, 9];
94
+ case 8:
95
+ error_1 = _j.sent();
96
+ // Rollback: restore backed up files
97
+ for (_i = 0, _e = Object.entries(backupData); _i < _e.length; _i++) {
98
+ _f = _e[_i], filePath = _f[0], content = _f[1];
99
+ if (content !== null) {
100
+ zip.file(filePath, content);
101
+ }
102
+ else {
103
+ // If file didn't exist before, remove it
104
+ zip.remove(filePath);
105
+ }
106
+ }
107
+ // Remove any custom XML files that might have been added
108
+ zip.remove(constants_1.customXML.itemPathTemplate(customXmlItemNumber));
109
+ zip.remove(constants_1.customXML.itemPropsPathTemplate(customXmlItemNumber));
110
+ zip.remove(constants_1.customXML.itemRelsPathTemplate(customXmlItemNumber));
111
+ return [2 /*return*/, false];
112
+ case 9: return [2 /*return*/, true];
113
+ }
114
+ });
115
+ }); };
50
116
  var updateWorkbookDataAndConfigurations = function (zip, fileConfigs, tableData, updateQueryTable) {
51
117
  if (updateQueryTable === void 0) { updateQueryTable = false; }
52
118
  return __awaiter(void 0, void 0, void 0, function () {
53
- var sheetName, tablePath, sheetPath, templateSettings, sheetLocation, _a, cellRangeRef;
54
- return __generator(this, function (_b) {
55
- switch (_b.label) {
119
+ var sheetName, tablePath, sheetPath, templateSettings, sheetLocation, _a, cellRangeRef, _b, row, column, endColumn, endRow;
120
+ return __generator(this, function (_c) {
121
+ switch (_c.label) {
56
122
  case 0:
57
123
  sheetName = constants_1.defaults.sheetName;
58
124
  tablePath = constants_1.tableXmlPath;
@@ -62,41 +128,48 @@ var updateWorkbookDataAndConfigurations = function (zip, fileConfigs, tableData,
62
128
  if (!((templateSettings === null || templateSettings === void 0 ? void 0 : templateSettings.sheetName) !== undefined)) return [3 /*break*/, 2];
63
129
  return [4 /*yield*/, xmlInnerPartsUtils_1.default.getSheetPathByNameFromZip(zip, templateSettings.sheetName)];
64
130
  case 1:
65
- sheetLocation = _b.sent();
131
+ sheetLocation = _c.sent();
66
132
  sheetName = templateSettings.sheetName;
67
133
  sheetPath = "xl/" + sheetLocation;
68
- _b.label = 2;
134
+ _c.label = 2;
69
135
  case 2:
70
136
  if (!((templateSettings === null || templateSettings === void 0 ? void 0 : templateSettings.tableName) !== undefined)) return [3 /*break*/, 4];
71
137
  _a = constants_1.tablesFolderPath;
72
138
  return [4 /*yield*/, xmlInnerPartsUtils_1.default.findTablePathFromZip(zip, templateSettings === null || templateSettings === void 0 ? void 0 : templateSettings.tableName)];
73
139
  case 3:
74
- tablePath = _a + (_b.sent());
75
- _b.label = 4;
140
+ tablePath = _a + (_c.sent());
141
+ _c.label = 4;
76
142
  case 4:
77
143
  cellRangeRef = "A1";
78
- if (!((fileConfigs === null || fileConfigs === void 0 ? void 0 : fileConfigs.templateFile) != null)) return [3 /*break*/, 6];
144
+ if (!((fileConfigs === null || fileConfigs === void 0 ? void 0 : fileConfigs.templateFile) !== undefined)) return [3 /*break*/, 6];
79
145
  return [4 /*yield*/, xmlInnerPartsUtils_1.default.getReferenceFromTable(zip, tablePath)];
80
146
  case 5:
81
- cellRangeRef = _b.sent();
82
- _b.label = 6;
147
+ cellRangeRef = _c.sent();
148
+ _c.label = 6;
83
149
  case 6:
84
150
  if (tableData) {
85
- cellRangeRef += ":".concat(documentUtils_1.default.getCellReferenceRelative(tableData.columnNames.length - 1, tableData.rows.length + 1));
151
+ _b = documentUtils_1.default.GetStartPosition(cellRangeRef), row = _b.row, column = _b.column;
152
+ endColumn = column - 1 + tableData.columnNames.length;
153
+ endRow = row - 1 + tableData.rows.length;
154
+ // Extend the cell range to include the entire table span
155
+ cellRangeRef += ":".concat(documentUtils_1.default.getCellReferenceRelative(endColumn - 1, endRow + 1));
86
156
  }
87
157
  return [4 /*yield*/, xmlInnerPartsUtils_1.default.updateDocProps(zip, fileConfigs === null || fileConfigs === void 0 ? void 0 : fileConfigs.docProps)];
88
158
  case 7:
89
- _b.sent();
159
+ _c.sent();
90
160
  if (!((fileConfigs === null || fileConfigs === void 0 ? void 0 : fileConfigs.templateFile) === undefined)) return [3 /*break*/, 9];
91
161
  // If we are using our base template, we need to clear label info
92
162
  return [4 /*yield*/, xmlInnerPartsUtils_1.default.clearLabelInfo(zip)];
93
163
  case 8:
94
164
  // If we are using our base template, we need to clear label info
95
- _b.sent();
96
- _b.label = 9;
165
+ _c.sent();
166
+ _c.label = 9;
97
167
  case 9: return [4 /*yield*/, tableUtils_1.default.updateTableInitialDataIfNeeded(zip, cellRangeRef, sheetPath, tablePath, sheetPath, tableData, updateQueryTable)];
98
168
  case 10:
99
- _b.sent();
169
+ _c.sent();
170
+ return [4 /*yield*/, addCustomXMLToWorkbook(zip)];
171
+ case 11:
172
+ _c.sent();
100
173
  return [2 /*return*/];
101
174
  }
102
175
  });
@@ -110,7 +183,7 @@ var updateWorkbookPowerQueryDocument = function (zip, queryName, queryMashupDoc)
110
183
  case 1:
111
184
  old_base64 = _a.sent();
112
185
  if (!old_base64) {
113
- throw new Error(constants_1.base64NotFoundErr);
186
+ throw new Error(constants_1.Errors.base64NotFound);
114
187
  }
115
188
  return [4 /*yield*/, (0, mashupDocumentParser_1.replaceSingleQuery)(old_base64, queryName, queryMashupDoc)];
116
189
  case 2:
@@ -131,7 +204,7 @@ var updateWorkbookSingleQueryAttributes = function (zip, queryName, refreshOnOpe
131
204
  case 1:
132
205
  connectionsXmlString = _f.sent();
133
206
  if (connectionsXmlString === undefined) {
134
- throw new Error(constants_1.connectionsNotFoundErr);
207
+ throw new Error(constants_1.Errors.connectionsNotFound);
135
208
  }
136
209
  _a = xmlInnerPartsUtils_1.default.updateConnections(connectionsXmlString, queryName, refreshOnOpen), connectionId = _a.connectionId, connectionXmlFileString = _a.connectionXmlFileString;
137
210
  zip.file(constants_1.connectionsXmlPath, connectionXmlFileString);
@@ -139,7 +212,7 @@ var updateWorkbookSingleQueryAttributes = function (zip, queryName, refreshOnOpe
139
212
  case 2:
140
213
  sharedStringsXmlString = _f.sent();
141
214
  if (sharedStringsXmlString === undefined) {
142
- throw new Error(constants_1.sharedStringsNotFoundErr);
215
+ throw new Error(constants_1.Errors.sharedStringsNotFound);
143
216
  }
144
217
  _b = xmlInnerPartsUtils_1.default.updateSharedStrings(sharedStringsXmlString, queryName), sharedStringIndex = _b.sharedStringIndex, newSharedStrings = _b.newSharedStrings;
145
218
  zip.file(constants_1.sharedStringsXmlPath, newSharedStrings);
@@ -154,7 +227,7 @@ var updateWorkbookSingleQueryAttributes = function (zip, queryName, refreshOnOpe
154
227
  case 5:
155
228
  sheetsXmlString = _f.sent();
156
229
  if (sheetsXmlString === undefined) {
157
- throw new Error(constants_1.sheetsNotFoundErr);
230
+ throw new Error(constants_1.Errors.sheetsNotFound);
158
231
  }
159
232
  worksheetString = xmlInnerPartsUtils_1.default.updateWorksheet(sheetsXmlString, sharedStringIndex.toString());
160
233
  zip.file(sheetPath, worksheetString);
@@ -171,4 +244,5 @@ exports.default = {
171
244
  updateWorkbookDataAndConfigurations: updateWorkbookDataAndConfigurations,
172
245
  updateWorkbookPowerQueryDocument: updateWorkbookPowerQueryDocument,
173
246
  updateWorkbookSingleQueryAttributes: updateWorkbookSingleQueryAttributes,
247
+ addCustomXMLToWorkbook: addCustomXMLToWorkbook
174
248
  };