node-pptx-templater 1.0.1 → 1.0.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.
- package/README.md +336 -281
- package/package.json +6 -6
- package/src/cli/commands/build.js +32 -31
- package/src/cli/commands/debug.js +25 -24
- package/src/cli/commands/extract.js +23 -21
- package/src/cli/commands/inspect.js +25 -23
- package/src/cli/commands/validate.js +19 -17
- package/src/cli/index.js +45 -43
- package/src/core/OutputWriter.js +81 -78
- package/src/core/PPTXTemplater.js +859 -274
- package/src/core/TemplateEngine.js +69 -71
- package/src/core/ValidationEngine.js +246 -0
- package/src/index.js +51 -15
- package/src/managers/ChartManager.js +197 -70
- package/src/managers/ContentTypesManager.js +51 -45
- package/src/managers/HyperlinkManager.js +148 -142
- package/src/managers/ImageManager.js +336 -0
- package/src/managers/MediaManager.js +64 -81
- package/src/managers/RelationshipManager.js +102 -96
- package/src/managers/ShapeManager.js +340 -0
- package/src/managers/SlideManager.js +410 -311
- package/src/managers/TableManager.js +981 -262
- package/src/managers/TextManager.js +197 -0
- package/src/managers/ZipManager.js +71 -69
- package/src/managers/charts/ChartCacheGenerator.js +77 -58
- package/src/managers/charts/ChartParser.js +11 -13
- package/src/managers/charts/ChartRelationshipManager.js +14 -10
- package/src/managers/charts/ChartWorkbookUpdater.js +61 -56
- package/src/parsers/XMLParser.js +50 -49
- package/src/templates/blankPptx.js +3 -1
- package/src/templates/slideTemplate.js +31 -32
- package/src/utils/contentTypesHelper.js +41 -53
- package/src/utils/errors.js +33 -23
- package/src/utils/idUtils.js +23 -15
- package/src/utils/logger.js +21 -15
- package/src/utils/relationshipUtils.js +28 -22
- package/src/utils/xmlUtils.js +37 -29
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TextManager - Handles slide text search, retrieval, and replacement.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { createLogger } = require('../utils/logger.js')
|
|
6
|
+
|
|
7
|
+
const logger = createLogger('TextManager')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @class TextManager
|
|
11
|
+
* @description Manages text elements, replacements, and search inside slide XML.
|
|
12
|
+
*/
|
|
13
|
+
class TextManager {
|
|
14
|
+
/** @private @type {XMLParser} */
|
|
15
|
+
#xmlParser
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {XMLParser} xmlParser
|
|
19
|
+
*/
|
|
20
|
+
constructor(xmlParser) {
|
|
21
|
+
this.#xmlParser = xmlParser
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Replaces a specific tag/placeholder with a value.
|
|
26
|
+
*
|
|
27
|
+
* @param {number} slideIndex
|
|
28
|
+
* @param {string} tag - E.g. '{{name}}' or 'name' (auto-wraps if simple).
|
|
29
|
+
* @param {string} value
|
|
30
|
+
* @param {Object} options
|
|
31
|
+
* @param {SlideManager} slideManager
|
|
32
|
+
* @param {TemplateEngine} templateEngine
|
|
33
|
+
*/
|
|
34
|
+
replaceTextByTag(slideIndex, tag, value, _options = {}, slideManager, templateEngine) {
|
|
35
|
+
const slideXml = slideManager.getSlideXml(slideIndex)
|
|
36
|
+
|
|
37
|
+
// Auto-wrap tag in {{}} if not already present
|
|
38
|
+
const normalizedTag = tag.startsWith('{{') && tag.endsWith('}}') ? tag : `{{${tag}}}`
|
|
39
|
+
|
|
40
|
+
const replacements = { [normalizedTag]: value }
|
|
41
|
+
const updatedXml = templateEngine.replaceTextInXml(slideXml, replacements)
|
|
42
|
+
|
|
43
|
+
slideManager.setSlideXml(slideIndex, updatedXml)
|
|
44
|
+
logger.debug(`Replaced text tag "${normalizedTag}" with value on slide ${slideIndex}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Performs multiple text replacements at once.
|
|
49
|
+
*
|
|
50
|
+
* @param {number} slideIndex
|
|
51
|
+
* @param {Object.<string, string>} replacements - Map of key -> value.
|
|
52
|
+
* @param {Object} options
|
|
53
|
+
* @param {SlideManager} slideManager
|
|
54
|
+
* @param {TemplateEngine} templateEngine
|
|
55
|
+
*/
|
|
56
|
+
replaceMultiple(slideIndex, replacements, _options = {}, slideManager, templateEngine) {
|
|
57
|
+
const slideXml = slideManager.getSlideXml(slideIndex)
|
|
58
|
+
|
|
59
|
+
// Normalize keys in the replacements map to ensure they are wrapped in placeholders
|
|
60
|
+
const normalized = {}
|
|
61
|
+
for (const [key, val] of Object.entries(replacements)) {
|
|
62
|
+
const normalizedKey = key.startsWith('{{') && key.endsWith('}}') ? key : `{{${key}}}`
|
|
63
|
+
normalized[normalizedKey] = val
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const updatedXml = templateEngine.replaceTextInXml(slideXml, normalized)
|
|
67
|
+
slideManager.setSlideXml(slideIndex, updatedXml)
|
|
68
|
+
logger.debug(`Replaced multiple tags on slide ${slideIndex}`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Searches for a text string inside all text runs on a slide.
|
|
73
|
+
*
|
|
74
|
+
* @param {number} slideIndex
|
|
75
|
+
* @param {string} searchText
|
|
76
|
+
* @param {SlideManager} slideManager
|
|
77
|
+
* @returns {Array<Object>} List of match details.
|
|
78
|
+
*/
|
|
79
|
+
findText(slideIndex, searchText, slideManager) {
|
|
80
|
+
const slideXml = slideManager.getSlideXml(slideIndex)
|
|
81
|
+
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
82
|
+
const results = []
|
|
83
|
+
|
|
84
|
+
// Find in shapes (p:sp)
|
|
85
|
+
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
86
|
+
if (!spTree) return results
|
|
87
|
+
|
|
88
|
+
this.#searchShapesForText(spTree, searchText, results)
|
|
89
|
+
return results
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extracts and returns all text elements on a slide.
|
|
94
|
+
*
|
|
95
|
+
* @param {number} slideIndex
|
|
96
|
+
* @param {SlideManager} slideManager
|
|
97
|
+
* @returns {Array<Object>} Elements with text contents.
|
|
98
|
+
*/
|
|
99
|
+
getTextElements(slideIndex, slideManager) {
|
|
100
|
+
const slideXml = slideManager.getSlideXml(slideIndex)
|
|
101
|
+
const slideObj = this.#xmlParser.parse(slideXml, `slide${slideIndex}.xml`)
|
|
102
|
+
const results = []
|
|
103
|
+
|
|
104
|
+
const spTree = slideObj?.['p:sld']?.['p:cSld']?.['p:spTree']
|
|
105
|
+
if (!spTree) return results
|
|
106
|
+
|
|
107
|
+
this.#collectTextElements(spTree, results)
|
|
108
|
+
return results
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#searchShapesForText(container, searchText, results) {
|
|
112
|
+
if (!container) return
|
|
113
|
+
|
|
114
|
+
let shapes = container['p:sp'] || []
|
|
115
|
+
if (!Array.isArray(shapes)) shapes = [shapes]
|
|
116
|
+
|
|
117
|
+
for (const shape of shapes) {
|
|
118
|
+
const cNvPr = shape?.['p:nvSpPr']?.['p:cNvPr']
|
|
119
|
+
const shapeName = cNvPr ? cNvPr['@_name'] : 'unnamed'
|
|
120
|
+
const shapeId = cNvPr ? String(cNvPr['@_id']) : 'unknown'
|
|
121
|
+
|
|
122
|
+
const txBody = shape['p:txBody']
|
|
123
|
+
if (txBody && txBody['a:p']) {
|
|
124
|
+
const paras = Array.isArray(txBody['a:p']) ? txBody['a:p'] : [txBody['a:p']]
|
|
125
|
+
paras.forEach((p, pIdx) => {
|
|
126
|
+
let pText = ''
|
|
127
|
+
if (p['a:r']) {
|
|
128
|
+
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
129
|
+
runs.forEach(r => {
|
|
130
|
+
if (r['a:t']) pText += String(r['a:t'])
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (pText.toLowerCase().includes(searchText.toLowerCase())) {
|
|
135
|
+
results.push({
|
|
136
|
+
shapeId,
|
|
137
|
+
shapeName,
|
|
138
|
+
paragraphIndex: pIdx,
|
|
139
|
+
text: pText,
|
|
140
|
+
match: searchText,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let groups = container['p:grpSp'] || []
|
|
148
|
+
if (!Array.isArray(groups)) groups = [groups]
|
|
149
|
+
for (const g of groups) {
|
|
150
|
+
this.#searchShapesForText(g, searchText, results)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#collectTextElements(container, results) {
|
|
155
|
+
if (!container) return
|
|
156
|
+
|
|
157
|
+
let shapes = container['p:sp'] || []
|
|
158
|
+
if (!Array.isArray(shapes)) shapes = [shapes]
|
|
159
|
+
|
|
160
|
+
for (const shape of shapes) {
|
|
161
|
+
const cNvPr = shape?.['p:nvSpPr']?.['p:cNvPr']
|
|
162
|
+
const shapeName = cNvPr ? cNvPr['@_name'] : 'unnamed'
|
|
163
|
+
const shapeId = cNvPr ? String(cNvPr['@_id']) : 'unknown'
|
|
164
|
+
|
|
165
|
+
const txBody = shape['p:txBody']
|
|
166
|
+
if (txBody && txBody['a:p']) {
|
|
167
|
+
const paras = Array.isArray(txBody['a:p']) ? txBody['a:p'] : [txBody['a:p']]
|
|
168
|
+
paras.forEach((p, pIdx) => {
|
|
169
|
+
let pText = ''
|
|
170
|
+
if (p['a:r']) {
|
|
171
|
+
const runs = Array.isArray(p['a:r']) ? p['a:r'] : [p['a:r']]
|
|
172
|
+
runs.forEach(r => {
|
|
173
|
+
if (r['a:t']) pText += String(r['a:t'])
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (pText.trim()) {
|
|
178
|
+
results.push({
|
|
179
|
+
shapeId,
|
|
180
|
+
shapeName,
|
|
181
|
+
paragraphIndex: pIdx,
|
|
182
|
+
text: pText,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let groups = container['p:grpSp'] || []
|
|
190
|
+
if (!Array.isArray(groups)) groups = [groups]
|
|
191
|
+
for (const g of groups) {
|
|
192
|
+
this.#collectTextElements(g, results)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = { TextManager }
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
* raw file content within the ZIP.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const JSZip = require('jszip')
|
|
17
|
+
const fsExtra = require('fs-extra')
|
|
18
|
+
const { createLogger } = require('../utils/logger.js')
|
|
19
|
+
const { PPTXError } = require('../utils/errors.js')
|
|
20
|
+
const { BLANK_PPTX_BASE64 } = require('../templates/blankPptx.js')
|
|
21
21
|
|
|
22
|
-
const logger = createLogger('ZipManager')
|
|
22
|
+
const logger = createLogger('ZipManager')
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @class ZipManager
|
|
@@ -27,30 +27,30 @@ const logger = createLogger('ZipManager');
|
|
|
27
27
|
*
|
|
28
28
|
* All file paths within the ZIP use forward slashes (e.g., 'ppt/slides/slide1.xml').
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
class ZipManager {
|
|
31
31
|
/**
|
|
32
32
|
* @private
|
|
33
33
|
* @type {JSZip}
|
|
34
34
|
*/
|
|
35
|
-
#zip = null
|
|
35
|
+
#zip = null
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @private
|
|
39
39
|
* @type {Map<string, string>} Cache of decoded XML strings for fast repeated access.
|
|
40
40
|
*/
|
|
41
|
-
#xmlCache = new Map()
|
|
41
|
+
#xmlCache = new Map()
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* @private
|
|
45
45
|
* @type {Map<string, string>} Dirty (modified) files that need to be re-written.
|
|
46
46
|
*/
|
|
47
|
-
#dirtyFiles = new Map()
|
|
47
|
+
#dirtyFiles = new Map()
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* @private
|
|
51
51
|
* @type {Map<string, string>} Core properties (dc:title, dc:creator, etc.)
|
|
52
52
|
*/
|
|
53
|
-
#coreProperties = new Map()
|
|
53
|
+
#coreProperties = new Map()
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Loads a PPTX file from a path or Buffer.
|
|
@@ -61,22 +61,24 @@ export class ZipManager {
|
|
|
61
61
|
*/
|
|
62
62
|
async load(source) {
|
|
63
63
|
try {
|
|
64
|
-
let data
|
|
64
|
+
let data
|
|
65
65
|
if (typeof source === 'string') {
|
|
66
|
-
logger.debug(`Reading file: ${source}`)
|
|
67
|
-
data = await fsExtra.readFile(source)
|
|
66
|
+
logger.debug(`Reading file: ${source}`)
|
|
67
|
+
data = await fsExtra.readFile(source)
|
|
68
68
|
} else if (Buffer.isBuffer(source) || source instanceof Uint8Array) {
|
|
69
|
-
data = source
|
|
69
|
+
data = source
|
|
70
70
|
} else {
|
|
71
|
-
throw new PPTXError(
|
|
71
|
+
throw new PPTXError(
|
|
72
|
+
`Invalid source type: ${typeof source}. Expected string path or Buffer.`
|
|
73
|
+
)
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
this.#zip = await JSZip.loadAsync(data)
|
|
75
|
-
await this.#loadCoreProperties()
|
|
76
|
-
logger.debug(`ZIP loaded successfully. Files: ${Object.keys(this.#zip.files).length}`)
|
|
76
|
+
this.#zip = await JSZip.loadAsync(data)
|
|
77
|
+
await this.#loadCoreProperties()
|
|
78
|
+
logger.debug(`ZIP loaded successfully. Files: ${Object.keys(this.#zip.files).length}`)
|
|
77
79
|
} catch (err) {
|
|
78
|
-
if (err instanceof PPTXError) throw err
|
|
79
|
-
throw new PPTXError(`Failed to load PPTX: ${err.message}`, err)
|
|
80
|
+
if (err instanceof PPTXError) throw err
|
|
81
|
+
throw new PPTXError(`Failed to load PPTX: ${err.message}`, err)
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -85,10 +87,10 @@ export class ZipManager {
|
|
|
85
87
|
* @returns {Promise<void>}
|
|
86
88
|
*/
|
|
87
89
|
async createBlank() {
|
|
88
|
-
const buffer = Buffer.from(BLANK_PPTX_BASE64, 'base64')
|
|
89
|
-
this.#zip = await JSZip.loadAsync(buffer)
|
|
90
|
-
await this.#loadCoreProperties()
|
|
91
|
-
logger.debug('Created blank PPTX structure')
|
|
90
|
+
const buffer = Buffer.from(BLANK_PPTX_BASE64, 'base64')
|
|
91
|
+
this.#zip = await JSZip.loadAsync(buffer)
|
|
92
|
+
await this.#loadCoreProperties()
|
|
93
|
+
logger.debug('Created blank PPTX structure')
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/**
|
|
@@ -99,27 +101,27 @@ export class ZipManager {
|
|
|
99
101
|
*/
|
|
100
102
|
async readFile(zipPath) {
|
|
101
103
|
// Normalize path separators
|
|
102
|
-
const normalPath = zipPath.replace(/\\/g, '/')
|
|
104
|
+
const normalPath = zipPath.replace(/\\/g, '/')
|
|
103
105
|
|
|
104
106
|
// Return cached version if available and not dirty
|
|
105
107
|
if (this.#xmlCache.has(normalPath) && !this.#dirtyFiles.has(normalPath)) {
|
|
106
|
-
return this.#xmlCache.get(normalPath)
|
|
108
|
+
return this.#xmlCache.get(normalPath)
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
// Check dirty files (pending writes)
|
|
110
112
|
if (this.#dirtyFiles.has(normalPath)) {
|
|
111
|
-
return this.#dirtyFiles.get(normalPath)
|
|
113
|
+
return this.#dirtyFiles.get(normalPath)
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
const file = this.#zip.file(normalPath)
|
|
116
|
+
const file = this.#zip.file(normalPath)
|
|
115
117
|
if (!file) {
|
|
116
|
-
logger.debug(`File not found in ZIP: ${normalPath}`)
|
|
117
|
-
return null
|
|
118
|
+
logger.debug(`File not found in ZIP: ${normalPath}`)
|
|
119
|
+
return null
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
const content = await file.async('text')
|
|
121
|
-
this.#xmlCache.set(normalPath, content)
|
|
122
|
-
return content
|
|
122
|
+
const content = await file.async('text')
|
|
123
|
+
this.#xmlCache.set(normalPath, content)
|
|
124
|
+
return content
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
/**
|
|
@@ -129,10 +131,10 @@ export class ZipManager {
|
|
|
129
131
|
* @returns {Promise<Uint8Array|null>} Binary content or null if not found.
|
|
130
132
|
*/
|
|
131
133
|
async readBinaryFile(zipPath) {
|
|
132
|
-
const normalPath = zipPath.replace(/\\/g, '/')
|
|
133
|
-
const file = this.#zip.file(normalPath)
|
|
134
|
-
if (!file) return null
|
|
135
|
-
return file.async('uint8array')
|
|
134
|
+
const normalPath = zipPath.replace(/\\/g, '/')
|
|
135
|
+
const file = this.#zip.file(normalPath)
|
|
136
|
+
if (!file) return null
|
|
137
|
+
return file.async('uint8array')
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
/**
|
|
@@ -143,12 +145,12 @@ export class ZipManager {
|
|
|
143
145
|
* @param {string} content - UTF-8 string content.
|
|
144
146
|
*/
|
|
145
147
|
writeFile(zipPath, content) {
|
|
146
|
-
const normalPath = zipPath.replace(/\\/g, '/')
|
|
147
|
-
this.#dirtyFiles.set(normalPath, content)
|
|
148
|
-
this.#xmlCache.set(normalPath, content)
|
|
148
|
+
const normalPath = zipPath.replace(/\\/g, '/')
|
|
149
|
+
this.#dirtyFiles.set(normalPath, content)
|
|
150
|
+
this.#xmlCache.set(normalPath, content)
|
|
149
151
|
// Also write to the underlying JSZip object
|
|
150
|
-
this.#zip.file(normalPath, content)
|
|
151
|
-
logger.debug(`Queued write: ${normalPath}`)
|
|
152
|
+
this.#zip.file(normalPath, content)
|
|
153
|
+
logger.debug(`Queued write: ${normalPath}`)
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
/**
|
|
@@ -158,23 +160,23 @@ export class ZipManager {
|
|
|
158
160
|
* @param {Buffer|Uint8Array} data - Binary data.
|
|
159
161
|
*/
|
|
160
162
|
writeBinaryFile(zipPath, data) {
|
|
161
|
-
const normalPath = zipPath.replace(/\\/g, '/')
|
|
162
|
-
this.#zip.file(normalPath, data)
|
|
163
|
-
logger.debug(`Queued binary write: ${normalPath}`)
|
|
163
|
+
const normalPath = zipPath.replace(/\\/g, '/')
|
|
164
|
+
this.#zip.file(normalPath, data)
|
|
165
|
+
logger.debug(`Queued binary write: ${normalPath}`)
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
/**
|
|
167
169
|
* @private
|
|
168
170
|
* @type {Promise[]}
|
|
169
171
|
*/
|
|
170
|
-
#pendingPromises = []
|
|
172
|
+
#pendingPromises = []
|
|
171
173
|
|
|
172
174
|
/**
|
|
173
175
|
* Adds a promise to the pending queue to be awaited before saving.
|
|
174
176
|
* @param {Promise} promise
|
|
175
177
|
*/
|
|
176
178
|
addPendingPromise(promise) {
|
|
177
|
-
this.#pendingPromises.push(promise)
|
|
179
|
+
this.#pendingPromises.push(promise)
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
/**
|
|
@@ -183,8 +185,8 @@ export class ZipManager {
|
|
|
183
185
|
*/
|
|
184
186
|
async waitForPendingWrites() {
|
|
185
187
|
if (this.#pendingPromises.length > 0) {
|
|
186
|
-
await Promise.all(this.#pendingPromises)
|
|
187
|
-
this.#pendingPromises = []
|
|
188
|
+
await Promise.all(this.#pendingPromises)
|
|
189
|
+
this.#pendingPromises = []
|
|
188
190
|
}
|
|
189
191
|
}
|
|
190
192
|
|
|
@@ -194,10 +196,10 @@ export class ZipManager {
|
|
|
194
196
|
* @param {string} zipPath - Path to remove.
|
|
195
197
|
*/
|
|
196
198
|
removeFile(zipPath) {
|
|
197
|
-
const normalPath = zipPath.replace(/\\/g, '/')
|
|
198
|
-
this.#zip.remove(normalPath)
|
|
199
|
-
this.#xmlCache.delete(normalPath)
|
|
200
|
-
this.#dirtyFiles.delete(normalPath)
|
|
199
|
+
const normalPath = zipPath.replace(/\\/g, '/')
|
|
200
|
+
this.#zip.remove(normalPath)
|
|
201
|
+
this.#xmlCache.delete(normalPath)
|
|
202
|
+
this.#dirtyFiles.delete(normalPath)
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
/**
|
|
@@ -207,8 +209,8 @@ export class ZipManager {
|
|
|
207
209
|
* @returns {boolean}
|
|
208
210
|
*/
|
|
209
211
|
hasFile(zipPath) {
|
|
210
|
-
const normalPath = zipPath.replace(/\\/g, '/')
|
|
211
|
-
return this.#zip.file(normalPath) !== null
|
|
212
|
+
const normalPath = zipPath.replace(/\\/g, '/')
|
|
213
|
+
return this.#zip.file(normalPath) !== null
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
/**
|
|
@@ -218,9 +220,7 @@ export class ZipManager {
|
|
|
218
220
|
* @returns {string[]} Array of matching file paths.
|
|
219
221
|
*/
|
|
220
222
|
listFiles(prefix = '') {
|
|
221
|
-
return Object.keys(this.#zip.files).filter(
|
|
222
|
-
f => !this.#zip.files[f].dir && f.startsWith(prefix)
|
|
223
|
-
);
|
|
223
|
+
return Object.keys(this.#zip.files).filter(f => !this.#zip.files[f].dir && f.startsWith(prefix))
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/**
|
|
@@ -234,7 +234,7 @@ export class ZipManager {
|
|
|
234
234
|
type: 'nodebuffer',
|
|
235
235
|
compression: 'DEFLATE',
|
|
236
236
|
compressionOptions: { level: 6 },
|
|
237
|
-
})
|
|
237
|
+
})
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
@@ -248,7 +248,7 @@ export class ZipManager {
|
|
|
248
248
|
compression: 'DEFLATE',
|
|
249
249
|
compressionOptions: { level: 6 },
|
|
250
250
|
streamFiles: true,
|
|
251
|
-
})
|
|
251
|
+
})
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
/**
|
|
@@ -258,7 +258,7 @@ export class ZipManager {
|
|
|
258
258
|
* @returns {string|undefined}
|
|
259
259
|
*/
|
|
260
260
|
getCoreProperty(key) {
|
|
261
|
-
return this.#coreProperties.get(key)
|
|
261
|
+
return this.#coreProperties.get(key)
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
/**
|
|
@@ -269,7 +269,7 @@ export class ZipManager {
|
|
|
269
269
|
* @param {string} value - Property value.
|
|
270
270
|
*/
|
|
271
271
|
setCoreProperty(key, value) {
|
|
272
|
-
this.#coreProperties.set(key, value)
|
|
272
|
+
this.#coreProperties.set(key, value)
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
/**
|
|
@@ -277,14 +277,14 @@ export class ZipManager {
|
|
|
277
277
|
* @private
|
|
278
278
|
*/
|
|
279
279
|
async #loadCoreProperties() {
|
|
280
|
-
const coreXml = await this.readFile('docProps/core.xml')
|
|
281
|
-
if (!coreXml) return
|
|
280
|
+
const coreXml = await this.readFile('docProps/core.xml')
|
|
281
|
+
if (!coreXml) return
|
|
282
282
|
|
|
283
283
|
// Simple regex extraction for core properties (lightweight vs full parse)
|
|
284
|
-
const propPattern = /<(dc:[a-zA-Z]+|dcterms:[a-zA-Z]+)[^>]*>([^<]*)<\/\1>/g
|
|
285
|
-
let match
|
|
284
|
+
const propPattern = /<(dc:[a-zA-Z]+|dcterms:[a-zA-Z]+)[^>]*>([^<]*)<\/\1>/g
|
|
285
|
+
let match
|
|
286
286
|
while ((match = propPattern.exec(coreXml)) !== null) {
|
|
287
|
-
this.#coreProperties.set(match[1], match[2])
|
|
287
|
+
this.#coreProperties.set(match[1], match[2])
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -293,6 +293,8 @@ export class ZipManager {
|
|
|
293
293
|
* @returns {JSZip}
|
|
294
294
|
*/
|
|
295
295
|
get rawZip() {
|
|
296
|
-
return this.#zip
|
|
296
|
+
return this.#zip
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
|
+
|
|
300
|
+
module.exports = { ZipManager }
|