pptx-automizer
Version:
A template based pptx generator
878 lines • 39.3 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const xml_relationship_helper_1 = require("../helper/xml-relationship-helper");
const xml_helper_1 = require("../helper/xml-helper");
const file_helper_1 = require("../helper/file-helper");
const chart_1 = require("../shapes/chart");
const image_1 = require("../shapes/image");
const element_type_1 = require("../enums/element-type");
const generic_1 = require("../shapes/generic");
const xml_slide_helper_1 = require("../helper/xml-slide-helper");
const ole_1 = require("../shapes/ole");
const hyperlink_1 = require("../shapes/hyperlink");
const hyperlink_processor_1 = require("../helper/hyperlink-processor");
const diagram_1 = require("../shapes/diagram");
const general_helper_1 = require("../helper/general-helper");
class HasShapes {
constructor(params) {
/**
* List of unsupported tags in slide xml
* @internal
*/
this.unsupportedTags = [
'p:custDataLst',
// exclude bullet images
'a:buBlip',
// 'p:oleObj',
// 'mc:AlternateContent',
// 'a14:imgProps',
// 'a14:imgLayer'
];
/**
* List of unsupported tags in slide xml
* @internal
*/
this.unsupportedRelationTypes = [
// 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject',
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags',
];
this.cleanupPlaceholders = false;
this.sourceTemplate = params.template;
this.preparations = [];
this.modifications = [];
this.relModifications = [];
this.importElements = [];
this.generateElements = [];
this.presentation = params.presentation;
this.status = params.presentation.status;
this.content = params.presentation.content;
this.cleanupPlaceholders = params.presentation.params.cleanupPlaceholders;
}
/**
* Asynchronously retrieves all text element IDs from the slide.
* @returns {Promise<string[]>} A promise that resolves to an array of text element IDs.
*/
getAllTextElementIds() {
return __awaiter(this, void 0, void 0, function* () {
const xmlSlideHelper = yield this.getSlideHelper();
// Get all text element IDs
return xmlSlideHelper.getAllTextElementIds(this.sourceTemplate.useCreationIds || false);
});
}
/**
* Asynchronously retrieves all elements from the slide.
* @param filterTags Use an array of strings to filter parent tags (e.g. 'sp')
* @param layoutPlaceholders
* @returns {Promise<ElementInfo[]>} A promise that resolves to an array of ElementInfo objects.
*/
getAllElements(filterTags, layoutPlaceholders) {
return __awaiter(this, void 0, void 0, function* () {
const xmlSlideHelper = yield this.getSlideHelper();
// Get all ElementInfo objects
return xmlSlideHelper.getAllElements(filterTags, layoutPlaceholders);
});
}
/**
* Asynchronously retrieves one element from the slide.
* @params selector Use shape name or creationId to find the shape
* @returns {Promise<ElementInfo>} A promise that resolves an ElementInfo object.
*/
getElement(selector) {
return __awaiter(this, void 0, void 0, function* () {
const xmlSlideHelper = yield this.getSlideHelper();
return xmlSlideHelper.getElement(selector);
});
}
/**
* Asynchronously retrieves the dimensions of the slide.
* This function utilizes the XmlSlideHelper to get the slide dimensions.
*
* @returns {Promise<{width: number, height: number}>} A promise that resolves to an object containing the width and height of the slide.
*/
getDimensions() {
return __awaiter(this, void 0, void 0, function* () {
const xmlSlideHelper = yield this.getSlideHelper();
return xmlSlideHelper.getDimensions();
});
}
/**
* Asynchronously retrieves an instance of XmlSlideHelper for slide.
* @returns {Promise<XmlSlideHelper>} An instance of XmlSlideHelper.
*/
getSlideHelper() {
return __awaiter(this, void 0, void 0, function* () {
return this.getSlideHelperInstance(this.sourceTemplate.archive, this.sourcePath, this.sourceNumber);
});
}
getSlideHelperInstance(archive, path, number) {
return __awaiter(this, void 0, void 0, function* () {
try {
// Retrieve the slide XML data
const slideXml = yield xml_helper_1.XmlHelper.getXmlFromArchive(archive, path);
const sourceLayoutId = yield xml_relationship_helper_1.XmlRelationshipHelper.getSlideLayoutNumber(archive, number);
// Initialize the XmlSlideHelper
return new xml_slide_helper_1.XmlSlideHelper(slideXml, {
sourceArchive: archive,
slideNumber: number,
sourceLayoutId,
});
}
catch (error) {
// Log the error message
throw new Error(error.message);
}
});
}
/**
* Push preparations list
* @internal
* @param callback
*/
prepare(callback) {
this.preparations.push(callback);
}
/**
* Push modifications list
* @internal
* @param callback
*/
modify(callback) {
this.modifications.push(callback);
}
/**
* Push relations modifications list
* @internal
* @param callback
*/
modifyRelations(callback) {
this.relModifications.push(callback);
}
/**
* Select and modify a single element on an added slide.
* @param {string} selector - Element's name on the slide.
* Should be a unique string defined on the "Selection"-pane within ppt.
* @param {ShapeModificationCallback | ShapeModificationCallback[]} callback - One or more callback functions to apply.
* Depending on the shape type (e.g. chart or table), different arguments will be passed to the callback.
*/
modifyElement(selector, callback) {
const presName = this.sourceTemplate.name;
const slideNumber = this.sourceNumber;
this.addElementToModificationsList(presName, slideNumber, selector, 'modify', callback);
return this;
}
generate(generate, objectName) {
this.generateElements.push({
objectName,
callback: generate,
});
return this;
}
getGeneratedElements() {
return this.generateElements;
}
/**
* Select, insert and (optionally) modify a single element to a slide.
* @param {string} presName - Filename or alias name of the template presentation.
* Must have been importet with Automizer.load().
* @param {number} slideNumber - Slide number within the specified template to search for the required element.
* @param {FindElementSelector} selector - a string or object to find the target element
* @param {ShapeModificationCallback | ShapeModificationCallback[]} callback - One or more callback functions to apply.
* Depending on the shape type (e.g. chart or table), different arguments will be passed to the callback.
*/
addElement(presName, slideNumber, selector, callback) {
this.addElementToModificationsList(presName, slideNumber, selector, 'append', callback);
return this;
}
/**
* Remove a single element from slide.
* @param {string} selector - Element's name on the slide.
*/
removeElement(selector) {
const presName = this.sourceTemplate.name;
const slideNumber = this.sourceNumber;
this.addElementToModificationsList(presName, slideNumber, selector, 'remove', undefined);
return this;
}
/**
* Adds element to modifications list
* @internal
* @param presName
* @param slideNumber
* @param selector
* @param mode
* @param [callback]
* @returns element to modifications list
*/
addElementToModificationsList(presName, slideNumber, selector, mode, callback) {
this.importElements.push({
presName,
slideNumber,
selector,
mode,
callback,
});
}
/**
* ToDo: Implement creationIds as well for slideMasters
*
* Try to convert a given slide's creationId to corresponding slide number.
* Used if automizer is run with useCreationIds: true
* @internal
* @param template
* @param slideIdentifier
*/
getSlideNumber(template, slideIdentifier) {
if (template.useCreationIds === true &&
template.creationIds !== undefined) {
const matchCreationId = template.creationIds.find((slideInfo) => slideInfo.id === Number(slideIdentifier));
if (matchCreationId) {
return matchCreationId.number;
}
throw ('Could not find slide number for creationId: ' +
slideIdentifier +
'@' +
template.name);
}
return slideIdentifier;
}
/**
* Processes and updates the list of imported elements by ensuring their uniqueness based on a generated hash.
* If duplicate elements are found, their callbacks are merged.
*
* @return {Promise<void>} Resolves when the process of identifying and updating unique imported elements is complete.
*/
getUniqueImportedElements() {
return __awaiter(this, void 0, void 0, function* () {
for (const element of this.importElements) {
const info = yield this.getElementInfo(element);
if (element.mode === 'append') {
element.info = info;
continue;
}
const selector = xml_slide_helper_1.XmlSlideHelper.getSelector(info.sourceElement);
const eleHash = JSON.stringify(selector);
const alreadyImported = this.importElements.find((ele) => { var _a; return ((_a = ele.info) === null || _a === void 0 ? void 0 : _a.hash) === eleHash; });
if (alreadyImported) {
const existingCallbacks = general_helper_1.GeneralHelper.arrayify(element.callback);
const pushCallbacks = general_helper_1.GeneralHelper.arrayify(alreadyImported.info.callback);
alreadyImported.info.callback = [
...existingCallbacks,
...pushCallbacks
];
}
else {
info.hash = eleHash;
element.info = info;
}
}
return this.importElements.filter((ele) => {
return ele.info;
});
});
}
/**
* Imported selected elements while merging multiple element modifications
* @internal
*/
importedSelectedElements() {
return __awaiter(this, void 0, void 0, function* () {
const importElements = yield this.getUniqueImportedElements();
for (const element of importElements) {
const info = element.info;
switch (info === null || info === void 0 ? void 0 : info.type) {
case element_type_1.ElementType.Chart:
yield new chart_1.Chart(info, this.targetType)[info.mode](this.targetTemplate, this.targetNumber, this.targetType);
break;
case element_type_1.ElementType.Image:
yield new image_1.Image(info, this.targetType)[info.mode](this.targetTemplate, this.targetNumber, this.targetType);
break;
case element_type_1.ElementType.Shape:
yield new generic_1.GenericShape(info, this.targetType)[info.mode](this.targetTemplate, this.targetNumber, this.targetType);
break;
case element_type_1.ElementType.Diagram:
yield new diagram_1.Diagram(info, this.targetType)[info.mode](this.targetTemplate, this.targetNumber, this.targetType);
break;
case element_type_1.ElementType.OLEObject:
yield new ole_1.OLEObject(info, this.targetType, this.sourceArchive)[info.mode](this.targetTemplate, this.targetNumber, this.targetType);
break;
case element_type_1.ElementType.Hyperlink:
// For hyperlinks, we need to handle them differently
if (info.target) {
yield new hyperlink_1.Hyperlink(info, this.targetType, this.sourceArchive, info.target.isExternal ? 'external' : 'internal', info.target.file)[info.mode](this.targetTemplate, this.targetNumber);
}
break;
default:
break;
}
}
});
}
/**
* Gets element info
* @internal
* @param importElement
* @returns element info
*/
getElementInfo(importElement) {
return __awaiter(this, void 0, void 0, function* () {
const template = this.root.getTemplate(importElement.presName);
const slideNumber = importElement.mode === 'append'
? this.getSlideNumber(template, importElement.slideNumber)
: importElement.slideNumber;
let currentMode = 'slideToSlide';
if (this.targetType === 'slideMaster') {
if (importElement.mode === 'append') {
currentMode = 'slideToMaster';
}
else {
currentMode = 'onMaster';
}
}
// It is possible to import shapes from loaded slides to slideMaster,
// as well as to modify an existing shape on current slideMaster
const sourcePath = currentMode === 'onMaster'
? `ppt/slideMasters/slideMaster${slideNumber}.xml`
: `ppt/slides/slide${slideNumber}.xml`;
const sourceRelPath = currentMode === 'onMaster'
? `ppt/slideMasters/_rels/slideMaster${slideNumber}.xml.rels`
: `ppt/slides/_rels/slide${slideNumber}.xml.rels`;
const sourceArchive = yield template.archive;
const useCreationIds = template.useCreationIds === true && template.creationIds !== undefined;
const { sourceElement, selector, mode } = yield this.findElementOnSlide(importElement.selector, sourceArchive, sourcePath, useCreationIds);
if (!sourceElement) {
console.error(`Can't find element on slide ${slideNumber} in ${importElement.presName}: `);
console.log(importElement);
return;
}
const appendElementParams = yield this.analyzeElement(sourceElement, sourceArchive, sourceRelPath);
return {
mode: importElement.mode,
name: selector,
selector: xml_slide_helper_1.XmlSlideHelper.getSelector(sourceElement),
hasCreationId: mode === 'findByElementCreationId',
sourceArchive,
sourceSlideNumber: slideNumber,
sourceElement,
callback: importElement.callback,
target: appendElementParams.target,
type: appendElementParams.type,
};
});
}
/**
* @param selector
* @param sourceArchive
* @param sourcePath
* @param useCreationIds
*/
findElementOnSlide(selector, sourceArchive, sourcePath, useCreationIds) {
return __awaiter(this, void 0, void 0, function* () {
const strategies = [];
if (typeof selector === 'string') {
if (useCreationIds) {
strategies.push({
mode: 'findByElementCreationId',
selector: selector,
});
}
strategies.push({
mode: 'findByElementName',
selector: selector,
});
}
else {
if (selector.creationId) {
strategies.push({
mode: 'findByElementCreationId',
selector: selector.creationId,
});
}
strategies.push({
mode: 'findByElementName',
selector: selector.name,
nameIdx: selector.nameIdx,
});
}
for (const findElement of strategies) {
const mode = findElement.mode;
const sourceElement = yield xml_helper_1.XmlHelper[mode](sourceArchive, sourcePath, findElement.selector, findElement.nameIdx);
if (sourceElement) {
return { sourceElement, selector: findElement.selector, mode };
}
}
return { sourceElement: undefined, selector: JSON.stringify(selector) };
});
}
checkIntegrity(info, assert) {
return __awaiter(this, void 0, void 0, function* () {
if (info || assert) {
const masterRels = (yield new xml_relationship_helper_1.XmlRelationshipHelper().initialize(this.targetArchive, `${this.targetType}${this.targetNumber}.xml.rels`, `ppt/${this.targetType}s/_rels`));
yield masterRels.assertRelatedContent(this.sourceArchive, info, assert);
}
});
}
/**
* Adds slide to presentation
* @internal
* @returns slide to presentation
*/
addToPresentation() {
return __awaiter(this, void 0, void 0, function* () {
const relId = yield xml_helper_1.XmlHelper.getNextRelId(this.targetArchive, 'ppt/_rels/presentation.xml.rels');
yield this.appendToSlideRel(this.targetArchive, relId, this.targetNumber);
if (this.targetType === 'slide') {
yield this.appendToSlideList(this.targetArchive, relId);
}
else if (this.targetType === 'slideMaster') {
yield this.appendToSlideMasterList(this.targetArchive, relId);
}
else if (this.targetType === 'slideLayout') {
// No changes to ppt/presentation.xml required for slideLayouts
}
yield this.appendToContentType(this.targetArchive, this.targetNumber);
});
}
/**
* Appends to slide rel
* @internal
* @param rootArchive
* @param relId
* @param slideCount
* @returns to slide rel
*/
appendToSlideRel(rootArchive, relId, slideCount) {
return xml_helper_1.XmlHelper.append({
archive: rootArchive,
file: `ppt/_rels/presentation.xml.rels`,
parent: (xml) => xml.getElementsByTagName('Relationships')[0],
tag: 'Relationship',
attributes: {
Id: relId,
Type: `http://schemas.openxmlformats.org/officeDocument/2006/relationships/${this.targetType}`,
Target: `${this.targetType}s/${this.targetType}${slideCount}.xml`,
},
});
}
/**
* Appends a new slide to slide list in presentation.xml.
* If rootArchive has no slides, a new node will be created.
* "id"-attribute of 'p:sldId'-element must be greater than 255.
* @internal
* @param rootArchive
* @param relId
* @returns to slide list
*/
appendToSlideList(rootArchive, relId) {
return xml_helper_1.XmlHelper.append({
archive: rootArchive,
file: `ppt/presentation.xml`,
assert: (xml) => __awaiter(this, void 0, void 0, function* () {
if (xml.getElementsByTagName('p:sldIdLst').length === 0) {
xml_helper_1.XmlHelper.insertAfter(xml.createElement('p:sldIdLst'), xml.getElementsByTagName('p:sldMasterIdLst')[0]);
}
}),
parent: (xml) => xml.getElementsByTagName('p:sldIdLst')[0],
tag: 'p:sldId',
attributes: {
'r:id': relId,
},
});
}
/**
* Appends a new slide to slide list in presentation.xml.
* If rootArchive has no slides, a new node will be created.
* "id"-attribute of 'p:sldId'-element must be greater than 255.
* @internal
* @param rootArchive
* @param relId
* @returns to slide list
*/
appendToSlideMasterList(rootArchive, relId) {
return xml_helper_1.XmlHelper.append({
archive: rootArchive,
file: `ppt/presentation.xml`,
parent: (xml) => xml.getElementsByTagName('p:sldMasterIdLst')[0],
tag: 'p:sldMasterId',
attributes: {
'r:id': relId,
},
});
}
/**
* Appends slide to content type
* @internal
* @param rootArchive
* @param slideCount
* @returns slide to content type
*/
appendToContentType(rootArchive, count) {
return xml_helper_1.XmlHelper.append(xml_helper_1.XmlHelper.createContentTypeChild(rootArchive, {
PartName: `/ppt/${this.targetType}s/${this.targetType}${count}.xml`,
ContentType: `application/vnd.openxmlformats-officedocument.presentationml.${this.targetType}+xml`,
}));
}
/**
* slideNote numbers differ from slide numbers if presentation
* contains slides without notes. We need to find out
* the proper enumeration of slideNote xml files.
* @internal
* @returns slide note file number
*/
getSlideNoteSourceNumber() {
return __awaiter(this, void 0, void 0, function* () {
const targets = yield xml_helper_1.XmlHelper.getTargetsByRelationshipType(this.sourceArchive, `ppt/slides/_rels/slide${this.sourceNumber}.xml.rels`, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide');
if (targets.length) {
const targetNumber = targets[0].file
.replace('../notesSlides/notesSlide', '')
.replace('.xml', '');
return Number(targetNumber);
}
});
}
/**
* Copys slide note files
* @internal
* @returns slide note files
*/
copySlideNoteFiles(sourceNotesNumber) {
return __awaiter(this, void 0, void 0, function* () {
yield file_helper_1.FileHelper.zipCopy(this.sourceArchive, `ppt/notesSlides/notesSlide${sourceNotesNumber}.xml`, this.targetArchive, `ppt/notesSlides/notesSlide${this.targetNumber}.xml`);
yield file_helper_1.FileHelper.zipCopy(this.sourceArchive, `ppt/notesSlides/_rels/notesSlide${sourceNotesNumber}.xml.rels`, this.targetArchive, `ppt/notesSlides/_rels/notesSlide${this.targetNumber}.xml.rels`);
});
}
/**
* Updates slide note file
* @internal
* @returns slide note file
*/
updateSlideNoteFile(sourceNotesNumber) {
return __awaiter(this, void 0, void 0, function* () {
yield xml_helper_1.XmlHelper.replaceAttribute(this.targetArchive, `ppt/notesSlides/_rels/notesSlide${this.targetNumber}.xml.rels`, 'Relationship', 'Target', `../slides/slide${this.sourceNumber}.xml`, `../slides/slide${this.targetNumber}.xml`);
yield xml_helper_1.XmlHelper.replaceAttribute(this.targetArchive, `ppt/slides/_rels/slide${this.targetNumber}.xml.rels`, 'Relationship', 'Target', `../notesSlides/notesSlide${sourceNotesNumber}.xml`, `../notesSlides/notesSlide${this.targetNumber}.xml`);
});
}
/**
* Appends notes to content type
* @internal
* @param rootArchive
* @param slideCount
* @returns notes to content type
*/
appendNotesToContentType(rootArchive, slideCount) {
return xml_helper_1.XmlHelper.append(xml_helper_1.XmlHelper.createContentTypeChild(rootArchive, {
PartName: `/ppt/notesSlides/notesSlide${slideCount}.xml`,
ContentType: `application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml`,
}));
}
/**
* Copys related content
* @internal
* @returns related content
*/
copyRelatedContent() {
return __awaiter(this, void 0, void 0, function* () {
const charts = yield chart_1.Chart.getAllOnSlide(this.sourceArchive, this.relsPath);
for (const chart of charts) {
yield new chart_1.Chart({
mode: 'append',
target: chart,
sourceArchive: this.sourceArchive,
sourceSlideNumber: this.sourceNumber,
}, this.targetType).modifyOnAddedSlide(this.targetTemplate, this.targetNumber);
}
const images = yield image_1.Image.getAllOnSlide(this.sourceArchive, this.relsPath);
for (const image of images) {
yield new image_1.Image({
mode: 'append',
target: image,
sourceArchive: this.sourceArchive,
sourceSlideNumber: this.sourceNumber,
}, this.targetType).modifyOnAddedSlide(this.targetTemplate, this.targetNumber);
}
const diagrams = yield diagram_1.Diagram.getAllOnSlide(this.sourceArchive, this.relsPath);
for (const diagram of diagrams) {
yield new diagram_1.Diagram({
mode: 'append',
target: diagram,
sourceArchive: this.sourceArchive,
sourceSlideNumber: this.sourceNumber,
}, this.targetType).modifyOnAddedSlide(this.targetTemplate, this.targetNumber);
}
const oleObjects = yield ole_1.OLEObject.getAllOnSlide(this.sourceArchive, this.relsPath);
for (const oleObject of oleObjects) {
yield new ole_1.OLEObject({
mode: 'append',
target: oleObject,
sourceArchive: this.sourceArchive,
sourceSlideNumber: this.sourceNumber,
}, this.targetType, this.sourceArchive).modifyOnAddedSlide(this.targetTemplate, this.targetNumber, oleObjects);
}
// Copy hyperlinks
const hyperlinks = yield hyperlink_1.Hyperlink.getAllOnSlide(this.sourceArchive, this.relsPath);
for (const hyperlink of hyperlinks) {
// Create a new hyperlink with the correct target information
const hyperlinkInstance = new hyperlink_1.Hyperlink({
mode: 'append',
target: hyperlink,
sourceArchive: this.sourceArchive,
sourceSlideNumber: this.sourceNumber,
sourceRid: hyperlink.rId,
}, this.targetType, this.sourceArchive, hyperlink.isExternal ? 'external' : 'internal', hyperlink.file);
// Ensure the target property is properly set
hyperlinkInstance.target = hyperlink;
// Process the hyperlink
yield hyperlinkInstance.modifyOnAddedSlide(this.targetTemplate, this.targetNumber);
}
});
}
/**
* Analyzes element
* @internal
* @param sourceElement
* @param sourceArchive
* @param slideNumber
* @returns element
*/
analyzeElement(sourceElement, sourceArchive, relsPath) {
return __awaiter(this, void 0, void 0, function* () {
const isChart = sourceElement.getElementsByTagName('c:chart');
if (isChart.length) {
const target = yield xml_helper_1.XmlHelper.getTargetByRelId(sourceArchive, relsPath, sourceElement, 'chart');
return {
type: element_type_1.ElementType.Chart,
target: target,
};
}
const isChartEx = sourceElement.getElementsByTagName('cx:chart');
if (isChartEx.length) {
const target = yield xml_helper_1.XmlHelper.getTargetByRelId(sourceArchive, relsPath, sourceElement, 'chartEx');
return {
type: element_type_1.ElementType.Chart,
target: target,
};
}
const isImage = sourceElement.getElementsByTagName('p:nvPicPr');
if (isImage.length) {
return {
type: element_type_1.ElementType.Image,
target: yield xml_helper_1.XmlHelper.getTargetByRelId(sourceArchive, relsPath, sourceElement, 'image'),
};
}
const isDiagram = sourceElement.getElementsByTagName('dgm:relIds');
if (isDiagram.length) {
return {
type: element_type_1.ElementType.Diagram,
target: yield xml_helper_1.XmlHelper.getTargetByRelId(sourceArchive, relsPath, sourceElement, 'diagram'),
};
}
const isOLEObject = sourceElement.getElementsByTagName('p:oleObj');
if (isOLEObject.length) {
const target = yield xml_helper_1.XmlHelper.getTargetByRelId(sourceArchive, relsPath, sourceElement, 'oleObject');
return {
type: element_type_1.ElementType.OLEObject,
target: target,
};
}
// Check for hyperlinks using the centralized processor
const hasHyperlink = hyperlink_processor_1.HyperlinkProcessor.hasHyperlinks(sourceElement);
if (hasHyperlink) {
try {
// Check if this element has multiple hyperlinks
if (hyperlink_processor_1.HyperlinkProcessor.hasMultipleHyperlinks(sourceElement)) {
// For elements with multiple hyperlinks (like tables), treat as generic shape
// The GenericShape class will handle copying the hyperlink relationships properly
return {
type: element_type_1.ElementType.Shape,
};
}
else {
// Single hyperlink - use existing logic
const target = yield xml_helper_1.XmlHelper.getTargetByRelId(sourceArchive, relsPath, sourceElement, 'hyperlink');
return {
type: element_type_1.ElementType.Hyperlink,
target: target,
element: sourceElement,
};
}
}
catch (error) {
console.warn('Error finding hyperlink target:', error);
}
}
return {
type: element_type_1.ElementType.Shape,
};
});
}
/**
* Applys slide preparation callbacks
* Will be executed before any shape modifications callback
* @internal
* @returns modifications
*/
applyPreparations() {
return __awaiter(this, void 0, void 0, function* () {
for (const modification of this.preparations) {
const xml = yield xml_helper_1.XmlHelper.getXmlFromArchive(this.targetArchive, this.targetPath);
yield modification(xml, this);
xml_helper_1.XmlHelper.writeXmlToArchive(this.targetArchive, this.targetPath, xml);
}
});
}
/**
* Applys slide modification callbacks
* Will be executed after all shape modifications callbacks
* @internal
* @returns modifications
*/
applyModifications() {
return __awaiter(this, void 0, void 0, function* () {
for (const modification of this.modifications) {
const xml = yield xml_helper_1.XmlHelper.getXmlFromArchive(this.targetArchive, this.targetPath);
yield modification(xml, this);
xml_helper_1.XmlHelper.writeXmlToArchive(this.targetArchive, this.targetPath, xml);
}
});
}
/**
* Apply modifications to slide relations
* @internal
* @returns modifications
*/
applyRelModifications() {
return __awaiter(this, void 0, void 0, function* () {
yield xml_helper_1.XmlHelper.modifyXmlInArchive(this.targetArchive, `ppt/${this.targetType}s/_rels/${this.targetType}${this.targetNumber}.xml.rels`, this.relModifications);
});
}
/**
* Removes all unsupported tags from slide xml.
* E.g. added relations & tags by Thinkcell cannot
* be processed by pptx-automizer at the moment.
* @internal
*/
cleanSlide(targetPath, sourcePlaceholderTypes) {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield xml_helper_1.XmlHelper.getXmlFromArchive(this.targetArchive, targetPath);
if (this.cleanupPlaceholders && sourcePlaceholderTypes) {
this.removeDuplicatePlaceholders(xml, sourcePlaceholderTypes);
this.normalizePlaceholderShapes(xml, sourcePlaceholderTypes);
}
this.unsupportedTags.forEach((tag) => {
const drop = xml.getElementsByTagName(tag);
const length = drop.length;
if (length && length > 0) {
console.log('Cleaning unsupported tag ' + tag);
// First get parent elements before removing
const parents = [];
for (let i = 0; i < drop.length; i++) {
const parent = drop[i].parentNode;
if (parent && !parents.includes(parent)) {
parents.push(parent);
}
}
// Remove the unsupported tags
xml_helper_1.XmlHelper.sliceCollection(drop, 0);
// Check each parent and remove it if it has no children left
parents.forEach((parent) => {
if (parent.childNodes.length === 0) {
xml_helper_1.XmlHelper.remove(parent);
}
});
}
});
xml_helper_1.XmlHelper.writeXmlToArchive(this.targetArchive, targetPath, xml);
});
}
/**
* If you insert a placeholder shape on a target slide with an empty
* placeholder of the same type, we need to remove the existing
* placeholder.
*
* @param xml
* @param sourcePlaceholderTypes
*/
removeDuplicatePlaceholders(xml, sourcePlaceholderTypes) {
const placeholders = xml.getElementsByTagName('p:ph');
const usedTypes = {};
xml_helper_1.XmlHelper.modifyCollection(placeholders, (placeholder) => {
const type = placeholder.getAttribute('type');
usedTypes[type] = usedTypes[type] || 0;
usedTypes[type]++;
});
for (const usedType in usedTypes) {
const count = usedTypes[usedType];
if (count > 1) {
// TODO: in case more than two placeholders are of a kind,
// this will likely remove more than intended. Should also match by id.
const removePlaceholders = sourcePlaceholderTypes.filter((sourcePlaceholder) => sourcePlaceholder.type === usedType);
removePlaceholders.forEach((removePlaceholder) => {
const parentShapeTag = 'p:sp';
const parentShape = xml_helper_1.XmlHelper.getClosestParent(parentShapeTag, removePlaceholder.xml);
if (parentShape) {
xml_helper_1.XmlHelper.remove(parentShape);
}
});
}
}
}
/**
* If a placeholder shape was inserted on a slide without a corresponding
* placeholder, powerPoint will usually smash the shape's formatting.
* This function removes the placeholder tag.
* @param xml
* @param sourcePlaceholderTypes
*/
normalizePlaceholderShapes(xml, sourcePlaceholderTypes) {
const placeholders = xml.getElementsByTagName('p:ph');
xml_helper_1.XmlHelper.modifyCollection(placeholders, (placeholder) => {
const usedType = placeholder.getAttribute('type');
const existingPlaceholder = sourcePlaceholderTypes.find((sourcePlaceholder) => sourcePlaceholder.type === usedType);
if (!existingPlaceholder) {
xml_helper_1.XmlHelper.remove(placeholder);
}
});
}
/**
* Removes all unsupported relations from _rels xml.
* @internal
*/
cleanRelations(targetRelsPath) {
return __awaiter(this, void 0, void 0, function* () {
yield xml_helper_1.XmlHelper.removeIf({
archive: this.targetArchive,
file: targetRelsPath,
tag: 'Relationship',
clause: (xml, item) => {
return this.unsupportedRelationTypes.includes(item.getAttribute('Type'));
},
});
});
}
parsePlaceholders() {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield xml_helper_1.XmlHelper.getXmlFromArchive(this.targetArchive, this.targetPath);
const placeholderTypes = [];
const placeholders = xml.getElementsByTagName('p:ph');
xml_helper_1.XmlHelper.modifyCollection(placeholders, (placeholder) => {
placeholderTypes.push({
type: placeholder.getAttribute('type'),
id: placeholder.getAttribute('id'),
xml: placeholder,
});
});
return placeholderTypes;
});
}
}
exports.default = HasShapes;
//# sourceMappingURL=has-shapes.js.map