UNPKG

pptx-automizer

Version:

A template based pptx generator

878 lines 39.3 kB
"use strict"; 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