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 +1 -1
- package/package.json +29 -11
- package/src/index.d.ts +138 -0
- package/src/index.mjs +39 -0
- package/src/managers/RelationshipManager.js +53 -25
- package/src/managers/SlideManager.js +11 -0
- package/src/managers/ZipManager.js +30 -11
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%
|
|
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.
|
|
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": ">=
|
|
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.
|
|
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
|
-
|
|
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
|
|
137
|
-
const
|
|
138
|
-
const
|
|
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
|
|
150
|
-
return this.#relationships.get(
|
|
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
|
|
214
|
+
const key = this.#getNormalizedKey(partPath)
|
|
188
215
|
|
|
189
|
-
if (!this.#relationships.has(
|
|
190
|
-
this.#relationships.set(
|
|
216
|
+
if (!this.#relationships.has(key)) {
|
|
217
|
+
this.#relationships.set(key, [])
|
|
191
218
|
}
|
|
192
219
|
|
|
193
|
-
const existing = this.#relationships.get(
|
|
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(
|
|
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
|
|
214
|
-
const existing = this.#relationships.get(
|
|
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(
|
|
217
|
-
this.#flushRels(
|
|
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
|
|
229
|
-
const existing = this.#relationships.get(
|
|
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(
|
|
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
|
|
275
|
+
const destKey = this.#getNormalizedKey(destPath)
|
|
249
276
|
const idMap = new Map()
|
|
250
277
|
|
|
251
|
-
if (!this.#relationships.has(
|
|
252
|
-
this.#relationships.set(
|
|
278
|
+
if (!this.#relationships.has(destKey)) {
|
|
279
|
+
this.#relationships.set(destKey, [])
|
|
253
280
|
}
|
|
254
281
|
|
|
255
|
-
const destRels = this.#relationships.get(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
243
|
-
|
|
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
|
|
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
|
|
295
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|