@lofcz/pptxtojson 2.2.2 → 2.2.4

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/dist/index.d.ts CHANGED
@@ -343,12 +343,41 @@ export interface SlideTransition {
343
343
  direction: string | null
344
344
  }
345
345
 
346
+ export type AnimationTrigger = 'onClick' | 'withPrevious' | 'afterPrevious'
347
+ export type AnimationClass = 'entr' | 'exit' | 'emph' | 'path' | 'verb' | 'mediacall'
348
+ export type SlideBuildType = 'paragraph' | 'diagram' | 'graphic' | 'oleChart'
349
+
350
+ export interface SlideAnimation {
351
+ /** cNvPr id of the target shape (`p:spTgt@spid`) */
352
+ spid: string
353
+ trigger: AnimationTrigger
354
+ class: AnimationClass
355
+ presetId: number
356
+ presetSubtype: number
357
+ /** Duration in milliseconds */
358
+ duration: number
359
+ /** Delay in milliseconds before the effect starts */
360
+ delay: number
361
+ /** `p:animEffect@filter` when present (fade, wipe, barn, …) */
362
+ filter?: string
363
+ }
364
+
365
+ export interface SlideBuild {
366
+ spid: string
367
+ type: SlideBuildType
368
+ animBg?: boolean
369
+ }
370
+
346
371
  export interface Slide {
347
372
  fill: Fill
348
373
  elements: Element[]
349
374
  layoutElements: Element[]
350
375
  note: string
351
376
  transition?: SlideTransition | null
377
+ /** Click / withPrevious / afterPrevious effects in presenter order */
378
+ animations: SlideAnimation[]
379
+ /** `p:bldLst` paragraph / graphic / diagram builds */
380
+ builds: SlideBuild[]
352
381
  }
353
382
 
354
383
  export interface Options {
package/dist/index.js CHANGED
@@ -9431,6 +9431,164 @@ function parseTransition(transitionNode) {
9431
9431
  }
9432
9432
  return transition;
9433
9433
  }
9434
+ const PRESET_CLASSES = new Set([
9435
+ 'entr',
9436
+ 'exit',
9437
+ 'emph',
9438
+ 'path',
9439
+ 'verb',
9440
+ 'mediacall'
9441
+ ]);
9442
+ const NODE_TYPE_TO_TRIGGER = {
9443
+ clickEffect: 'onClick',
9444
+ withEffect: 'withPrevious',
9445
+ afterEffect: 'afterPrevious',
9446
+ clickPar: 'onClick',
9447
+ withGroup: 'withPrevious',
9448
+ afterGroup: 'afterPrevious',
9449
+ interactiveSeq: 'onClick'
9450
+ };
9451
+ function asArray(value) {
9452
+ if (null == value) return [];
9453
+ return Array.isArray(value) ? value : [
9454
+ value
9455
+ ];
9456
+ }
9457
+ function parseMs(value) {
9458
+ if (null == value || '' === value || 'indefinite' === value) return null;
9459
+ const ms = parseInt(value, 10);
9460
+ return Number.isFinite(ms) ? ms : null;
9461
+ }
9462
+ function findTimingNode(content) {
9463
+ if (!content) return null;
9464
+ return getTextByPathList(content, [
9465
+ 'p:sld',
9466
+ 'p:timing'
9467
+ ]) || getTextByPathList(content, [
9468
+ 'p:sld',
9469
+ 'mc:AlternateContent',
9470
+ 'mc:Choice',
9471
+ 'p:timing'
9472
+ ]) || getTextByPathList(content, [
9473
+ 'p:sld',
9474
+ 'mc:AlternateContent',
9475
+ 'mc:Fallback',
9476
+ 'p:timing'
9477
+ ]);
9478
+ }
9479
+ function collectSpids(node, out = []) {
9480
+ if (!node || 'object' != typeof node) return out;
9481
+ const spid = node.attrs?.spid;
9482
+ if (null != spid && '' !== spid) out.push(String(spid));
9483
+ for (const key of Object.keys(node))if ('attrs' !== key && 'value' !== key) for (const child of asArray(node[key]))collectSpids(child, out);
9484
+ return out;
9485
+ }
9486
+ function collectDuration(node) {
9487
+ let found = null;
9488
+ const walk = (n)=>{
9489
+ if (!n || 'object' != typeof n) return;
9490
+ const ms = parseMs(n.attrs?.dur);
9491
+ if (null != ms && ms >= 0) found = ms;
9492
+ for (const key of Object.keys(n))if ('attrs' !== key && 'value' !== key) for (const child of asArray(n[key]))walk(child);
9493
+ };
9494
+ walk(node);
9495
+ return found;
9496
+ }
9497
+ function collectFilter(node) {
9498
+ let filter = '';
9499
+ const walk = (n)=>{
9500
+ if (!n || 'object' != typeof n) return;
9501
+ if (n.attrs?.filter) filter = n.attrs.filter;
9502
+ for (const key of Object.keys(n))if ('attrs' !== key && 'value' !== key) for (const child of asArray(n[key]))walk(child);
9503
+ };
9504
+ walk(node);
9505
+ return filter;
9506
+ }
9507
+ function collectDelay(node, inherited = 0) {
9508
+ const fromAttr = parseMs(node?.attrs?.delay);
9509
+ if (null != fromAttr && fromAttr >= 0) return fromAttr;
9510
+ const condRoot = node?.['p:stCondLst'];
9511
+ for (const cond of asArray(condRoot?.['p:cond'] || condRoot)){
9512
+ const fromCond = parseMs(cond?.attrs?.delay);
9513
+ if (null != fromCond && fromCond >= 0) return fromCond;
9514
+ }
9515
+ return inherited;
9516
+ }
9517
+ function collectEffects(node, inheritedTrigger = 'onClick', inheritedDelay = 0, results = []) {
9518
+ if (!node || 'object' != typeof node) return results;
9519
+ const attrs = node.attrs || {};
9520
+ const trigger = NODE_TYPE_TO_TRIGGER[attrs.nodeType] || inheritedTrigger;
9521
+ const delay = collectDelay(node, inheritedDelay);
9522
+ if (attrs.presetClass && PRESET_CLASSES.has(attrs.presetClass)) {
9523
+ const spids = [
9524
+ ...new Set(collectSpids(node))
9525
+ ];
9526
+ const duration = collectDuration(node);
9527
+ const filter = collectFilter(node);
9528
+ const effectTrigger = NODE_TYPE_TO_TRIGGER[attrs.nodeType] || trigger;
9529
+ for (const spid of spids){
9530
+ const animation = {
9531
+ spid,
9532
+ trigger: effectTrigger,
9533
+ class: attrs.presetClass,
9534
+ presetId: parseInt(attrs.presetID || '0', 10) || 0,
9535
+ presetSubtype: parseInt(attrs.presetSubtype || '0', 10) || 0,
9536
+ duration: null != duration ? duration : 1000,
9537
+ delay
9538
+ };
9539
+ if (filter) animation.filter = filter;
9540
+ results.push(animation);
9541
+ }
9542
+ return results;
9543
+ }
9544
+ for (const key of Object.keys(node))if ('attrs' !== key && 'value' !== key) for (const child of asArray(node[key]))collectEffects(child, trigger, delay, results);
9545
+ return results;
9546
+ }
9547
+ function parseBuildList(timing) {
9548
+ const bldLst = timing?.['p:bldLst'];
9549
+ if (!bldLst) return [];
9550
+ const kinds = [
9551
+ [
9552
+ 'p:bldP',
9553
+ 'paragraph'
9554
+ ],
9555
+ [
9556
+ 'p:bldDgm',
9557
+ 'diagram'
9558
+ ],
9559
+ [
9560
+ 'p:bldGraphic',
9561
+ 'graphic'
9562
+ ],
9563
+ [
9564
+ 'p:bldOleChart',
9565
+ 'oleChart'
9566
+ ]
9567
+ ];
9568
+ const builds = [];
9569
+ for (const [tag, type] of kinds)for (const node of asArray(bldLst[tag])){
9570
+ const spid = node?.attrs?.spid;
9571
+ if (null == spid || '' === spid) continue;
9572
+ const item = {
9573
+ spid: String(spid),
9574
+ type
9575
+ };
9576
+ if (node.attrs?.animBg === '1' || node.attrs?.animBg === 'true') item.animBg = true;
9577
+ builds.push(item);
9578
+ }
9579
+ return builds;
9580
+ }
9581
+ function parseTiming(slideContent) {
9582
+ const timing = findTimingNode(slideContent);
9583
+ if (!timing) return {
9584
+ animations: [],
9585
+ builds: []
9586
+ };
9587
+ return {
9588
+ animations: collectEffects(timing),
9589
+ builds: parseBuildList(timing)
9590
+ };
9591
+ }
9434
9592
  async function loadDiagramFile(warpObj, filename, transformDrawing = false) {
9435
9593
  if (!filename) return null;
9436
9594
  const cacheKey = `${transformDrawing ? 'drawing:' : 'xml:'}${filename}`;
@@ -9628,27 +9786,97 @@ async function parse(file, options = {}) {
9628
9786
  }
9629
9787
  };
9630
9788
  }
9789
+ function asNodeArray(node) {
9790
+ if (!node) return [];
9791
+ return node.constructor === Array ? node : [
9792
+ node
9793
+ ];
9794
+ }
9795
+ function sortSlideXml(p1, p2) {
9796
+ const n1 = +(/(\d+)\.xml/.exec(p1)?.[1] || 0);
9797
+ const n2 = +(/(\d+)\.xml/.exec(p2)?.[1] || 0);
9798
+ return n1 - n2;
9799
+ }
9800
+ function resolvePresentationTarget(target) {
9801
+ let relTarget = String(target || '').replace(/\\/g, '/');
9802
+ if (!relTarget) return '';
9803
+ if (0 === relTarget.indexOf('/ppt/')) return relTarget.substr(1);
9804
+ if (-1 !== relTarget.indexOf('../')) return relTarget.replace('../', 'ppt/');
9805
+ if (0 === relTarget.indexOf('ppt/')) return relTarget;
9806
+ return 'ppt/' + relTarget.replace(/^\//, '');
9807
+ }
9808
+ function relationshipRid(attrs) {
9809
+ if (!attrs) return '';
9810
+ return attrs['r:id'] || attrs.rId || '';
9811
+ }
9812
+ async function getSlidesFromPresentation(zip) {
9813
+ const relsContent = await readXmlFile(zip, 'ppt/_rels/presentation.xml.rels');
9814
+ const relItems = asNodeArray(getTextByPathList(relsContent, [
9815
+ 'Relationships',
9816
+ 'Relationship'
9817
+ ]));
9818
+ const relsMap = {};
9819
+ const slideTargets = [];
9820
+ for (const rel of relItems){
9821
+ const id = getTextByPathList(rel, [
9822
+ 'attrs',
9823
+ 'Id'
9824
+ ]);
9825
+ const type = getTextByPathList(rel, [
9826
+ 'attrs',
9827
+ 'Type'
9828
+ ]);
9829
+ const target = getTextByPathList(rel, [
9830
+ 'attrs',
9831
+ 'Target'
9832
+ ]);
9833
+ if (!id || !target) continue;
9834
+ const loc = resolvePresentationTarget(target);
9835
+ if (loc) {
9836
+ relsMap[id] = loc;
9837
+ if ('slide' === getRelTypeName(type)) slideTargets.push(loc);
9838
+ }
9839
+ }
9840
+ const presentation = await readXmlFile(zip, 'ppt/presentation.xml');
9841
+ const sldIds = asNodeArray(getTextByPathList(presentation, [
9842
+ 'p:presentation',
9843
+ 'p:sldIdLst',
9844
+ 'p:sldId'
9845
+ ]));
9846
+ const ordered = [];
9847
+ for (const sldId of sldIds){
9848
+ const rid = relationshipRid(sldId?.attrs);
9849
+ if (rid && relsMap[rid]) ordered.push(relsMap[rid]);
9850
+ }
9851
+ if (ordered.length) return ordered;
9852
+ return slideTargets.sort(sortSlideXml);
9853
+ }
9631
9854
  async function getContentTypes(zip) {
9632
9855
  const ContentTypesJson = await readXmlFile(zip, '[Content_Types].xml');
9633
- const subObj = ContentTypesJson['Types']['Override'];
9856
+ const overrides = asNodeArray(getTextByPathList(ContentTypesJson, [
9857
+ 'Types',
9858
+ 'Override'
9859
+ ]));
9634
9860
  let slidesLocArray = [];
9635
9861
  let slideLayoutsLocArray = [];
9636
- for (const item of subObj)switch(item['attrs']['ContentType']){
9637
- case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml':
9638
- slidesLocArray.push(item['attrs']['PartName'].substr(1));
9639
- break;
9640
- case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml':
9641
- slideLayoutsLocArray.push(item['attrs']['PartName'].substr(1));
9642
- break;
9643
- default:
9862
+ for (const item of overrides){
9863
+ const contentType = item?.attrs?.ContentType;
9864
+ const partName = item?.attrs?.PartName;
9865
+ if (!contentType || !partName) continue;
9866
+ const loc = String(partName).replace(/^\//, '');
9867
+ switch(contentType){
9868
+ case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml':
9869
+ slidesLocArray.push(loc);
9870
+ break;
9871
+ case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml':
9872
+ slideLayoutsLocArray.push(loc);
9873
+ break;
9874
+ default:
9875
+ }
9644
9876
  }
9645
- const sortSlideXml = (p1, p2)=>{
9646
- const n1 = +/(\d+)\.xml/.exec(p1)[1];
9647
- const n2 = +/(\d+)\.xml/.exec(p2)[1];
9648
- return n1 - n2;
9649
- };
9650
9877
  slidesLocArray = slidesLocArray.sort(sortSlideXml);
9651
9878
  slideLayoutsLocArray = slideLayoutsLocArray.sort(sortSlideXml);
9879
+ if (!slidesLocArray.length) slidesLocArray = await getSlidesFromPresentation(zip);
9652
9880
  return {
9653
9881
  slides: slidesLocArray,
9654
9882
  slideLayouts: slideLayoutsLocArray
@@ -9944,6 +10172,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
9944
10172
  if (!transitionNode) transitionNode = findTransitionNode(slideLayoutContent, 'p:sldLayout');
9945
10173
  if (!transitionNode) transitionNode = findTransitionNode(slideMasterContent, 'p:sldMaster');
9946
10174
  const transition = parseTransition(transitionNode);
10175
+ const { animations, builds } = parseTiming(slideContent);
9947
10176
  const showMasterSpOnSlide = getTextByPathList(slideContent, [
9948
10177
  'p:sld',
9949
10178
  'attrs',
@@ -9957,7 +10186,9 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
9957
10186
  layoutElements,
9958
10187
  note,
9959
10188
  transition,
9960
- hideBackground
10189
+ hideBackground,
10190
+ animations,
10191
+ builds
9961
10192
  };
9962
10193
  }
9963
10194
  function getHyperlinkFromCNvPr(cNvPr, warpObj) {