node-pptx-templater 1.0.11 → 1.0.12

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.11",
3
+ "version": "1.0.12",
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",
@@ -69,24 +69,43 @@ class ShapeManager {
69
69
  throw new PPTXError(`Shape "${shapeId}" not found in slide ${slideIndex}`)
70
70
  }
71
71
 
72
- const xfrm = res.shape['p:spPr']?.['a:xfrm']
73
- if (xfrm) {
74
- if (options.x !== undefined) {
75
- if (!xfrm['a:off']) xfrm['a:off'] = {}
76
- xfrm['a:off']['@_x'] = String(Math.round(options.x))
77
- }
78
- if (options.y !== undefined) {
79
- if (!xfrm['a:off']) xfrm['a:off'] = {}
80
- xfrm['a:off']['@_y'] = String(Math.round(options.y))
81
- }
82
- if (options.width !== undefined) {
83
- if (!xfrm['a:ext']) xfrm['a:ext'] = {}
84
- xfrm['a:ext']['@_cx'] = String(Math.round(options.width))
85
- }
86
- if (options.height !== undefined) {
87
- if (!xfrm['a:ext']) xfrm['a:ext'] = {}
88
- xfrm['a:ext']['@_cy'] = String(Math.round(options.height))
89
- }
72
+ if (!res.shape['p:spPr']) {
73
+ res.shape['p:spPr'] = {}
74
+ }
75
+ if (!res.shape['p:spPr']['a:xfrm']) {
76
+ res.shape['p:spPr']['a:xfrm'] = {}
77
+ }
78
+ const xfrm = res.shape['p:spPr']['a:xfrm']
79
+
80
+ if (!xfrm['a:off']) {
81
+ xfrm['a:off'] = {}
82
+ }
83
+ if (!xfrm['a:ext']) {
84
+ xfrm['a:ext'] = {}
85
+ }
86
+
87
+ if (options.x !== undefined) {
88
+ xfrm['a:off']['@_x'] = String(Math.round(options.x))
89
+ } else if (xfrm['a:off']['@_x'] === undefined) {
90
+ xfrm['a:off']['@_x'] = '0'
91
+ }
92
+
93
+ if (options.y !== undefined) {
94
+ xfrm['a:off']['@_y'] = String(Math.round(options.y))
95
+ } else if (xfrm['a:off']['@_y'] === undefined) {
96
+ xfrm['a:off']['@_y'] = '0'
97
+ }
98
+
99
+ if (options.width !== undefined) {
100
+ xfrm['a:ext']['@_cx'] = String(Math.round(options.width))
101
+ } else if (xfrm['a:ext']['@_cx'] === undefined) {
102
+ xfrm['a:ext']['@_cx'] = '0'
103
+ }
104
+
105
+ if (options.height !== undefined) {
106
+ xfrm['a:ext']['@_cy'] = String(Math.round(options.height))
107
+ } else if (xfrm['a:ext']['@_cy'] === undefined) {
108
+ xfrm['a:ext']['@_cy'] = '0'
90
109
  }
91
110
 
92
111
  const decl = this.#xmlParser.extractDeclaration(slideXml)
@@ -293,16 +293,17 @@ class ChartCacheGenerator {
293
293
  if (countMatch) pointsCount = parseInt(countMatch[1], 10)
294
294
  }
295
295
  }
296
- if (pointsCount === 0 && options.labels) {
297
- pointsCount = options.labels.length
298
- }
299
296
 
300
- // Parse existing styling and flags from the current <c:dLbls> block
301
297
  let existingTxPr = ''
302
298
  let existingDLblPos = ''
303
299
  let existingNumFmt = ''
304
300
  let existingSpPr = ''
301
+ let existingExtLst = ''
302
+ let existingShowLeaderLines = ''
305
303
  const existingDLblSpPrs = {}
304
+ const existingDLblTxPrs = {}
305
+ const existingDLblLayouts = {}
306
+ const existingDLblPositions = {}
306
307
  const existingShowTags = {}
307
308
 
308
309
  const dLblsMatch = /<c:dLbls>([\s\S]*?)<\/c:dLbls>/.exec(content)
@@ -324,8 +325,16 @@ class ChartCacheGenerator {
324
325
  if (spPrMatch) {
325
326
  existingSpPr = spPrMatch[1]
326
327
  }
328
+ const extLstMatch = /(<c:extLst>[\s\S]*?<\/c:extLst>)/.exec(dLblsContent)
329
+ if (extLstMatch) {
330
+ existingExtLst = extLstMatch[1]
331
+ }
332
+ const showLeaderMatch = /(<c:showLeaderLines\s+[^>]*\/>)/.exec(dLblsContent)
333
+ if (showLeaderMatch) {
334
+ existingShowLeaderLines = showLeaderMatch[1]
335
+ }
327
336
 
328
- // Parse individual <c:dLbl> shape properties to map their background fills
337
+ // Parse individual <c:dLbl> properties: fills, text styling, layouts, and positions
329
338
  const dLblPattern = /<c:dLbl>([\s\S]*?)<\/c:dLbl>/g
330
339
  let dLblMatch
331
340
  while ((dLblMatch = dLblPattern.exec(dLblsContent)) !== null) {
@@ -337,6 +346,18 @@ class ChartCacheGenerator {
337
346
  if (dLblSpPrMatch) {
338
347
  existingDLblSpPrs[idx] = dLblSpPrMatch[1]
339
348
  }
349
+ const dLblTxPrMatch = /(<c:txPr>[\s\S]*?<\/c:txPr>)/.exec(dLblContent)
350
+ if (dLblTxPrMatch) {
351
+ existingDLblTxPrs[idx] = dLblTxPrMatch[1]
352
+ }
353
+ const dLblLayoutMatch = /(<c:layout>[\s\S]*?<\/c:layout>|<c:layout\/>)/.exec(dLblContent)
354
+ if (dLblLayoutMatch) {
355
+ existingDLblLayouts[idx] = dLblLayoutMatch[1]
356
+ }
357
+ const dLblPosMatch = /(<c:dLblPos\s+[^>]*\/>)/.exec(dLblContent)
358
+ if (dLblPosMatch) {
359
+ existingDLblPositions[idx] = dLblPosMatch[1]
360
+ }
340
361
  }
341
362
  }
342
363
 
@@ -367,7 +388,12 @@ class ChartCacheGenerator {
367
388
  existingNumFmt,
368
389
  existingShowTags,
369
390
  existingSpPr,
370
- existingDLblSpPrs
391
+ existingDLblSpPrs,
392
+ existingDLblTxPrs,
393
+ existingDLblLayouts,
394
+ existingDLblPositions,
395
+ existingExtLst,
396
+ existingShowLeaderLines
371
397
  )
372
398
 
373
399
  let updatedContent = content
@@ -397,7 +423,12 @@ class ChartCacheGenerator {
397
423
  existingNumFmt = '',
398
424
  existingShowTags = {},
399
425
  existingSpPr = '',
400
- existingDLblSpPrs = {}
426
+ existingDLblSpPrs = {},
427
+ existingDLblTxPrs = {},
428
+ existingDLblLayouts = {},
429
+ existingDLblPositions = {},
430
+ existingExtLst = '',
431
+ existingShowLeaderLines = ''
401
432
  ) {
402
433
  const {
403
434
  labels,
@@ -463,6 +494,11 @@ class ChartCacheGenerator {
463
494
  xml += `<c:dLbl>`
464
495
  xml += `<c:idx val="${i}"/>`
465
496
 
497
+ // Restore per-point layout (manual position offsets) from template
498
+ if (existingDLblLayouts && existingDLblLayouts[i]) {
499
+ xml += existingDLblLayouts[i]
500
+ }
501
+
466
502
  if (labelsFromCells && !template) {
467
503
  const range = ChartWorkbookUpdater.parseCellRange(labelsFromCells)
468
504
  const startColNum = ChartWorkbookUpdater.colLetterToNum(range.startCol)
@@ -510,8 +546,9 @@ class ChartCacheGenerator {
510
546
  let bodyPr = '<a:bodyPr/>'
511
547
  let pPrXml = ''
512
548
  let rPrXml = ''
513
- if (existingTxPr) {
514
- const extracted = this.extractTxPrParts(existingTxPr)
549
+ const dLblTxPr = (existingDLblTxPrs && existingDLblTxPrs[i]) || existingTxPr
550
+ if (dLblTxPr) {
551
+ const extracted = this.extractTxPrParts(dLblTxPr)
515
552
  bodyPr = extracted.bodyPr
516
553
  pPrXml = extracted.pPrXml
517
554
  rPrXml = extracted.rPrXml
@@ -556,12 +593,16 @@ class ChartCacheGenerator {
556
593
 
557
594
  if (labelStyle) {
558
595
  xml += this.generateTxPrXml(labelStyle)
596
+ } else if (existingDLblTxPrs && existingDLblTxPrs[i]) {
597
+ xml += existingDLblTxPrs[i]
559
598
  } else if (existingTxPr) {
560
599
  xml += existingTxPr
561
600
  }
562
601
 
563
602
  if (openxmlPos) {
564
603
  xml += `<c:dLblPos val="${openxmlPos}"/>`
604
+ } else if (existingDLblPositions && existingDLblPositions[i]) {
605
+ xml += existingDLblPositions[i]
565
606
  } else if (existingDLblPos) {
566
607
  xml += existingDLblPos
567
608
  }
@@ -648,6 +689,14 @@ class ChartCacheGenerator {
648
689
  xml += `<c:showBubbleSize val="0"/>`
649
690
  }
650
691
 
692
+ // Restore showLeaderLines and extension list from template
693
+ if (existingShowLeaderLines) {
694
+ xml += existingShowLeaderLines
695
+ }
696
+ if (existingExtLst) {
697
+ xml += existingExtLst
698
+ }
699
+
651
700
  xml += '</c:dLbls>'
652
701
  return xml
653
702
  }