├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── pull-request.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── .releaserc.json ├── .travis.yml ├── README.md ├── bin └── convert ├── jest.config.json ├── lib ├── index.d.ts ├── index.js ├── index.js.map ├── parser.d.ts ├── parser.js ├── parser.js.map ├── types.d.ts ├── types.js └── types.js.map ├── package-lock.json ├── package.json ├── src ├── __tests__ │ ├── fixtures │ │ ├── sample.jpg │ │ ├── sample.pptx │ │ └── sample.zip │ ├── index.spec.ts │ └── parser.spec.ts ├── index.ts ├── parser.ts └── types.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the configurations used 2 | # in this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.scss] 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | 20 | [{package.json}] 21 | indent_size = 2 22 | indent_style = space 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | on: pull_request 3 | 4 | jobs: 5 | build: 6 | name: PR Checker 7 | runs-on: ubuntu-18.04 8 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 9 | 10 | steps: 11 | # checkout branch 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | # setup node 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 12 20 | 21 | # install deps 22 | - name: Install dependencies 23 | run: npm ci 24 | 25 | # run tests 26 | - name: Run tests 27 | run: npm run test 28 | 29 | # build lib 30 | - name: Build library 31 | run: npm run build 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | name: Library Release 9 | runs-on: ubuntu-18.04 10 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 11 | 12 | steps: 13 | # checkout branch 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | # setup node 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: 12 22 | 23 | # install deps 24 | - name: Install dependencies 25 | run: npm ci 26 | 27 | # build lib 28 | - name: Build library 29 | run: npm run build 30 | 31 | # run tests 32 | - name: Run tests 33 | run: npm run test 34 | 35 | # release new version 36 | - name: Release 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | run: npx semantic-release 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .DS_Store 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # TypeScript cache 46 | *.tsbuildinfo 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | yarn.lock 63 | 64 | # dotenv environment variables file 65 | .env 66 | .env.test 67 | 68 | # parcel-bundler cache (https://parceljs.org/) 69 | .cache 70 | 71 | # Gatsby files 72 | .cache/ 73 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 74 | # https://nextjs.org/blog/next-9-1#public-directory-support 75 | # public 76 | # DynamoDB Local files 77 | .dynamodb/ 78 | 79 | # TernJS port file 80 | .tern-port 81 | 82 | # Built files 83 | dist/ 84 | output/ 85 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "tabWidth": 2, 4 | "printWidth": 120, 5 | "semi": true 6 | } 7 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "semantic-release-gitmoji", 5 | { 6 | "releaseRules": { 7 | "patch": { 8 | "include": [":bento:", ":recycle:"] 9 | } 10 | } 11 | } 12 | ], 13 | "@semantic-release/github", 14 | "@semantic-release/npm", 15 | [ 16 | "@semantic-release/git", 17 | { 18 | "message": ":bookmark: v${nextRelease.version} [skip ci]\n\nhttps://github.com/shobhitsharma/pptx-compose/releases/tag/${nextRelease.gitTag}" 19 | } 20 | ] 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | script: 5 | - "npm run build" 6 | - "npm run test:report" 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pptx-compose [![npm version](https://badge.fury.io/js/pptx-compose.svg)](https://badge.fury.io/js/pptx-compose) 2 | 3 | > Parses Open Office XML generated PPTX to JSON 4 | 5 | ## Install 6 | 7 | ``` 8 | $ npm install pptx-compose 9 | ``` 10 | 11 | See [changelog here](https://github.com/shobhitsharma/pptx-compose/releases) for API updates and compatibility issues. 12 | 13 | ## Usage 14 | 15 | ```js 16 | import PPTXCompose from "pptx-compose"; 17 | 18 | // Initialize repo 19 | const composer = new PPTXCompose(options); 20 | 21 | // Parses a PPTX file to JSON 22 | const pptx = await composer.toJSON("/path/to/my.pptx"); 23 | 24 | // Parses JSON output to PPTX 25 | const json = await composer.toPPTX("/path/to/my.json"); 26 | ``` 27 | 28 | ## CLI 29 | 30 | Composer is able to generate JSON from PPTX source directly from CLI, run: 31 | 32 | ```bash 33 | # Usage: convert [options] 34 | # Options: 35 | # -V, --version output the version number 36 | # -i, --input PPTX File 37 | # -o, --output Output JSON file (optional) 38 | # -h, --help display help for command 39 | 40 | $ node bin/convert ./path/to/my.pptx path/to/your/directory/generated.json 41 | ``` 42 | 43 | ## Options 44 | 45 | | attribute | type | default | 46 | | --------------------- | :-------------------------------------------------------------------------------------------------------: | -----------: | 47 | | **jszipBinary** | `"nodebuffer" / "base64" / "text" / "binarystring" /` \ `"array" / "uint8array" / "arraybuffer" / "blob"` | `nodebuffer` | 48 | | **jszipGenerateType** | `"nodebuffer" / "base64" / "text" / "binarystring" /` \ `"array" / "uint8array" / "arraybuffer" / "blob"` | `nodebuffer` | 49 | 50 | ## Methods 51 | 52 | PPTX Composer has following built-in methods: 53 | 54 | ### `.toJSON(, )` 55 | 56 | Parse PowerPoint file to JSON. 57 | 58 | ```js 59 | const composer = new PPTXCompose(); 60 | 61 | // Parses a PPTX file to JSON 62 | const pptx = await composer.toJSON("/path/to/my.pptx"); 63 | 64 | // Convert a PPTX file to JSON file 65 | composer.toJSON("/path/to/my.pptx", { 66 | output: "/path/to/output/file.pptx", 67 | }); 68 | ``` 69 | 70 | ### `.toPPTX(, )` 71 | 72 | Convert JSON file to PPTX. 73 | 74 | ```js 75 | const composer = new PPTXCompose('{ "my": "json" ... }'); 76 | 77 | // Parses JSON output to PPTX 78 | const json = await composer.toPPTX("/path/to/my.json"); 79 | 80 | // Convert JSON to PPTX file 81 | composer.toJSON("/path/to/my.json", { 82 | output: "/path/to/output/file.pptx", 83 | }); 84 | ``` 85 | 86 | ## License 87 | 88 | MIT 89 | -------------------------------------------------------------------------------- /bin/convert: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | const program = require("commander"); 6 | const pkg = require("../package"); 7 | const Composer = require("../lib/index.js").default; 8 | 9 | program 10 | .usage("[options] ") 11 | .version(pkg.version) 12 | .option("-i, --input", "PPTX File") 13 | .option("-o, --output", "Output JSON file (optional)") 14 | .parse(process.argv); 15 | 16 | if (!program.args || !program.args[0]) { 17 | program.help(); 18 | } 19 | 20 | const input = program.args[0]; 21 | const output = program.args[1]; 22 | 23 | const composer = new Composer(); 24 | 25 | composer 26 | .toJSON(input, { output: output }) 27 | .then((output) => { 28 | console.log("Results saved at:", output); 29 | process.exit(); 30 | }) 31 | .catch((err) => { 32 | console.error(err); 33 | process.exit(); 34 | }); 35 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ts-jest", 3 | "testEnvironment": "node", 4 | "testRunner": "jest-circus/runner" 5 | } 6 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module pptx-compose 3 | * @fileoverview Composes Open Office XML pptx buffer to JSON and ecoding XML 4 | * 5 | * @author Shobhit Sharma 6 | */ 7 | import type { ComposerOptions, OutputJSON, OutputOptions } from "./types"; 8 | export interface IComposer { 9 | /** 10 | * Converts PPTX file input to JSON output 11 | */ 12 | toJSON: (file: string, options?: OutputOptions) => {}; 13 | /** 14 | * Converts JSON input to PPTX output 15 | */ 16 | toPPTX: (json: Object, options?: OutputOptions) => {}; 17 | } 18 | declare class Composer implements IComposer { 19 | private options; 20 | constructor(options?: ComposerOptions); 21 | /** 22 | * @method toJSON 23 | * 24 | * Parse PowerPoint file to JSON. 25 | * 26 | * @param {string} file Give a path of PowerPoint file. 27 | * @param {OutputOptions} options 28 | * @returns {Promise} json 29 | */ 30 | toJSON(file: string, options?: OutputOptions): Promise; 31 | /** 32 | * @method toPPTX 33 | * 34 | * Convert JSON to PPTX. 35 | * 36 | * @param {OutputJSON} json created from PowerPoint XMLs 37 | * @param {OutputOptions} options 38 | * @returns {Promise} 39 | */ 40 | toPPTX(json: OutputJSON, options?: OutputOptions): Promise; 41 | } 42 | export default Composer; 43 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * @module pptx-compose 4 | * @fileoverview Composes Open Office XML pptx buffer to JSON and ecoding XML 5 | * 6 | * @author Shobhit Sharma 7 | */ 8 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 9 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 10 | return new (P || (P = Promise))(function (resolve, reject) { 11 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 12 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 13 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 14 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 15 | }); 16 | }; 17 | var __importDefault = (this && this.__importDefault) || function (mod) { 18 | return (mod && mod.__esModule) ? mod : { "default": mod }; 19 | }; 20 | Object.defineProperty(exports, "__esModule", { value: true }); 21 | const path_1 = __importDefault(require("path")); 22 | const jszip_1 = __importDefault(require("jszip")); 23 | const fs_1 = require("fs"); 24 | const parser_1 = require("./parser"); 25 | class Composer { 26 | constructor(options) { 27 | this.options = { 28 | jszipBinary: "nodebuffer", 29 | jszipGenerateType: "nodebuffer", 30 | }; 31 | if (options) { 32 | this.options = options; 33 | } 34 | } 35 | /** 36 | * @method toJSON 37 | * 38 | * Parse PowerPoint file to JSON. 39 | * 40 | * @param {string} file Give a path of PowerPoint file. 41 | * @param {OutputOptions} options 42 | * @returns {Promise} json 43 | */ 44 | toJSON(file, options) { 45 | return __awaiter(this, void 0, void 0, function* () { 46 | options = Object.assign({}, this.options, options); 47 | const fileBuffer = yield fs_1.promises.readFile(path_1.default.resolve(__dirname, file)); 48 | const zip = yield jszip_1.default().loadAsync(fileBuffer); 49 | const jsonOutput = yield parser_1.jszip2json(zip, options); 50 | if (options.output) { 51 | return yield fs_1.promises.writeFile(options.output, JSON.stringify(jsonOutput, null, 2)); 52 | } 53 | return jsonOutput; 54 | }); 55 | } 56 | /** 57 | * @method toPPTX 58 | * 59 | * Convert JSON to PPTX. 60 | * 61 | * @param {OutputJSON} json created from PowerPoint XMLs 62 | * @param {OutputOptions} options 63 | * @returns {Promise} 64 | */ 65 | toPPTX(json, options) { 66 | return __awaiter(this, void 0, void 0, function* () { 67 | options = Object.assign({}, this.options, options); 68 | const zip = yield parser_1.json2jszip(json, options); 69 | const contentBuffer = yield zip.generateAsync({ 70 | type: this.options.jszipGenerateType || "nodebuffer", 71 | }); 72 | if (options.output) { 73 | return yield fs_1.promises.writeFile(options.output, contentBuffer); 74 | } 75 | return contentBuffer; 76 | }); 77 | } 78 | } 79 | exports.default = Composer; 80 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;AAEH,gDAAwB;AACxB,kDAA0B;AAC1B,2BAA2D;AAC3D,qCAAkD;AAgBlD,MAAM,QAAQ;IAMZ,YAAY,OAAyB;QAL7B,YAAO,GAAoB;YACjC,WAAW,EAAE,YAAY;YACzB,iBAAiB,EAAE,YAAY;SAChC,CAAC;QAGA,IAAI,OAAO,EAAE;YACX,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;SACxB;IACH,CAAC;IAED;;;;;;;;OAQG;IACU,MAAM,CAAC,IAAY,EAAE,OAAuB;;YACvD,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAEnD,MAAM,UAAU,GAAG,MAAM,aAAE,CAAC,QAAQ,CAAC,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;YACpE,MAAM,GAAG,GAAG,MAAM,eAAK,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;YAChD,MAAM,UAAU,GAAG,MAAM,mBAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAElD,IAAI,OAAO,CAAC,MAAM,EAAE;gBAClB,OAAO,MAAM,aAAE,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;aAChF;YAED,OAAO,UAAU,CAAC;QACpB,CAAC;KAAA;IAED;;;;;;;;OAQG;IACU,MAAM,CAAC,IAAgB,EAAE,OAAuB;;YAC3D,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAEnD,MAAM,GAAG,GAAG,MAAM,mBAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5C,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC;gBAC5C,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,iBAAiB,IAAI,YAAY;aACrD,CAAC,CAAC;YAEH,IAAI,OAAO,CAAC,MAAM,EAAE;gBAClB,OAAO,MAAM,aAAE,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,aAAuB,CAAC,CAAC;aACpE;YAED,OAAO,aAAa,CAAC;QACvB,CAAC;KAAA;CACF;AAED,kBAAe,QAAQ,CAAC"} -------------------------------------------------------------------------------- /lib/parser.d.ts: -------------------------------------------------------------------------------- 1 | import JSZip from "jszip"; 2 | import type { OutputJSON, OutputOptions } from "./types"; 3 | export declare const jszip2json: (jszip: JSZip, options?: OutputOptions | undefined) => Promise; 4 | export declare const json2jszip: (json: OutputJSON, options?: OutputOptions | undefined) => Promise; 5 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.json2jszip = exports.jszip2json = void 0; 16 | const path_1 = __importDefault(require("path")); 17 | const xml2js_1 = __importDefault(require("xml2js")); 18 | const jszip_1 = __importDefault(require("jszip")); 19 | exports.jszip2json = (jszip, options) => __awaiter(void 0, void 0, void 0, function* () { 20 | const json = {}; 21 | const jszipBinaryType = (options || {}).jszipBinary || "nodebuffer"; 22 | yield Promise.all(Object.keys(jszip.files).map((relativePath) => __awaiter(void 0, void 0, void 0, function* () { 23 | const file = jszip.file(relativePath); 24 | const ext = path_1.default.extname(relativePath); 25 | let content; 26 | if (!file || file.dir) { 27 | return; 28 | } 29 | else if (ext === ".xml" || ext === ".rels") { 30 | const xml = yield file.async("binarystring"); 31 | content = yield xml2js_1.default.parseStringPromise(xml); 32 | } 33 | else { 34 | // Handles media assets (image, audio, video, etc.) 35 | content = yield file.async(jszipBinaryType); 36 | } 37 | json[relativePath] = content; 38 | }))); 39 | return json; 40 | }); 41 | exports.json2jszip = (json, options) => __awaiter(void 0, void 0, void 0, function* () { 42 | const zip = new jszip_1.default(); 43 | Object.keys(json).forEach((relativePath) => { 44 | const ext = path_1.default.extname(relativePath); 45 | if (ext === ".xml" || ext === ".rels") { 46 | const builder = new xml2js_1.default.Builder({ 47 | renderOpts: { 48 | pretty: false, 49 | }, 50 | }); 51 | const xml = builder.buildObject(json[relativePath]); 52 | zip.file(relativePath, xml); 53 | } 54 | else { 55 | zip.file(relativePath, json[relativePath]); 56 | } 57 | }); 58 | return zip; 59 | }); 60 | //# sourceMappingURL=parser.js.map -------------------------------------------------------------------------------- /lib/parser.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,gDAAwB;AACxB,oDAA4B;AAC5B,kDAA0B;AAIb,QAAA,UAAU,GAAG,CAAO,KAAY,EAAE,OAAuB,EAAE,EAAE;IACxE,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,MAAM,eAAe,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,WAAW,IAAI,YAAY,CAAC;IAEpE,MAAM,OAAO,CAAC,GAAG,CACf,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAO,YAAY,EAAE,EAAE;QAClD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAEvC,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE;YACrB,OAAO;SACR;aAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO,EAAE;YAC5C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YAC7C,OAAO,GAAG,MAAM,gBAAM,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;SAChD;aAAM;YACL,mDAAmD;YACnD,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;SAC7C;QAED,IAAI,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC;IAC/B,CAAC,CAAA,CAAC,CACH,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC,CAAA,CAAC;AAEW,QAAA,UAAU,GAAG,CAAO,IAAgB,EAAE,OAAuB,EAAE,EAAE;IAC5E,MAAM,GAAG,GAAG,IAAI,eAAK,EAAE,CAAC;IAExB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE;QACzC,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO,EAAE;YACrC,MAAM,OAAO,GAAG,IAAI,gBAAM,CAAC,OAAO,CAAC;gBACjC,UAAU,EAAE;oBACV,MAAM,EAAE,KAAK;iBACd;aACF,CAAC,CAAC;YACH,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;YACpD,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;SAC7B;aAAM;YACL,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;SAC5C;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC,CAAA,CAAC"} -------------------------------------------------------------------------------- /lib/types.d.ts: -------------------------------------------------------------------------------- 1 | import JSZip from "jszip"; 2 | export declare type ComposerOptions = { 3 | jszipBinary: JSZip.OutputType; 4 | jszipGenerateType: JSZip.OutputType; 5 | }; 6 | export declare type OutputJSON = { 7 | [key: string]: any; 8 | }; 9 | export declare type OutputOptions = ComposerOptions & { 10 | output?: string; 11 | }; 12 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=types.js.map -------------------------------------------------------------------------------- /lib/types.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pptx-compose", 3 | "version": "0.4.0", 4 | "description": "PPTX parser to JSON format", 5 | "main": "lib/index.js", 6 | "bin": "lib/index.js", 7 | "types": "lib/index.d.ts", 8 | "files": [ 9 | "lib" 10 | ], 11 | "scripts": { 12 | "dev": "tsc --watch", 13 | "build": "tsc", 14 | "test": "jest", 15 | "test:coverage": "jest --coverage", 16 | "test:report": "jest --coverage --coverageReporters=text-lcov | coveralls", 17 | "semantic-release": "semantic-release" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/shobhitsharma/pptx-compose.git" 22 | }, 23 | "keywords": [ 24 | "pptx", 25 | "pptx-composer", 26 | "pptx-json", 27 | "pptx-xml", 28 | "ppt", 29 | "pptx-to-json", 30 | "pptx-to-xml" 31 | ], 32 | "author": "Shobhit Sharma (https://shobh.it/)", 33 | "engines": { 34 | "node": ">=10.0.0" 35 | }, 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/shobhitsharma/pptx-compose/issues" 39 | }, 40 | "homepage": "https://github.com/shobhitsharma/pptx-compose#readme", 41 | "dependencies": { 42 | "commander": "5.1.0", 43 | "jszip": "3.4.0", 44 | "xml2js": "0.4.23" 45 | }, 46 | "devDependencies": { 47 | "@semantic-release/git": "^9.0.0", 48 | "@types/async": "^3.2.3", 49 | "@types/jest": "^25.2.3", 50 | "@types/node": "^14.0.11", 51 | "@types/xml2js": "^0.4.5", 52 | "coveralls": "^3.1.0", 53 | "jest": "^26.0.1", 54 | "jest-circus": "^26.0.1", 55 | "semantic-release": "^17.0.8", 56 | "semantic-release-gitmoji": "^1.3.4", 57 | "ts-jest": "^26.1.0", 58 | "typescript": "^3.9.5" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shobhitsharma/pptx-compose/34352c6168270703c16ac2e18716037b0ebcc58d/src/__tests__/fixtures/sample.jpg -------------------------------------------------------------------------------- /src/__tests__/fixtures/sample.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shobhitsharma/pptx-compose/34352c6168270703c16ac2e18716037b0ebcc58d/src/__tests__/fixtures/sample.pptx -------------------------------------------------------------------------------- /src/__tests__/fixtures/sample.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shobhitsharma/pptx-compose/34352c6168270703c16ac2e18716037b0ebcc58d/src/__tests__/fixtures/sample.zip -------------------------------------------------------------------------------- /src/__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import JSZip from "jszip"; 4 | import { jszip2json } from "../parser"; 5 | import PPTXComposer from ".."; 6 | 7 | import type { OutputJSON } from "../types"; 8 | 9 | const samplePPTX = path.join(__dirname, "./fixtures/sample.pptx"); 10 | const sampleMedia = path.join(__dirname, "./fixtures/sample.jpg"); 11 | const sampleZip = path.join(__dirname, "./fixtures/sample.zip"); 12 | 13 | describe("PPTX Compose", () => { 14 | test("should give valid pptx object, toJSON returns valid json", async () => { 15 | const composer = new PPTXComposer(); 16 | const json = (await composer.toJSON(samplePPTX)) as OutputJSON; 17 | 18 | expect("ppt/presentation.xml" in json).toBe(true); 19 | }); 20 | 21 | test("should give valid pptx object, call toJSON and then call toPPTX return valid pptx", async () => { 22 | const composer = new PPTXComposer(); 23 | const json = (await composer.toJSON(samplePPTX)) as OutputJSON; 24 | const pptx = await composer.toPPTX(json); 25 | 26 | expect(pptx).toEqual(expect.anything()); 27 | }); 28 | 29 | test("should give valid pptx object, call toJSON and add jpeg, then call toPPTX return valid pptx", async () => { 30 | const composer = new PPTXComposer(); 31 | const json = (await composer.toJSON(samplePPTX)) as OutputJSON; 32 | 33 | const image = fs.readFileSync(sampleMedia); 34 | json["ppt/media/image6.jpeg"] = image; 35 | 36 | const pptx = await composer.toPPTX(json); 37 | 38 | expect(pptx).toEqual(expect.anything()); 39 | }); 40 | 41 | test("should give valid zip object, jszip2json returns valid json", async () => { 42 | const buff = fs.readFileSync(sampleZip); 43 | const zip = await JSZip().loadAsync(buff); 44 | 45 | const composer = new PPTXComposer(); 46 | const json = await jszip2json(zip); 47 | 48 | expect(Object.keys(json).length).toBe(3); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/__tests__/parser.spec.ts: -------------------------------------------------------------------------------- 1 | import { jszip2json, json2jszip } from "../parser"; 2 | 3 | describe("PPTX Compose Parser", () => { 4 | test("should give valid json, json2jszip returns valid zip.", async () => { 5 | const json = { 6 | "apple.xml": { 7 | fruits: { 8 | fruit: [ 9 | { 10 | name: "apple", 11 | color: "red", 12 | }, 13 | ], 14 | }, 15 | }, 16 | }; 17 | const jszip = await json2jszip(json); 18 | const files = Object.keys(jszip.files); 19 | 20 | expect(files.length).toBe(1); 21 | expect(jszip.file("apple.xml").dir).toBe(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module pptx-compose 3 | * @fileoverview Composes Open Office XML pptx buffer to JSON and ecoding XML 4 | * 5 | * @author Shobhit Sharma 6 | */ 7 | 8 | import path from "path"; 9 | import JSZip from "jszip"; 10 | import { promises as fs, existsSync, mkdirSync } from "fs"; 11 | import { jszip2json, json2jszip } from "./parser"; 12 | 13 | import type { ComposerOptions, OutputJSON, OutputOptions } from "./types"; 14 | 15 | export interface IComposer { 16 | /** 17 | * Converts PPTX file input to JSON output 18 | */ 19 | toJSON: (file: string, options?: OutputOptions) => {}; 20 | 21 | /** 22 | * Converts JSON input to PPTX output 23 | */ 24 | toPPTX: (json: Object, options?: OutputOptions) => {}; 25 | } 26 | 27 | class Composer implements IComposer { 28 | private options: ComposerOptions = { 29 | jszipBinary: "nodebuffer", 30 | jszipGenerateType: "nodebuffer", 31 | }; 32 | 33 | constructor(options?: ComposerOptions) { 34 | if (options) { 35 | this.options = options; 36 | } 37 | } 38 | 39 | /** 40 | * @method toJSON 41 | * 42 | * Parse PowerPoint file to JSON. 43 | * 44 | * @param {string} file Give a path of PowerPoint file. 45 | * @param {OutputOptions} options 46 | * @returns {Promise} json 47 | */ 48 | public async toJSON(file: string, options?: OutputOptions) { 49 | options = Object.assign({}, this.options, options); 50 | 51 | const fileBuffer = await fs.readFile(path.resolve(__dirname, file)); 52 | const zip = await JSZip().loadAsync(fileBuffer); 53 | const jsonOutput = await jszip2json(zip, options); 54 | 55 | if (options.output) { 56 | return await fs.writeFile(options.output, JSON.stringify(jsonOutput, null, 2)); 57 | } 58 | 59 | return jsonOutput; 60 | } 61 | 62 | /** 63 | * @method toPPTX 64 | * 65 | * Convert JSON to PPTX. 66 | * 67 | * @param {OutputJSON} json created from PowerPoint XMLs 68 | * @param {OutputOptions} options 69 | * @returns {Promise} 70 | */ 71 | public async toPPTX(json: OutputJSON, options?: OutputOptions) { 72 | options = Object.assign({}, this.options, options); 73 | 74 | const zip = await json2jszip(json, options); 75 | const contentBuffer = await zip.generateAsync({ 76 | type: this.options.jszipGenerateType || "nodebuffer", 77 | }); 78 | 79 | if (options.output) { 80 | return await fs.writeFile(options.output, contentBuffer as string); 81 | } 82 | 83 | return contentBuffer; 84 | } 85 | } 86 | 87 | export default Composer; 88 | -------------------------------------------------------------------------------- /src/parser.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import xml2js from "xml2js"; 3 | import JSZip from "jszip"; 4 | 5 | import type { OutputJSON, OutputOptions } from "./types"; 6 | 7 | export const jszip2json = async (jszip: JSZip, options?: OutputOptions) => { 8 | const json: OutputJSON = {}; 9 | const jszipBinaryType = (options || {}).jszipBinary || "nodebuffer"; 10 | 11 | await Promise.all( 12 | Object.keys(jszip.files).map(async (relativePath) => { 13 | const file = jszip.file(relativePath); 14 | const ext = path.extname(relativePath); 15 | 16 | let content; 17 | if (!file || file.dir) { 18 | return; 19 | } else if (ext === ".xml" || ext === ".rels") { 20 | const xml = await file.async("binarystring"); 21 | content = await xml2js.parseStringPromise(xml); 22 | } else { 23 | // Handles media assets (image, audio, video, etc.) 24 | content = await file.async(jszipBinaryType); 25 | } 26 | 27 | json[relativePath] = content; 28 | }) 29 | ); 30 | 31 | return json; 32 | }; 33 | 34 | export const json2jszip = async (json: OutputJSON, options?: OutputOptions) => { 35 | const zip = new JSZip(); 36 | 37 | Object.keys(json).forEach((relativePath) => { 38 | const ext = path.extname(relativePath); 39 | if (ext === ".xml" || ext === ".rels") { 40 | const builder = new xml2js.Builder({ 41 | renderOpts: { 42 | pretty: false, 43 | }, 44 | }); 45 | const xml = builder.buildObject(json[relativePath]); 46 | zip.file(relativePath, xml); 47 | } else { 48 | zip.file(relativePath, json[relativePath]); 49 | } 50 | }); 51 | 52 | return zip; 53 | }; 54 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import JSZip from "jszip"; 2 | 3 | export type ComposerOptions = { 4 | jszipBinary: JSZip.OutputType; 5 | jszipGenerateType: JSZip.OutputType; 6 | }; 7 | 8 | export type OutputJSON = { 9 | [key: string]: any; 10 | }; 11 | 12 | export type OutputOptions = ComposerOptions & { 13 | output?: string; 14 | }; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true, 14 | "declarationDir": "./lib", 15 | "outDir": "./lib", 16 | "typeRoots": ["node_modules/@types", "@types"] 17 | }, 18 | "include": ["src/**.ts"], 19 | "exclude": ["node_modules", "__tests__/**/*"] 20 | } 21 | --------------------------------------------------------------------------------