easy-template-x 6.2.2 → 6.2.3

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.
@@ -202,8 +202,16 @@ function isNumber(value) {
202
202
  class Path {
203
203
  static getFilename(path) {
204
204
  const lastSlashIndex = path.lastIndexOf('/');
205
- return path.substr(lastSlashIndex + 1);
205
+ return path.substring(lastSlashIndex + 1);
206
206
  }
207
+
208
+ /**
209
+ * Get the directory of a path.
210
+ * Exclude the last slash.
211
+ *
212
+ * Example:
213
+ * /folder/subfolder/file.txt -> /folder/subfolder
214
+ */
207
215
  static getDirectory(path) {
208
216
  const lastSlashIndex = path.lastIndexOf('/');
209
217
  return path.substring(0, lastSlashIndex);
@@ -461,6 +469,9 @@ class Zip {
461
469
  this.binaryFormat = binaryFormat;
462
470
  }
463
471
  getFile(path) {
472
+ if (path && path.startsWith('/')) {
473
+ path = path.substring(1);
474
+ }
464
475
  const internalZipObject = this.zip.files[path];
465
476
  if (!internalZipObject) return null;
466
477
  return new ZipObject(internalZipObject, this.binaryFormat);
@@ -872,7 +883,7 @@ let Modify$1 = class Modify {
872
883
  insertBefore(newNode, referenceNode) {
873
884
  if (!newNode) throw new InternalArgumentMissingError("newNode");
874
885
  if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
875
- if (!referenceNode.parentNode) throw new Error(`'${"referenceNode"}' has no parent`);
886
+ if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
876
887
  const childNodes = referenceNode.parentNode.childNodes;
877
888
  const beforeNodeIndex = childNodes.indexOf(referenceNode);
878
889
  xml.modify.insertChild(referenceNode.parentNode, newNode, beforeNodeIndex);
@@ -887,7 +898,7 @@ let Modify$1 = class Modify {
887
898
  insertAfter(newNode, referenceNode) {
888
899
  if (!newNode) throw new InternalArgumentMissingError("newNode");
889
900
  if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
890
- if (!referenceNode.parentNode) throw new Error(`'${"referenceNode"}' has no parent`);
901
+ if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
891
902
  const childNodes = referenceNode.parentNode.childNodes;
892
903
  const referenceNodeIndex = childNodes.indexOf(referenceNode);
893
904
  xml.modify.insertChild(referenceNode.parentNode, newNode, referenceNodeIndex + 1);
@@ -1024,7 +1035,7 @@ let Modify$1 = class Modify {
1024
1035
  * `false` then the original child node is the first child of `right`.
1025
1036
  */
1026
1037
  splitByChild(parent, child, removeChild) {
1027
- if (child.parentNode != parent) throw new Error(`Node '${"child"}' is not a direct child of '${"parent"}'.`);
1038
+ if (child.parentNode != parent) throw new Error(`Node 'child' is not a direct child of 'parent'.`);
1028
1039
 
1029
1040
  // create childless clone 'left'
1030
1041
  const left = xml.create.cloneNode(parent, false);
@@ -1132,20 +1143,35 @@ const RelType = Object.freeze({
1132
1143
  Table: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table'
1133
1144
  });
1134
1145
  class Relationship {
1135
- static fromXml(xml) {
1146
+ static fromXml(partDir, xml) {
1136
1147
  return new Relationship({
1137
1148
  id: xml.attributes?.['Id'],
1138
1149
  type: xml.attributes?.['Type'],
1139
- target: Relationship.normalizeRelTarget(xml.attributes?.['Target']),
1150
+ target: Relationship.normalizeRelTarget(partDir, xml.attributes?.['Target']),
1140
1151
  targetMode: xml.attributes?.['TargetMode']
1141
1152
  });
1142
1153
  }
1143
- static normalizeRelTarget(target) {
1154
+ static normalizeRelTarget(partDir, target) {
1144
1155
  if (!target) {
1145
1156
  return target;
1146
1157
  }
1158
+
1159
+ // Remove leading slashes from input
1160
+ if (partDir.startsWith('/')) {
1161
+ partDir = partDir.substring(1);
1162
+ }
1147
1163
  if (target.startsWith('/')) {
1148
- return target.substring(1);
1164
+ target = target.substring(1);
1165
+ }
1166
+
1167
+ // Convert target to relative path
1168
+ if (target.startsWith(partDir)) {
1169
+ target = target.substring(partDir.length);
1170
+ }
1171
+
1172
+ // Remove leading slashes from output
1173
+ if (target.startsWith('/')) {
1174
+ target = target.substring(1);
1149
1175
  }
1150
1176
  return target;
1151
1177
  }
@@ -1156,11 +1182,11 @@ class Relationship {
1156
1182
  const node = xml.create.generalNode('Relationship');
1157
1183
  node.attributes = {};
1158
1184
 
1159
- // set only non-empty attributes
1185
+ // Set only non-empty attributes
1160
1186
  for (const propKey of Object.keys(this)) {
1161
1187
  const value = this[propKey];
1162
1188
  if (value && typeof value === 'string') {
1163
- const attrName = propKey[0].toUpperCase() + propKey.substr(1);
1189
+ const attrName = propKey[0].toUpperCase() + propKey.substring(1);
1164
1190
  node.attributes[attrName] = value;
1165
1191
  }
1166
1192
  }
@@ -1365,20 +1391,20 @@ class RelsFile {
1365
1391
  * Returns the rel ID.
1366
1392
  */
1367
1393
  async add(relTarget, relType, relTargetMode) {
1368
- // if relTarget is an internal file it should be relative to the part dir
1394
+ // If relTarget is an internal file it should be relative to the part dir
1369
1395
  if (this.partDir && relTarget.startsWith(this.partDir)) {
1370
- relTarget = relTarget.substr(this.partDir.length + 1);
1396
+ relTarget = relTarget.substring(this.partDir.length + 1);
1371
1397
  }
1372
1398
 
1373
- // parse rels file
1399
+ // Parse rels file
1374
1400
  await this.parseRelsFile();
1375
1401
 
1376
- // already exists?
1402
+ // Already exists?
1377
1403
  const relTargetKey = this.getRelTargetKey(relType, relTarget);
1378
1404
  let relId = this.relTargets[relTargetKey];
1379
1405
  if (relId) return relId;
1380
1406
 
1381
- // create rel node
1407
+ // Create rel node
1382
1408
  relId = this.getNextRelId();
1383
1409
  const rel = new Relationship({
1384
1410
  id: relId,
@@ -1387,11 +1413,11 @@ class RelsFile {
1387
1413
  targetMode: relTargetMode
1388
1414
  });
1389
1415
 
1390
- // update lookups
1416
+ // Update lookups
1391
1417
  this.rels[relId] = rel;
1392
1418
  this.relTargets[relTargetKey] = relId;
1393
1419
 
1394
- // return
1420
+ // Return
1395
1421
  return relId;
1396
1422
  }
1397
1423
  async list() {
@@ -1410,20 +1436,20 @@ class RelsFile {
1410
1436
  * Called automatically by the holding `Docx` before exporting.
1411
1437
  */
1412
1438
  async save() {
1413
- // not change - no need to save
1439
+ // Not change - no need to save
1414
1440
  if (!this.rels) return;
1415
1441
 
1416
- // create rels xml
1442
+ // Create rels xml
1417
1443
  const root = this.createRootNode();
1418
1444
  root.childNodes = Object.values(this.rels).map(rel => rel.toXml());
1419
1445
 
1420
- // serialize and save
1446
+ // Serialize and save
1421
1447
  const xmlContent = xml.parser.serializeFile(root);
1422
1448
  this.zip.setFile(this.relsFilePath, xmlContent);
1423
1449
  }
1424
1450
 
1425
1451
  //
1426
- // private methods
1452
+ // Private methods
1427
1453
  //
1428
1454
 
1429
1455
  getNextRelId() {
@@ -1435,10 +1461,10 @@ class RelsFile {
1435
1461
  return relId;
1436
1462
  }
1437
1463
  async parseRelsFile() {
1438
- // already parsed
1464
+ // Already parsed
1439
1465
  if (this.rels) return;
1440
1466
 
1441
- // parse xml
1467
+ // Parse xml
1442
1468
  let root;
1443
1469
  const relsFile = this.zip.getFile(this.relsFilePath);
1444
1470
  if (relsFile) {
@@ -1448,24 +1474,23 @@ class RelsFile {
1448
1474
  root = this.createRootNode();
1449
1475
  }
1450
1476
 
1451
- // parse relationship nodes
1477
+ // Parse relationship nodes
1452
1478
  this.rels = {};
1453
1479
  this.relTargets = {};
1454
1480
  for (const relNode of root.childNodes) {
1455
- const attributes = relNode.attributes;
1481
+ const genRelNode = relNode;
1482
+ const attributes = genRelNode.attributes;
1456
1483
  if (!attributes) continue;
1457
1484
  const idAttr = attributes['Id'];
1458
1485
  if (!idAttr) continue;
1459
1486
 
1460
- // store rel
1461
- const rel = Relationship.fromXml(relNode);
1487
+ // Store rel
1488
+ const rel = Relationship.fromXml(this.partDir, genRelNode);
1462
1489
  this.rels[idAttr] = rel;
1463
1490
 
1464
- // create rel target lookup
1465
- const typeAttr = attributes['Type'];
1466
- const targetAttr = Relationship.normalizeRelTarget(attributes['Target']);
1467
- if (typeAttr && targetAttr) {
1468
- const relTargetKey = this.getRelTargetKey(typeAttr, targetAttr);
1491
+ // Create rel target lookup
1492
+ if (rel.type && rel.target) {
1493
+ const relTargetKey = this.getRelTargetKey(rel.type, rel.target);
1469
1494
  this.relTargets[relTargetKey] = idAttr;
1470
1495
  }
1471
1496
  }
@@ -1521,7 +1546,7 @@ class OpenXmlPart {
1521
1546
  async getText() {
1522
1547
  const xmlDocument = await this.xmlRoot();
1523
1548
 
1524
- // ugly but good enough...
1549
+ // Ugly but good enough...
1525
1550
  const xmlString = xml.parser.serializeFile(xmlDocument);
1526
1551
  const domDocument = xml.parser.domParse(xmlString);
1527
1552
  return domDocument.documentElement.textContent;
@@ -1836,7 +1861,7 @@ class Query {
1836
1861
  */
1837
1862
  containingTextNode(node) {
1838
1863
  if (!node) return null;
1839
- if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument ${"node"}. Expected a XmlTextNode.`);
1864
+ if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument node. Expected a XmlTextNode.`);
1840
1865
  return xml.query.findParent(node, officeMarkup.query.isTextNode);
1841
1866
  }
1842
1867
 
@@ -1953,7 +1978,7 @@ class Modify {
1953
1978
  splitParagraphByTextNode(paragraph, textNode, removeTextNode) {
1954
1979
  // input validation
1955
1980
  const containingParagraph = officeMarkup.query.containingParagraphNode(textNode);
1956
- if (containingParagraph != paragraph) throw new Error(`Node '${"textNode"}' is not a descendant of '${"paragraph"}'.`);
1981
+ if (containingParagraph != paragraph) throw new Error(`Node 'textNode' is not a contained in the specified paragraph.`);
1957
1982
  const runNode = officeMarkup.query.containingRunNode(textNode);
1958
1983
  const wordTextNode = officeMarkup.query.containingTextNode(textNode);
1959
1984
 
@@ -4654,7 +4679,7 @@ class Delimiters {
4654
4679
  constructor(initial) {
4655
4680
  Object.assign(this, initial);
4656
4681
  this.encodeAndValidate();
4657
- if (this.containerTagOpen === this.containerTagClose) throw new Error(`${"containerTagOpen"} can not be equal to ${"containerTagClose"}`);
4682
+ if (this.containerTagOpen === this.containerTagClose) throw new Error(`containerTagOpen can not be equal to containerTagClose`);
4658
4683
  }
4659
4684
  encodeAndValidate() {
4660
4685
  const keys = ['tagStart', 'tagEnd', 'containerTagOpen', 'containerTagClose'];
@@ -4697,12 +4722,12 @@ class TemplateHandler {
4697
4722
  /**
4698
4723
  * Version number of the `easy-template-x` library.
4699
4724
  */
4700
- version = "6.2.2" ;
4725
+ version = "6.2.3" ;
4701
4726
  constructor(options) {
4702
4727
  this.options = new TemplateHandlerOptions(options);
4703
4728
 
4704
4729
  //
4705
- // this is the library's composition root
4730
+ // This is the library's composition root
4706
4731
  //
4707
4732
 
4708
4733
  const delimiterSearcher = new DelimiterSearcher();
@@ -4733,14 +4758,14 @@ class TemplateHandler {
4733
4758
  }
4734
4759
 
4735
4760
  //
4736
- // public methods
4761
+ // Public methods
4737
4762
  //
4738
4763
 
4739
4764
  async process(templateFile, data) {
4740
- // load the docx file
4765
+ // Load the docx file
4741
4766
  const docx = await Docx.load(templateFile);
4742
4767
 
4743
- // prepare context
4768
+ // Prepare context
4744
4769
  const scopeData = new ScopeData(data);
4745
4770
  scopeData.scopeDataResolver = this.options.scopeDataResolver;
4746
4771
  const context = {
@@ -4755,18 +4780,18 @@ class TemplateHandler {
4755
4780
  for (const part of contentParts) {
4756
4781
  context.currentPart = part;
4757
4782
 
4758
- // extensions - before compilation
4783
+ // Extensions - before compilation
4759
4784
  await this.callExtensions(this.options.extensions?.beforeCompilation, scopeData, context);
4760
4785
 
4761
- // compilation (do replacements)
4786
+ // Compilation (do replacements)
4762
4787
  const xmlRoot = await part.xmlRoot();
4763
4788
  await this.compiler.compile(xmlRoot, scopeData, context);
4764
4789
 
4765
- // extensions - after compilation
4790
+ // Extensions - after compilation
4766
4791
  await this.callExtensions(this.options.extensions?.afterCompilation, scopeData, context);
4767
4792
  }
4768
4793
 
4769
- // export the result
4794
+ // Export the result
4770
4795
  return docx.export();
4771
4796
  }
4772
4797
  async parseTags(templateFile) {
@@ -4828,7 +4853,7 @@ class TemplateHandler {
4828
4853
  }
4829
4854
 
4830
4855
  //
4831
- // private methods
4856
+ // Private methods
4832
4857
  //
4833
4858
 
4834
4859
  async callExtensions(extensions, scopeData, context) {
@@ -200,8 +200,16 @@ function isNumber(value) {
200
200
  class Path {
201
201
  static getFilename(path) {
202
202
  const lastSlashIndex = path.lastIndexOf('/');
203
- return path.substr(lastSlashIndex + 1);
203
+ return path.substring(lastSlashIndex + 1);
204
204
  }
205
+
206
+ /**
207
+ * Get the directory of a path.
208
+ * Exclude the last slash.
209
+ *
210
+ * Example:
211
+ * /folder/subfolder/file.txt -> /folder/subfolder
212
+ */
205
213
  static getDirectory(path) {
206
214
  const lastSlashIndex = path.lastIndexOf('/');
207
215
  return path.substring(0, lastSlashIndex);
@@ -459,6 +467,9 @@ class Zip {
459
467
  this.binaryFormat = binaryFormat;
460
468
  }
461
469
  getFile(path) {
470
+ if (path && path.startsWith('/')) {
471
+ path = path.substring(1);
472
+ }
462
473
  const internalZipObject = this.zip.files[path];
463
474
  if (!internalZipObject) return null;
464
475
  return new ZipObject(internalZipObject, this.binaryFormat);
@@ -870,7 +881,7 @@ let Modify$1 = class Modify {
870
881
  insertBefore(newNode, referenceNode) {
871
882
  if (!newNode) throw new InternalArgumentMissingError("newNode");
872
883
  if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
873
- if (!referenceNode.parentNode) throw new Error(`'${"referenceNode"}' has no parent`);
884
+ if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
874
885
  const childNodes = referenceNode.parentNode.childNodes;
875
886
  const beforeNodeIndex = childNodes.indexOf(referenceNode);
876
887
  xml.modify.insertChild(referenceNode.parentNode, newNode, beforeNodeIndex);
@@ -885,7 +896,7 @@ let Modify$1 = class Modify {
885
896
  insertAfter(newNode, referenceNode) {
886
897
  if (!newNode) throw new InternalArgumentMissingError("newNode");
887
898
  if (!referenceNode) throw new InternalArgumentMissingError("referenceNode");
888
- if (!referenceNode.parentNode) throw new Error(`'${"referenceNode"}' has no parent`);
899
+ if (!referenceNode.parentNode) throw new Error(`'referenceNode' has no parent`);
889
900
  const childNodes = referenceNode.parentNode.childNodes;
890
901
  const referenceNodeIndex = childNodes.indexOf(referenceNode);
891
902
  xml.modify.insertChild(referenceNode.parentNode, newNode, referenceNodeIndex + 1);
@@ -1022,7 +1033,7 @@ let Modify$1 = class Modify {
1022
1033
  * `false` then the original child node is the first child of `right`.
1023
1034
  */
1024
1035
  splitByChild(parent, child, removeChild) {
1025
- if (child.parentNode != parent) throw new Error(`Node '${"child"}' is not a direct child of '${"parent"}'.`);
1036
+ if (child.parentNode != parent) throw new Error(`Node 'child' is not a direct child of 'parent'.`);
1026
1037
 
1027
1038
  // create childless clone 'left'
1028
1039
  const left = xml.create.cloneNode(parent, false);
@@ -1130,20 +1141,35 @@ const RelType = Object.freeze({
1130
1141
  Table: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table'
1131
1142
  });
1132
1143
  class Relationship {
1133
- static fromXml(xml) {
1144
+ static fromXml(partDir, xml) {
1134
1145
  return new Relationship({
1135
1146
  id: xml.attributes?.['Id'],
1136
1147
  type: xml.attributes?.['Type'],
1137
- target: Relationship.normalizeRelTarget(xml.attributes?.['Target']),
1148
+ target: Relationship.normalizeRelTarget(partDir, xml.attributes?.['Target']),
1138
1149
  targetMode: xml.attributes?.['TargetMode']
1139
1150
  });
1140
1151
  }
1141
- static normalizeRelTarget(target) {
1152
+ static normalizeRelTarget(partDir, target) {
1142
1153
  if (!target) {
1143
1154
  return target;
1144
1155
  }
1156
+
1157
+ // Remove leading slashes from input
1158
+ if (partDir.startsWith('/')) {
1159
+ partDir = partDir.substring(1);
1160
+ }
1145
1161
  if (target.startsWith('/')) {
1146
- return target.substring(1);
1162
+ target = target.substring(1);
1163
+ }
1164
+
1165
+ // Convert target to relative path
1166
+ if (target.startsWith(partDir)) {
1167
+ target = target.substring(partDir.length);
1168
+ }
1169
+
1170
+ // Remove leading slashes from output
1171
+ if (target.startsWith('/')) {
1172
+ target = target.substring(1);
1147
1173
  }
1148
1174
  return target;
1149
1175
  }
@@ -1154,11 +1180,11 @@ class Relationship {
1154
1180
  const node = xml.create.generalNode('Relationship');
1155
1181
  node.attributes = {};
1156
1182
 
1157
- // set only non-empty attributes
1183
+ // Set only non-empty attributes
1158
1184
  for (const propKey of Object.keys(this)) {
1159
1185
  const value = this[propKey];
1160
1186
  if (value && typeof value === 'string') {
1161
- const attrName = propKey[0].toUpperCase() + propKey.substr(1);
1187
+ const attrName = propKey[0].toUpperCase() + propKey.substring(1);
1162
1188
  node.attributes[attrName] = value;
1163
1189
  }
1164
1190
  }
@@ -1363,20 +1389,20 @@ class RelsFile {
1363
1389
  * Returns the rel ID.
1364
1390
  */
1365
1391
  async add(relTarget, relType, relTargetMode) {
1366
- // if relTarget is an internal file it should be relative to the part dir
1392
+ // If relTarget is an internal file it should be relative to the part dir
1367
1393
  if (this.partDir && relTarget.startsWith(this.partDir)) {
1368
- relTarget = relTarget.substr(this.partDir.length + 1);
1394
+ relTarget = relTarget.substring(this.partDir.length + 1);
1369
1395
  }
1370
1396
 
1371
- // parse rels file
1397
+ // Parse rels file
1372
1398
  await this.parseRelsFile();
1373
1399
 
1374
- // already exists?
1400
+ // Already exists?
1375
1401
  const relTargetKey = this.getRelTargetKey(relType, relTarget);
1376
1402
  let relId = this.relTargets[relTargetKey];
1377
1403
  if (relId) return relId;
1378
1404
 
1379
- // create rel node
1405
+ // Create rel node
1380
1406
  relId = this.getNextRelId();
1381
1407
  const rel = new Relationship({
1382
1408
  id: relId,
@@ -1385,11 +1411,11 @@ class RelsFile {
1385
1411
  targetMode: relTargetMode
1386
1412
  });
1387
1413
 
1388
- // update lookups
1414
+ // Update lookups
1389
1415
  this.rels[relId] = rel;
1390
1416
  this.relTargets[relTargetKey] = relId;
1391
1417
 
1392
- // return
1418
+ // Return
1393
1419
  return relId;
1394
1420
  }
1395
1421
  async list() {
@@ -1408,20 +1434,20 @@ class RelsFile {
1408
1434
  * Called automatically by the holding `Docx` before exporting.
1409
1435
  */
1410
1436
  async save() {
1411
- // not change - no need to save
1437
+ // Not change - no need to save
1412
1438
  if (!this.rels) return;
1413
1439
 
1414
- // create rels xml
1440
+ // Create rels xml
1415
1441
  const root = this.createRootNode();
1416
1442
  root.childNodes = Object.values(this.rels).map(rel => rel.toXml());
1417
1443
 
1418
- // serialize and save
1444
+ // Serialize and save
1419
1445
  const xmlContent = xml.parser.serializeFile(root);
1420
1446
  this.zip.setFile(this.relsFilePath, xmlContent);
1421
1447
  }
1422
1448
 
1423
1449
  //
1424
- // private methods
1450
+ // Private methods
1425
1451
  //
1426
1452
 
1427
1453
  getNextRelId() {
@@ -1433,10 +1459,10 @@ class RelsFile {
1433
1459
  return relId;
1434
1460
  }
1435
1461
  async parseRelsFile() {
1436
- // already parsed
1462
+ // Already parsed
1437
1463
  if (this.rels) return;
1438
1464
 
1439
- // parse xml
1465
+ // Parse xml
1440
1466
  let root;
1441
1467
  const relsFile = this.zip.getFile(this.relsFilePath);
1442
1468
  if (relsFile) {
@@ -1446,24 +1472,23 @@ class RelsFile {
1446
1472
  root = this.createRootNode();
1447
1473
  }
1448
1474
 
1449
- // parse relationship nodes
1475
+ // Parse relationship nodes
1450
1476
  this.rels = {};
1451
1477
  this.relTargets = {};
1452
1478
  for (const relNode of root.childNodes) {
1453
- const attributes = relNode.attributes;
1479
+ const genRelNode = relNode;
1480
+ const attributes = genRelNode.attributes;
1454
1481
  if (!attributes) continue;
1455
1482
  const idAttr = attributes['Id'];
1456
1483
  if (!idAttr) continue;
1457
1484
 
1458
- // store rel
1459
- const rel = Relationship.fromXml(relNode);
1485
+ // Store rel
1486
+ const rel = Relationship.fromXml(this.partDir, genRelNode);
1460
1487
  this.rels[idAttr] = rel;
1461
1488
 
1462
- // create rel target lookup
1463
- const typeAttr = attributes['Type'];
1464
- const targetAttr = Relationship.normalizeRelTarget(attributes['Target']);
1465
- if (typeAttr && targetAttr) {
1466
- const relTargetKey = this.getRelTargetKey(typeAttr, targetAttr);
1489
+ // Create rel target lookup
1490
+ if (rel.type && rel.target) {
1491
+ const relTargetKey = this.getRelTargetKey(rel.type, rel.target);
1467
1492
  this.relTargets[relTargetKey] = idAttr;
1468
1493
  }
1469
1494
  }
@@ -1519,7 +1544,7 @@ class OpenXmlPart {
1519
1544
  async getText() {
1520
1545
  const xmlDocument = await this.xmlRoot();
1521
1546
 
1522
- // ugly but good enough...
1547
+ // Ugly but good enough...
1523
1548
  const xmlString = xml.parser.serializeFile(xmlDocument);
1524
1549
  const domDocument = xml.parser.domParse(xmlString);
1525
1550
  return domDocument.documentElement.textContent;
@@ -1834,7 +1859,7 @@ class Query {
1834
1859
  */
1835
1860
  containingTextNode(node) {
1836
1861
  if (!node) return null;
1837
- if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument ${"node"}. Expected a XmlTextNode.`);
1862
+ if (!xml.query.isTextNode(node)) throw new Error(`'Invalid argument node. Expected a XmlTextNode.`);
1838
1863
  return xml.query.findParent(node, officeMarkup.query.isTextNode);
1839
1864
  }
1840
1865
 
@@ -1951,7 +1976,7 @@ class Modify {
1951
1976
  splitParagraphByTextNode(paragraph, textNode, removeTextNode) {
1952
1977
  // input validation
1953
1978
  const containingParagraph = officeMarkup.query.containingParagraphNode(textNode);
1954
- if (containingParagraph != paragraph) throw new Error(`Node '${"textNode"}' is not a descendant of '${"paragraph"}'.`);
1979
+ if (containingParagraph != paragraph) throw new Error(`Node 'textNode' is not a contained in the specified paragraph.`);
1955
1980
  const runNode = officeMarkup.query.containingRunNode(textNode);
1956
1981
  const wordTextNode = officeMarkup.query.containingTextNode(textNode);
1957
1982
 
@@ -4652,7 +4677,7 @@ class Delimiters {
4652
4677
  constructor(initial) {
4653
4678
  Object.assign(this, initial);
4654
4679
  this.encodeAndValidate();
4655
- if (this.containerTagOpen === this.containerTagClose) throw new Error(`${"containerTagOpen"} can not be equal to ${"containerTagClose"}`);
4680
+ if (this.containerTagOpen === this.containerTagClose) throw new Error(`containerTagOpen can not be equal to containerTagClose`);
4656
4681
  }
4657
4682
  encodeAndValidate() {
4658
4683
  const keys = ['tagStart', 'tagEnd', 'containerTagOpen', 'containerTagClose'];
@@ -4695,12 +4720,12 @@ class TemplateHandler {
4695
4720
  /**
4696
4721
  * Version number of the `easy-template-x` library.
4697
4722
  */
4698
- version = "6.2.2" ;
4723
+ version = "6.2.3" ;
4699
4724
  constructor(options) {
4700
4725
  this.options = new TemplateHandlerOptions(options);
4701
4726
 
4702
4727
  //
4703
- // this is the library's composition root
4728
+ // This is the library's composition root
4704
4729
  //
4705
4730
 
4706
4731
  const delimiterSearcher = new DelimiterSearcher();
@@ -4731,14 +4756,14 @@ class TemplateHandler {
4731
4756
  }
4732
4757
 
4733
4758
  //
4734
- // public methods
4759
+ // Public methods
4735
4760
  //
4736
4761
 
4737
4762
  async process(templateFile, data) {
4738
- // load the docx file
4763
+ // Load the docx file
4739
4764
  const docx = await Docx.load(templateFile);
4740
4765
 
4741
- // prepare context
4766
+ // Prepare context
4742
4767
  const scopeData = new ScopeData(data);
4743
4768
  scopeData.scopeDataResolver = this.options.scopeDataResolver;
4744
4769
  const context = {
@@ -4753,18 +4778,18 @@ class TemplateHandler {
4753
4778
  for (const part of contentParts) {
4754
4779
  context.currentPart = part;
4755
4780
 
4756
- // extensions - before compilation
4781
+ // Extensions - before compilation
4757
4782
  await this.callExtensions(this.options.extensions?.beforeCompilation, scopeData, context);
4758
4783
 
4759
- // compilation (do replacements)
4784
+ // Compilation (do replacements)
4760
4785
  const xmlRoot = await part.xmlRoot();
4761
4786
  await this.compiler.compile(xmlRoot, scopeData, context);
4762
4787
 
4763
- // extensions - after compilation
4788
+ // Extensions - after compilation
4764
4789
  await this.callExtensions(this.options.extensions?.afterCompilation, scopeData, context);
4765
4790
  }
4766
4791
 
4767
- // export the result
4792
+ // Export the result
4768
4793
  return docx.export();
4769
4794
  }
4770
4795
  async parseTags(templateFile) {
@@ -4826,7 +4851,7 @@ class TemplateHandler {
4826
4851
  }
4827
4852
 
4828
4853
  //
4829
- // private methods
4854
+ // Private methods
4830
4855
  //
4831
4856
 
4832
4857
  async callExtensions(extensions, scopeData, context) {
@@ -15,8 +15,8 @@ export declare const RelType: Readonly<{
15
15
  }>;
16
16
  export type RelTargetMode = 'Internal' | 'External';
17
17
  export declare class Relationship {
18
- static fromXml(xml: XmlGeneralNode): Relationship;
19
- static normalizeRelTarget(target: string): string;
18
+ static fromXml(partDir: string, xml: XmlGeneralNode): Relationship;
19
+ static normalizeRelTarget(partDir: string, target: string): string;
20
20
  id: string;
21
21
  type: string;
22
22
  target: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-template-x",
3
- "version": "6.2.2",
3
+ "version": "6.2.3",
4
4
  "description": "Generate docx documents from templates, in Node or in the browser.",
5
5
  "keywords": [
6
6
  "docx",
@@ -42,7 +42,9 @@
42
42
  "clean": "rimraf .tmp dist test-reports && yarn clean-dist-verify",
43
43
  "typecheck": "tsc --noEmit",
44
44
  "lint": "eslint \"./{src,test}/**/!(*.d).ts\"",
45
- "test": "jest --verbose",
45
+ "test": "vitest run",
46
+ "test-ui": "vitest --ui",
47
+ "test-watch": "vitest",
46
48
  "quality": "yarn typecheck && yarn lint && yarn test",
47
49
  "build-src": "rollup -c",
48
50
  "build-types": "tsc -p tsconfig.types.json --emitDeclarationOnly",
@@ -63,33 +65,28 @@
63
65
  "lodash.get": "4.4.2"
64
66
  },
65
67
  "devDependencies": {
66
- "@babel/core": "7.26.10",
67
- "@babel/preset-env": "7.26.9",
68
- "@babel/preset-typescript": "7.26.0",
69
- "@eslint/js": "9.22.0",
68
+ "@babel/core": "7.28.4",
69
+ "@babel/preset-env": "7.28.3",
70
+ "@babel/preset-typescript": "7.27.1",
71
+ "@eslint/js": "9.35.0",
70
72
  "@rollup/plugin-alias": "5.1.1",
71
73
  "@rollup/plugin-replace": "6.0.2",
72
74
  "@types/babel__preset-env": "7.10.0",
73
75
  "@types/eslint__js": "8.42.3",
74
- "@types/jest": "29.5.14",
75
76
  "@types/jszip": "3.4.1",
76
- "@types/node": "22.13.10",
77
- "@types/ts-nameof": "4.2.5",
78
- "babel-jest": "29.7.0",
77
+ "@types/node": "24.3.1",
78
+ "@vitest/ui": "2.1.8",
79
79
  "babel-loader": "10.0.0",
80
- "babel-plugin-ts-nameof": "4.2.1",
81
- "eslint": "9.22.0",
82
- "globals": "16.0.0",
83
- "jest": "29.7.0",
84
- "jest-html-reporters": "3.1.7",
85
- "jest-junit": "16.0.0",
80
+ "eslint": "9.35.0",
81
+ "globals": "16.3.0",
86
82
  "lorem-ipsum": "2.0.8",
87
83
  "rimraf": "6.0.1",
88
- "rollup": "4.35.0",
84
+ "rollup": "4.50.1",
89
85
  "rollup-plugin-auto-external": "2.0.0",
90
86
  "rollup-plugin-babel": "4.4.0",
91
87
  "rollup-plugin-node-resolve": "5.2.0",
92
- "typescript": "5.8.2",
93
- "typescript-eslint": "8.26.1"
88
+ "typescript": "5.9.2",
89
+ "typescript-eslint": "8.42.0",
90
+ "vitest": "2.1.8"
94
91
  }
95
92
  }
@@ -13,7 +13,7 @@ export class TagParser {
13
13
 
14
14
  constructor(delimiters: Delimiters) {
15
15
  if (!delimiters)
16
- throw new InternalArgumentMissingError(nameof(delimiters));
16
+ throw new InternalArgumentMissingError("delimiters");
17
17
 
18
18
  this.delimiters = delimiters;
19
19
 
package/src/delimiters.ts CHANGED
@@ -14,7 +14,7 @@ export class Delimiters {
14
14
  this.encodeAndValidate();
15
15
 
16
16
  if (this.containerTagOpen === this.containerTagClose)
17
- throw new Error(`${nameof(this.containerTagOpen)} can not be equal to ${nameof(this.containerTagClose)}`);
17
+ throw new Error(`containerTagOpen can not be equal to containerTagClose`);
18
18
  }
19
19
 
20
20
  private encodeAndValidate() {
@@ -116,7 +116,7 @@ class Query {
116
116
  return null;
117
117
 
118
118
  if (!xml.query.isTextNode(node))
119
- throw new Error(`'Invalid argument ${nameof(node)}. Expected a XmlTextNode.`);
119
+ throw new Error(`'Invalid argument node. Expected a XmlTextNode.`);
120
120
 
121
121
  return xml.query.findParent(node, officeMarkup.query.isTextNode) as XmlGeneralNode;
122
122
  }
@@ -262,7 +262,7 @@ class Modify {
262
262
  // input validation
263
263
  const containingParagraph = officeMarkup.query.containingParagraphNode(textNode);
264
264
  if (containingParagraph != paragraph)
265
- throw new Error(`Node '${nameof(textNode)}' is not a descendant of '${nameof(paragraph)}'.`);
265
+ throw new Error(`Node 'textNode' is not a contained in the specified paragraph.`);
266
266
 
267
267
  const runNode = officeMarkup.query.containingRunNode(textNode);
268
268
  const wordTextNode = officeMarkup.query.containingTextNode(textNode);
@@ -50,7 +50,7 @@ export class OpenXmlPart {
50
50
  public async getText(): Promise<string> {
51
51
  const xmlDocument = await this.xmlRoot();
52
52
 
53
- // ugly but good enough...
53
+ // Ugly but good enough...
54
54
  const xmlString = xml.parser.serializeFile(xmlDocument);
55
55
  const domDocument = xml.parser.domParse(xmlString);
56
56
 
@@ -23,22 +23,38 @@ export type RelTargetMode = 'Internal' | 'External';
23
23
 
24
24
  export class Relationship {
25
25
 
26
- public static fromXml(xml: XmlGeneralNode): Relationship {
26
+ public static fromXml(partDir: string, xml: XmlGeneralNode): Relationship {
27
27
  return new Relationship({
28
28
  id: xml.attributes?.['Id'],
29
29
  type: xml.attributes?.['Type'],
30
- target: Relationship.normalizeRelTarget(xml.attributes?.['Target']),
30
+ target: Relationship.normalizeRelTarget(partDir, xml.attributes?.['Target']),
31
31
  targetMode: xml.attributes?.['TargetMode'] as RelTargetMode,
32
32
  });
33
33
  }
34
34
 
35
- public static normalizeRelTarget(target: string): string {
35
+ public static normalizeRelTarget(partDir: string, target: string): string {
36
36
  if (!target) {
37
37
  return target;
38
38
  }
39
+
40
+ // Remove leading slashes from input
41
+ if (partDir.startsWith('/')) {
42
+ partDir = partDir.substring(1);
43
+ }
44
+ if (target.startsWith('/')) {
45
+ target = target.substring(1);
46
+ }
47
+
48
+ // Convert target to relative path
49
+ if (target.startsWith(partDir)) {
50
+ target = target.substring(partDir.length);
51
+ }
52
+
53
+ // Remove leading slashes from output
39
54
  if (target.startsWith('/')) {
40
- return target.substring(1);
55
+ target = target.substring(1);
41
56
  }
57
+
42
58
  return target;
43
59
  }
44
60
 
@@ -56,11 +72,11 @@ export class Relationship {
56
72
  const node = xml.create.generalNode('Relationship');
57
73
  node.attributes = {};
58
74
 
59
- // set only non-empty attributes
75
+ // Set only non-empty attributes
60
76
  for (const propKey of Object.keys(this)) {
61
77
  const value = (this as any)[propKey];
62
78
  if (value && typeof value === 'string') {
63
- const attrName = propKey[0].toUpperCase() + propKey.substr(1);
79
+ const attrName = propKey[0].toUpperCase() + propKey.substring(1);
64
80
  node.attributes[attrName] = value;
65
81
  }
66
82
  }
@@ -31,21 +31,21 @@ export class RelsFile {
31
31
  */
32
32
  public async add(relTarget: string, relType: string, relTargetMode?: RelTargetMode): Promise<string> {
33
33
 
34
- // if relTarget is an internal file it should be relative to the part dir
34
+ // If relTarget is an internal file it should be relative to the part dir
35
35
  if (this.partDir && relTarget.startsWith(this.partDir)) {
36
- relTarget = relTarget.substr(this.partDir.length + 1);
36
+ relTarget = relTarget.substring(this.partDir.length + 1);
37
37
  }
38
38
 
39
- // parse rels file
39
+ // Parse rels file
40
40
  await this.parseRelsFile();
41
41
 
42
- // already exists?
42
+ // Already exists?
43
43
  const relTargetKey = this.getRelTargetKey(relType, relTarget);
44
44
  let relId = this.relTargets[relTargetKey];
45
45
  if (relId)
46
46
  return relId;
47
47
 
48
- // create rel node
48
+ // Create rel node
49
49
  relId = this.getNextRelId();
50
50
  const rel = new Relationship({
51
51
  id: relId,
@@ -54,11 +54,11 @@ export class RelsFile {
54
54
  targetMode: relTargetMode
55
55
  });
56
56
 
57
- // update lookups
57
+ // Update lookups
58
58
  this.rels[relId] = rel;
59
59
  this.relTargets[relTargetKey] = relId;
60
60
 
61
- // return
61
+ // Return
62
62
  return relId;
63
63
  }
64
64
 
@@ -80,21 +80,21 @@ export class RelsFile {
80
80
  */
81
81
  public async save(): Promise<void> {
82
82
 
83
- // not change - no need to save
83
+ // Not change - no need to save
84
84
  if (!this.rels)
85
85
  return;
86
86
 
87
- // create rels xml
87
+ // Create rels xml
88
88
  const root = this.createRootNode();
89
89
  root.childNodes = Object.values(this.rels).map(rel => rel.toXml());
90
90
 
91
- // serialize and save
91
+ // Serialize and save
92
92
  const xmlContent = xml.parser.serializeFile(root);
93
93
  this.zip.setFile(this.relsFilePath, xmlContent);
94
94
  }
95
95
 
96
96
  //
97
- // private methods
97
+ // Private methods
98
98
  //
99
99
 
100
100
  private getNextRelId(): string {
@@ -110,11 +110,11 @@ export class RelsFile {
110
110
 
111
111
  private async parseRelsFile(): Promise<void> {
112
112
 
113
- // already parsed
113
+ // Already parsed
114
114
  if (this.rels)
115
115
  return;
116
116
 
117
- // parse xml
117
+ // Parse xml
118
118
  let root: XmlNode;
119
119
  const relsFile = this.zip.getFile(this.relsFilePath);
120
120
  if (relsFile) {
@@ -124,12 +124,13 @@ export class RelsFile {
124
124
  root = this.createRootNode();
125
125
  }
126
126
 
127
- // parse relationship nodes
127
+ // Parse relationship nodes
128
128
  this.rels = {};
129
129
  this.relTargets = {};
130
130
  for (const relNode of root.childNodes) {
131
+ const genRelNode = relNode as XmlGeneralNode;
131
132
 
132
- const attributes = (relNode as XmlGeneralNode).attributes;
133
+ const attributes = genRelNode.attributes;
133
134
  if (!attributes)
134
135
  continue;
135
136
 
@@ -137,15 +138,13 @@ export class RelsFile {
137
138
  if (!idAttr)
138
139
  continue;
139
140
 
140
- // store rel
141
- const rel = Relationship.fromXml(relNode as XmlGeneralNode);
141
+ // Store rel
142
+ const rel = Relationship.fromXml(this.partDir, genRelNode);
142
143
  this.rels[idAttr] = rel;
143
144
 
144
- // create rel target lookup
145
- const typeAttr = attributes['Type'];
146
- const targetAttr = Relationship.normalizeRelTarget(attributes['Target']);
147
- if (typeAttr && targetAttr) {
148
- const relTargetKey = this.getRelTargetKey(typeAttr, targetAttr);
145
+ // Create rel target lookup
146
+ if (rel.type && rel.target) {
147
+ const relTargetKey = this.getRelTargetKey(rel.type, rel.target);
149
148
  this.relTargets[relTargetKey] = idAttr;
150
149
  }
151
150
  }
@@ -22,7 +22,7 @@ export class TemplateHandler {
22
22
  this.options = new TemplateHandlerOptions(options);
23
23
 
24
24
  //
25
- // this is the library's composition root
25
+ // This is the library's composition root
26
26
  //
27
27
 
28
28
  const delimiterSearcher = new DelimiterSearcher();
@@ -64,15 +64,15 @@ export class TemplateHandler {
64
64
  }
65
65
 
66
66
  //
67
- // public methods
67
+ // Public methods
68
68
  //
69
69
 
70
70
  public async process<T extends Binary>(templateFile: T, data: TemplateData): Promise<T> {
71
71
 
72
- // load the docx file
72
+ // Load the docx file
73
73
  const docx = await Docx.load(templateFile);
74
74
 
75
- // prepare context
75
+ // Prepare context
76
76
  const scopeData = new ScopeData(data);
77
77
  scopeData.scopeDataResolver = this.options.scopeDataResolver;
78
78
  const context: TemplateContext = {
@@ -89,18 +89,18 @@ export class TemplateHandler {
89
89
 
90
90
  context.currentPart = part;
91
91
 
92
- // extensions - before compilation
92
+ // Extensions - before compilation
93
93
  await this.callExtensions(this.options.extensions?.beforeCompilation, scopeData, context);
94
94
 
95
- // compilation (do replacements)
95
+ // Compilation (do replacements)
96
96
  const xmlRoot = await part.xmlRoot();
97
97
  await this.compiler.compile(xmlRoot, scopeData, context);
98
98
 
99
- // extensions - after compilation
99
+ // Extensions - after compilation
100
100
  await this.callExtensions(this.options.extensions?.afterCompilation, scopeData, context);
101
101
  }
102
102
 
103
- // export the result
103
+ // Export the result
104
104
  return docx.export();
105
105
  }
106
106
 
@@ -171,7 +171,7 @@ export class TemplateHandler {
171
171
  }
172
172
 
173
173
  //
174
- // private methods
174
+ // Private methods
175
175
  //
176
176
 
177
177
  private async callExtensions(extensions: TemplateExtension[], scopeData: ScopeData, context: TemplateContext): Promise<void> {
package/src/utils/path.ts CHANGED
@@ -2,9 +2,16 @@ export class Path {
2
2
 
3
3
  public static getFilename(path: string): string {
4
4
  const lastSlashIndex = path.lastIndexOf('/');
5
- return path.substr(lastSlashIndex + 1);
5
+ return path.substring(lastSlashIndex + 1);
6
6
  }
7
7
 
8
+ /**
9
+ * Get the directory of a path.
10
+ * Exclude the last slash.
11
+ *
12
+ * Example:
13
+ * /folder/subfolder/file.txt -> /folder/subfolder
14
+ */
8
15
  public static getDirectory(path: string): string {
9
16
  const lastSlashIndex = path.lastIndexOf('/');
10
17
  return path.substring(0, lastSlashIndex);
package/src/xml/xml.ts CHANGED
@@ -41,7 +41,7 @@ class Parser {
41
41
 
42
42
  public domParse(str: string): Document {
43
43
  if (str === null || str === undefined)
44
- throw new InternalArgumentMissingError(nameof(str));
44
+ throw new InternalArgumentMissingError("str");
45
45
 
46
46
  return Parser.parser.parseFromString(str, "text/xml");
47
47
  }
@@ -53,7 +53,7 @@ class Parser {
53
53
  */
54
54
  public encodeValue(str: string): string {
55
55
  if (str === null || str === undefined)
56
- throw new InternalArgumentMissingError(nameof(str));
56
+ throw new InternalArgumentMissingError("str");
57
57
  if (typeof str !== 'string')
58
58
  throw new TypeError(`Expected a string, got '${(str as any).constructor.name}'.`);
59
59
 
@@ -165,7 +165,7 @@ class Create {
165
165
 
166
166
  public cloneNode<T extends XmlNode>(node: T, deep: boolean): T {
167
167
  if (!node)
168
- throw new InternalArgumentMissingError(nameof(node));
168
+ throw new InternalArgumentMissingError("node");
169
169
 
170
170
  if (!deep) {
171
171
  const clone = Object.assign({}, node);
@@ -339,9 +339,9 @@ class Query {
339
339
  */
340
340
  public siblingsInRange(firstNode: XmlNode, lastNode: XmlNode): XmlNode[] {
341
341
  if (!firstNode)
342
- throw new InternalArgumentMissingError(nameof(firstNode));
342
+ throw new InternalArgumentMissingError("firstNode");
343
343
  if (!lastNode)
344
- throw new InternalArgumentMissingError(nameof(lastNode));
344
+ throw new InternalArgumentMissingError("lastNode");
345
345
 
346
346
  const range: XmlNode[] = [];
347
347
  let curNode = firstNode;
@@ -380,12 +380,12 @@ class Modify {
380
380
  */
381
381
  public insertBefore(newNode: XmlNode, referenceNode: XmlNode): void {
382
382
  if (!newNode)
383
- throw new InternalArgumentMissingError(nameof(newNode));
383
+ throw new InternalArgumentMissingError("newNode");
384
384
  if (!referenceNode)
385
- throw new InternalArgumentMissingError(nameof(referenceNode));
385
+ throw new InternalArgumentMissingError("referenceNode");
386
386
 
387
387
  if (!referenceNode.parentNode)
388
- throw new Error(`'${nameof(referenceNode)}' has no parent`);
388
+ throw new Error(`'referenceNode' has no parent`);
389
389
 
390
390
  const childNodes = referenceNode.parentNode.childNodes;
391
391
  const beforeNodeIndex = childNodes.indexOf(referenceNode);
@@ -400,12 +400,12 @@ class Modify {
400
400
  */
401
401
  public insertAfter(newNode: XmlNode, referenceNode: XmlNode): void {
402
402
  if (!newNode)
403
- throw new InternalArgumentMissingError(nameof(newNode));
403
+ throw new InternalArgumentMissingError("newNode");
404
404
  if (!referenceNode)
405
- throw new InternalArgumentMissingError(nameof(referenceNode));
405
+ throw new InternalArgumentMissingError("referenceNode");
406
406
 
407
407
  if (!referenceNode.parentNode)
408
- throw new Error(`'${nameof(referenceNode)}' has no parent`);
408
+ throw new Error(`'referenceNode' has no parent`);
409
409
 
410
410
  const childNodes = referenceNode.parentNode.childNodes;
411
411
  const referenceNodeIndex = childNodes.indexOf(referenceNode);
@@ -414,11 +414,11 @@ class Modify {
414
414
 
415
415
  public insertChild(parent: XmlNode, child: XmlNode, childIndex: number): void {
416
416
  if (!parent)
417
- throw new InternalArgumentMissingError(nameof(parent));
417
+ throw new InternalArgumentMissingError("parent");
418
418
  if (xml.query.isTextNode(parent))
419
419
  throw new Error('Appending children to text nodes is forbidden');
420
420
  if (!child)
421
- throw new InternalArgumentMissingError(nameof(child));
421
+ throw new InternalArgumentMissingError("child");
422
422
 
423
423
  if (!parent.childNodes)
424
424
  parent.childNodes = [];
@@ -449,11 +449,11 @@ class Modify {
449
449
 
450
450
  public appendChild(parent: XmlNode, child: XmlNode): void {
451
451
  if (!parent)
452
- throw new InternalArgumentMissingError(nameof(parent));
452
+ throw new InternalArgumentMissingError("parent");
453
453
  if (xml.query.isTextNode(parent))
454
454
  throw new Error('Appending children to text nodes is forbidden');
455
455
  if (!child)
456
- throw new InternalArgumentMissingError(nameof(child));
456
+ throw new InternalArgumentMissingError("child");
457
457
 
458
458
  if (!parent.childNodes)
459
459
  parent.childNodes = [];
@@ -477,7 +477,7 @@ class Modify {
477
477
  */
478
478
  public remove(node: XmlNode): void {
479
479
  if (!node)
480
- throw new InternalArgumentMissingError(nameof(node));
480
+ throw new InternalArgumentMissingError("node");
481
481
 
482
482
  if (!node.parentNode)
483
483
  throw new Error('Node has no parent');
@@ -497,9 +497,9 @@ class Modify {
497
497
  public removeChild(parent: XmlNode, childIndex: number): XmlNode;
498
498
  public removeChild(parent: XmlNode, childOrIndex: XmlNode | number): XmlNode {
499
499
  if (!parent)
500
- throw new InternalArgumentMissingError(nameof(parent));
500
+ throw new InternalArgumentMissingError("parent");
501
501
  if (childOrIndex === null || childOrIndex === undefined)
502
- throw new InternalArgumentMissingError(nameof(childOrIndex));
502
+ throw new InternalArgumentMissingError("childOrIndex");
503
503
 
504
504
  if (!parent.childNodes || !parent.childNodes.length)
505
505
  throw new Error('Parent node has node children');
@@ -579,7 +579,7 @@ class Modify {
579
579
  public splitByChild(parent: XmlNode, child: XmlNode, removeChild: boolean): [XmlNode, XmlNode] {
580
580
 
581
581
  if (child.parentNode != parent)
582
- throw new Error(`Node '${nameof(child)}' is not a direct child of '${nameof(parent)}'.`);
582
+ throw new Error(`Node 'child' is not a direct child of 'parent'.`);
583
583
 
584
584
  // create childless clone 'left'
585
585
  const left = xml.create.cloneNode(parent, false);
@@ -10,7 +10,7 @@ export class JsZipHelper {
10
10
  public static toJsZipOutputType(binaryOrType: Binary | Constructor<Binary>): JSZip.OutputType {
11
11
 
12
12
  if (!binaryOrType)
13
- throw new InternalArgumentMissingError(nameof(binaryOrType));
13
+ throw new InternalArgumentMissingError("binaryOrType");
14
14
 
15
15
  let binaryType: Constructor<Binary>;
16
16
  if (typeof binaryOrType === 'function') {
package/src/zip/zip.ts CHANGED
@@ -19,6 +19,9 @@ export class Zip {
19
19
  }
20
20
 
21
21
  public getFile(path: string): ZipObject {
22
+ if (path && path.startsWith('/')) {
23
+ path = path.substring(1);
24
+ }
22
25
  const internalZipObject = this.zip.files[path];
23
26
  if (!internalZipObject)
24
27
  return null;