Skip to content

Commit 289c459

Browse files
authored
Merge pull request #182 from kirkeskov/feat/multitext-inline-hyperlink-html
fix(multitext): added support for <a> tags
2 parents 15e43ab + d978e20 commit 289c459

File tree

6 files changed

+354
-6
lines changed

6 files changed

+354
-6
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import Automizer, { modify } from '../src/index';
2+
3+
test('create presentation with external hyperlinks using htmlToMultiText', async () => {
4+
const automizer = new Automizer({
5+
templateDir: `${__dirname}/pptx-templates`,
6+
outputDir: `${__dirname}/pptx-output`,
7+
removeExistingSlides: true,
8+
});
9+
10+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
11+
12+
const html =
13+
'<p>Visit our <a href="https://example.com">website</a> for more information</p>';
14+
15+
const result = await pres
16+
.addSlide('TextReplace.pptx', 1, (slide) => {
17+
slide.modifyElement('setText', modify.htmlToMultiText(html));
18+
})
19+
.write(`htmlToMultiText-external-hyperlinks.test.pptx`);
20+
21+
// Test passes if file is written successfully
22+
});
23+
24+
test('create presentation with internal slide hyperlinks using htmlToMultiText', async () => {
25+
const automizer = new Automizer({
26+
templateDir: `${__dirname}/pptx-templates`,
27+
outputDir: `${__dirname}/pptx-output`,
28+
removeExistingSlides: true,
29+
});
30+
31+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
32+
33+
const html = '<p>See <a href="3">slide 3</a> for details</p>';
34+
35+
const result = await pres
36+
.addSlide('TextReplace.pptx', 1, (slide) => {
37+
slide.modifyElement('setText', modify.htmlToMultiText(html));
38+
})
39+
.write(`htmlToMultiText-internal-hyperlinks.test.pptx`);
40+
41+
// Test passes if file is written successfully
42+
});
43+
44+
test('create presentation with multiple hyperlinks using htmlToMultiText', async () => {
45+
const automizer = new Automizer({
46+
templateDir: `${__dirname}/pptx-templates`,
47+
outputDir: `${__dirname}/pptx-output`,
48+
removeExistingSlides: true,
49+
});
50+
51+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
52+
53+
const html =
54+
'<html><body>' +
55+
'<p>Check out <a href="https://google.com">Google</a> or <a href="https://github.com">GitHub</a></p>' +
56+
'<p>Jump to <a href="2">slide 2</a> or <a href="5">slide 5</a></p>' +
57+
'</body></html>';
58+
59+
const result = await pres
60+
.addSlide('TextReplace.pptx', 1, (slide) => {
61+
slide.modifyElement('setText', modify.htmlToMultiText(html));
62+
})
63+
.write(`htmlToMultiText-multiple-hyperlinks.test.pptx`);
64+
65+
// Test passes if file is written successfully
66+
});
67+
68+
test('create presentation with hyperlinks and formatting using htmlToMultiText', async () => {
69+
const automizer = new Automizer({
70+
templateDir: `${__dirname}/pptx-templates`,
71+
outputDir: `${__dirname}/pptx-output`,
72+
removeExistingSlides: true,
73+
});
74+
75+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
76+
77+
const html =
78+
'<html><body>' +
79+
'<p><strong>Important:</strong> Visit <a href="https://example.com"><em>our site</em></a></p>' +
80+
'<p>Regular text with <a href="https://test.com"><strong>bold link</strong></a> inline</p>' +
81+
'</body></html>';
82+
83+
const result = await pres
84+
.addSlide('TextReplace.pptx', 1, (slide) => {
85+
slide.modifyElement('setText', modify.htmlToMultiText(html));
86+
})
87+
.write(`htmlToMultiText-hyperlinks-with-formatting.test.pptx`);
88+
89+
// Test passes if file is written successfully
90+
});
91+
92+
test('create presentation with hyperlinks in bullet lists using htmlToMultiText', async () => {
93+
const automizer = new Automizer({
94+
templateDir: `${__dirname}/pptx-templates`,
95+
outputDir: `${__dirname}/pptx-output`,
96+
removeExistingSlides: true,
97+
});
98+
99+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
100+
101+
const html =
102+
'<html><body>' +
103+
'<ul>' +
104+
'<li><a href="https://site1.com">First link</a></li>' +
105+
'<li><a href="https://site2.com">Second link</a></li>' +
106+
'<li>Regular bullet with <a href="3">internal link</a></li>' +
107+
'</ul>' +
108+
'</body></html>';
109+
110+
const result = await pres
111+
.addSlide('TextReplace.pptx', 1, (slide) => {
112+
slide.modifyElement('setText', modify.htmlToMultiText(html));
113+
})
114+
.write(`htmlToMultiText-hyperlinks-in-lists.test.pptx`);
115+
116+
// Test passes if file is written successfully
117+
});
118+
119+
test('create presentation with mixed external and internal hyperlinks', async () => {
120+
const automizer = new Automizer({
121+
templateDir: `${__dirname}/pptx-templates`,
122+
outputDir: `${__dirname}/pptx-output`,
123+
removeExistingSlides: true,
124+
});
125+
126+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
127+
128+
const html =
129+
'<html><body>' +
130+
'<p>Visit <a href="https://example.com">example.com</a> or go to <a href="2">slide 2</a></p>' +
131+
'<ul>' +
132+
'<li>External: <a href="https://google.com">Google</a></li>' +
133+
'<li>Internal: <a href="4">Jump to slide 4</a></li>' +
134+
'<li>More info: <a href="https://github.com">GitHub</a></li>' +
135+
'</ul>' +
136+
'</body></html>';
137+
138+
const result = await pres
139+
.addSlide('TextReplace.pptx', 1, (slide) => {
140+
slide.modifyElement('setText', modify.htmlToMultiText(html));
141+
})
142+
.write(`htmlToMultiText-mixed-hyperlinks.test.pptx`);
143+
144+
// Test passes if file is written successfully
145+
});
146+
147+
test('verify hyperlink relationships are created correctly', async () => {
148+
const automizer = new Automizer({
149+
templateDir: `${__dirname}/pptx-templates`,
150+
outputDir: `${__dirname}/pptx-output`,
151+
removeExistingSlides: true,
152+
});
153+
154+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
155+
156+
const html = '<p>Visit <a href="https://example.com">our website</a></p>';
157+
158+
const result = await pres
159+
.addSlide('TextReplace.pptx', 1, async (slide) => {
160+
slide.modifyElement('setText', modify.htmlToMultiText(html));
161+
})
162+
.write(`htmlToMultiText-verify-relationships.test.pptx`);
163+
164+
// Test passes if file is written successfully
165+
// Test passes if file is written successfully
166+
});
167+
168+
test('verify internal slide hyperlink relationships are created correctly', async () => {
169+
const automizer = new Automizer({
170+
templateDir: `${__dirname}/pptx-templates`,
171+
outputDir: `${__dirname}/pptx-output`,
172+
removeExistingSlides: true,
173+
});
174+
175+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
176+
177+
const html = '<p>Go to <a href="3">slide 3</a></p>';
178+
179+
const result = await pres
180+
.addSlide('TextReplace.pptx', 1, async (slide) => {
181+
slide.modifyElement('setText', modify.htmlToMultiText(html));
182+
})
183+
.write(`htmlToMultiText-verify-internal-relationships.test.pptx`);
184+
185+
// Test passes if file is written successfully
186+
// Test passes if file is written successfully
187+
});
188+
189+
test('htmlToMultiText with hyperlinks but no relation element should log warning', async () => {
190+
const automizer = new Automizer({
191+
templateDir: `${__dirname}/pptx-templates`,
192+
outputDir: `${__dirname}/pptx-output`,
193+
verbosity: 2,
194+
removeExistingSlides: true,
195+
});
196+
197+
const pres = automizer.loadRoot(`RootTemplate.pptx`).load(`TextReplace.pptx`);
198+
199+
const html = '<p>Visit <a href="https://example.com">website</a></p>';
200+
201+
// This should complete without error, but hyperlinks won't be created
202+
const result = await pres
203+
.addSlide('TextReplace.pptx', 1, (slide) => {
204+
// Directly calling htmlToMultiText without relation element access
205+
// would normally skip hyperlink creation
206+
slide.modifyElement('setText', modify.htmlToMultiText(html));
207+
})
208+
.write(`htmlToMultiText-no-relation-warning.test.pptx`);
209+
210+
// Test passes if file is written successfully
211+
});

src/helper/html-to-multitext-helper.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,41 @@ export class HtmlToMultiTextHelper {
237237
newStyle.isItalics = true;
238238
} else if (tagName === 'ins') {
239239
newStyle.isUnderlined = true;
240-
} else if (tagName === 'span') {
240+
} else if (tagName === 'a') {
241+
this.processHyperlink(element, newStyle);
242+
} else if (tagName === 'span') {
241243
this.processSpanStyles(element, newStyle);
242244
}
243245

244246
return newStyle;
245247
}
246248

249+
/**
250+
* Processes anchor element for hyperlinks
251+
*/
252+
private processHyperlink(element: Element, style: TextStyle): void {
253+
const href = element.getAttribute('href');
254+
if (!href) return;
255+
256+
if (!isNaN(parseInt(href))) {
257+
// Internal slide link: <a href="3">Link to slide 3</a>
258+
const slideNumber = parseInt(href);
259+
style.hyperlink = {
260+
url: `slide${slideNumber}.xml`,
261+
isInternal: true,
262+
slideNumber: slideNumber,
263+
};
264+
} else {
265+
// External link: <a href="https://example.com">Link</a>
266+
style.hyperlink = {
267+
url: href,
268+
isInternal: false,
269+
};
270+
}
271+
272+
console.log("style", style);
273+
}
274+
247275
/**
248276
* Processes span element styles
249277
*/

src/helper/modify-text-helper.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ export default class ModifyTextHelper {
3939

4040
static setMultiText =
4141
(paragraphs: MultiTextParagraph[]) =>
42-
(element: XmlElement): void => {
43-
new MultiTextHelper(element).run(paragraphs);
42+
(element: XmlElement, relation?: XmlElement): void => {
43+
new MultiTextHelper(element, relation).run(paragraphs);
4444
};
4545

4646
static htmlToMultiText = (html: string) => {
4747
const paragraphs = new HtmlToMultiTextHelper().run(html);
48-
return (element: XmlElement): void => {
49-
this.setMultiText(paragraphs)(element);
48+
return (element: XmlElement, relation?: XmlElement): void => {
49+
this.setMultiText(paragraphs)(element, relation);
5050
};
5151
};
5252

0 commit comments

Comments
 (0)