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
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
* generateRelationshipId(['rId1', 'rId3']) // → 'rId4'
|
|
19
19
|
* generateRelationshipId([]) // → 'rId1'
|
|
20
20
|
*/
|
|
21
|
-
|
|
22
|
-
if (!existingIds || existingIds.length === 0) return 'rId1'
|
|
21
|
+
function generateRelationshipId(existingIds) {
|
|
22
|
+
if (!existingIds || existingIds.length === 0) return 'rId1'
|
|
23
23
|
|
|
24
24
|
const maxNum = existingIds.reduce((max, id) => {
|
|
25
|
-
const match = /^rId(\d+)$/.exec(id)
|
|
26
|
-
if (!match) return max
|
|
27
|
-
return Math.max(max, parseInt(match[1], 10))
|
|
28
|
-
}, 0)
|
|
25
|
+
const match = /^rId(\d+)$/.exec(id)
|
|
26
|
+
if (!match) return max
|
|
27
|
+
return Math.max(max, parseInt(match[1], 10))
|
|
28
|
+
}, 0)
|
|
29
29
|
|
|
30
|
-
return `rId${maxNum + 1}
|
|
30
|
+
return `rId${maxNum + 1}`
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -40,9 +40,9 @@ export function generateRelationshipId(existingIds) {
|
|
|
40
40
|
* parseRelationshipId('rId5') // → 5
|
|
41
41
|
* parseRelationshipId('foo') // → -1
|
|
42
42
|
*/
|
|
43
|
-
|
|
44
|
-
const match = /^rId(\d+)$/.exec(rId)
|
|
45
|
-
return match ? parseInt(match[1], 10) : -1
|
|
43
|
+
function parseRelationshipId(rId) {
|
|
44
|
+
const match = /^rId(\d+)$/.exec(rId)
|
|
45
|
+
return match ? parseInt(match[1], 10) : -1
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -51,8 +51,8 @@ export function parseRelationshipId(rId) {
|
|
|
51
51
|
* @param {string} str
|
|
52
52
|
* @returns {boolean}
|
|
53
53
|
*/
|
|
54
|
-
|
|
55
|
-
return /^rId\d+$/.test(str)
|
|
54
|
+
function isValidRelationshipId(str) {
|
|
55
|
+
return /^rId\d+$/.test(str)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -66,24 +66,30 @@ export function isValidRelationshipId(str) {
|
|
|
66
66
|
* @example
|
|
67
67
|
* remapRelationshipIds(xml, new Map([['rId1', 'rId5'], ['rId2', 'rId6']]));
|
|
68
68
|
*/
|
|
69
|
-
|
|
70
|
-
let updated = xml
|
|
69
|
+
function remapRelationshipIds(xml, idMap) {
|
|
70
|
+
let updated = xml
|
|
71
71
|
|
|
72
72
|
// Sort by length descending to avoid partial replacements (e.g., rId1 replacing part of rId10)
|
|
73
|
-
const sortedEntries = Array.from(idMap.entries())
|
|
74
|
-
.sort(([a], [b]) => b.length - a.length);
|
|
73
|
+
const sortedEntries = Array.from(idMap.entries()).sort(([a], [b]) => b.length - a.length)
|
|
75
74
|
|
|
76
75
|
for (const [oldId, newId] of sortedEntries) {
|
|
77
76
|
// Replace rId references in attribute values: r:id="rId1", r:embed="rId1"
|
|
78
|
-
const pattern = new RegExp(`(r:[a-zA-Z]+=")${oldId}(")|rId="${oldId}(")`, 'g')
|
|
77
|
+
const pattern = new RegExp(`(r:[a-zA-Z]+=")${oldId}(")|rId="${oldId}(")`, 'g')
|
|
79
78
|
updated = updated.replace(pattern, (match, pre, post) => {
|
|
80
|
-
if (pre) return `${pre}${newId}${post}
|
|
81
|
-
return match.replace(oldId, newId)
|
|
82
|
-
})
|
|
79
|
+
if (pre) return `${pre}${newId}${post}`
|
|
80
|
+
return match.replace(oldId, newId)
|
|
81
|
+
})
|
|
83
82
|
|
|
84
83
|
// Simple global replace as fallback
|
|
85
|
-
updated = updated.split(`"${oldId}"`).join(`"${newId}"`)
|
|
84
|
+
updated = updated.split(`"${oldId}"`).join(`"${newId}"`)
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
return updated
|
|
87
|
+
return updated
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
generateRelationshipId,
|
|
92
|
+
parseRelationshipId,
|
|
93
|
+
isValidRelationshipId,
|
|
94
|
+
remapRelationshipIds,
|
|
89
95
|
}
|
package/src/utils/xmlUtils.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* attempt automatic repairs for common PPTX corruption issues.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const { XMLParser } = require('../parsers/XMLParser.js')
|
|
9
9
|
|
|
10
|
-
const parser = new XMLParser()
|
|
10
|
+
const parser = new XMLParser()
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Validates that an XML string is well-formed.
|
|
@@ -19,8 +19,8 @@ const parser = new XMLParser();
|
|
|
19
19
|
* const { valid, error } = validateXML(xml);
|
|
20
20
|
* if (!valid) console.error('XML error:', error);
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
return parser.validate(xmlString)
|
|
22
|
+
function validateXML(xmlString) {
|
|
23
|
+
return parser.validate(xmlString)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -38,40 +38,40 @@ export function validateXML(xmlString) {
|
|
|
38
38
|
* const { xml, repaired, changes } = repairXML(brokenXml);
|
|
39
39
|
* if (repaired) console.log('Repaired:', changes);
|
|
40
40
|
*/
|
|
41
|
-
|
|
42
|
-
const changes = []
|
|
43
|
-
let xml = xmlString
|
|
41
|
+
function repairXML(xmlString) {
|
|
42
|
+
const changes = []
|
|
43
|
+
let xml = xmlString
|
|
44
44
|
|
|
45
45
|
// Fix 1: Remove invalid XML control characters
|
|
46
|
-
const before = xml
|
|
47
|
-
xml = xml.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '')
|
|
48
|
-
if (xml !== before) changes.push('Removed invalid control characters')
|
|
46
|
+
const before = xml
|
|
47
|
+
xml = xml.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '')
|
|
48
|
+
if (xml !== before) changes.push('Removed invalid control characters')
|
|
49
49
|
|
|
50
50
|
// Fix 2: Fix unescaped ampersands in text content (not in entities)
|
|
51
51
|
// Match & not followed by valid entity patterns
|
|
52
|
-
const fixedAmp = xml.replace(/&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[0-9a-fA-F]+;)/g, '&')
|
|
52
|
+
const fixedAmp = xml.replace(/&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[0-9a-fA-F]+;)/g, '&')
|
|
53
53
|
if (fixedAmp !== xml) {
|
|
54
|
-
xml = fixedAmp
|
|
55
|
-
changes.push('Escaped unescaped ampersands')
|
|
54
|
+
xml = fixedAmp
|
|
55
|
+
changes.push('Escaped unescaped ampersands')
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Fix 3: Replace null bytes
|
|
59
59
|
if (xml.includes('\x00')) {
|
|
60
|
-
xml = xml.replace(/\x00/g, '')
|
|
61
|
-
changes.push('Removed null bytes')
|
|
60
|
+
xml = xml.replace(/\x00/g, '')
|
|
61
|
+
changes.push('Removed null bytes')
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Fix 4: Ensure XML declaration is present
|
|
65
65
|
if (!xml.trimStart().startsWith('<?xml')) {
|
|
66
|
-
xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' + xml
|
|
67
|
-
changes.push('Added missing XML declaration')
|
|
66
|
+
xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' + xml
|
|
67
|
+
changes.push('Added missing XML declaration')
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
return {
|
|
71
71
|
xml,
|
|
72
72
|
repaired: changes.length > 0,
|
|
73
73
|
changes,
|
|
74
|
-
}
|
|
74
|
+
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
@@ -81,8 +81,8 @@ export function repairXML(xmlString) {
|
|
|
81
81
|
* @param {string} elementName - Element tag name (e.g., 'a:tbl').
|
|
82
82
|
* @returns {boolean}
|
|
83
83
|
*/
|
|
84
|
-
|
|
85
|
-
return xmlString.includes(`<${elementName}`) || xmlString.includes(`<${elementName}>`)
|
|
84
|
+
function xmlContainsElement(xmlString, elementName) {
|
|
85
|
+
return xmlString.includes(`<${elementName}`) || xmlString.includes(`<${elementName}>`)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
@@ -92,9 +92,9 @@ export function xmlContainsElement(xmlString, elementName) {
|
|
|
92
92
|
* @param {string} elementName
|
|
93
93
|
* @returns {number}
|
|
94
94
|
*/
|
|
95
|
-
|
|
96
|
-
const pattern = new RegExp(`<${elementName}[\\s>/]`, 'g')
|
|
97
|
-
return (xmlString.match(pattern) || []).length
|
|
95
|
+
function countElements(xmlString, elementName) {
|
|
96
|
+
const pattern = new RegExp(`<${elementName}[\\s>/]`, 'g')
|
|
97
|
+
return (xmlString.match(pattern) || []).length
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
@@ -104,12 +104,20 @@ export function countElements(xmlString, elementName) {
|
|
|
104
104
|
* @param {string} attrName - Attribute name (e.g., 'r:id', 'name').
|
|
105
105
|
* @returns {string[]} Array of attribute values found.
|
|
106
106
|
*/
|
|
107
|
-
|
|
108
|
-
const pattern = new RegExp(`${attrName.replace(':', '\\:')}="([^"]*)"`, 'g')
|
|
109
|
-
const values = []
|
|
110
|
-
let match
|
|
107
|
+
function extractAttributeValues(xmlString, attrName) {
|
|
108
|
+
const pattern = new RegExp(`${attrName.replace(':', '\\:')}="([^"]*)"`, 'g')
|
|
109
|
+
const values = []
|
|
110
|
+
let match
|
|
111
111
|
while ((match = pattern.exec(xmlString)) !== null) {
|
|
112
|
-
values.push(match[1])
|
|
112
|
+
values.push(match[1])
|
|
113
113
|
}
|
|
114
|
-
return values
|
|
114
|
+
return values
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
validateXML,
|
|
119
|
+
repairXML,
|
|
120
|
+
xmlContainsElement,
|
|
121
|
+
countElements,
|
|
122
|
+
extractAttributeValues,
|
|
115
123
|
}
|