pptx2json
Version:
Parse a Powerpoint file to a Json.
205 lines (182 loc) • 5.81 kB
JavaScript
/**
* @module pptx2json
* @fileoverview Convert Open Office XML pptx buffer to JSON and ecoding XML
*
* @author x1- <viva008@gmail.com>
*/
;
const fs = require('fs').promises;
const os = require('os');
const path = require('path');
const assert = require('assert');
const JSZip = require('jszip');
const xml2js = require('xml2js');
const PresentationXML = 'ppt/presentation.xml';
/**
* @class PPTX2Json
*
*/
class PPTX2Json {
/**
* @method constructor
*
* constructor for PPTX2Json
*
* @param {Object} options {
* 'jszipBinary': nodebuffer(default) | blob | arraybuffer | uint8array | nodestream, see: https://stuk.github.io/jszip/documentation/api_jszip/support.html
* 'jszipGenerateType': nodebuffer(default) | blob | arraybuffer | uint8array | nodestream
* }
* @returns PPTX2Json
*/
constructor(options) {
this.options = options || {};
}
async jszip2json(jszip) {
const json = {};
await Promise.all(
Object.keys(jszip.files).map(async relativePath => {
const file = jszip.file(relativePath);
const ext = path.extname(relativePath);
let content;
if (!file || file.dir) {
return;
} else if (ext === '.xml' || ext === '.rels') {
const xml = await file.async("string");
content = await xml2js.parseStringPromise(xml);
} else {
content = await file.async(this.options['jszipBinary'] || 'nodebuffer'); // images, audio files, movies, etc.
}
json[relativePath] = content;
})
);
return json;
}
/**
* @method toJson
*
* Parse PowerPoint file to Json.
*
* @param {string} file Give a path of PowerPoint.
* @returns {Promise} json
*/
async toJson(file) {
assert.equal(typeof file, 'string', "argument 'file' must be a string");
const buff = await fs.readFile(file);
const zip = await JSZip().loadAsync(buff);
return await this.jszip2json(zip);
}
/**
* @method buffer2json
*
* Parse PowerPoint file to Json.
*
* @param {string} buffer Binary contents of a PowerPoint file.
* @returns {Promise} json
*/
async buffer2json(buff) {
const zip = await JSZip().loadAsync(buff);
return await this.jszip2json(zip);
}
json2jszip(json) {
const zip = new JSZip();
Object.keys(json).forEach(relativePath => {
const ext = path.extname(relativePath);
if (ext === '.xml' || ext === '.rels') {
const builder = new xml2js.Builder({
renderOpts: {
pretty: false
}
});
const xml = (builder.buildObject(json[relativePath]));
zip.file(relativePath, xml);
} else {
zip.file(relativePath, json[relativePath]);
}
});
return zip;
}
/**
* @method toPPTX
*
* Convert json to pptx.
* It is available to add or delete slides, media before call this method.
*
* @param {Object} json created from PowerPoint XMLs.
* @param {Object} options {
* 'file': If you want to write file, please give the output path. If not, return buffer.
* }
* @returns {Promise} Buffer if not file, otherwise empty.
*/
async toPPTX(json, options) {
options = options || {};
assert.equal(typeof json, 'object', "argument 'json' must be an object");
assert.equal(typeof options, 'object', "argument 'options' must be an object");
const zip = this.json2jszip(json);
let buf = await zip.generateAsync({
type: this.options['jszipGenerateType'] || 'nodebuffer'
});
if (!options.file) {
return buf;
}
return await fs.writeFile(options.file, buf);
}
/**
* @method getMaxSlideIds
*
* Find max id and r:id in the slides at presentation.xml.
* If any slides do not present at presentation.xml, returns {'id': -1, 'rid': -1}.
* notice: 'rid' is represented as {'r:id':'rId1'} at presentation.xml, but this method returns Number.
*
* @param {Object} json created from PowerPoint XMLs.
* @returns {Object} ex) {'id': 27, 'rid': 7};
*/
getMaxSlideIds(json) {
let mx = {'id': -1, 'rid': -1};
if (!PresentationXML in json) {
return mx;
}
const presen = json[PresentationXML];
if (!'p:presentation' in json[PresentationXML] || !'p:sldIdLst' in presen['p:presentation']) {
return mx;
}
presen['p:presentation']['p:sldIdLst'].forEach(xs => {
const maxId = xs['p:sldId'].reduce((a, b) => {
const bId = parseInt(b.$.id);
return a > bId ? a : bId;
}, -1);
const maxRid = xs['p:sldId'].reduce((a, b) => {
const bId = parseInt(b.$['r:id'].replace('rId', ''));
return a > bId ? a : bId;
}, -1);
mx.id = Math.max(maxId, mx.id);
mx.rid = Math.max(maxRid, mx.rid);
});
return mx;
}
/**
* @method getSlideLayoutHash
*
* Find the slideLayouts in json, and generate Hash has SlideLayoutType as a key and a file path as a value.
* If SlideLayoutType does not appear in xml, exclude these slideLayouts.
* SlideLayoutType see: https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-ppt/df8f3d7b-db67-47dc-8c89-20f5cbbf0fa9
*
* @param {Object} json created from PowerPoint XMLs.
* @returns {Object} Key: SlideLayoutType, Value: file path.
*/
getSlideLayoutTypeHash(json) {
const table = {};
const layouts =
Object.keys(json).filter(key => /^ppt\/slideLayouts\/[^_]+\.xml$/.test(key) && json[key]['p:sldLayout']);
if (!layouts) {
return table;
}
layouts.forEach(layout => {
if (!json[layout]['p:sldLayout'].$.type) {
return;
}
table[json[layout]['p:sldLayout'].$.type] = layout;
})
return table;
}
}
module.exports = PPTX2Json;