easy-template-x 6.2.1 → 6.2.2

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.
@@ -404,6 +404,10 @@ function stringValue(val) {
404
404
  function normalizeDoubleQuotes(text) {
405
405
  return text.replace(nonStandardDoubleQuotesRegex, standardDoubleQuotes);
406
406
  }
407
+ function countOccurrences(text, substring) {
408
+ // https://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string
409
+ return (text.match(new RegExp(substring, 'g')) || []).length;
410
+ }
407
411
 
408
412
  class JsZipHelper {
409
413
  static toJsZipOutputType(binaryOrType) {
@@ -1215,13 +1219,12 @@ class ContentTypesFile {
1215
1219
  // Parse the content types file
1216
1220
  await this.parseContentTypesFile();
1217
1221
 
1218
- // Mime type already exists
1219
- if (this.contentTypes[mime]) return;
1220
-
1221
1222
  // Extension already exists
1222
- // Unfortunately, this can happen in real life so we need to handle it.
1223
+ //
1224
+ // Multiple extensions may map to the same mime type, but a single
1225
+ // extension must only map to one mime type.
1223
1226
  const extension = MimeTypeHelper.getDefaultExtension(mime);
1224
- if (Object.values(this.contentTypes).includes(extension)) return;
1227
+ if (this.contentTypes[extension]) return;
1225
1228
 
1226
1229
  // Add new node
1227
1230
  const typeNode = xml.create.generalNode('Default');
@@ -1233,11 +1236,11 @@ class ContentTypesFile {
1233
1236
 
1234
1237
  // Update state
1235
1238
  this.addedNew = true;
1236
- this.contentTypes[mime] = extension;
1239
+ this.contentTypes[extension] = mime;
1237
1240
  }
1238
- async count() {
1241
+ async xmlString() {
1239
1242
  await this.parseContentTypesFile();
1240
- return this.root.childNodes.filter(node => !xml.query.isTextNode(node)).length;
1243
+ return xml.parser.serializeFile(this.root);
1241
1244
  }
1242
1245
 
1243
1246
  /**
@@ -1266,7 +1269,7 @@ class ContentTypesFile {
1266
1269
  if (!contentTypeAttribute) continue;
1267
1270
  const extensionAttribute = genNode.attributes['Extension'];
1268
1271
  if (!extensionAttribute) continue;
1269
- this.contentTypes[contentTypeAttribute] = extensionAttribute;
1272
+ this.contentTypes[extensionAttribute] = contentTypeAttribute;
1270
1273
  }
1271
1274
  }
1272
1275
  }
@@ -1286,39 +1289,38 @@ class MediaFiles {
1286
1289
  * Returns the media file path.
1287
1290
  */
1288
1291
  async add(mediaFile, mime) {
1289
- // check if already added
1292
+ // Check if already added
1290
1293
  if (this.files.has(mediaFile)) return this.files.get(mediaFile);
1291
1294
 
1292
- // hash existing media files
1295
+ // Hash existing media files
1293
1296
  await this.hashMediaFiles();
1294
1297
 
1295
- // hash the new file
1298
+ // Hash the new file
1296
1299
  // Note: Even though hashing the base64 string may seem inefficient
1297
1300
  // (requires extra step in some cases) in practice it is significantly
1298
1301
  // faster than hashing a 'binarystring'.
1299
1302
  const base64 = await Binary.toBase64(mediaFile);
1300
1303
  const hash = sha1(base64);
1301
1304
 
1302
- // check if file already exists
1303
- // note: this can be optimized by keeping both mapping by filename as well as by hash
1305
+ // Check if file already exists
1306
+ // Note: this can be optimized by keeping both mapping by filename as well as by hash
1304
1307
  let path = Object.keys(this.hashes).find(p => this.hashes[p] === hash);
1305
1308
  if (path) return path;
1306
1309
 
1307
- // generate unique media file name
1310
+ // Generate unique media file name
1311
+ const baseFilename = this.baseFilename(mime);
1308
1312
  const extension = MimeTypeHelper.getDefaultExtension(mime);
1309
1313
  do {
1310
1314
  this.nextFileId++;
1311
- path = `${MediaFiles.mediaDir}/media${this.nextFileId}.${extension}`;
1315
+ path = `${MediaFiles.mediaDir}/${baseFilename}${this.nextFileId}.${extension}`;
1312
1316
  } while (this.hashes[path]);
1313
1317
 
1314
- // add media to zip
1315
- await this.zip.setFile(path, mediaFile);
1318
+ // Add media to zip
1319
+ this.zip.setFile(path, mediaFile);
1316
1320
 
1317
- // add media to our lookups
1321
+ // Add media to our lookups
1318
1322
  this.hashes[path] = hash;
1319
1323
  this.files.set(mediaFile, path);
1320
-
1321
- // return
1322
1324
  return path;
1323
1325
  }
1324
1326
  async count() {
@@ -1334,9 +1336,15 @@ class MediaFiles {
1334
1336
  if (!filename) continue;
1335
1337
  const fileData = await this.zip.getFile(path).getContentBase64();
1336
1338
  const fileHash = sha1(fileData);
1337
- this.hashes[filename] = fileHash;
1339
+ this.hashes[path] = fileHash;
1338
1340
  }
1339
1341
  }
1342
+ baseFilename(mime) {
1343
+ // Naive heuristic.
1344
+ // May need to be modified if we're going to support more mime types.
1345
+ const parts = mime.split('/');
1346
+ return parts[0];
1347
+ }
1340
1348
  }
1341
1349
 
1342
1350
  /**
@@ -2603,19 +2611,17 @@ class ImagePlugin extends TemplatePlugin {
2603
2611
  if (!context.pluginContext[this.contentType]) {
2604
2612
  context.pluginContext[this.contentType] = {};
2605
2613
  }
2606
- if (!context.pluginContext[this.contentType]) {
2607
- context.pluginContext[this.contentType] = {};
2608
- }
2609
2614
  const pluginContext = context.pluginContext[this.contentType];
2610
2615
  if (!pluginContext.lastDrawingObjectId) {
2611
2616
  pluginContext.lastDrawingObjectId = {};
2612
2617
  }
2613
2618
  const lastIdMap = pluginContext.lastDrawingObjectId;
2619
+ const lastIdKey = context.currentPart.path;
2614
2620
 
2615
2621
  // Get next image ID if already initialized.
2616
- if (lastIdMap[context.currentPart.path]) {
2617
- lastIdMap[context.currentPart.path]++;
2618
- return lastIdMap[context.currentPart.path];
2622
+ if (lastIdMap[lastIdKey]) {
2623
+ lastIdMap[lastIdKey]++;
2624
+ return lastIdMap[lastIdKey];
2619
2625
  }
2620
2626
 
2621
2627
  // Init next image ID.
@@ -2632,8 +2638,8 @@ class ImagePlugin extends TemplatePlugin {
2632
2638
  // Start counting from the current max
2633
2639
  const ids = docProps.map(prop => parseInt(prop.attributes.id)).filter(isNumber);
2634
2640
  const maxId = Math.max(...ids, 0);
2635
- lastIdMap[context.currentPart.path] = maxId + 1;
2636
- return lastIdMap[context.currentPart.path];
2641
+ lastIdMap[lastIdKey] = maxId + 1;
2642
+ return lastIdMap[lastIdKey];
2637
2643
  }
2638
2644
  createMarkup(imageId, relId, content) {
2639
2645
  // http://officeopenxml.com/drwPicInline.php
@@ -4691,7 +4697,7 @@ class TemplateHandler {
4691
4697
  /**
4692
4698
  * Version number of the `easy-template-x` library.
4693
4699
  */
4694
- version = "6.2.1" ;
4700
+ version = "6.2.2" ;
4695
4701
  constructor(options) {
4696
4702
  this.options = new TemplateHandlerOptions(options);
4697
4703
 
@@ -4836,6 +4842,7 @@ class TemplateHandler {
4836
4842
  exports.Base64 = Base64;
4837
4843
  exports.Binary = Binary;
4838
4844
  exports.COMMENT_NODE_NAME = COMMENT_NODE_NAME;
4845
+ exports.ChartPlugin = ChartPlugin;
4839
4846
  exports.DelimiterSearcher = DelimiterSearcher;
4840
4847
  exports.Delimiters = Delimiters;
4841
4848
  exports.Docx = Docx;
@@ -4887,6 +4894,7 @@ exports.XmlTreeIterator = XmlTreeIterator;
4887
4894
  exports.XmlUtils = XmlUtils;
4888
4895
  exports.Zip = Zip;
4889
4896
  exports.ZipObject = ZipObject;
4897
+ exports.countOccurrences = countOccurrences;
4890
4898
  exports.createDefaultPlugins = createDefaultPlugins;
4891
4899
  exports.first = first;
4892
4900
  exports.inheritsFrom = inheritsFrom;
@@ -402,6 +402,10 @@ function stringValue(val) {
402
402
  function normalizeDoubleQuotes(text) {
403
403
  return text.replace(nonStandardDoubleQuotesRegex, standardDoubleQuotes);
404
404
  }
405
+ function countOccurrences(text, substring) {
406
+ // https://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string
407
+ return (text.match(new RegExp(substring, 'g')) || []).length;
408
+ }
405
409
 
406
410
  class JsZipHelper {
407
411
  static toJsZipOutputType(binaryOrType) {
@@ -1213,13 +1217,12 @@ class ContentTypesFile {
1213
1217
  // Parse the content types file
1214
1218
  await this.parseContentTypesFile();
1215
1219
 
1216
- // Mime type already exists
1217
- if (this.contentTypes[mime]) return;
1218
-
1219
1220
  // Extension already exists
1220
- // Unfortunately, this can happen in real life so we need to handle it.
1221
+ //
1222
+ // Multiple extensions may map to the same mime type, but a single
1223
+ // extension must only map to one mime type.
1221
1224
  const extension = MimeTypeHelper.getDefaultExtension(mime);
1222
- if (Object.values(this.contentTypes).includes(extension)) return;
1225
+ if (this.contentTypes[extension]) return;
1223
1226
 
1224
1227
  // Add new node
1225
1228
  const typeNode = xml.create.generalNode('Default');
@@ -1231,11 +1234,11 @@ class ContentTypesFile {
1231
1234
 
1232
1235
  // Update state
1233
1236
  this.addedNew = true;
1234
- this.contentTypes[mime] = extension;
1237
+ this.contentTypes[extension] = mime;
1235
1238
  }
1236
- async count() {
1239
+ async xmlString() {
1237
1240
  await this.parseContentTypesFile();
1238
- return this.root.childNodes.filter(node => !xml.query.isTextNode(node)).length;
1241
+ return xml.parser.serializeFile(this.root);
1239
1242
  }
1240
1243
 
1241
1244
  /**
@@ -1264,7 +1267,7 @@ class ContentTypesFile {
1264
1267
  if (!contentTypeAttribute) continue;
1265
1268
  const extensionAttribute = genNode.attributes['Extension'];
1266
1269
  if (!extensionAttribute) continue;
1267
- this.contentTypes[contentTypeAttribute] = extensionAttribute;
1270
+ this.contentTypes[extensionAttribute] = contentTypeAttribute;
1268
1271
  }
1269
1272
  }
1270
1273
  }
@@ -1284,39 +1287,38 @@ class MediaFiles {
1284
1287
  * Returns the media file path.
1285
1288
  */
1286
1289
  async add(mediaFile, mime) {
1287
- // check if already added
1290
+ // Check if already added
1288
1291
  if (this.files.has(mediaFile)) return this.files.get(mediaFile);
1289
1292
 
1290
- // hash existing media files
1293
+ // Hash existing media files
1291
1294
  await this.hashMediaFiles();
1292
1295
 
1293
- // hash the new file
1296
+ // Hash the new file
1294
1297
  // Note: Even though hashing the base64 string may seem inefficient
1295
1298
  // (requires extra step in some cases) in practice it is significantly
1296
1299
  // faster than hashing a 'binarystring'.
1297
1300
  const base64 = await Binary.toBase64(mediaFile);
1298
1301
  const hash = sha1(base64);
1299
1302
 
1300
- // check if file already exists
1301
- // note: this can be optimized by keeping both mapping by filename as well as by hash
1303
+ // Check if file already exists
1304
+ // Note: this can be optimized by keeping both mapping by filename as well as by hash
1302
1305
  let path = Object.keys(this.hashes).find(p => this.hashes[p] === hash);
1303
1306
  if (path) return path;
1304
1307
 
1305
- // generate unique media file name
1308
+ // Generate unique media file name
1309
+ const baseFilename = this.baseFilename(mime);
1306
1310
  const extension = MimeTypeHelper.getDefaultExtension(mime);
1307
1311
  do {
1308
1312
  this.nextFileId++;
1309
- path = `${MediaFiles.mediaDir}/media${this.nextFileId}.${extension}`;
1313
+ path = `${MediaFiles.mediaDir}/${baseFilename}${this.nextFileId}.${extension}`;
1310
1314
  } while (this.hashes[path]);
1311
1315
 
1312
- // add media to zip
1313
- await this.zip.setFile(path, mediaFile);
1316
+ // Add media to zip
1317
+ this.zip.setFile(path, mediaFile);
1314
1318
 
1315
- // add media to our lookups
1319
+ // Add media to our lookups
1316
1320
  this.hashes[path] = hash;
1317
1321
  this.files.set(mediaFile, path);
1318
-
1319
- // return
1320
1322
  return path;
1321
1323
  }
1322
1324
  async count() {
@@ -1332,9 +1334,15 @@ class MediaFiles {
1332
1334
  if (!filename) continue;
1333
1335
  const fileData = await this.zip.getFile(path).getContentBase64();
1334
1336
  const fileHash = sha1(fileData);
1335
- this.hashes[filename] = fileHash;
1337
+ this.hashes[path] = fileHash;
1336
1338
  }
1337
1339
  }
1340
+ baseFilename(mime) {
1341
+ // Naive heuristic.
1342
+ // May need to be modified if we're going to support more mime types.
1343
+ const parts = mime.split('/');
1344
+ return parts[0];
1345
+ }
1338
1346
  }
1339
1347
 
1340
1348
  /**
@@ -2601,19 +2609,17 @@ class ImagePlugin extends TemplatePlugin {
2601
2609
  if (!context.pluginContext[this.contentType]) {
2602
2610
  context.pluginContext[this.contentType] = {};
2603
2611
  }
2604
- if (!context.pluginContext[this.contentType]) {
2605
- context.pluginContext[this.contentType] = {};
2606
- }
2607
2612
  const pluginContext = context.pluginContext[this.contentType];
2608
2613
  if (!pluginContext.lastDrawingObjectId) {
2609
2614
  pluginContext.lastDrawingObjectId = {};
2610
2615
  }
2611
2616
  const lastIdMap = pluginContext.lastDrawingObjectId;
2617
+ const lastIdKey = context.currentPart.path;
2612
2618
 
2613
2619
  // Get next image ID if already initialized.
2614
- if (lastIdMap[context.currentPart.path]) {
2615
- lastIdMap[context.currentPart.path]++;
2616
- return lastIdMap[context.currentPart.path];
2620
+ if (lastIdMap[lastIdKey]) {
2621
+ lastIdMap[lastIdKey]++;
2622
+ return lastIdMap[lastIdKey];
2617
2623
  }
2618
2624
 
2619
2625
  // Init next image ID.
@@ -2630,8 +2636,8 @@ class ImagePlugin extends TemplatePlugin {
2630
2636
  // Start counting from the current max
2631
2637
  const ids = docProps.map(prop => parseInt(prop.attributes.id)).filter(isNumber);
2632
2638
  const maxId = Math.max(...ids, 0);
2633
- lastIdMap[context.currentPart.path] = maxId + 1;
2634
- return lastIdMap[context.currentPart.path];
2639
+ lastIdMap[lastIdKey] = maxId + 1;
2640
+ return lastIdMap[lastIdKey];
2635
2641
  }
2636
2642
  createMarkup(imageId, relId, content) {
2637
2643
  // http://officeopenxml.com/drwPicInline.php
@@ -4689,7 +4695,7 @@ class TemplateHandler {
4689
4695
  /**
4690
4696
  * Version number of the `easy-template-x` library.
4691
4697
  */
4692
- version = "6.2.1" ;
4698
+ version = "6.2.2" ;
4693
4699
  constructor(options) {
4694
4700
  this.options = new TemplateHandlerOptions(options);
4695
4701
 
@@ -4831,4 +4837,4 @@ class TemplateHandler {
4831
4837
  }
4832
4838
  }
4833
4839
 
4834
- export { Base64, Binary, COMMENT_NODE_NAME, DelimiterSearcher, Delimiters, Docx, ImagePlugin, InternalArgumentMissingError, InternalError, LOOP_CONTENT_TYPE, LinkPlugin, LoopPlugin, MalformedFileError, MaxXmlDepthError, MimeType, MimeTypeHelper, MissingCloseDelimiterError, MissingStartDelimiterError, OfficeMarkup, OmlAttribute, OmlNode, OpenXmlPart, Path, PluginContent, RawXmlPlugin, Regex, RelType, Relationship, ScopeData, TEXT_CONTENT_TYPE, TEXT_NODE_NAME, TagDisposition, TagOptionsParseError, TagParser, TemplateCompiler, TemplateDataError, TemplateExtension, TemplateHandler, TemplateHandlerOptions, TemplatePlugin, TemplateSyntaxError, TextPlugin, UnclosedTagError, UnidentifiedFileTypeError, UnknownContentTypeError, UnopenedTagError, UnsupportedFileTypeError, Xlsx, XmlDepthTracker, XmlNodeType, XmlTreeIterator, XmlUtils, Zip, ZipObject, createDefaultPlugins, first, inheritsFrom, isNumber, isPromiseLike, last, normalizeDoubleQuotes, officeMarkup, pushMany, sha1, stringValue, toDictionary, xml };
4840
+ export { Base64, Binary, COMMENT_NODE_NAME, ChartPlugin, DelimiterSearcher, Delimiters, Docx, ImagePlugin, InternalArgumentMissingError, InternalError, LOOP_CONTENT_TYPE, LinkPlugin, LoopPlugin, MalformedFileError, MaxXmlDepthError, MimeType, MimeTypeHelper, MissingCloseDelimiterError, MissingStartDelimiterError, OfficeMarkup, OmlAttribute, OmlNode, OpenXmlPart, Path, PluginContent, RawXmlPlugin, Regex, RelType, Relationship, ScopeData, TEXT_CONTENT_TYPE, TEXT_NODE_NAME, TagDisposition, TagOptionsParseError, TagParser, TemplateCompiler, TemplateDataError, TemplateExtension, TemplateHandler, TemplateHandlerOptions, TemplatePlugin, TemplateSyntaxError, TextPlugin, UnclosedTagError, UnidentifiedFileTypeError, UnknownContentTypeError, UnopenedTagError, UnsupportedFileTypeError, Xlsx, XmlDepthTracker, XmlNodeType, XmlTreeIterator, XmlUtils, Zip, ZipObject, countOccurrences, createDefaultPlugins, first, inheritsFrom, isNumber, isPromiseLike, last, normalizeDoubleQuotes, officeMarkup, pushMany, sha1, stringValue, toDictionary, xml };
@@ -8,7 +8,7 @@ export declare class ContentTypesFile {
8
8
  private readonly zip;
9
9
  constructor(zip: Zip);
10
10
  ensureContentType(mime: MimeType): Promise<void>;
11
- count(): Promise<number>;
11
+ xmlString(): Promise<string>;
12
12
  save(): Promise<void>;
13
13
  private parseContentTypesFile;
14
14
  }
@@ -11,4 +11,5 @@ export declare class MediaFiles {
11
11
  add(mediaFile: Binary, mime: MimeType): Promise<string>;
12
12
  count(): Promise<number>;
13
13
  private hashMediaFiles;
14
+ private baseFilename;
14
15
  }
@@ -3,6 +3,7 @@ export * from './link';
3
3
  export * from './loop';
4
4
  export * from './rawXml';
5
5
  export * from './text';
6
+ export * from "./chart";
6
7
  export * from './defaultPlugins';
7
8
  export * from './pluginContent';
8
9
  export * from './templatePlugin';
@@ -1,2 +1,3 @@
1
1
  export declare function stringValue(val: unknown): string;
2
2
  export declare function normalizeDoubleQuotes(text: string): string;
3
+ export declare function countOccurrences(text: string, substring: string): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-template-x",
3
- "version": "6.2.1",
3
+ "version": "6.2.2",
4
4
  "description": "Generate docx documents from templates, in Node or in the browser.",
5
5
  "keywords": [
6
6
  "docx",
@@ -13,7 +13,7 @@ export class ContentTypesFile {
13
13
 
14
14
  private root: XmlNode;
15
15
 
16
- private contentTypes: Partial<Record<MimeType, string>>;
16
+ private contentTypes: Partial<Record<string, MimeType>>;
17
17
 
18
18
  private readonly zip: Zip;
19
19
 
@@ -26,14 +26,12 @@ export class ContentTypesFile {
26
26
  // Parse the content types file
27
27
  await this.parseContentTypesFile();
28
28
 
29
- // Mime type already exists
30
- if (this.contentTypes[mime])
31
- return;
32
-
33
29
  // Extension already exists
34
- // Unfortunately, this can happen in real life so we need to handle it.
30
+ //
31
+ // Multiple extensions may map to the same mime type, but a single
32
+ // extension must only map to one mime type.
35
33
  const extension = MimeTypeHelper.getDefaultExtension(mime);
36
- if (Object.values(this.contentTypes).includes(extension))
34
+ if (this.contentTypes[extension])
37
35
  return;
38
36
 
39
37
  // Add new node
@@ -46,12 +44,12 @@ export class ContentTypesFile {
46
44
 
47
45
  // Update state
48
46
  this.addedNew = true;
49
- this.contentTypes[mime] = extension;
47
+ this.contentTypes[extension] = mime;
50
48
  }
51
49
 
52
- public async count(): Promise<number> {
50
+ public async xmlString(): Promise<string> {
53
51
  await this.parseContentTypesFile();
54
- return this.root.childNodes.filter(node => !xml.query.isTextNode(node)).length;
52
+ return xml.parser.serializeFile(this.root);
55
53
  }
56
54
 
57
55
  /**
@@ -92,7 +90,7 @@ export class ContentTypesFile {
92
90
  if (!extensionAttribute)
93
91
  continue;
94
92
 
95
- this.contentTypes[contentTypeAttribute] = extensionAttribute;
93
+ this.contentTypes[extensionAttribute] = contentTypeAttribute;
96
94
  }
97
95
  }
98
96
  }
@@ -25,41 +25,41 @@ export class MediaFiles {
25
25
  */
26
26
  public async add(mediaFile: Binary, mime: MimeType): Promise<string> {
27
27
 
28
- // check if already added
28
+ // Check if already added
29
29
  if (this.files.has(mediaFile))
30
30
  return this.files.get(mediaFile);
31
31
 
32
- // hash existing media files
32
+ // Hash existing media files
33
33
  await this.hashMediaFiles();
34
34
 
35
- // hash the new file
35
+ // Hash the new file
36
36
  // Note: Even though hashing the base64 string may seem inefficient
37
37
  // (requires extra step in some cases) in practice it is significantly
38
38
  // faster than hashing a 'binarystring'.
39
39
  const base64 = await Binary.toBase64(mediaFile);
40
40
  const hash = sha1(base64);
41
41
 
42
- // check if file already exists
43
- // note: this can be optimized by keeping both mapping by filename as well as by hash
42
+ // Check if file already exists
43
+ // Note: this can be optimized by keeping both mapping by filename as well as by hash
44
44
  let path = Object.keys(this.hashes).find(p => this.hashes[p] === hash);
45
45
  if (path)
46
46
  return path;
47
47
 
48
- // generate unique media file name
48
+ // Generate unique media file name
49
+ const baseFilename = this.baseFilename(mime);
49
50
  const extension = MimeTypeHelper.getDefaultExtension(mime);
50
51
  do {
51
52
  this.nextFileId++;
52
- path = `${MediaFiles.mediaDir}/media${this.nextFileId}.${extension}`;
53
+ path = `${MediaFiles.mediaDir}/${baseFilename}${this.nextFileId}.${extension}`;
53
54
  } while (this.hashes[path]);
54
55
 
55
- // add media to zip
56
- await this.zip.setFile(path, mediaFile);
56
+ // Add media to zip
57
+ this.zip.setFile(path, mediaFile);
57
58
 
58
- // add media to our lookups
59
+ // Add media to our lookups
59
60
  this.hashes[path] = hash;
60
61
  this.files.set(mediaFile, path);
61
62
 
62
- // return
63
63
  return path;
64
64
  }
65
65
 
@@ -84,7 +84,14 @@ export class MediaFiles {
84
84
 
85
85
  const fileData = await this.zip.getFile(path).getContentBase64();
86
86
  const fileHash = sha1(fileData);
87
- this.hashes[filename] = fileHash;
87
+ this.hashes[path] = fileHash;
88
88
  }
89
89
  }
90
+
91
+ private baseFilename(mime: MimeType): string {
92
+ // Naive heuristic.
93
+ // May need to be modified if we're going to support more mime types.
94
+ const parts = mime.split('/');
95
+ return parts[0];
96
+ }
90
97
  }
@@ -48,20 +48,19 @@ export class ImagePlugin extends TemplatePlugin {
48
48
  if (!context.pluginContext[this.contentType]) {
49
49
  context.pluginContext[this.contentType] = {};
50
50
  }
51
- if (!context.pluginContext[this.contentType]) {
52
- context.pluginContext[this.contentType] = {};
53
- }
54
51
 
55
52
  const pluginContext: ImagePluginContext = context.pluginContext[this.contentType];
56
53
  if (!pluginContext.lastDrawingObjectId) {
57
54
  pluginContext.lastDrawingObjectId = {};
58
55
  }
56
+
59
57
  const lastIdMap = pluginContext.lastDrawingObjectId;
58
+ const lastIdKey = context.currentPart.path;
60
59
 
61
60
  // Get next image ID if already initialized.
62
- if (lastIdMap[context.currentPart.path]) {
63
- lastIdMap[context.currentPart.path]++;
64
- return lastIdMap[context.currentPart.path];
61
+ if (lastIdMap[lastIdKey]) {
62
+ lastIdMap[lastIdKey]++;
63
+ return lastIdMap[lastIdKey];
65
64
  }
66
65
 
67
66
  // Init next image ID.
@@ -79,8 +78,8 @@ export class ImagePlugin extends TemplatePlugin {
79
78
  const ids = docProps.map(prop => parseInt(prop.attributes.id)).filter(isNumber);
80
79
  const maxId = Math.max(...ids, 0);
81
80
 
82
- lastIdMap[context.currentPart.path] = maxId + 1;
83
- return lastIdMap[context.currentPart.path];
81
+ lastIdMap[lastIdKey] = maxId + 1;
82
+ return lastIdMap[lastIdKey];
84
83
  }
85
84
 
86
85
  private createMarkup(imageId: number, relId: string, content: ImageContent): XmlNode {
@@ -3,6 +3,7 @@ export * from './link';
3
3
  export * from './loop';
4
4
  export * from './rawXml';
5
5
  export * from './text';
6
+ export * from "./chart";
6
7
  export * from './defaultPlugins';
7
8
  export * from './pluginContent';
8
9
  export * from './templatePlugin';
package/src/utils/txt.ts CHANGED
@@ -32,3 +32,8 @@ export function stringValue(val: unknown): string {
32
32
  export function normalizeDoubleQuotes(text: string): string {
33
33
  return text.replace(nonStandardDoubleQuotesRegex, standardDoubleQuotes);
34
34
  }
35
+
36
+ export function countOccurrences(text: string, substring: string): number {
37
+ // https://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string
38
+ return (text.match(new RegExp(substring, 'g')) || []).length;
39
+ }