has-shapes.jsā¢25.4 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");
class HasShapes {
constructor(params) {
/**
* List of unsupported tags in slide xml
* @internal
*/
this.unsupportedTags = [
'p:custDataLst',
'p:oleObj',
// 'mc:AlternateContent',
//'a14:imgProps',
];
/**
* 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.sourceTemplate = params.template;
this.modifications = [];
this.relModifications = [];
this.importElements = [];
this.status = params.presentation.status;
this.content = params.presentation.content;
}
/**
* 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.
* @params filterTags Use an array of strings to filter parent tags (e.g. 'sp')
* @returns {Promise<ElementInfo[]>} A promise that resolves to an array of ElementInfo objects.
*/
getAllElements(filterTags) {
return __awaiter(this, void 0, void 0, function* () {
const xmlSlideHelper = yield this.getSlideHelper();
// Get all ElementInfo objects
return xmlSlideHelper.getAllElements(filterTags);
});
}
/**
* 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* () {
try {
// Retrieve the slide XML data
const slideXml = yield xml_helper_1.XmlHelper.getXmlFromArchive(this.sourceTemplate.archive, this.sourcePath);
// Initialize the XmlSlideHelper
return new xml_slide_helper_1.XmlSlideHelper(slideXml, this);
}
catch (error) {
// Log the error message
throw new Error(error.message);
}
});
}
/**
* 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;
}
/**
* 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 {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 PresTemplate
* @slideNumber SourceSlideIdentifier
* @returns number
*/
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;
}
/**
* Imported selected elements
* @internal
* @returns selected elements
*/
importedSelectedElements() {
return __awaiter(this, void 0, void 0, function* () {
for (const element of this.importElements) {
const info = yield this.getElementInfo(element);
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;
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,
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.name) {
strategies.push({
mode: 'findByElementCreationId',
selector: selector.creationId,
});
strategies.push({
mode: 'findByElementName',
selector: selector.name,
});
}
for (const findElement of strategies) {
const mode = findElement.mode;
const sourceElement = yield xml_helper_1.XmlHelper[mode](sourceArchive, sourcePath, findElement.selector);
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);
}
});
}
/**
* 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'),
};
}
return {
type: element_type_1.ElementType.Shape,
};
});
}
/**
* Applys modifications
* @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);
modification(xml);
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) {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield xml_helper_1.XmlHelper.getXmlFromArchive(this.targetArchive, targetPath);
this.unsupportedTags.forEach((tag) => {
const drop = xml.getElementsByTagName(tag);
const length = drop.length;
if (length && length > 0) {
xml_helper_1.XmlHelper.sliceCollection(drop, 0);
}
});
xml_helper_1.XmlHelper.writeXmlToArchive(this.targetArchive, targetPath, xml);
});
}
/**
* 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'));
},
});
});
}
}
exports.default = HasShapes;
//# sourceMappingURL=has-shapes.js.map