pptx-automizer
Version:
A template based pptx generator
261 lines • 9.29 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HtmlToMultiTextHelper = void 0;
const xmldom_1 = require("@xmldom/xmldom");
const general_helper_1 = require("./general-helper");
class HtmlToMultiTextHelper {
/**
* Converts HTML string to MultiTextParagraph array
* @param html HTML string to convert
* @returns Array of MultiTextParagraph objects
*/
run(html) {
const paragraphs = [];
const parser = new xmldom_1.DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const currentBulletLevel = 0;
// Get the body element using getElementsByTagName
const bodyElement = doc.getElementsByTagName('body')[0];
// Process all top-level elements
if (bodyElement) {
Array.from(bodyElement.childNodes).forEach((node) => {
if (node.nodeType === xmldom_1.Node.ELEMENT_NODE) {
this.processNode(node, currentBulletLevel, paragraphs);
}
});
}
else {
(0, general_helper_1.log)('You need to provide a <body> tag for HtmlToMultiText', 0);
}
return paragraphs;
}
/**
* Processes an HTML node and converts it to MultiTextParagraph objects
*/
processNode(node, level = 0, paragraphs, bulletLevel = { value: 0 }) {
const tagName = node.nodeName.toLowerCase();
switch (tagName) {
case 'p':
this.processParagraph(node, paragraphs);
break;
case 'ul':
case 'ol':
this.processList(node, paragraphs, bulletLevel);
break;
case 'li':
this.processListItem(node, level, paragraphs);
break;
default:
// For other elements, process their children
Array.from(node.childNodes).forEach((child) => {
this.processNode(child, level, paragraphs, bulletLevel);
});
}
}
/**
* Processes a paragraph element
*/
processParagraph(node, paragraphs) {
const textRuns = this.createTextRuns(node);
// If no text runs were created, add an empty one
if (textRuns.length === 0) {
textRuns.push({ text: '' });
}
// Create the paragraph
paragraphs.push({
paragraph: {
level: 0,
bullet: false,
alignment: 'l',
},
textRuns,
});
}
/**
* Processes a list (ul/ol) element
*/
processList(node, paragraphs, bulletLevel) {
// Increase bullet level for nested lists
bulletLevel.value++;
// Process all list items
Array.from(node.childNodes).forEach((child) => {
this.processNode(child, bulletLevel.value, paragraphs, bulletLevel);
});
// Decrease bullet level after processing list
bulletLevel.value--;
}
/**
* Processes a list item element
*/
processListItem(node, level, paragraphs) {
const textRuns = [];
// Process all child nodes to create text runs (except nested lists)
Array.from(node.childNodes).forEach((child) => {
// Skip nested lists, they'll be processed separately
if (child.nodeType === xmldom_1.Node.ELEMENT_NODE &&
(child.nodeName.toLowerCase() === 'ul' ||
child.nodeName.toLowerCase() === 'ol')) {
return;
}
const result = this.processTextNode(child);
if (Array.isArray(result)) {
textRuns.push(...result.filter((run) => run.text));
}
else if (result.text) {
textRuns.push(result);
}
});
// If no text runs were created, add an empty one
if (textRuns.length === 0) {
textRuns.push({ text: '' });
}
// Create the paragraph for the list item
paragraphs.push({
paragraph: {
level,
bullet: true,
alignment: 'l',
},
textRuns,
});
// Process nested lists if any
Array.from(node.childNodes).forEach((child) => {
if (child.nodeName.toLowerCase() === 'ul' ||
child.nodeName.toLowerCase() === 'ol') {
const bulletLevel = { value: level };
this.processNode(child, level + 1, paragraphs, bulletLevel);
}
});
}
/**
* Creates text runs from an element's child nodes
*/
createTextRuns(node) {
const textRuns = [];
// Process all child nodes to create text runs
// Using Array.from to convert NodeList to array that has forEach
Array.from(node.childNodes).forEach((child) => {
const result = this.processTextNode(child);
if (Array.isArray(result)) {
textRuns.push(...result.filter((run) => run.text));
}
else if (result.text) {
textRuns.push(result);
}
});
return textRuns;
}
/**
* Processes a text node and creates a TextRun or array of TextRuns
*/
processTextNode(node, style = {}) {
// If this is a text node, return its content
if (node.nodeType === xmldom_1.Node.TEXT_NODE) {
const text = node.textContent || '';
if (text.trim() === '') {
return { text: text, style };
}
return { text, style };
}
// If this is an element, handle specific styling
if (node.nodeType === xmldom_1.Node.ELEMENT_NODE) {
const element = node;
const newStyle = this.applyElementStyles(element, Object.assign({}, style));
// For leaf nodes (no children), just return the text content with style
if (element.childNodes.length === 0) {
return { text: element.textContent || '', style: newStyle };
}
// For nodes with children, recursively process children
return this.processElementWithChildren(element, newStyle);
}
return { text: '' };
}
/**
* Applies styles based on the element type and attributes
*/
applyElementStyles(element, style) {
const newStyle = Object.assign({}, style);
const tagName = element.tagName.toLowerCase();
// Handle styling based on element type
if (tagName === 'strong' || tagName === 'b') {
newStyle.isBold = true;
}
else if (tagName === 'em' || tagName === 'i') {
newStyle.isItalics = true;
}
else if (tagName === 'ins') {
newStyle.isUnderlined = true;
}
else if (tagName === 'a') {
this.processHyperlink(element, newStyle);
}
else if (tagName === 'span') {
this.processSpanStyles(element, newStyle);
}
return newStyle;
}
/**
* Processes anchor element for hyperlinks
*/
processHyperlink(element, style) {
const href = element.getAttribute('href');
if (!href)
return;
if (!isNaN(parseInt(href))) {
// Internal slide link: <a href="3">Link to slide 3</a>
const slideNumber = parseInt(href);
style.hyperlink = {
target: slideNumber,
isInternal: true,
};
}
else {
// External link: <a href="https://example.com">Link</a>
style.hyperlink = {
target: href,
isInternal: false,
};
}
}
/**
* Processes span element styles
*/
processSpanStyles(element, style) {
const styleAttr = element.getAttribute('style');
if (!styleAttr)
return;
// Extract font size
const fontSizeMatch = styleAttr.match(/font-size:\s*(\d+)px/i);
if (fontSizeMatch && fontSizeMatch[1]) {
style.size = parseInt(fontSizeMatch[1]) * 100; // Convert px to points (100ths of point)
}
// Extract color
const colorMatch = styleAttr.match(/color:\s*([^;]+)/i);
if (colorMatch && colorMatch[1]) {
style.color = {
type: 'srgbClr',
value: colorMatch[1].trim(),
};
}
}
/**
* Processes an element with child nodes
*/
processElementWithChildren(element, style) {
const runs = [];
Array.from(element.childNodes).forEach((child) => {
const childRun = this.processTextNode(child, style);
if (Array.isArray(childRun)) {
// If the result is an array of runs, add them all
runs.push(...childRun.filter((run) => run.text));
}
else if (childRun.text) {
// If it's a single run, add it
runs.push(childRun);
}
});
return runs;
}
}
exports.HtmlToMultiTextHelper = HtmlToMultiTextHelper;
//# sourceMappingURL=html-to-multitext-helper.js.map