node-pptx-templater 1.0.8 → 1.0.9
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-pptx-templater",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
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
6
|
"type": "commonjs",
|
|
@@ -154,14 +154,55 @@ class ChartCacheGenerator {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* Updates the chart title in chart XML
|
|
157
|
+
* Updates the chart title text in chart XML while preserving all existing
|
|
158
|
+
* styling (spPr, txPr, overlay, layout) from the template.
|
|
159
|
+
*
|
|
160
|
+
* Because <c:txPr> is ignored by PowerPoint once <c:tx><c:rich> is present,
|
|
161
|
+
* this method extracts <a:defRPr> from <c:txPr> and injects it as <a:rPr>
|
|
162
|
+
* into the run, and uses <a:bodyPr> from <c:txPr> inside <c:rich>, so that
|
|
163
|
+
* the template's font, size, and color are faithfully applied to the title text.
|
|
158
164
|
*/
|
|
159
165
|
static updateTitle(xml, title) {
|
|
160
|
-
const
|
|
166
|
+
const escapedTitle = this.#escapeXml(title)
|
|
167
|
+
|
|
161
168
|
if (xml.includes('<c:title>')) {
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
return xml.replace(/<c:title>([\s\S]*?)<\/c:title>/, (match, titleContent) => {
|
|
170
|
+
// Extract <a:bodyPr .../> from existing <c:txPr> to use in <c:rich>
|
|
171
|
+
let bodyPr = '<a:bodyPr/>'
|
|
172
|
+
const txPrMatch = /<c:txPr>([\s\S]*?)<\/c:txPr>/.exec(titleContent)
|
|
173
|
+
if (txPrMatch) {
|
|
174
|
+
const bodyPrMatch = /(<a:bodyPr[^>]*\/>|<a:bodyPr[^>]*>[\s\S]*?<\/a:bodyPr>)/.exec(
|
|
175
|
+
txPrMatch[1]
|
|
176
|
+
)
|
|
177
|
+
if (bodyPrMatch) bodyPr = bodyPrMatch[1]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Extract <a:defRPr .../> from <c:txPr><a:p><a:pPr> to use as <a:rPr> in the run
|
|
181
|
+
let rPr = ''
|
|
182
|
+
if (txPrMatch) {
|
|
183
|
+
const defRPrMatch = /(<a:defRPr[\s\S]*?<\/a:defRPr>|<a:defRPr[^>]*\/>)/.exec(txPrMatch[1])
|
|
184
|
+
if (defRPrMatch) {
|
|
185
|
+
// Convert <a:defRPr ...> to <a:rPr ...> (same attributes, different tag name)
|
|
186
|
+
rPr = defRPrMatch[1]
|
|
187
|
+
.replace(/^<a:defRPr/, '<a:rPr')
|
|
188
|
+
.replace(/<\/a:defRPr>$/, '</a:rPr>')
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const newTxBlock = `<c:tx><c:rich>${bodyPr}<a:lstStyle/><a:p><a:r>${rPr}<a:t>${escapedTitle}</a:t></a:r></a:p></c:rich></c:tx>`
|
|
193
|
+
|
|
194
|
+
if (titleContent.includes('<c:tx>')) {
|
|
195
|
+
// Replace existing c:tx, keep all other siblings intact
|
|
196
|
+
const updatedContent = titleContent.replace(/<c:tx>[\s\S]*?<\/c:tx>/, newTxBlock)
|
|
197
|
+
return `<c:title>${updatedContent}</c:title>`
|
|
198
|
+
} else {
|
|
199
|
+
// No c:tx yet – prepend before first existing sibling
|
|
200
|
+
return `<c:title>${newTxBlock}${titleContent}</c:title>`
|
|
201
|
+
}
|
|
202
|
+
})
|
|
164
203
|
} else {
|
|
204
|
+
// No title block exists yet – create a minimal one
|
|
205
|
+
const titleBlock = `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${escapedTitle}</a:t></a:r></a:p></c:rich></c:tx><c:layout/></c:title>`
|
|
165
206
|
const chartPattern = /(<c:chart>)/
|
|
166
207
|
return xml.replace(chartPattern, `$1${titleBlock}`)
|
|
167
208
|
}
|