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.8",
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 titleBlock = `<c:title><c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${this.#escapeXml(title)}</a:t></a:r></a:p></c:rich></c:tx><c:layout/></c:title>`
166
+ const escapedTitle = this.#escapeXml(title)
167
+
161
168
  if (xml.includes('<c:title>')) {
162
- const fullTitlePattern = /(<c:title>[\s\S]*?<\/c:title>)/
163
- return xml.replace(fullTitlePattern, titleBlock)
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
  }