Files
docx-js/src/export/packer/next-compiler.ts

193 lines
7.4 KiB
TypeScript

import * as JSZip from "jszip";
import * as xml from "xml";
import { File } from "file";
import { Formatter } from "../formatter";
import { ImageReplacer } from "./image-replacer";
interface IXmlifyedFile {
readonly data: string;
readonly path: string;
}
interface IXmlifyedFileMapping {
readonly Document: IXmlifyedFile;
readonly Styles: IXmlifyedFile;
readonly Properties: IXmlifyedFile;
readonly Numbering: IXmlifyedFile;
readonly Relationships: IXmlifyedFile;
readonly FileRelationships: IXmlifyedFile;
readonly Headers: IXmlifyedFile[];
readonly Footers: IXmlifyedFile[];
readonly HeaderRelationships: IXmlifyedFile[];
readonly FooterRelationships: IXmlifyedFile[];
readonly ContentTypes: IXmlifyedFile;
readonly AppProperties: IXmlifyedFile;
readonly FootNotes: IXmlifyedFile;
readonly Settings: IXmlifyedFile;
}
export class Compiler {
private readonly formatter: Formatter;
private readonly imageReplacer: ImageReplacer;
constructor() {
this.formatter = new Formatter();
this.imageReplacer = new ImageReplacer();
}
public compile(file: File, prettifyXml?: boolean): JSZip {
const zip = new JSZip();
const xmlifiedFileMapping = this.xmlifyFile(file, prettifyXml);
for (const key in xmlifiedFileMapping) {
if (!xmlifiedFileMapping[key]) {
continue;
}
const obj = xmlifiedFileMapping[key] as IXmlifyedFile | IXmlifyedFile[];
if (Array.isArray(obj)) {
for (const subFile of obj) {
zip.file(subFile.path, subFile.data);
}
} else {
zip.file(obj.path, obj.data);
}
}
for (const data of file.Media.Array) {
const mediaData = data.stream;
zip.file(`word/media/${data.fileName}`, mediaData);
}
return zip;
}
private xmlifyFile(file: File, prettify?: boolean): IXmlifyedFileMapping {
file.verifyUpdateFields();
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
const documentXmlData = xml(this.formatter.format(file.Document), prettify);
const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media);
return {
Relationships: {
data: (() => {
documentMediaDatas.forEach((mediaData, i) => {
file.DocumentRelationships.createRelationship(
documentRelationshipCount + i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
});
return xml(this.formatter.format(file.DocumentRelationships), prettify);
})(),
path: "word/_rels/document.xml.rels",
},
Document: {
data: (() => {
const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount);
return xmlData;
})(),
path: "word/document.xml",
},
Styles: {
data: xml(this.formatter.format(file.Styles), prettify),
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), prettify),
path: "word/numbering.xml",
},
FileRelationships: {
data: xml(this.formatter.format(file.FileRelationships), prettify),
path: "_rels/.rels",
},
HeaderRelationships: file.Headers.map((headerWrapper, index) => {
const xmlData = xml(this.formatter.format(headerWrapper.Header), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
headerWrapper.Relationships.createRelationship(
i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
});
return {
data: xml(this.formatter.format(headerWrapper.Relationships), prettify),
path: `word/_rels/header${index + 1}.xml.rels`,
};
}),
FooterRelationships: file.Footers.map((footerWrapper, index) => {
const xmlData = xml(this.formatter.format(footerWrapper.Footer), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
footerWrapper.Relationships.createRelationship(
i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
});
return {
data: xml(this.formatter.format(footerWrapper.Relationships), prettify),
path: `word/_rels/footer${index + 1}.xml.rels`,
};
}),
Headers: file.Headers.map((headerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(headerWrapper.Header), prettify);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
return {
data: xmlData,
path: `word/header${index + 1}.xml`,
};
}),
Footers: file.Footers.map((footerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(footerWrapper.Footer), prettify);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
return {
data: xmlData,
path: `word/footer${index + 1}.xml`,
};
}),
ContentTypes: {
data: xml(this.formatter.format(file.ContentTypes), prettify),
path: "[Content_Types].xml",
},
AppProperties: {
data: xml(this.formatter.format(file.AppProperties), prettify),
path: "docProps/app.xml",
},
FootNotes: {
data: xml(this.formatter.format(file.FootNotes), prettify),
path: "word/footnotes.xml",
},
Settings: {
data: xml(this.formatter.format(file.Settings), prettify),
path: "word/settings.xml",
},
};
}
}