node-pptx-templater 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +415 -0
- package/package.json +83 -0
- package/src/cli/commands/build.js +79 -0
- package/src/cli/commands/debug.js +46 -0
- package/src/cli/commands/extract.js +42 -0
- package/src/cli/commands/inspect.js +39 -0
- package/src/cli/commands/validate.js +36 -0
- package/src/cli/index.js +132 -0
- package/src/core/OutputWriter.js +181 -0
- package/src/core/PPTXTemplater.js +961 -0
- package/src/core/TemplateEngine.js +321 -0
- package/src/index.js +43 -0
- package/src/managers/ChartManager.js +317 -0
- package/src/managers/ContentTypesManager.js +160 -0
- package/src/managers/HyperlinkManager.js +451 -0
- package/src/managers/MediaManager.js +307 -0
- package/src/managers/RelationshipManager.js +401 -0
- package/src/managers/SlideManager.js +950 -0
- package/src/managers/TableManager.js +416 -0
- package/src/managers/ZipManager.js +298 -0
- package/src/managers/charts/ChartCacheGenerator.js +156 -0
- package/src/managers/charts/ChartParser.js +43 -0
- package/src/managers/charts/ChartRelationshipManager.js +33 -0
- package/src/managers/charts/ChartWorkbookUpdater.js +130 -0
- package/src/parsers/XMLParser.js +291 -0
- package/src/templates/blankPptx.js +1 -0
- package/src/templates/slideTemplate.js +314 -0
- package/src/utils/contentTypesHelper.js +149 -0
- package/src/utils/errors.js +129 -0
- package/src/utils/idUtils.js +54 -0
- package/src/utils/logger.js +113 -0
- package/src/utils/relationshipUtils.js +89 -0
- package/src/utils/xmlUtils.js +115 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `extract` CLI command — extracts XML parts from a PPTX.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { resolve } from 'path';
|
|
6
|
+
import { writeFileSync } from 'fs';
|
|
7
|
+
import { PPTXTemplater } from '../../index.js';
|
|
8
|
+
|
|
9
|
+
export async function extractCommand(filePath, opts) {
|
|
10
|
+
try {
|
|
11
|
+
const ppt = await PPTXTemplater.load(resolve(filePath));
|
|
12
|
+
|
|
13
|
+
if (opts.slide) {
|
|
14
|
+
const slideNum = parseInt(opts.slide, 10);
|
|
15
|
+
// Access internal zip via the engine's buffer
|
|
16
|
+
const buffer = await ppt.toBuffer();
|
|
17
|
+
const JSZip = (await import('jszip')).default;
|
|
18
|
+
const zip = await JSZip.loadAsync(buffer);
|
|
19
|
+
const slideFile = zip.file(`ppt/slides/slide${slideNum}.xml`);
|
|
20
|
+
|
|
21
|
+
if (!slideFile) {
|
|
22
|
+
console.error(chalk.red(`Slide ${slideNum} not found`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const xml = await slideFile.async('text');
|
|
27
|
+
|
|
28
|
+
if (opts.out) {
|
|
29
|
+
writeFileSync(resolve(opts.out), xml, 'utf-8');
|
|
30
|
+
console.log(chalk.green(`✓ Extracted slide ${slideNum} to ${opts.out}`));
|
|
31
|
+
} else {
|
|
32
|
+
console.log(xml);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
console.log(chalk.yellow('Specify --slide <number> to extract'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(chalk.red(`Extract failed: ${err.message}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `inspect` CLI command — detailed PPTX structure inspection.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import { PPTXTemplater } from '../../index.js';
|
|
8
|
+
|
|
9
|
+
export async function inspectCommand(filePath, opts) {
|
|
10
|
+
const showAll = opts.all;
|
|
11
|
+
const spinner = ora(`Inspecting: ${filePath}`).start();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const ppt = await PPTXTemplater.load(resolve(filePath));
|
|
15
|
+
const info = ppt.getInfo();
|
|
16
|
+
spinner.stop();
|
|
17
|
+
|
|
18
|
+
console.log(chalk.bold.cyan('\n═══ PPTX Inspection Report ═══\n'));
|
|
19
|
+
console.log(chalk.bold('General:'));
|
|
20
|
+
console.log(` Title: ${info.title || chalk.dim('(none)')}`);
|
|
21
|
+
console.log(` Author: ${info.author || chalk.dim('(none)')}`);
|
|
22
|
+
console.log(` Created: ${info.created || chalk.dim('(unknown)')}`);
|
|
23
|
+
console.log(` Slides: ${chalk.cyan(info.slideCount)}`);
|
|
24
|
+
console.log(` Media: ${chalk.cyan(info.mediaCount)} files`);
|
|
25
|
+
|
|
26
|
+
if (opts.slides || showAll) {
|
|
27
|
+
console.log(chalk.bold('\nSlides:'));
|
|
28
|
+
for (const slide of info.slides) {
|
|
29
|
+
const tags = slide.tags.length > 0 ? chalk.dim(` [${slide.tags.join(', ')}]`) : '';
|
|
30
|
+
console.log(` ${chalk.cyan(slide.index.toString().padStart(2))}. ${slide.zipPath}${tags}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('');
|
|
35
|
+
} catch (err) {
|
|
36
|
+
spinner.fail(chalk.red(`Inspect failed: ${err.message}`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `validate` CLI command.
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import { PPTXTemplater } from '../../index.js';
|
|
8
|
+
|
|
9
|
+
export async function validateCommand(filePath, opts) {
|
|
10
|
+
const spinner = ora(`Validating: ${filePath}`).start();
|
|
11
|
+
try {
|
|
12
|
+
const ppt = await PPTXTemplater.load(resolve(filePath));
|
|
13
|
+
const result = ppt.validate();
|
|
14
|
+
spinner.stop();
|
|
15
|
+
|
|
16
|
+
if (result.valid && result.warnings.length === 0) {
|
|
17
|
+
console.log(chalk.green(`\n✓ Valid PPTX (${ppt.slideCount} slides)\n`));
|
|
18
|
+
} else {
|
|
19
|
+
if (result.errors.length > 0) {
|
|
20
|
+
console.log(chalk.red(`\n✗ Validation errors (${result.errors.length}):\n`));
|
|
21
|
+
result.errors.forEach(e => console.log(chalk.red(` • ${e}`)));
|
|
22
|
+
}
|
|
23
|
+
if (result.warnings.length > 0) {
|
|
24
|
+
console.log(chalk.yellow(`\n⚠ Warnings (${result.warnings.length}):\n`));
|
|
25
|
+
result.warnings.forEach(w => console.log(chalk.yellow(` • ${w}`)));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!result.valid || (opts.strict && result.warnings.length > 0)) {
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
spinner.fail(chalk.red(`Validation failed: ${err.message}`));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview CLI entry point for node-pptx-templater.
|
|
4
|
+
*
|
|
5
|
+
* Provides command-line access to the template engine's core features.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node-pptx-templater build template.pptx output.pptx [options]
|
|
9
|
+
* node-pptx-templater validate template.pptx
|
|
10
|
+
* node-pptx-templater inspect template.pptx
|
|
11
|
+
* node-pptx-templater extract template.pptx --slide 1 --out ./slide1.xml
|
|
12
|
+
* node-pptx-templater debug template.pptx
|
|
13
|
+
*
|
|
14
|
+
* Install globally:
|
|
15
|
+
* npm install -g node-pptx-templater
|
|
16
|
+
*
|
|
17
|
+
* Then run:
|
|
18
|
+
* node-pptx-templater --help
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Command } from 'commander';
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
import ora from 'ora';
|
|
24
|
+
import { readFileSync } from 'fs';
|
|
25
|
+
import { resolve, dirname } from 'path';
|
|
26
|
+
import { fileURLToPath } from 'url';
|
|
27
|
+
import { PPTXTemplater } from '../index.js';
|
|
28
|
+
import { buildCommand } from './commands/build.js';
|
|
29
|
+
import { validateCommand } from './commands/validate.js';
|
|
30
|
+
import { inspectCommand } from './commands/inspect.js';
|
|
31
|
+
import { extractCommand } from './commands/extract.js';
|
|
32
|
+
import { debugCommand } from './commands/debug.js';
|
|
33
|
+
|
|
34
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
|
|
36
|
+
// Read version from package.json
|
|
37
|
+
const pkg = JSON.parse(
|
|
38
|
+
readFileSync(resolve(__dirname, '../../package.json'), 'utf-8')
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* CLI banner displayed on startup.
|
|
43
|
+
*/
|
|
44
|
+
function printBanner() {
|
|
45
|
+
console.log(chalk.bold.cyan(`
|
|
46
|
+
╔═══════════════════════════════════════════════╗
|
|
47
|
+
║ node-pptx-templater v${pkg.version.padEnd(17)}║
|
|
48
|
+
║ Low-level OpenXML PowerPoint template engine ║
|
|
49
|
+
╚═══════════════════════════════════════════════╝
|
|
50
|
+
`));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const program = new Command();
|
|
54
|
+
|
|
55
|
+
program
|
|
56
|
+
.name('node-pptx-templater')
|
|
57
|
+
.description('Low-level PowerPoint OpenXML template engine for Node.js')
|
|
58
|
+
.version(pkg.version, '-v, --version', 'Display version number')
|
|
59
|
+
.addHelpText('before', chalk.bold.cyan('\nnode-pptx-templater — PowerPoint XML manipulation engine\n'));
|
|
60
|
+
|
|
61
|
+
// ─── build command ─────────────────────────────────────────────────────────
|
|
62
|
+
program
|
|
63
|
+
.command('build <template> <output>')
|
|
64
|
+
.description('Build a PPTX from a template with data injected from a JSON file')
|
|
65
|
+
.option('-d, --data <file>', 'JSON file with template data (text replacements, etc.)')
|
|
66
|
+
.option('-s, --slide <numbers>', 'Comma-separated slide numbers to include (e.g., 1,3,5)')
|
|
67
|
+
.option('--no-banner', 'Suppress the banner')
|
|
68
|
+
.action(async (template, output, opts) => {
|
|
69
|
+
if (!opts.noBanner) printBanner();
|
|
70
|
+
await buildCommand(template, output, opts);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ─── validate command ───────────────────────────────────────────────────────
|
|
74
|
+
program
|
|
75
|
+
.command('validate <file>')
|
|
76
|
+
.description('Validate the structure of a PPTX file')
|
|
77
|
+
.option('--strict', 'Exit with error code on warnings too')
|
|
78
|
+
.action(async (file, opts) => {
|
|
79
|
+
printBanner();
|
|
80
|
+
await validateCommand(file, opts);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ─── inspect command ────────────────────────────────────────────────────────
|
|
84
|
+
program
|
|
85
|
+
.command('inspect <file>')
|
|
86
|
+
.description('Inspect the internal structure of a PPTX file')
|
|
87
|
+
.option('--slides', 'Show slide details')
|
|
88
|
+
.option('--charts', 'Show chart details')
|
|
89
|
+
.option('--tables', 'Show table details')
|
|
90
|
+
.option('--media', 'Show embedded media files')
|
|
91
|
+
.option('--rels', 'Show relationship tree')
|
|
92
|
+
.option('--all', 'Show everything')
|
|
93
|
+
.action(async (file, opts) => {
|
|
94
|
+
printBanner();
|
|
95
|
+
await inspectCommand(file, opts);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ─── extract command ────────────────────────────────────────────────────────
|
|
99
|
+
program
|
|
100
|
+
.command('extract <file>')
|
|
101
|
+
.description('Extract specific parts from a PPTX file')
|
|
102
|
+
.option('-s, --slide <number>', 'Slide number to extract XML from')
|
|
103
|
+
.option('-o, --out <path>', 'Output file path (default: stdout)')
|
|
104
|
+
.option('--chart <name>', 'Extract chart XML by name')
|
|
105
|
+
.option('--rels', 'Extract relationship files')
|
|
106
|
+
.action(async (file, opts) => {
|
|
107
|
+
await extractCommand(file, opts);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ─── debug command ──────────────────────────────────────────────────────────
|
|
111
|
+
program
|
|
112
|
+
.command('debug <file>')
|
|
113
|
+
.description('Debug a potentially corrupted PPTX structure')
|
|
114
|
+
.option('--fix', 'Attempt automatic repairs')
|
|
115
|
+
.option('-o, --out <path>', 'Output repaired file path')
|
|
116
|
+
.action(async (file, opts) => {
|
|
117
|
+
printBanner();
|
|
118
|
+
await debugCommand(file, opts);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ─── Global error handling ──────────────────────────────────────────────────
|
|
122
|
+
program.exitOverride();
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await program.parseAsync(process.argv);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err.code === 'commander.helpDisplayed' || err.code === 'commander.version') {
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
console.error(chalk.red(`\n✗ Error: ${err.message}\n`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview OutputWriter - Handles PPTX serialization and output.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* 1. Flush all pending changes from all managers into the ZipManager
|
|
6
|
+
* 2. Ensure [Content_Types].xml is up to date
|
|
7
|
+
* 3. Generate the final ZIP archive
|
|
8
|
+
* 4. Write to file, buffer, or stream
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fsExtra from 'fs-extra';
|
|
12
|
+
const { writeFile, ensureDir } = fsExtra;
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { XMLParser } from '../parsers/XMLParser.js';
|
|
15
|
+
import { createLogger } from '../utils/logger.js';
|
|
16
|
+
import { PPTXError } from '../utils/errors.js';
|
|
17
|
+
import { Readable } from 'stream';
|
|
18
|
+
|
|
19
|
+
const logger = createLogger('OutputWriter');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @class OutputWriter
|
|
23
|
+
* @description Serializes the modified PPTX to various output formats.
|
|
24
|
+
*/
|
|
25
|
+
export class OutputWriter {
|
|
26
|
+
/** @private @type {ZipManager} */
|
|
27
|
+
#zipManager;
|
|
28
|
+
/** @private @type {ContentTypesManager} */
|
|
29
|
+
#contentTypesManager;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {ZipManager} zipManager
|
|
33
|
+
* @param {ContentTypesManager} contentTypesManager
|
|
34
|
+
*/
|
|
35
|
+
constructor(zipManager, contentTypesManager) {
|
|
36
|
+
this.#zipManager = zipManager;
|
|
37
|
+
this.#contentTypesManager = contentTypesManager;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Writes the final PPTX to a file on disk.
|
|
42
|
+
* Creates parent directories if needed.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} filePath - Output file path.
|
|
45
|
+
* @param {SlideManager} slideManager
|
|
46
|
+
* @param {ZipManager} zipManager
|
|
47
|
+
* @returns {Promise<void>}
|
|
48
|
+
*/
|
|
49
|
+
async saveToFile(filePath, slideManager, zipManager) {
|
|
50
|
+
try {
|
|
51
|
+
const buffer = await this.toBuffer(slideManager, zipManager);
|
|
52
|
+
const dir = path.dirname(filePath);
|
|
53
|
+
await ensureDir(dir);
|
|
54
|
+
await writeFile(filePath, buffer);
|
|
55
|
+
logger.info(`Saved to ${filePath} (${(buffer.length / 1024).toFixed(1)} KB)`);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err instanceof PPTXError) throw err;
|
|
58
|
+
throw new PPTXError(`Failed to save file to ${filePath}: ${err.message}`, err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the PPTX as a Node.js Buffer.
|
|
64
|
+
*
|
|
65
|
+
* @param {SlideManager} slideManager
|
|
66
|
+
* @param {ZipManager} zipManager
|
|
67
|
+
* @returns {Promise<Buffer>}
|
|
68
|
+
*/
|
|
69
|
+
async toBuffer(slideManager, zipManager) {
|
|
70
|
+
// Ensure all slides are flushed to the ZIP
|
|
71
|
+
await this.#flushAllSlides(slideManager, zipManager);
|
|
72
|
+
|
|
73
|
+
// Flush Content Types safely
|
|
74
|
+
this.#contentTypesManager.flush(zipManager);
|
|
75
|
+
|
|
76
|
+
// Wait for any queued asynchronous writes (like content types, media hashing)
|
|
77
|
+
await zipManager.waitForPendingWrites();
|
|
78
|
+
|
|
79
|
+
const buffer = await zipManager.toBuffer();
|
|
80
|
+
logger.debug(`Generated buffer: ${(buffer.length / 1024).toFixed(1)} KB`);
|
|
81
|
+
return buffer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns the PPTX as a readable Node.js stream.
|
|
86
|
+
*
|
|
87
|
+
* @param {SlideManager} slideManager
|
|
88
|
+
* @param {ZipManager} zipManager
|
|
89
|
+
* @returns {Promise<Readable>}
|
|
90
|
+
*/
|
|
91
|
+
async toStream(slideManager, zipManager) {
|
|
92
|
+
await this.#flushAllSlides(slideManager, zipManager);
|
|
93
|
+
|
|
94
|
+
// Flush Content Types safely
|
|
95
|
+
this.#contentTypesManager.flush(zipManager);
|
|
96
|
+
|
|
97
|
+
await zipManager.waitForPendingWrites();
|
|
98
|
+
const nodeStream = await zipManager.toStream();
|
|
99
|
+
return nodeStream;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Ensures all dirty slide XML is committed to the ZipManager.
|
|
104
|
+
* This is called before any output operation.
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
* @param {SlideManager} slideManager
|
|
108
|
+
* @param {ZipManager} zipManager
|
|
109
|
+
* @returns {Promise<void>}
|
|
110
|
+
*/
|
|
111
|
+
async #flushAllSlides(slideManager, zipManager) {
|
|
112
|
+
// SlideManager already writes to zipManager via setSlideXml,
|
|
113
|
+
// so this is mostly a no-op with a validation step.
|
|
114
|
+
const info = slideManager.getAllSlideInfo();
|
|
115
|
+
|
|
116
|
+
for (const slide of info) {
|
|
117
|
+
if (!zipManager.hasFile(slide.zipPath)) {
|
|
118
|
+
logger.warn(`Slide file missing in ZIP: ${slide.zipPath}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Update the slide count and titles in docProps/app.xml to prevent repair mode issues
|
|
123
|
+
if (zipManager.hasFile('docProps/app.xml')) {
|
|
124
|
+
zipManager.addPendingPromise(
|
|
125
|
+
zipManager.rawZip.file('docProps/app.xml').async('text').then(content => {
|
|
126
|
+
const parser = new XMLParser();
|
|
127
|
+
const appObj = parser.parse(content, 'app.xml');
|
|
128
|
+
const properties = appObj.Properties;
|
|
129
|
+
|
|
130
|
+
if (properties) {
|
|
131
|
+
// 1. Update Slides count
|
|
132
|
+
properties.Slides = info.length;
|
|
133
|
+
|
|
134
|
+
// 2. Find old slide titles count and update HeadingPairs
|
|
135
|
+
let oldSlideTitlesCount = 0;
|
|
136
|
+
const variants = properties.HeadingPairs?.['vt:vector']?.['vt:variant'];
|
|
137
|
+
if (Array.isArray(variants)) {
|
|
138
|
+
for (let i = 0; i < variants.length; i++) {
|
|
139
|
+
if (variants[i]['vt:lpstr'] === 'Slide Titles') {
|
|
140
|
+
const countVar = variants[i + 1];
|
|
141
|
+
if (countVar) {
|
|
142
|
+
oldSlideTitlesCount = parseInt(countVar['vt:i4'], 10) || 0;
|
|
143
|
+
countVar['vt:i4'] = info.length;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 3. Update TitlesOfParts
|
|
151
|
+
const titlesVector = properties.TitlesOfParts?.['vt:vector'];
|
|
152
|
+
if (titlesVector) {
|
|
153
|
+
let lpstrs = titlesVector['vt:lpstr'];
|
|
154
|
+
if (lpstrs) {
|
|
155
|
+
if (!Array.isArray(lpstrs)) lpstrs = [lpstrs];
|
|
156
|
+
|
|
157
|
+
// Remove the old slide titles (which are at the end)
|
|
158
|
+
if (oldSlideTitlesCount > 0 && lpstrs.length >= oldSlideTitlesCount) {
|
|
159
|
+
lpstrs = lpstrs.slice(0, lpstrs.length - oldSlideTitlesCount);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Append new slide titles
|
|
163
|
+
const newSlideTitles = info.map(slide => slide.title || `Slide ${slide.index}`);
|
|
164
|
+
lpstrs.push(...newSlideTitles);
|
|
165
|
+
|
|
166
|
+
titlesVector['vt:lpstr'] = lpstrs;
|
|
167
|
+
titlesVector['@_size'] = String(lpstrs.length);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const declaration = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
|
|
172
|
+
const updatedXml = parser.build(appObj, declaration);
|
|
173
|
+
zipManager.writeFile('docProps/app.xml', updatedXml);
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
logger.debug(`Flushed ${info.length} slide(s) to ZIP`);
|
|
180
|
+
}
|
|
181
|
+
}
|