Skip to content

Commit 7f6ca47

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 386ad47 + e59b2fa commit 7f6ca47

File tree

13 files changed

+1067
-98
lines changed

13 files changed

+1067
-98
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ If you require commercial support for complex .pptx automation, you can explore
3838
- [Modify Charts](#modify-charts)
3939
- [Modify Extended Charts](#modify-extended-charts)
4040
- [Remove elements from a slide](#remove-elements-from-a-slide)
41+
- [🔗 Hyperlink Management](#🔗-hyperlink-management)
42+
- [Hyperlink Helper Functions](#hyperlink-helper-functions)
43+
- [Adding Hyperlinks](#adding-hyperlinks)
44+
45+
4146
- [Tipps and Tricks](#tipps-and-tricks)
4247
- [Loop through the slides of a presentation](#loop-through-the-slides-of-a-presentation)
4348
- [Quickly get all slide numbers of a template](#quickly-get-all-slide-numbers-of-a-template)
@@ -49,6 +54,7 @@ If you require commercial support for complex .pptx automation, you can explore
4954
- [Troubleshooting](#troubleshooting)
5055
- [Testing](#testing)
5156
- [Special Thanks](#special-thanks)
57+
5258
<!-- TOC -->
5359

5460
# Requirements and Limitations
@@ -590,6 +596,36 @@ pres
590596
});
591597
```
592598

599+
## 🔗 Hyperlink Management
600+
601+
PowerPoint presentations often use hyperlinks to connect to external websites or internal slides. The PPTX Automizer provides simple and powerful functions to manage hyperlinks in your presentations.
602+
603+
### Hyperlink Helper Functions
604+
605+
Three core functions are available for all your hyperlink needs:
606+
607+
| Function | Description |
608+
|----------|-------------|
609+
| `addHyperlink` | Add a new hyperlink to an element |
610+
611+
612+
### Adding Hyperlinks
613+
614+
You can add hyperlinks to text elements using the `addHyperlink` helper function. The function accepts either a URL string for external links or a slide number for internal slide links:
615+
616+
```ts
617+
// Add an external hyperlink
618+
slide.modifyElement('TextShape', modify.addHyperlink('https://example.com'));
619+
620+
621+
// Add an internal slide link (to slide 3)
622+
slide.modifyElement('TextShape', (element, relation) => {
623+
modify.addHyperlink(3)(element, relation);
624+
});
625+
```
626+
627+
The `addHyperlink` function will automatically detect whether the target is an external URL or an internal slide number and set up the appropriate relationship type and attributes.
628+
593629
# Tipps and Tricks
594630

595631
## Loop through the slides of a presentation

__tests__/copy-hyperlink.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import Automizer from '../src/index';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
import * as JSZip from 'jszip';
5+
import { DOMParser } from '@xmldom/xmldom';
6+
7+
test('Add and modify hyperlinks', async () => {
8+
const automizer = new Automizer({
9+
templateDir: `${__dirname}/pptx-templates`,
10+
outputDir: `${__dirname}/pptx-output`,
11+
});
12+
13+
const pres = automizer
14+
.loadRoot(`RootTemplate.pptx`)
15+
.load(`EmptySlide.pptx`, 'empty')
16+
.load(`SlideWithLink.pptx`, 'link');
17+
18+
// Track if the hyperlink was added
19+
const outputFile = `modify-hyperlink.test.pptx`;
20+
const outputPath = path.join(`${__dirname}/pptx-output`, outputFile);
21+
22+
const result = await pres
23+
// Add the slide with the existing hyperlink
24+
.addSlide('empty', 1, (slide) => {
25+
// Add the element with the hyperlink from the source slide
26+
slide.addElement('link', 1, 'ExternalLink');
27+
})
28+
.write(outputFile);
29+
30+
// Verify the number of slides
31+
expect(result.slides).toBe(2);
32+
33+
// Now verify that the hyperlink was actually copied by checking the PPTX file
34+
// Read the generated PPTX file
35+
const fileData = fs.readFileSync(outputPath);
36+
const zip = await JSZip.loadAsync(fileData);
37+
38+
// The second slide should be slide2.xml (index starts at 1 in PPTX)
39+
// Check its relationships file for hyperlink entries
40+
const slideRelsPath = 'ppt/slides/_rels/slide2.xml.rels';
41+
const slideRelsFile = zip.file(slideRelsPath);
42+
43+
// Make sure the file exists
44+
expect(slideRelsFile).not.toBeNull();
45+
46+
// Get the file content
47+
const slideRelsXml = await slideRelsFile!.async('text');
48+
49+
// Parse the XML
50+
const parser = new DOMParser();
51+
const xmlDoc = parser.parseFromString(slideRelsXml, 'application/xml');
52+
53+
// Look for hyperlink relationships
54+
const relationships = xmlDoc.getElementsByTagName('Relationship');
55+
let hasHyperlink = false;
56+
let hyperlinkId = '';
57+
58+
for (let i = 0; i < relationships.length; i++) {
59+
const relationship = relationships[i];
60+
const type = relationship.getAttribute('Type');
61+
if (type === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
62+
hasHyperlink = true;
63+
const id = relationship.getAttribute('Id');
64+
hyperlinkId = id || '';
65+
break;
66+
}
67+
}
68+
69+
// Verify that a hyperlink relationship exists
70+
expect(hasHyperlink).toBe(true);
71+
expect(hyperlinkId).not.toBe('');
72+
73+
// Now check if the slide XML contains the hyperlink reference
74+
const slidePath = 'ppt/slides/slide2.xml';
75+
const slideFile = zip.file(slidePath);
76+
77+
// Make sure the file exists
78+
expect(slideFile).not.toBeNull();
79+
80+
// Get the file content
81+
const slideXml = await slideFile!.async('text');
82+
83+
// Verify that the hyperlink ID is referenced in the slide content
84+
expect(slideXml.includes(`r:id="${hyperlinkId}"`)).toBe(true);
85+
});
86+
87+
test('Copy full slide with hyperlinks', async () => {
88+
const automizer = new Automizer({
89+
templateDir: `${__dirname}/pptx-templates`,
90+
outputDir: `${__dirname}/pptx-output`,
91+
});
92+
93+
const pres = automizer
94+
.loadRoot(`RootTemplate.pptx`)
95+
.load(`SlideWithLink.pptx`, 'link');
96+
97+
const outputFile = `copy-slide-with-hyperlink.test.pptx`;
98+
const outputPath = path.join(`${__dirname}/pptx-output`, outputFile);
99+
100+
const result = await pres
101+
.addSlide('link', 1)
102+
.write(outputFile);
103+
104+
// Verify the number of slides
105+
expect(result.slides).toBe(2);
106+
107+
// Read the generated PPTX file
108+
const fileData = fs.readFileSync(outputPath);
109+
const zip = await JSZip.loadAsync(fileData);
110+
111+
// Check relationships file for slide 2
112+
const slideRelsPath = 'ppt/slides/_rels/slide2.xml.rels';
113+
const slideRelsFile = zip.file(slideRelsPath);
114+
expect(slideRelsFile).not.toBeNull();
115+
116+
const slideRelsXml = await slideRelsFile!.async('text');
117+
const parser = new DOMParser();
118+
const xmlDoc = parser.parseFromString(slideRelsXml, 'application/xml');
119+
120+
// Look for hyperlink relationships
121+
const relationships = xmlDoc.getElementsByTagName('Relationship');
122+
let hasExternalHyperlink = false;
123+
let hasInternalHyperlink = false;
124+
let externalHyperlinkId = '';
125+
let internalHyperlinkId = '';
126+
127+
for (let i = 0; i < relationships.length; i++) {
128+
const relationship = relationships[i];
129+
const type = relationship.getAttribute('Type');
130+
if (type === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
131+
const targetMode = relationship.getAttribute('TargetMode');
132+
const id = relationship.getAttribute('Id') || '';
133+
134+
if (targetMode === 'External') {
135+
hasExternalHyperlink = true;
136+
externalHyperlinkId = id;
137+
} else if (!targetMode) {
138+
hasInternalHyperlink = true;
139+
internalHyperlinkId = id;
140+
}
141+
}
142+
}
143+
144+
// Verify that hyperlink relationships exist
145+
expect(hasExternalHyperlink).toBe(true);
146+
expect(externalHyperlinkId).not.toBe('');
147+
148+
// Check the slide XML content
149+
const slidePath = 'ppt/slides/slide2.xml';
150+
const slideFile = zip.file(slidePath);
151+
expect(slideFile).not.toBeNull();
152+
153+
const slideXml = await slideFile!.async('text');
154+
155+
// Verify hyperlink references in slide content
156+
expect(slideXml.includes(`r:id="${externalHyperlinkId}"`)).toBe(true);
157+
158+
if (hasInternalHyperlink) {
159+
expect(slideXml.includes(`r:id="${internalHyperlinkId}"`)).toBe(true);
160+
}
161+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { Automizer } from '../src';
2+
import fs from 'fs';
3+
import path from 'path';
4+
5+
describe('Hyperlink works with generate', () => {
6+
it('Hyperlink works with generate', async () => {
7+
const automizer = new Automizer({
8+
templateDir: path.join(__dirname, 'pptx-templates'),
9+
outputDir: path.join(__dirname, 'pptx-output')
10+
});
11+
12+
// Load root and template files
13+
await automizer.loadRoot('EmptyTemplate.pptx');
14+
await automizer.load('EmptySlide.pptx', 'template');
15+
16+
// Create slide 1 with hyperlinks
17+
await automizer.addSlide('template', 1, async (slide) => {
18+
// Test external links
19+
const externalUrls = [
20+
'https://google.com/1',
21+
'https://google.com/2',
22+
'https://google.com/3',
23+
];
24+
25+
for (let i = 0; i < externalUrls.length; i++) {
26+
slide.generate(async pptxGenJSSlide => {
27+
// External link
28+
pptxGenJSSlide.addText(`External Link ${i + 1}`, {
29+
hyperlink: { url: externalUrls[i] },
30+
x: 1,
31+
y: 1 + i,
32+
fontFace: 'Kanit',
33+
});
34+
});
35+
}
36+
37+
// Test internal links
38+
const targetSlides = [2, 3, 4];
39+
for (let i = 0; i < targetSlides.length; i++) {
40+
slide.generate(async pptxGenJSSlide => {
41+
// Internal link
42+
pptxGenJSSlide.addText(`Go to Slide ${targetSlides[i]}`, {
43+
hyperlink: { slide: targetSlides[i] },
44+
x: 3,
45+
y: 1 + i,
46+
fontFace: 'Kanit',
47+
});
48+
});
49+
}
50+
});
51+
52+
// Create target slides that we'll link to
53+
for (let i = 2; i <= 4; i++) {
54+
await automizer.addSlide('template', 1, async (slide) => {
55+
slide.generate(async pptxGenJSSlide => {
56+
pptxGenJSSlide.addText(`This is Slide ${i}`, {
57+
x: 1,
58+
y: 1,
59+
fontFace: 'Kanit',
60+
});
61+
});
62+
});
63+
}
64+
65+
// Write the presentation
66+
await automizer.write('hyperlink-test.pptx');
67+
});
68+
});

0 commit comments

Comments
 (0)