Skip to content

Commit 4d6c1ea

Browse files
committed
chore(shape): replace/remove shape placeholders properly
1 parent 5f860c1 commit 4d6c1ea

File tree

5 files changed

+116
-182
lines changed

5 files changed

+116
-182
lines changed
33.8 KB
Binary file not shown.

src/classes/has-shapes.ts

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
ShapeModificationCallback,
1717
ShapeTargetType,
1818
SlideModificationCallback,
19+
SlidePlaceholder,
1920
SourceIdentifier,
2021
StatusTracker,
2122
} from '../types/types';
@@ -34,7 +35,6 @@ import { Image } from '../shapes/image';
3435
import { ElementType } from '../enums/element-type';
3536
import { GenericShape } from '../shapes/generic';
3637
import { XmlSlideHelper } from '../helper/xml-slide-helper';
37-
import { vd } from '../helper/general-helper';
3838
import { OLEObject } from '../shapes/ole';
3939

4040
export default class HasShapes {
@@ -130,7 +130,7 @@ export default class HasShapes {
130130
* @internal
131131
*/
132132
unsupportedRelationTypes = [
133-
// 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject',
133+
// 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject',
134134
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
135135
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags',
136136
];
@@ -412,11 +412,9 @@ export default class HasShapes {
412412
);
413413
break;
414414
case ElementType.OLEObject:
415-
await new OLEObject(info, this.targetType, this.sourceArchive)[info.mode](
416-
this.targetTemplate,
417-
this.targetNumber,
418-
this.targetType,
419-
);
415+
await new OLEObject(info, this.targetType, this.sourceArchive)[
416+
info.mode
417+
](this.targetTemplate, this.targetNumber, this.targetType);
420418
break;
421419
default:
422420
break;
@@ -785,25 +783,27 @@ export default class HasShapes {
785783
sourceArchive: this.sourceArchive,
786784
sourceSlideNumber: this.sourceNumber,
787785
},
788-
this.targetType
786+
this.targetType,
789787
).modifyOnAddedSlide(this.targetTemplate, this.targetNumber);
790788
}
791789

792790
const images = await Image.getAllOnSlide(this.sourceArchive, this.relsPath);
793791
for (const image of images) {
794-
795792
await new Image(
796793
{
797794
mode: 'append',
798795
target: image,
799796
sourceArchive: this.sourceArchive,
800797
sourceSlideNumber: this.sourceNumber,
801798
},
802-
this.targetType
799+
this.targetType,
803800
).modifyOnAddedSlide(this.targetTemplate, this.targetNumber);
804801
}
805802

806-
const oleObjects = await OLEObject.getAllOnSlide(this.sourceArchive, this.relsPath);
803+
const oleObjects = await OLEObject.getAllOnSlide(
804+
this.sourceArchive,
805+
this.relsPath,
806+
);
807807
for (const oleObject of oleObjects) {
808808
await new OLEObject(
809809
{
@@ -813,7 +813,7 @@ export default class HasShapes {
813813
sourceSlideNumber: this.sourceNumber,
814814
},
815815
this.targetType,
816-
this.sourceArchive
816+
this.sourceArchive,
817817
).modifyOnAddedSlide(this.targetTemplate, this.targetNumber, oleObjects);
818818
}
819819
}
@@ -881,7 +881,7 @@ export default class HasShapes {
881881
sourceArchive,
882882
relsPath,
883883
sourceElement,
884-
'oleObject'
884+
'oleObject',
885885
);
886886

887887
return {
@@ -930,12 +930,20 @@ export default class HasShapes {
930930
* be processed by pptx-automizer at the moment.
931931
* @internal
932932
*/
933-
async cleanSlide(targetPath: string): Promise<void> {
933+
async cleanSlide(
934+
targetPath: string,
935+
sourcePlaceholderTypes?: SlidePlaceholder[],
936+
): Promise<void> {
934937
const xml = await XmlHelper.getXmlFromArchive(
935938
this.targetArchive,
936939
targetPath,
937940
);
938941

942+
if (sourcePlaceholderTypes) {
943+
this.removeDuplicatePlaceholders(xml, sourcePlaceholderTypes);
944+
this.normalizePlaceholderShapes(xml, sourcePlaceholderTypes);
945+
}
946+
939947
this.unsupportedTags.forEach((tag) => {
940948
const drop = xml.getElementsByTagName(tag);
941949
const length = drop.length;
@@ -946,6 +954,66 @@ export default class HasShapes {
946954
XmlHelper.writeXmlToArchive(this.targetArchive, targetPath, xml);
947955
}
948956

957+
/**
958+
* If you insert a placeholder shape on a target slide with an empty
959+
* placeholder of the same type, we need to remove the existing
960+
* placeholder.
961+
*
962+
* @param xml
963+
* @param sourcePlaceholderTypes
964+
*/
965+
removeDuplicatePlaceholders(
966+
xml: XmlDocument,
967+
sourcePlaceholderTypes: SlidePlaceholder[],
968+
) {
969+
const placeholders = xml.getElementsByTagName('p:ph');
970+
const usedTypes = {};
971+
XmlHelper.modifyCollection(placeholders, (placeholder: XmlElement) => {
972+
const type = placeholder.getAttribute('type');
973+
usedTypes[type] = usedTypes[type] || 0;
974+
usedTypes[type]++;
975+
});
976+
977+
for (const usedType in usedTypes) {
978+
const count = usedTypes[usedType];
979+
if (count > 1) {
980+
// TODO: in case more than two placeholders are of a kind,
981+
// this will likely remove more than intended. Should also match by id.
982+
const removePlaceholders = sourcePlaceholderTypes.filter(
983+
(sourcePlaceholder) => sourcePlaceholder.type === usedType,
984+
);
985+
removePlaceholders.forEach((removePlaceholder) => {
986+
const removePlaceholderShape = removePlaceholder.xml.parentNode
987+
.parentNode.parentNode as XmlElement;
988+
XmlHelper.remove(removePlaceholderShape);
989+
});
990+
}
991+
}
992+
}
993+
994+
/**
995+
* If a placeholder shape was inserted on a slide without a corresponding
996+
* placeholder, powerPoint will usually smash the shape's formatting.
997+
* This function removes the placeholder tag.
998+
* @param xml
999+
* @param sourcePlaceholderTypes
1000+
*/
1001+
normalizePlaceholderShapes(
1002+
xml: XmlDocument,
1003+
sourcePlaceholderTypes: SlidePlaceholder[],
1004+
) {
1005+
const placeholders = xml.getElementsByTagName('p:ph');
1006+
XmlHelper.modifyCollection(placeholders, (placeholder: XmlElement) => {
1007+
const usedType = placeholder.getAttribute('type');
1008+
const existingPlaceholder = sourcePlaceholderTypes.find(
1009+
(sourcePlaceholder) => sourcePlaceholder.type === usedType,
1010+
);
1011+
if (!existingPlaceholder) {
1012+
XmlHelper.remove(placeholder);
1013+
}
1014+
});
1015+
}
1016+
9491017
/**
9501018
* Removes all unsupported relations from _rels xml.
9511019
* @internal
@@ -962,4 +1030,21 @@ export default class HasShapes {
9621030
},
9631031
});
9641032
}
1033+
1034+
async parsePlaceholders(): Promise<SlidePlaceholder[]> {
1035+
const xml = await XmlHelper.getXmlFromArchive(
1036+
this.targetArchive,
1037+
this.targetPath,
1038+
);
1039+
const placeholderTypes = [];
1040+
const placeholders = xml.getElementsByTagName('p:ph');
1041+
XmlHelper.modifyCollection(placeholders, (placeholder: XmlElement) => {
1042+
placeholderTypes.push({
1043+
type: placeholder.getAttribute('type'),
1044+
id: placeholder.getAttribute('id'),
1045+
xml: placeholder,
1046+
});
1047+
});
1048+
return placeholderTypes;
1049+
}
9651050
}

src/classes/slide.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { FileHelper } from '../helper/file-helper';
2-
import { ShapeTargetType, SourceIdentifier, SlideModificationCallback } from '../types/types';
2+
import { ShapeTargetType, SourceIdentifier } from '../types/types';
33
import { ISlide } from '../interfaces/islide';
44
import { IPresentationProps } from '../interfaces/ipresentation-props';
55
import { PresTemplate } from '../interfaces/pres-template';
66
import { RootPresTemplate } from '../interfaces/root-pres-template';
7-
import { last, vd } from '../helper/general-helper';
7+
import { last } from '../helper/general-helper';
88
import { XmlRelationshipHelper } from '../helper/xml-relationship-helper';
99
import { IMaster } from '../interfaces/imaster';
1010
import HasShapes from './has-shapes';
@@ -60,6 +60,8 @@ export class Slide extends HasShapes implements ISlide {
6060
);
6161
}
6262

63+
const placeholderTypes = await this.parsePlaceholders();
64+
6365
if (this.importElements.length) {
6466
await this.importedSelectedElements();
6567
}
@@ -71,7 +73,7 @@ export class Slide extends HasShapes implements ISlide {
7173
const assert = this.targetTemplate.automizer.params.showIntegrityInfo;
7274
await this.checkIntegrity(info, assert);
7375

74-
await this.cleanSlide(this.targetPath);
76+
await this.cleanSlide(this.targetPath, placeholderTypes);
7577

7678
this.status.increment();
7779
}

0 commit comments

Comments
 (0)