2018-04-24 21:12:09 +01:00
|
|
|
import * as JSZip from "jszip";
|
|
|
|
import * as xml from "xml";
|
|
|
|
|
|
|
|
import { File } from "file";
|
|
|
|
import { Formatter } from "../formatter";
|
|
|
|
|
|
|
|
interface IXmlifyedFile {
|
|
|
|
data: string;
|
|
|
|
path: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IXmlifyedFileMapping {
|
|
|
|
Document: IXmlifyedFile;
|
|
|
|
Styles: IXmlifyedFile;
|
|
|
|
Properties: IXmlifyedFile;
|
|
|
|
Numbering: IXmlifyedFile;
|
|
|
|
Relationships: IXmlifyedFile;
|
|
|
|
FileRelationships: IXmlifyedFile;
|
2018-08-14 01:46:48 +01:00
|
|
|
Headers: IXmlifyedFile[];
|
|
|
|
Footers: IXmlifyedFile[];
|
|
|
|
HeaderRelationships: IXmlifyedFile[];
|
|
|
|
FooterRelationships: IXmlifyedFile[];
|
2018-04-24 21:12:09 +01:00
|
|
|
ContentTypes: IXmlifyedFile;
|
|
|
|
AppProperties: IXmlifyedFile;
|
2018-08-14 01:46:48 +01:00
|
|
|
FootNotes: IXmlifyedFile;
|
2018-09-20 10:11:59 -03:00
|
|
|
Settings: IXmlifyedFile;
|
2018-04-24 21:12:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export class Compiler {
|
2018-08-10 01:40:29 +01:00
|
|
|
private readonly formatter: Formatter;
|
2018-04-24 21:12:09 +01:00
|
|
|
|
2018-08-14 01:46:48 +01:00
|
|
|
constructor() {
|
2018-04-24 21:12:09 +01:00
|
|
|
this.formatter = new Formatter();
|
|
|
|
}
|
|
|
|
|
2018-08-14 01:46:48 +01:00
|
|
|
public async compile(file: File): Promise<JSZip> {
|
2018-04-24 21:12:09 +01:00
|
|
|
const zip = new JSZip();
|
|
|
|
|
2018-08-14 01:46:48 +01:00
|
|
|
const xmlifiedFileMapping = this.xmlifyFile(file);
|
2018-04-24 21:12:09 +01:00
|
|
|
|
|
|
|
for (const key in xmlifiedFileMapping) {
|
|
|
|
if (!xmlifiedFileMapping[key]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-08-14 01:46:48 +01:00
|
|
|
const obj = xmlifiedFileMapping[key] as IXmlifyedFile | IXmlifyedFile[];
|
2018-04-24 21:12:09 +01:00
|
|
|
|
2018-08-14 01:46:48 +01:00
|
|
|
if (Array.isArray(obj)) {
|
|
|
|
for (const subFile of obj) {
|
|
|
|
zip.file(subFile.path, subFile.data);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
zip.file(obj.path, obj.data);
|
|
|
|
}
|
2018-04-24 21:12:09 +01:00
|
|
|
}
|
|
|
|
|
2018-08-14 01:46:48 +01:00
|
|
|
for (const data of file.Media.Array) {
|
2018-08-10 01:40:29 +01:00
|
|
|
const mediaData = data.stream;
|
2018-04-24 22:35:31 +01:00
|
|
|
zip.file(`word/media/${data.fileName}`, mediaData);
|
|
|
|
}
|
2018-09-06 08:30:23 +01:00
|
|
|
|
|
|
|
for (const header of file.Headers) {
|
2018-09-07 21:48:59 +01:00
|
|
|
for (const data of header.Media.Array) {
|
2018-09-04 17:16:31 +03:00
|
|
|
zip.file(`word/media/${data.fileName}`, data.stream);
|
|
|
|
}
|
|
|
|
}
|
2018-09-06 08:30:23 +01:00
|
|
|
|
|
|
|
for (const footer of file.Footers) {
|
2018-09-07 21:48:59 +01:00
|
|
|
for (const data of footer.Media.Array) {
|
2018-09-04 17:16:31 +03:00
|
|
|
zip.file(`word/media/${data.fileName}`, data.stream);
|
|
|
|
}
|
|
|
|
}
|
2018-04-24 21:12:09 +01:00
|
|
|
|
|
|
|
return zip;
|
|
|
|
}
|
|
|
|
|
|
|
|
private xmlifyFile(file: File): IXmlifyedFileMapping {
|
2018-09-21 11:16:14 -03:00
|
|
|
file.verifyUpdateFields();
|
2018-04-24 21:12:09 +01:00
|
|
|
return {
|
|
|
|
Document: {
|
|
|
|
data: xml(this.formatter.format(file.Document), true),
|
|
|
|
path: "word/document.xml",
|
|
|
|
},
|
|
|
|
Styles: {
|
|
|
|
data: xml(this.formatter.format(file.Styles)),
|
|
|
|
path: "word/styles.xml",
|
|
|
|
},
|
|
|
|
Properties: {
|
|
|
|
data: xml(this.formatter.format(file.CoreProperties), {
|
|
|
|
declaration: {
|
|
|
|
standalone: "yes",
|
|
|
|
encoding: "UTF-8",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
path: "docProps/core.xml",
|
|
|
|
},
|
|
|
|
Numbering: {
|
|
|
|
data: xml(this.formatter.format(file.Numbering)),
|
|
|
|
path: "word/numbering.xml",
|
|
|
|
},
|
|
|
|
Relationships: {
|
|
|
|
data: xml(this.formatter.format(file.DocumentRelationships)),
|
|
|
|
path: "word/_rels/document.xml.rels",
|
|
|
|
},
|
|
|
|
FileRelationships: {
|
|
|
|
data: xml(this.formatter.format(file.FileRelationships)),
|
|
|
|
path: "_rels/.rels",
|
|
|
|
},
|
2018-08-14 01:46:48 +01:00
|
|
|
Headers: file.Headers.map((headerWrapper, index) => ({
|
|
|
|
data: xml(this.formatter.format(headerWrapper.Header)),
|
|
|
|
path: `word/header${index + 1}.xml`,
|
|
|
|
})),
|
|
|
|
Footers: file.Footers.map((footerWrapper, index) => ({
|
|
|
|
data: xml(this.formatter.format(footerWrapper.Footer)),
|
|
|
|
path: `word/footer${index + 1}.xml`,
|
|
|
|
})),
|
|
|
|
HeaderRelationships: file.Headers.map((headerWrapper, index) => ({
|
|
|
|
data: xml(this.formatter.format(headerWrapper.Relationships)),
|
|
|
|
path: `word/_rels/header${index + 1}.xml.rels`,
|
|
|
|
})),
|
|
|
|
FooterRelationships: file.Footers.map((footerWrapper, index) => ({
|
|
|
|
data: xml(this.formatter.format(footerWrapper.Relationships)),
|
|
|
|
path: `word/_rels/footer${index + 1}.xml.rels`,
|
|
|
|
})),
|
2018-04-24 21:12:09 +01:00
|
|
|
ContentTypes: {
|
|
|
|
data: xml(this.formatter.format(file.ContentTypes)),
|
|
|
|
path: "[Content_Types].xml",
|
|
|
|
},
|
|
|
|
AppProperties: {
|
|
|
|
data: xml(this.formatter.format(file.AppProperties)),
|
|
|
|
path: "docProps/app.xml",
|
|
|
|
},
|
2018-08-14 01:46:48 +01:00
|
|
|
FootNotes: {
|
|
|
|
data: xml(this.formatter.format(file.FootNotes)),
|
|
|
|
path: "word/footnotes.xml",
|
|
|
|
},
|
2018-09-20 10:11:59 -03:00
|
|
|
Settings: {
|
|
|
|
data: xml(this.formatter.format(file.Settings)),
|
|
|
|
path: "word/settings.xml",
|
|
|
|
},
|
2018-04-24 21:12:09 +01:00
|
|
|
};
|
|
|
|
}
|
2018-09-04 17:16:31 +03:00
|
|
|
|
|
|
|
/* By default docx collapse empty tags. <a></a> -> <a/>. this function mimic it
|
2018-09-06 08:30:23 +01:00
|
|
|
so comparing (diff) original docx file and the library output is easier
|
|
|
|
Currently not used, so commenting out */
|
|
|
|
// private collapseEmptyTags(xmlData: string): string {
|
|
|
|
// const regEx = /<(([^ <>]+)[^<>]*)><\/\2>/g;
|
|
|
|
// const collapsed = xmlData.replace(regEx, "<$1/>");
|
|
|
|
// return collapsed;
|
|
|
|
// }
|
2018-04-24 21:12:09 +01:00
|
|
|
}
|