node-pptx-templater 1.1.7 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  <a href="https://github.com/jsuyog2/node-pptx-templater/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/jsuyog2/node-pptx-templater/ci.yml?branch=main&style=flat-square&color=34d399" alt="CI"></a>
14
14
  <a href="https://www.npmjs.com/package/node-pptx-templater"><img src="https://img.shields.io/npm/dm/node-pptx-templater.svg?style=flat-square&color=a855f7" alt="Downloads"></a>
15
15
  <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="MIT License"></a>
16
- <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen?style=flat-square" alt="Node.js 18+"></a>
16
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D20.12.0-brightgreen?style=flat-square" alt="Node.js 20.12.0+"></a>
17
17
  </p>
18
18
 
19
19
  <p align="center">
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "node-pptx-templater",
3
- "version": "1.1.7",
3
+ "version": "1.1.10",
4
4
  "description": "High-performance, low-level PowerPoint (PPTX) OpenXML template engine for Node.js. Dynamically replace text, insert images, update charts (with Excel workbook data caching), and merge table cells without PowerPoint corruption or Repair Mode prompts.",
5
5
  "main": "./src/index.js",
6
+ "module": "./src/index.mjs",
7
+ "types": "./src/index.d.ts",
6
8
  "type": "commonjs",
9
+ "sideEffects": false,
7
10
  "exports": {
8
11
  ".": {
12
+ "types": "./src/index.d.ts",
13
+ "import": "./src/index.mjs",
9
14
  "require": "./src/index.js"
10
15
  }
11
16
  },
@@ -169,26 +174,39 @@
169
174
  },
170
175
  "homepage": "https://jsuyog2.github.io/node-pptx-templater",
171
176
  "engines": {
172
- "node": ">=18.0.0"
177
+ "node": ">=20.12.0"
173
178
  },
174
179
  "dependencies": {
175
- "fast-xml-parser": "^4.3.6",
176
- "jszip": "^3.10.1",
177
- "fs-extra": "^11.2.0",
178
- "commander": "^12.0.0",
179
180
  "chalk": "^4.1.2",
181
+ "commander": "^12.0.0",
182
+ "fast-xml-parser": "^5.9.3",
183
+ "fs-extra": "^11.2.0",
184
+ "jszip": "^3.10.1",
180
185
  "ora": "^5.4.1"
181
186
  },
182
187
  "devDependencies": {
183
- "vitest": "^1.6.0",
184
- "@vitest/coverage-v8": "^1.6.0",
188
+ "@vitest/coverage-v8": "^4.1.9",
185
189
  "eslint": "^8.57.0",
186
- "prettier": "^3.2.5"
190
+ "prettier": "^3.2.5",
191
+ "vitest": "^4.1.9"
187
192
  },
188
193
  "files": [
189
194
  "src/",
190
195
  "README.md",
191
196
  "LICENSE",
192
197
  "CHANGELOG.md"
193
- ]
194
- }
198
+ ],
199
+ "browser": {
200
+ "fs": false,
201
+ "path": false,
202
+ "os": false,
203
+ "stream": false,
204
+ "fs-extra": false,
205
+ "ora": false,
206
+ "chalk": false,
207
+ "commander": false
208
+ },
209
+ "publishConfig": {
210
+ "access": "public"
211
+ }
212
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,138 @@
1
+ export class PPTXTemplater {
2
+ static load(source: string | Buffer, options?: any): Promise<PPTXTemplater>;
3
+ static setLogLevel(level: string): void;
4
+ static preload(source: string | Buffer): Promise<any>;
5
+ static cache(source: string | Buffer): Promise<any>;
6
+ static fromCache(source: string | Buffer): Promise<PPTXTemplater>;
7
+ static clearCache(): void;
8
+ static fromPresentationXml(options: any): Promise<PPTXTemplater>;
9
+ static create(): Promise<PPTXTemplater>;
10
+ static extractPptx(pptxPath: string, outputPath: string, options?: any): Promise<void>;
11
+ static buildPptx(folderPath: string, pptxPath: string): Promise<void>;
12
+
13
+ slideCount: number;
14
+ useSlide(slideNumber: number): this;
15
+ replaceText(replacements: Record<string, string | number>): this;
16
+ updateChart(chartName: string, data: any): this;
17
+ updateChartTitle(chartName: string, title: string): this;
18
+ applyZOrder(slideNumber: number, zOrderConfig: any[]): this;
19
+ removeSlide(slideNumber: number): this;
20
+ addHyperlink(options: any): this;
21
+ addSlideLink(options: any): this;
22
+ getTableRows(tableName: string): Promise<any[]>;
23
+ updateTable(tableName: string, rows: any[][]): this;
24
+ addTableRow(tableName: string, row: any[]): this;
25
+ removeTableRow(tableName: string, rowIndex: number): this;
26
+ mergeCells(tableName: string, startRow: number, startCol: number, endRow: number, endCol: number): this;
27
+ saveToFile(filePath: string, options?: any): Promise<void>;
28
+ save(filePath: string, options?: any): Promise<void>;
29
+ saveXml(folderPath: string): Promise<void>;
30
+ saveToFolder(folderPath: string): Promise<void>;
31
+ toBuffer(options?: any): Promise<Buffer>;
32
+ toStream(options?: any): Promise<any>;
33
+ saveToStream(writableOrOptions: any, options?: any): Promise<void>;
34
+ exportSlides(...slideNumbers: number[]): Promise<any>;
35
+ importSlideFrom(sourceEngine: PPTXTemplater, slideRef: any): Promise<any>;
36
+ repair(): Promise<this>;
37
+ inspectXML(xmlPath: string): Promise<any>;
38
+ validateCharts(): Promise<any>;
39
+ repairCharts(): Promise<any>;
40
+ inspectChartXML(chartFileName: string): Promise<any>;
41
+ getDataLabels(chartId: string, options?: any): Promise<any>;
42
+ validateDataLabels(chartId: string, options?: any): Promise<any>;
43
+ validateChartLabels(chartId: string, options?: any): Promise<any>;
44
+ validateSeriesNameLabels(chartId: string, options?: any): Promise<any>;
45
+ getChartLabelPositions(chartId: string): Promise<any>;
46
+ getChartBarPositions(chartId: string): Promise<any>;
47
+ addShape(typeOrOptions: any, options?: any): Promise<any>;
48
+ updateShape(shapeId: string, options: any): Promise<any>;
49
+ removeShape(shapeId: string): Promise<any>;
50
+ addCellShape(tableId: string, rowIndex: number, colIndex: number, options: any): Promise<any>;
51
+ updateCellShape(tableId: string, rowIndex: number, colIndex: number, shapeIndex: number, options: any): Promise<any>;
52
+ removeCellShape(tableId: string, rowIndex: number, colIndex: number, shapeIndex: number): Promise<any>;
53
+ replaceImage(imageIdOrName: string, sourcePathOrBuffer: string | Buffer): Promise<any>;
54
+ addImage(sourcePathOrBuffer: string | Buffer, options?: any): Promise<any>;
55
+ validatePresentation(): Promise<any>;
56
+ validatePresentationXml(): Promise<any>;
57
+ validateSlide(slideIndex: number): Promise<any>;
58
+ validateTable(tableId: string): Promise<any>;
59
+ validateArchive(): Promise<any>;
60
+ }
61
+
62
+ export const PPTXTemplate: typeof PPTXTemplater;
63
+
64
+ export class ZipManager {
65
+ [key: string]: any;
66
+ }
67
+
68
+ export class XMLParser {
69
+ [key: string]: any;
70
+ }
71
+
72
+ export const Z_ORDER_SYMBOL: unique symbol;
73
+
74
+ export class SlideManager {
75
+ [key: string]: any;
76
+ }
77
+
78
+ export class ChartManager {
79
+ [key: string]: any;
80
+ }
81
+
82
+ export class TableManager {
83
+ [key: string]: any;
84
+ }
85
+
86
+ export class ShapeManager {
87
+ [key: string]: any;
88
+ }
89
+
90
+ export class ImageManager {
91
+ [key: string]: any;
92
+ }
93
+
94
+ export class TextManager {
95
+ [key: string]: any;
96
+ }
97
+
98
+ export class HyperlinkManager {
99
+ [key: string]: any;
100
+ }
101
+
102
+ export class MediaManager {
103
+ [key: string]: any;
104
+ }
105
+
106
+ export class RelationshipManager {
107
+ [key: string]: any;
108
+ }
109
+
110
+ export class OutputWriter {
111
+ [key: string]: any;
112
+ }
113
+
114
+ export class TemplateEngine {
115
+ [key: string]: any;
116
+ }
117
+
118
+ export class ValidationEngine {
119
+ [key: string]: any;
120
+ }
121
+
122
+ export function generateRelationshipId(slideId: any): string;
123
+ export function parseRelationshipId(relId: string): any;
124
+ export function validateXml(xmlStr: string): any;
125
+ export function validateXML(xmlStr: string): any;
126
+ export function safeParseXml(xmlStr: string): any;
127
+ export function repairXML(xmlStr: string): string;
128
+ export function scanForEntities(xmlStr: string): any;
129
+ export function analyzeXmlFile(xmlStr: string): any;
130
+ export function reportXmlComplexity(xmlStr: string): any;
131
+ export function createLogger(name: string): any;
132
+ export function setGlobalLogLevel(level: string): void;
133
+ export function resetLogLevel(): void;
134
+
135
+ export class PPTXError extends Error {}
136
+ export class SlideNotFoundError extends PPTXError {}
137
+ export class ChartNotFoundError extends PPTXError {}
138
+ export class TableNotFoundError extends PPTXError {}
package/src/index.mjs ADDED
@@ -0,0 +1,39 @@
1
+ import pkg from './index.js'
2
+
3
+ export const {
4
+ PPTXTemplater,
5
+ PPTXTemplate,
6
+ ZipManager,
7
+ XMLParser,
8
+ Z_ORDER_SYMBOL,
9
+ SlideManager,
10
+ ChartManager,
11
+ TableManager,
12
+ ShapeManager,
13
+ ImageManager,
14
+ TextManager,
15
+ HyperlinkManager,
16
+ MediaManager,
17
+ RelationshipManager,
18
+ OutputWriter,
19
+ TemplateEngine,
20
+ ValidationEngine,
21
+ generateRelationshipId,
22
+ parseRelationshipId,
23
+ validateXml,
24
+ validateXML,
25
+ safeParseXml,
26
+ repairXML,
27
+ scanForEntities,
28
+ analyzeXmlFile,
29
+ reportXmlComplexity,
30
+ createLogger,
31
+ setGlobalLogLevel,
32
+ resetLogLevel,
33
+ PPTXError,
34
+ SlideNotFoundError,
35
+ ChartNotFoundError,
36
+ TableNotFoundError,
37
+ } = pkg
38
+
39
+ export default pkg
@@ -114,7 +114,8 @@ class RelationshipManager {
114
114
  relFiles.map(async relsPath => {
115
115
  const content = await zipManager.readFile(relsPath)
116
116
  if (content) {
117
- this.#relationships.set(relsPath, this.#parseRels(content, relsPath))
117
+ const normalizedPath = this.#normalizeRelsPath(relsPath)
118
+ this.#relationships.set(normalizedPath, this.#parseRels(content, relsPath))
118
119
  }
119
120
  })
120
121
  )
@@ -122,6 +123,31 @@ class RelationshipManager {
122
123
  logger.debug(`Loaded ${this.#relationships.size} relationship files`)
123
124
  }
124
125
 
126
+ /**
127
+ * Normalizes path separators and strips leading slashes for robust lookup.
128
+ * @private
129
+ * @param {string} path
130
+ * @returns {string}
131
+ */
132
+ #normalizeRelsPath(path) {
133
+ if (!path) return ''
134
+ let normalized = path.replace(/\\/g, '/')
135
+ if (normalized.startsWith('/')) {
136
+ normalized = normalized.substring(1)
137
+ }
138
+ return normalized
139
+ }
140
+
141
+ /**
142
+ * Gets the normalized key for a part's relationships map.
143
+ * @private
144
+ * @param {string} partPath
145
+ * @returns {string}
146
+ */
147
+ #getNormalizedKey(partPath) {
148
+ return this.#normalizeRelsPath(this.getRelsPath(partPath))
149
+ }
150
+
125
151
  /**
126
152
  * Returns the relationship file path for a given part path.
127
153
  *
@@ -133,9 +159,10 @@ class RelationshipManager {
133
159
  * @returns {string} Path to the corresponding .rels file.
134
160
  */
135
161
  getRelsPath(partPath) {
136
- const lastSlash = partPath.lastIndexOf('/')
137
- const dir = lastSlash >= 0 ? partPath.substring(0, lastSlash) : ''
138
- const file = lastSlash >= 0 ? partPath.substring(lastSlash + 1) : partPath
162
+ const normalizedPartPath = partPath.replace(/\\/g, '/')
163
+ const lastSlash = normalizedPartPath.lastIndexOf('/')
164
+ const dir = lastSlash >= 0 ? normalizedPartPath.substring(0, lastSlash) : ''
165
+ const file = lastSlash >= 0 ? normalizedPartPath.substring(lastSlash + 1) : normalizedPartPath
139
166
  return dir ? `${dir}/_rels/${file}.rels` : `_rels/${file}.rels`
140
167
  }
141
168
 
@@ -146,8 +173,8 @@ class RelationshipManager {
146
173
  * @returns {Relationship[]} Array of relationships.
147
174
  */
148
175
  getRelationships(partPath) {
149
- const relsPath = this.getRelsPath(partPath)
150
- return this.#relationships.get(relsPath) || []
176
+ const key = this.#getNormalizedKey(partPath)
177
+ return this.#relationships.get(key) || []
151
178
  }
152
179
 
153
180
  /**
@@ -184,20 +211,20 @@ class RelationshipManager {
184
211
  * @returns {string} The assigned relationship ID (e.g., 'rId3').
185
212
  */
186
213
  addRelationship(partPath, type, target, targetMode) {
187
- const relsPath = this.getRelsPath(partPath)
214
+ const key = this.#getNormalizedKey(partPath)
188
215
 
189
- if (!this.#relationships.has(relsPath)) {
190
- this.#relationships.set(relsPath, [])
216
+ if (!this.#relationships.has(key)) {
217
+ this.#relationships.set(key, [])
191
218
  }
192
219
 
193
- const existing = this.#relationships.get(relsPath)
220
+ const existing = this.#relationships.get(key)
194
221
  const newId = generateRelationshipId(existing.map(r => r.id))
195
222
 
196
223
  const rel = { id: newId, type, target }
197
224
  if (targetMode) rel.targetMode = targetMode
198
225
 
199
226
  existing.push(rel)
200
- this.#flushRels(relsPath, partPath)
227
+ this.#flushRels(key, partPath)
201
228
 
202
229
  logger.debug(`Added relationship ${newId} (${type.split('/').pop()}) to ${partPath}`)
203
230
  return newId
@@ -210,11 +237,11 @@ class RelationshipManager {
210
237
  * @param {string} rId - Relationship ID to remove.
211
238
  */
212
239
  removeRelationship(partPath, rId) {
213
- const relsPath = this.getRelsPath(partPath)
214
- const existing = this.#relationships.get(relsPath) || []
240
+ const key = this.#getNormalizedKey(partPath)
241
+ const existing = this.#relationships.get(key) || []
215
242
  const filtered = existing.filter(r => r.id !== rId)
216
- this.#relationships.set(relsPath, filtered)
217
- this.#flushRels(relsPath, partPath)
243
+ this.#relationships.set(key, filtered)
244
+ this.#flushRels(key, partPath)
218
245
  }
219
246
 
220
247
  /**
@@ -225,12 +252,12 @@ class RelationshipManager {
225
252
  * @param {string} newTarget - New target value.
226
253
  */
227
254
  updateRelationshipTarget(partPath, rId, newTarget) {
228
- const relsPath = this.getRelsPath(partPath)
229
- const existing = this.#relationships.get(relsPath) || []
255
+ const key = this.#getNormalizedKey(partPath)
256
+ const existing = this.#relationships.get(key) || []
230
257
  const rel = existing.find(r => r.id === rId)
231
258
  if (rel) {
232
259
  rel.target = newTarget
233
- this.#flushRels(relsPath, partPath)
260
+ this.#flushRels(key, partPath)
234
261
  }
235
262
  }
236
263
 
@@ -245,14 +272,14 @@ class RelationshipManager {
245
272
  */
246
273
  copyRelationships(sourcePath, destPath, excludeTypes = []) {
247
274
  const sourceRels = this.getRelationships(sourcePath)
248
- const destRelsPath = this.getRelsPath(destPath)
275
+ const destKey = this.#getNormalizedKey(destPath)
249
276
  const idMap = new Map()
250
277
 
251
- if (!this.#relationships.has(destRelsPath)) {
252
- this.#relationships.set(destRelsPath, [])
278
+ if (!this.#relationships.has(destKey)) {
279
+ this.#relationships.set(destKey, [])
253
280
  }
254
281
 
255
- const destRels = this.#relationships.get(destRelsPath)
282
+ const destRels = this.#relationships.get(destKey)
256
283
 
257
284
  for (const rel of sourceRels) {
258
285
  if (excludeTypes.includes(rel.type)) continue
@@ -262,7 +289,7 @@ class RelationshipManager {
262
289
  idMap.set(rel.id, newId)
263
290
  }
264
291
 
265
- this.#flushRels(destRelsPath, destPath)
292
+ this.#flushRels(destKey, destPath)
266
293
  return idMap
267
294
  }
268
295
 
@@ -329,10 +356,11 @@ class RelationshipManager {
329
356
  * @param {string} partPath - For logging.
330
357
  */
331
358
  #flushRels(relsPath, _partPath) {
332
- const rels = this.#relationships.get(relsPath) || []
359
+ const key = this.#normalizeRelsPath(relsPath)
360
+ const rels = this.#relationships.get(key) || []
333
361
  const xml = this.#buildRelsXml(rels)
334
362
  if (this.#zipManager) {
335
- this.#zipManager.writeFile(relsPath, xml)
363
+ this.#zipManager.writeFile(key, xml)
336
364
  }
337
365
  }
338
366
 
@@ -433,6 +433,7 @@ class SlideManager {
433
433
  cloneSlide(sourceIndex, atPosition, relationshipManager) {
434
434
  this.#assertSlideExists(sourceIndex)
435
435
  const sourceInfo = this.#slides.get(sourceIndex)
436
+ console.log('[DEBUG] Source Slide Info:', sourceInfo)
436
437
 
437
438
  const newIndex = this.#slides.size + 1
438
439
  let nextFileIndex = 1
@@ -444,11 +445,21 @@ class SlideManager {
444
445
 
445
446
  // Copy the source XML
446
447
  let sourceXml = this.getSlideXml(sourceIndex)
448
+ console.log('[DEBUG] Source XML length:', sourceXml ? sourceXml.length : 0)
449
+
450
+ // Copy relationships
451
+ const sourceRels = relationshipManager.getRelationships(sourceInfo.zipPath)
452
+ console.log(
453
+ '[DEBUG] Source Rels Path searched:',
454
+ relationshipManager.getRelsPath(sourceInfo.zipPath)
455
+ )
456
+ console.log('[DEBUG] Source Rels found:', sourceRels)
447
457
 
448
458
  // Copy relationships from source slide (excluding notes, which are slide-specific)
449
459
  const idMap = relationshipManager.copyRelationships(sourceInfo.zipPath, slideZipPath, [
450
460
  REL_TYPES.NOTES_SLIDE,
451
461
  ])
462
+ console.log('[DEBUG] Copied relationship ID map:', Array.from(idMap.entries()))
452
463
 
453
464
  // Remap relationship IDs in the cloned XML to match the new targets
454
465
  sourceXml = remapRelationshipIds(sourceXml, idMap)
@@ -213,6 +213,19 @@ class ZipManager {
213
213
  logger.debug('Created blank PPTX structure')
214
214
  }
215
215
 
216
+ /**
217
+ * Normalizes path separators and strips leading slashes.
218
+ * @private
219
+ */
220
+ #normalizePath(zipPath) {
221
+ if (!zipPath) return ''
222
+ let normal = zipPath.replace(/\\/g, '/')
223
+ if (normal.startsWith('/')) {
224
+ normal = normal.substring(1)
225
+ }
226
+ return normal
227
+ }
228
+
216
229
  /**
217
230
  * Reads and caches a text file from the ZIP archive.
218
231
  *
@@ -221,7 +234,7 @@ class ZipManager {
221
234
  */
222
235
  async readFile(zipPath) {
223
236
  // Normalize path separators
224
- const normalPath = zipPath.replace(/\\/g, '/')
237
+ const normalPath = this.#normalizePath(zipPath)
225
238
 
226
239
  // Return cached version if available and not dirty
227
240
  if (this.#xmlCache.has(normalPath) && !this.#dirtyFiles.has(normalPath)) {
@@ -239,8 +252,11 @@ class ZipManager {
239
252
  if (entry.type === 'text') {
240
253
  content = entry.content
241
254
  } else {
242
- const { TextDecoder } = require('util')
243
- content = new TextDecoder('utf-8').decode(entry.content)
255
+ const decoder =
256
+ typeof globalThis.TextDecoder !== 'undefined'
257
+ ? new globalThis.TextDecoder('utf-8')
258
+ : new (require('util').TextDecoder)('utf-8')
259
+ content = decoder.decode(entry.content)
244
260
  }
245
261
  this.#xmlCache.set(normalPath, content)
246
262
  return content
@@ -278,7 +294,7 @@ class ZipManager {
278
294
  * @returns {string|null} Cached content or null.
279
295
  */
280
296
  readCachedFile(zipPath) {
281
- const normalPath = zipPath.replace(/\\/g, '/')
297
+ const normalPath = this.#normalizePath(zipPath)
282
298
  if (this.#dirtyFiles.has(normalPath)) {
283
299
  return this.#dirtyFiles.get(normalPath)
284
300
  }
@@ -291,8 +307,11 @@ class ZipManager {
291
307
  if (entry.type === 'text') {
292
308
  content = entry.content
293
309
  } else {
294
- const { TextDecoder } = require('util')
295
- content = new TextDecoder('utf-8').decode(entry.content)
310
+ const decoder =
311
+ typeof globalThis.TextDecoder !== 'undefined'
312
+ ? new globalThis.TextDecoder('utf-8')
313
+ : new (require('util').TextDecoder)('utf-8')
314
+ content = decoder.decode(entry.content)
296
315
  }
297
316
  this.#xmlCache.set(normalPath, content)
298
317
  return content
@@ -307,7 +326,7 @@ class ZipManager {
307
326
  * @returns {Promise<Uint8Array|null>} Binary content or null if not found.
308
327
  */
309
328
  async readBinaryFile(zipPath) {
310
- const normalPath = zipPath.replace(/\\/g, '/')
329
+ const normalPath = this.#normalizePath(zipPath)
311
330
  if (this.#dirtyBinaryFiles.has(normalPath)) {
312
331
  return this.#dirtyBinaryFiles.get(normalPath)
313
332
  }
@@ -329,7 +348,7 @@ class ZipManager {
329
348
  }
330
349
 
331
350
  writeFile(zipPath, content) {
332
- const normalPath = zipPath.replace(/\\/g, '/')
351
+ const normalPath = this.#normalizePath(zipPath)
333
352
  this.#dirtyFiles.set(normalPath, content)
334
353
  this.#xmlCache.set(normalPath, content)
335
354
  this.#removedFiles.delete(normalPath)
@@ -340,7 +359,7 @@ class ZipManager {
340
359
  }
341
360
 
342
361
  writeBinaryFile(zipPath, data) {
343
- const normalPath = zipPath.replace(/\\/g, '/')
362
+ const normalPath = this.#normalizePath(zipPath)
344
363
  this.#dirtyBinaryFiles.set(normalPath, data)
345
364
  this.#removedFiles.delete(normalPath)
346
365
  if (this.#zip) {
@@ -380,7 +399,7 @@ class ZipManager {
380
399
  * @param {string} zipPath - Path to remove.
381
400
  */
382
401
  removeFile(zipPath) {
383
- const normalPath = zipPath.replace(/\\/g, '/')
402
+ const normalPath = this.#normalizePath(zipPath)
384
403
  this.#removedFiles.add(normalPath)
385
404
  this.#xmlCache.delete(normalPath)
386
405
  this.#dirtyFiles.delete(normalPath)
@@ -397,7 +416,7 @@ class ZipManager {
397
416
  * @returns {boolean}
398
417
  */
399
418
  hasFile(zipPath) {
400
- const normalPath = zipPath.replace(/\\/g, '/')
419
+ const normalPath = this.#normalizePath(zipPath)
401
420
  if (this.#removedFiles.has(normalPath)) return false
402
421
  if (this.#dirtyFiles.has(normalPath) || this.#dirtyBinaryFiles.has(normalPath)) return true
403
422
  if (this.#cachedFiles && this.#cachedFiles.has(normalPath)) return true