Merge branch 'master' into feat/browser-packer
# Conflicts: # demo/demo13.js # package.json # src/export/packer/compiler.ts # src/file/media/data.ts # src/file/media/media.ts
This commit is contained in:
28
src/export/packer/buffer-stream.ts
Normal file
28
src/export/packer/buffer-stream.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Writable } from "stream";
|
||||
|
||||
export class BufferStream extends Writable {
|
||||
private readonly data: Buffer[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
public _write(chunk: any, _: string, next: (err?: Error) => void): void {
|
||||
this.data.push(Buffer.from(chunk));
|
||||
next();
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:ban-types
|
||||
public end(cb?: Function): void {
|
||||
super.end(cb);
|
||||
|
||||
this.emit("close");
|
||||
}
|
||||
|
||||
public get Buffer(): Buffer {
|
||||
return Buffer.concat(this.data);
|
||||
}
|
||||
}
|
44
src/export/packer/buffer.spec.ts
Normal file
44
src/export/packer/buffer.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/* tslint:disable:typedef space-before-function-paren */
|
||||
import { assert } from "chai";
|
||||
import { stub } from "sinon";
|
||||
|
||||
import { BufferPacker } from "../../export/packer/buffer";
|
||||
import { File, Paragraph } from "../../file";
|
||||
|
||||
describe("BufferPacker", () => {
|
||||
let packer: BufferPacker;
|
||||
|
||||
beforeEach(() => {
|
||||
const file = new File({
|
||||
creator: "Dolan Miu",
|
||||
revision: "1",
|
||||
lastModifiedBy: "Dolan Miu",
|
||||
});
|
||||
const paragraph = new Paragraph("test text");
|
||||
const heading = new Paragraph("Hello world").heading1();
|
||||
file.addParagraph(new Paragraph("title").title());
|
||||
file.addParagraph(heading);
|
||||
file.addParagraph(new Paragraph("heading 2").heading2());
|
||||
file.addParagraph(paragraph);
|
||||
|
||||
packer = new BufferPacker(file);
|
||||
});
|
||||
|
||||
describe("#pack()", () => {
|
||||
it("should create a standard docx file", async function() {
|
||||
this.timeout(99999999);
|
||||
const buffer = await packer.pack();
|
||||
assert.isDefined(buffer);
|
||||
assert.isTrue(buffer.byteLength > 0);
|
||||
});
|
||||
|
||||
it("should handle exception if it throws any", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
const compiler = stub((packer as any).packer, "compile");
|
||||
compiler.throwsException();
|
||||
return packer.pack().catch((error) => {
|
||||
assert.isDefined(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
20
src/export/packer/buffer.ts
Normal file
20
src/export/packer/buffer.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { File } from "../../file";
|
||||
import { BufferStream } from "./buffer-stream";
|
||||
import { Compiler } from "./compiler";
|
||||
import { IPacker } from "./packer";
|
||||
|
||||
export class BufferPacker implements IPacker {
|
||||
private readonly packer: Compiler;
|
||||
|
||||
constructor(file: File) {
|
||||
this.packer = new Compiler(file);
|
||||
}
|
||||
|
||||
public async pack(): Promise<Buffer> {
|
||||
const stream = new BufferStream();
|
||||
|
||||
await this.packer.compile(stream);
|
||||
|
||||
return stream.Buffer;
|
||||
}
|
||||
}
|
76
src/export/packer/compiler.spec.ts
Normal file
76
src/export/packer/compiler.spec.ts
Normal file
@ -0,0 +1,76 @@
|
||||
/* tslint:disable:typedef space-before-function-paren */
|
||||
import * as fs from "fs";
|
||||
import * as JSZip from "jszip";
|
||||
|
||||
import { expect } from "chai";
|
||||
import { File } from "../../file";
|
||||
import { Compiler } from "./compiler";
|
||||
|
||||
describe("Compiler", () => {
|
||||
let compiler: Compiler;
|
||||
let file: File;
|
||||
|
||||
beforeEach(() => {
|
||||
file = new File();
|
||||
compiler = new Compiler(file);
|
||||
});
|
||||
|
||||
describe("#compile()", () => {
|
||||
it("should pack all the content", async function() {
|
||||
this.timeout(99999999);
|
||||
const fileName = "build/tests/test.docx";
|
||||
await compiler.compile(fs.createWriteStream(fileName));
|
||||
|
||||
const docxFile = fs.readFileSync(fileName);
|
||||
const zipFile: JSZip = await JSZip.loadAsync(docxFile);
|
||||
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
|
||||
|
||||
expect(fileNames).is.an.instanceof(Array);
|
||||
expect(fileNames).has.length(13);
|
||||
expect(fileNames).to.include("word/document.xml");
|
||||
expect(fileNames).to.include("word/styles.xml");
|
||||
expect(fileNames).to.include("docProps/core.xml");
|
||||
expect(fileNames).to.include("docProps/app.xml");
|
||||
expect(fileNames).to.include("word/numbering.xml");
|
||||
expect(fileNames).to.include("word/header1.xml");
|
||||
expect(fileNames).to.include("word/_rels/header1.xml.rels");
|
||||
expect(fileNames).to.include("word/footer1.xml");
|
||||
expect(fileNames).to.include("word/footnotes.xml");
|
||||
expect(fileNames).to.include("word/_rels/footer1.xml.rels");
|
||||
expect(fileNames).to.include("word/_rels/document.xml.rels");
|
||||
expect(fileNames).to.include("[Content_Types].xml");
|
||||
expect(fileNames).to.include("_rels/.rels");
|
||||
});
|
||||
|
||||
it("should pack all additional headers and footers", async function() {
|
||||
file.createFooter();
|
||||
file.createFooter();
|
||||
file.createHeader();
|
||||
file.createHeader();
|
||||
|
||||
this.timeout(99999999);
|
||||
const fileName = "build/tests/test2.docx";
|
||||
await compiler.compile(fs.createWriteStream(fileName));
|
||||
|
||||
const docxFile = fs.readFileSync(fileName);
|
||||
const zipFile: JSZip = await JSZip.loadAsync(docxFile);
|
||||
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
|
||||
|
||||
expect(fileNames).is.an.instanceof(Array);
|
||||
expect(fileNames).has.length(21);
|
||||
|
||||
expect(fileNames).to.include("word/header1.xml");
|
||||
expect(fileNames).to.include("word/_rels/header1.xml.rels");
|
||||
expect(fileNames).to.include("word/header2.xml");
|
||||
expect(fileNames).to.include("word/_rels/header2.xml.rels");
|
||||
expect(fileNames).to.include("word/header3.xml");
|
||||
expect(fileNames).to.include("word/_rels/header3.xml.rels");
|
||||
expect(fileNames).to.include("word/footer1.xml");
|
||||
expect(fileNames).to.include("word/_rels/footer1.xml.rels");
|
||||
expect(fileNames).to.include("word/footer2.xml");
|
||||
expect(fileNames).to.include("word/_rels/footer2.xml.rels");
|
||||
expect(fileNames).to.include("word/footer3.xml");
|
||||
expect(fileNames).to.include("word/_rels/footer3.xml.rels");
|
||||
});
|
||||
});
|
||||
});
|
@ -1,6 +1,5 @@
|
||||
import * as archiver from "archiver";
|
||||
import * as express from "express";
|
||||
import * as fs from "fs";
|
||||
import { Writable } from "stream";
|
||||
import * as xml from "xml";
|
||||
|
||||
@ -9,9 +8,9 @@ import { Formatter } from "../formatter";
|
||||
|
||||
export class Compiler {
|
||||
protected archive: archiver.Archiver;
|
||||
private formatter: Formatter;
|
||||
private readonly formatter: Formatter;
|
||||
|
||||
constructor(private file: File) {
|
||||
constructor(private readonly file: File) {
|
||||
this.formatter = new Formatter();
|
||||
this.archive = archiver.create("zip", {});
|
||||
|
||||
@ -23,7 +22,7 @@ export class Compiler {
|
||||
public async compile(output: Writable | express.Response): Promise<void> {
|
||||
this.archive.pipe(output);
|
||||
|
||||
const xmlDocument = xml(this.formatter.format(this.file.Document), true);
|
||||
const xmlDocument = xml(this.formatter.format(this.file.Document));
|
||||
const xmlStyles = xml(this.formatter.format(this.file.Styles));
|
||||
const xmlProperties = xml(this.formatter.format(this.file.CoreProperties), {
|
||||
declaration: {
|
||||
@ -34,12 +33,9 @@ export class Compiler {
|
||||
const xmlNumbering = xml(this.formatter.format(this.file.Numbering));
|
||||
const xmlRelationships = xml(this.formatter.format(this.file.DocumentRelationships));
|
||||
const xmlFileRelationships = xml(this.formatter.format(this.file.FileRelationships));
|
||||
const xmlHeader = xml(this.formatter.format(this.file.Header.Header));
|
||||
const xmlFooter = xml(this.formatter.format(this.file.Footer.Footer));
|
||||
const xmlHeaderRelationships = xml(this.formatter.format(this.file.Header.Relationships));
|
||||
const xmlFooterRelationships = xml(this.formatter.format(this.file.Footer.Relationships));
|
||||
const xmlContentTypes = xml(this.formatter.format(this.file.ContentTypes));
|
||||
const xmlAppProperties = xml(this.formatter.format(this.file.AppProperties));
|
||||
const xmlFootnotes = xml(this.formatter.format(this.file.FootNotes));
|
||||
|
||||
this.archive.append(xmlDocument, {
|
||||
name: "word/document.xml",
|
||||
@ -61,26 +57,38 @@ export class Compiler {
|
||||
name: "word/numbering.xml",
|
||||
});
|
||||
|
||||
this.archive.append(xmlHeader, {
|
||||
name: "word/header1.xml",
|
||||
});
|
||||
// headers
|
||||
for (let i = 0; i < this.file.Headers.length; i++) {
|
||||
const element = this.file.Headers[i];
|
||||
this.archive.append(xml(this.formatter.format(element.Header)), {
|
||||
name: `word/header${i + 1}.xml`,
|
||||
});
|
||||
|
||||
this.archive.append(xmlFooter, {
|
||||
name: "word/footer1.xml",
|
||||
this.archive.append(xml(this.formatter.format(element.Relationships)), {
|
||||
name: `word/_rels/header${i + 1}.xml.rels`,
|
||||
});
|
||||
}
|
||||
|
||||
// footers
|
||||
for (let i = 0; i < this.file.Footers.length; i++) {
|
||||
const element = this.file.Footers[i];
|
||||
this.archive.append(xml(this.formatter.format(element.Footer)), {
|
||||
name: `word/footer${i + 1}.xml`,
|
||||
});
|
||||
|
||||
this.archive.append(xml(this.formatter.format(element.Relationships)), {
|
||||
name: `word/_rels/footer${i + 1}.xml.rels`,
|
||||
});
|
||||
}
|
||||
|
||||
this.archive.append(xmlFootnotes, {
|
||||
name: "word/footnotes.xml",
|
||||
});
|
||||
|
||||
this.archive.append(xmlRelationships, {
|
||||
name: "word/_rels/document.xml.rels",
|
||||
});
|
||||
|
||||
this.archive.append(xmlHeaderRelationships, {
|
||||
name: "word/_rels/header1.xml.rels",
|
||||
});
|
||||
|
||||
this.archive.append(xmlFooterRelationships, {
|
||||
name: "word/_rels/footer1.xml.rels",
|
||||
});
|
||||
|
||||
this.archive.append(xmlContentTypes, {
|
||||
name: "[Content_Types].xml",
|
||||
});
|
||||
@ -89,8 +97,8 @@ export class Compiler {
|
||||
name: "_rels/.rels",
|
||||
});
|
||||
|
||||
for (const data of this.file.Media.array) {
|
||||
this.archive.append(fs.createReadStream(data.path), {
|
||||
for (const data of this.file.Media.Array) {
|
||||
this.archive.append(data.stream, {
|
||||
name: `word/media/${data.fileName}`,
|
||||
});
|
||||
}
|
||||
|
43
src/export/packer/express.spec.ts
Normal file
43
src/export/packer/express.spec.ts
Normal file
@ -0,0 +1,43 @@
|
||||
// tslint:disable:typedef space-before-function-paren
|
||||
// tslint:disable:no-empty
|
||||
// tslint:disable:no-any
|
||||
import { assert } from "chai";
|
||||
import { stub } from "sinon";
|
||||
|
||||
import { ExpressPacker } from "../../export/packer/express";
|
||||
import { File, Paragraph } from "../../file";
|
||||
|
||||
describe("LocalPacker", () => {
|
||||
let packer: ExpressPacker;
|
||||
|
||||
beforeEach(() => {
|
||||
const file = new File({
|
||||
creator: "Dolan Miu",
|
||||
revision: "1",
|
||||
lastModifiedBy: "Dolan Miu",
|
||||
});
|
||||
const paragraph = new Paragraph("test text");
|
||||
const heading = new Paragraph("Hello world").heading1();
|
||||
file.addParagraph(new Paragraph("title").title());
|
||||
file.addParagraph(heading);
|
||||
file.addParagraph(new Paragraph("heading 2").heading2());
|
||||
file.addParagraph(paragraph);
|
||||
|
||||
const expressResMock = {
|
||||
on: () => {},
|
||||
attachment: () => {},
|
||||
};
|
||||
|
||||
packer = new ExpressPacker(file, expressResMock as any);
|
||||
});
|
||||
|
||||
describe("#pack()", () => {
|
||||
it("should handle exception if it throws any", () => {
|
||||
const compiler = stub((packer as any).packer, "compile");
|
||||
compiler.throwsException();
|
||||
return packer.pack("build/tests/test").catch((error) => {
|
||||
assert.isDefined(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -4,6 +4,9 @@ import { File } from "file";
|
||||
import { Compiler } from "./compiler";
|
||||
import { IPacker } from "./packer";
|
||||
|
||||
/**
|
||||
* @deprecated ExpressPacker is now deprecated. Please use the StreamPacker instead and pipe that to `express`' `res` object
|
||||
*/
|
||||
export class ExpressPacker implements IPacker {
|
||||
private readonly packer: Compiler;
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* tslint:disable:typedef space-before-function-paren */
|
||||
import { assert } from "chai";
|
||||
import * as fs from "fs";
|
||||
import { stub } from "sinon";
|
||||
|
||||
import { LocalPacker } from "../../export/packer/local";
|
||||
import { File, Paragraph } from "../../file";
|
||||
@ -29,14 +31,36 @@ describe("LocalPacker", () => {
|
||||
await packer.pack("build/tests/test");
|
||||
fs.statSync("build/tests/test.docx");
|
||||
});
|
||||
|
||||
it("should handle exception if it throws any", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
const compiler = stub((packer as any).packer, "compile");
|
||||
compiler.throwsException();
|
||||
return packer.pack("build/tests/test").catch((error) => {
|
||||
assert.isDefined(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#packPdf", () => {
|
||||
it("should create a standard PDF file", async function() {
|
||||
this.timeout(99999999);
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
const pdfConverterConvert = stub((packer as any).pdfConverter, "convert");
|
||||
pdfConverterConvert.returns("Test PDF Contents");
|
||||
|
||||
await packer.packPdf("build/tests/pdf-test");
|
||||
fs.statSync("build/tests/pdf-test.pdf");
|
||||
});
|
||||
|
||||
it("should handle exception if it throws any", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
const compiler = stub((packer as any).packer, "compile");
|
||||
compiler.throwsException();
|
||||
return packer.packPdf("build/tests/pdf-test").catch((error) => {
|
||||
assert.isDefined(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ export class LocalPacker implements IPacker {
|
||||
filePath = filePath.replace(/.docx$/, "");
|
||||
|
||||
const zip = await this.packer.compile();
|
||||
const zipData = await zip.generateAsync({ type: "base64" }) as string;
|
||||
const zipData = (await zip.generateAsync({ type: "base64" })) as string;
|
||||
|
||||
await this.writeToFile(`${filePath}.docx`, zipData);
|
||||
}
|
||||
@ -32,7 +32,7 @@ export class LocalPacker implements IPacker {
|
||||
const tempPath = path.join(os.tmpdir(), `${fileName}.docx`);
|
||||
|
||||
const zip = await this.packer.compile();
|
||||
const zipData = await zip.generateAsync({ type: "base64" }) as string;
|
||||
const zipData = (await zip.generateAsync({ type: "base64" })) as string;
|
||||
await this.writeToFile(tempPath, zipData);
|
||||
|
||||
const text = await this.pdfConverter.convert(tempPath);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import * as fs from "fs";
|
||||
import * as JSZip from "jszip";
|
||||
import * as xml from "xml";
|
||||
|
||||
@ -26,9 +25,9 @@ interface IXmlifyedFileMapping {
|
||||
}
|
||||
|
||||
export class Compiler {
|
||||
private formatter: Formatter;
|
||||
private readonly formatter: Formatter;
|
||||
|
||||
constructor(private file: File) {
|
||||
constructor(private readonly file: File) {
|
||||
this.formatter = new Formatter();
|
||||
}
|
||||
|
||||
@ -47,8 +46,8 @@ export class Compiler {
|
||||
zip.file(xmlifiedFile.path, xmlifiedFile.data);
|
||||
}
|
||||
|
||||
for (const data of this.file.Media.array) {
|
||||
const mediaData = await this.readFile(data.path);
|
||||
for (const data of this.file.Media.Array) {
|
||||
const mediaData = data.stream;
|
||||
zip.file(`word/media/${data.fileName}`, mediaData);
|
||||
}
|
||||
|
||||
@ -112,17 +111,4 @@ export class Compiler {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private readFile(path: string): Promise<Buffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, (err, data) => {
|
||||
if (err) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,7 @@ export interface IPacker {
|
||||
}
|
||||
|
||||
// Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export const WORKAROUND = "";
|
||||
|
@ -1,5 +1,3 @@
|
||||
/* tslint:disable:object-literal-key-quotes */
|
||||
// This tslint disable is needed, or it simply won't work
|
||||
import * as fs from "fs";
|
||||
import * as request from "request-promise";
|
||||
|
||||
@ -11,6 +9,7 @@ export class PdfConvertWrapper {
|
||||
public convert(filePath: string): request.RequestPromise {
|
||||
return request.post({
|
||||
url: "http://mirror1.convertonlinefree.com",
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
encoding: null,
|
||||
headers: {
|
||||
"User-Agent":
|
||||
|
Reference in New Issue
Block a user