pptx-automizer
Version:
A template based pptx generator
503 lines • 21 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 });
exports.XmlHelper = void 0;
const xmldom_1 = require("@xmldom/xmldom");
const constants_1 = require("../constants/constants");
const xml_pretty_print_1 = require("./xml-pretty-print");
const general_helper_1 = require("./general-helper");
const content_tracker_1 = require("./content-tracker");
const content_type_map_1 = require("../enums/content-type-map");
class XmlHelper {
static modifyXmlInArchive(archive, file, callbacks) {
return __awaiter(this, void 0, void 0, function* () {
const fileProxy = yield archive;
const xml = yield XmlHelper.getXmlFromArchive(fileProxy, file);
let i = 0;
for (const callback of callbacks) {
yield callback(xml, i++, fileProxy);
}
XmlHelper.writeXmlToArchive(yield archive, file, xml);
});
}
static getXmlFromArchive(archive, file) {
return __awaiter(this, void 0, void 0, function* () {
return archive.readXml(file);
});
}
static writeXmlToArchive(archive, file, xml) {
archive.writeXml(file, xml);
}
static appendIf(element) {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield XmlHelper.getXmlFromArchive(element.archive, element.file);
return element.clause !== undefined && !element.clause(xml)
? false
: XmlHelper.append(element);
});
}
static append(element) {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield XmlHelper.getXmlFromArchive(element.archive, element.file);
const newElement = xml.createElement(element.tag);
for (const attribute in element.attributes) {
const value = element.attributes[attribute];
const setValue = typeof value === 'function' ? value(xml) : value;
newElement.setAttribute(attribute, setValue);
}
content_tracker_1.contentTracker.trackRelation(element.file, element.attributes);
if (element.assert) {
element.assert(xml);
}
const parent = element.parent(xml);
parent.appendChild(newElement);
XmlHelper.writeXmlToArchive(element.archive, element.file, xml);
return newElement;
});
}
static removeIf(element) {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield XmlHelper.getXmlFromArchive(element.archive, element.file);
const collection = xml.getElementsByTagName(element.tag);
const toRemove = [];
XmlHelper.modifyCollection(collection, (item, index) => {
if (element.clause(xml, item)) {
toRemove.push(item);
}
});
toRemove.forEach((item) => {
XmlHelper.remove(item);
});
XmlHelper.writeXmlToArchive(element.archive, element.file, xml);
return toRemove;
});
}
static getNextRelId(rootArchive, file) {
return __awaiter(this, void 0, void 0, function* () {
const presentationRelsXml = yield XmlHelper.getXmlFromArchive(rootArchive, file);
const increment = (max) => 'rId' + max;
const relationNodes = presentationRelsXml.documentElement.childNodes;
const rid = XmlHelper.getMaxId(relationNodes, 'Id', true);
return increment(rid) + '-created';
});
}
static getMaxId(rels, attribute, increment, minId) {
let max = 0;
for (const i in rels) {
const rel = rels[i];
if (rel.getAttribute !== undefined && rel.getAttribute(attribute)) {
const id = Number(rel
.getAttribute(attribute)
.replace('rId', '')
.replace('-created', ''));
max = id > max ? id : max;
}
}
switch (typeof increment) {
case 'boolean':
++max;
break;
}
if (max < minId) {
return minId;
}
return max;
}
static getRelationshipTargetsByPrefix(archive, path, prefix) {
return __awaiter(this, void 0, void 0, function* () {
const prefixes = typeof prefix === 'string' ? [prefix] : prefix;
return XmlHelper.getRelationshipItems(archive, path, (element, targets) => {
prefixes.forEach((prefix) => {
const target = XmlHelper.parseRelationTarget(element, prefix);
if (target.prefix) {
targets.push(target);
}
});
});
});
}
static parseRelationTarget(element, prefix) {
const type = element.getAttribute('Type');
const file = element.getAttribute('Target');
const last = (arr) => arr[arr.length - 1];
const filename = last(file.split('/'));
let subtype = last(prefix.split('/'));
const mapSubtype = {
data: 'diagramData',
};
subtype = mapSubtype[subtype] || subtype;
const relType = last(type.split('/'));
const rId = element.getAttribute('Id');
const filenameExt = last(filename.split('.'));
const filenameMatch = filename
.replace('.' + filenameExt, '')
.match(/^(.+?)(\d+)*$/);
const filenameBase = filenameMatch && filenameMatch[1] ? filenameMatch[1] : filename;
const number = filenameMatch && filenameMatch[2] ? Number(filenameMatch[2]) : 0;
const target = {
rId,
type,
file,
filename,
filenameBase,
number,
subtype,
relType,
element,
};
if (prefix &&
XmlHelper.targetMatchesRelationship(relType, subtype, file, prefix)) {
return Object.assign(Object.assign({}, target), { prefix });
}
if (prefix && prefix.indexOf('../') === 0) {
// Try again with absolute path instead of relative
return XmlHelper.parseRelationTarget(element, prefix.replace('../', '/ppt/'));
}
return target;
}
static targetMatchesRelationship(relType, subtype, file, prefix) {
if (relType === 'package')
return true;
// pptgenjs uses absolute paths in "Target" attributes
if (file.indexOf('/ppt/') === 0) {
file = file.replace('/ppt/', '../');
}
return relType === subtype && file.indexOf(prefix) === 0;
}
static getTargetsByRelationshipType(archive, path, type) {
return __awaiter(this, void 0, void 0, function* () {
return yield XmlHelper.getRelationshipItems(archive, path, (element, rels) => {
const target = element.getAttribute('Type');
if (target === type) {
rels.push({
file: element.getAttribute('Target'),
rId: element.getAttribute('Id'),
element: element,
});
}
});
});
}
static getRelationshipItems(archive, path, cb, tag) {
return __awaiter(this, void 0, void 0, function* () {
tag = tag || 'Relationship';
const xml = yield XmlHelper.getXmlFromArchive(archive, path);
const relationshipItems = xml.getElementsByTagName(tag);
const rels = [];
for (const i in relationshipItems) {
if (relationshipItems[i].getAttribute) {
cb(relationshipItems[i], rels);
}
}
return rels;
});
}
static findByAttribute(xml, tagName, attributeName, attributeValue) {
const elements = xml.getElementsByTagName(tagName);
for (const i in elements) {
const element = elements[i];
if (element.getAttribute !== undefined) {
if (element.getAttribute(attributeName) === attributeValue) {
return true;
}
}
}
return false;
}
static replaceAttribute(archive, path, tagName, attributeName, attributeValue, replaceValue, replaceAttributeName) {
return __awaiter(this, void 0, void 0, function* () {
const xml = yield XmlHelper.getXmlFromArchive(archive, path);
const elements = xml.getElementsByTagName(tagName);
for (const i in elements) {
const element = elements[i];
if (element.getAttribute !== undefined &&
element.getAttribute(attributeName) === attributeValue) {
element.setAttribute(replaceAttributeName || attributeName, replaceValue);
}
if (element.getAttribute !== undefined) {
content_tracker_1.contentTracker.trackRelation(path, {
Id: element.getAttribute('Id'),
Target: element.getAttribute('Target'),
Type: element.getAttribute('Type'),
});
}
}
XmlHelper.writeXmlToArchive(archive, path, xml);
});
}
static getTargetByRelId(archive, relsPath, element, type) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const params = constants_1.TargetByRelIdMap[type];
// For elements that need to search all instances (like hyperlinks)
if (params.findAll) {
// Find all hyperlink elements
const hyperlinks = element.getElementsByTagName(params.relRootTag);
if (hyperlinks.length > 0) {
// Use the first hyperlink found
const sourceRid = hyperlinks[0].getAttribute(params.relAttribute);
// Get all relationships
const allRels = yield XmlHelper.getRelationshipItems(archive, relsPath, (element, rels) => {
rels.push({
rId: element.getAttribute('Id'),
type: element.getAttribute('Type'),
file: element.getAttribute('Target'),
filename: element.getAttribute('Target'),
element: element,
isExternal: element.getAttribute('TargetMode') === 'External',
});
});
// Find the matching relationship
const target = allRels.find((rel) => rel.rId === sourceRid);
return target;
}
}
else {
// Standard behavior for other element types
const sourceRid = (_a = element
.getElementsByTagName(params.relRootTag)
.item(0)) === null || _a === void 0 ? void 0 : _a.getAttribute(params.relAttribute);
if (!sourceRid) {
throw 'No sourceRid for ' + params.relRootTag;
}
const shapeRels = yield XmlHelper.getRelationshipTargetsByPrefix(archive, relsPath, params.prefix);
const target = shapeRels.find((rel) => {
return rel.rId === sourceRid;
});
return target;
}
});
}
// Determine whether a given string is a creationId or a shape name
// Example creationId: '{EFC74B4C-D832-409B-9CF4-73C1EFF132D8}'
static isElementCreationId(selector) {
return selector.indexOf('{') === 0 && selector.split('-').length === 5;
}
static findByElementCreationId(archive, path, creationId) {
return __awaiter(this, void 0, void 0, function* () {
const slideXml = yield XmlHelper.getXmlFromArchive(archive, path);
return XmlHelper.findByCreationId(slideXml, creationId);
});
}
static findByElementName(archive, path, name, nameIdx) {
return __awaiter(this, void 0, void 0, function* () {
const slideXml = yield XmlHelper.getXmlFromArchive(archive, path);
return XmlHelper.findByName(slideXml, name, nameIdx);
});
}
static findByName(doc, name, nameIdx) {
const names = doc.getElementsByTagName('p:cNvPr');
let matchCount = 0;
// Default nameIdx to 0 if not provided
const targetIdx = nameIdx !== undefined ? nameIdx : 0;
for (let i = 0; i < names.length; i++) {
const item = names.item(i);
if (item && item.getAttribute && item.getAttribute('name') === name) {
// Check if this is the occurrence we're looking for
if (matchCount === targetIdx) {
return item.parentNode.parentNode;
}
matchCount++;
}
}
return null;
}
static findByCreationId(doc, creationId) {
const creationIds = doc.getElementsByTagName('a16:creationId');
for (const i in creationIds) {
if (creationIds[i].getAttribute &&
creationIds[i].getAttribute('id') === creationId) {
return creationIds[i].parentNode.parentNode.parentNode.parentNode
.parentNode;
}
}
return null;
}
static findFirstByAttributeValue(nodes, attributeName, attributeValue) {
for (const i in nodes) {
const node = nodes[i];
if (node.getAttribute &&
node.getAttribute(attributeName) === attributeValue) {
return node;
}
}
return null;
}
static findByAttributeValue(nodes, attributeName, attributeValue) {
const matchingNodes = [];
for (const i in nodes) {
const node = nodes[i];
if (node.getAttribute &&
node.getAttribute(attributeName) === attributeValue) {
matchingNodes.push(node);
}
}
return matchingNodes;
}
static createContentTypeChild(archive, attributes) {
return {
archive,
file: `[Content_Types].xml`,
parent: (xml) => xml.getElementsByTagName('Types')[0],
tag: 'Override',
attributes,
};
}
static createRelationshipChild(archive, targetRelFile, attributes) {
content_tracker_1.contentTracker.trackRelation(targetRelFile, attributes);
return {
archive,
file: targetRelFile,
parent: (xml) => xml.getElementsByTagName('Relationships')[0],
tag: 'Relationship',
attributes,
};
}
static appendImageExtensionToContentType(targetArchive, extension) {
const contentType = content_type_map_1.ContentTypeMap[extension]
? content_type_map_1.ContentTypeMap[extension]
: 'image/' + extension;
return XmlHelper.appendIf(Object.assign(Object.assign({}, XmlHelper.createContentTypeChild(targetArchive, {
Extension: extension,
ContentType: contentType,
})), { tag: 'Default', clause: (xml) => !XmlHelper.findByAttribute(xml, 'Default', 'Extension', extension) }));
}
static appendSharedString(sharedStrings, stringValue) {
const strings = sharedStrings.getElementsByTagName('sst')[0];
const newLabel = sharedStrings.createTextNode(stringValue);
const newText = sharedStrings.createElement('t');
newText.appendChild(newLabel);
const newString = sharedStrings.createElement('si');
newString.appendChild(newText);
strings.appendChild(newString);
return strings.getElementsByTagName('si').length - 1;
}
static insertAfter(newNode, referenceNode) {
return referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
static sliceCollection(collection, length, from) {
if (from !== undefined) {
for (let i = from; i < length; i++) {
XmlHelper.remove(collection[i]);
}
}
else {
for (let i = collection.length; i > length; i--) {
XmlHelper.remove(collection[i - 1]);
}
}
}
static getClosestParent(tag, element) {
if (element.parentNode) {
if (element.parentNode.nodeName === tag) {
return element.parentNode;
}
return XmlHelper.getClosestParent(tag, element.parentNode);
}
}
static remove(toRemove) {
if (toRemove === null || toRemove === void 0 ? void 0 : toRemove.parentNode) {
toRemove.parentNode.removeChild(toRemove);
}
}
static removeAllChildren(element) {
if (element === null || element === void 0 ? void 0 : element.childNodes) {
while (element.childNodes.length > 0) {
element.removeChild(element.childNodes[0]);
}
}
}
static moveChild(childToMove, insertBefore) {
const parent = childToMove.parentNode;
parent.insertBefore(childToMove, insertBefore);
}
static appendClone(childToClone, parent) {
const clone = childToClone.cloneNode(true);
parent.appendChild(clone);
return clone;
}
static sortCollection(collection, order, callback) {
if (collection.length === 0) {
return;
}
const parent = collection[0].parentNode;
order.forEach((index, i) => {
if (!collection[index]) {
(0, general_helper_1.log)('sortCollection index not found' + index, 1);
return;
}
const item = collection[index];
if (callback) {
callback(item, i);
}
parent.appendChild(item);
});
}
static modifyCollection(collection, callback) {
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
callback(item, i);
}
}
static modifyCollectionAsync(collection, callback) {
return __awaiter(this, void 0, void 0, function* () {
for (let i = 0; i < collection.length; i++) {
const item = collection[i];
yield callback(item, i);
}
});
}
static dump(element) {
const s = new xmldom_1.XMLSerializer();
const xmlBuffer = s.serializeToString(element);
const p = new xml_pretty_print_1.XmlPrettyPrint(xmlBuffer);
p.dump();
}
/**
* Find an element with the given tag name, searching deeply through the structure
* @param element The root element to search in
* @param tagName The tag name to search for
* @returns The found element or null
*/
static findElement(element, tagName) {
// Check if element exists
if (!element) {
return null;
}
// Direct child check
const directMatch = element.getElementsByTagName(tagName)[0];
if (directMatch) {
return directMatch;
}
// Recursive check in child elements
// Make sure element.children exists before accessing it
if (element.children && element.children.length) {
for (let i = 0; i < element.children.length; i++) {
const child = element.children[i];
if (child.nodeType === 1) {
// Element node
const found = XmlHelper.findElement(child, tagName);
if (found) {
return found;
}
}
}
}
return null;
}
}
exports.XmlHelper = XmlHelper;
XmlHelper.removeByTagName = (element, tagName, i = 0) => {
const tagToRemove = element.getElementsByTagName(tagName).item(i);
if (tagToRemove)
XmlHelper.remove(tagToRemove);
};
//# sourceMappingURL=xml-helper.js.map