Merge pull request #179 from dolanmiu/feat/import-dotx

Feat/import dotx
This commit is contained in:
Dolan
2018-10-23 00:47:18 +01:00
committed by GitHub
40 changed files with 871 additions and 275 deletions

24
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,24 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "typescript",
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": [
"$tsc-watch"
]
},
{
"type": "npm",
"script": "ts-node",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -15,8 +15,12 @@ const footer = doc.createFooter();
footer.createParagraph("Footer on another page"); footer.createParagraph("Footer on another page");
doc.addSection({ doc.addSection({
headerId: header.Header.ReferenceId, headers: {
footerId: footer.Footer.ReferenceId, default: header,
},
footers: {
default: footer,
},
pageNumberStart: 1, pageNumberStart: 1,
pageNumberFormatType: PageNumberFormat.DECIMAL, pageNumberFormatType: PageNumberFormat.DECIMAL,
}); });
@ -24,8 +28,12 @@ doc.addSection({
doc.createParagraph("hello"); doc.createParagraph("hello");
doc.addSection({ doc.addSection({
headerId: header.Header.ReferenceId, headers: {
footerId: footer.Footer.ReferenceId, default: header,
},
footers: {
default: footer,
},
pageNumberStart: 1, pageNumberStart: 1,
pageNumberFormatType: PageNumberFormat.DECIMAL, pageNumberFormatType: PageNumberFormat.DECIMAL,
orientation: PageOrientation.LANDSCAPE, orientation: PageOrientation.LANDSCAPE,

View File

@ -8,11 +8,11 @@ const doc = new Document();
const paragraph = new Paragraph("Hello World"); const paragraph = new Paragraph("Hello World");
doc.addParagraph(paragraph); doc.addParagraph(paragraph);
const image = Media.addImage(doc, "./demo/images/image1.jpeg"); const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
const image2 = Media.addImage(doc, "./demo/images/dog.png"); const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/dog.png"));
const image3 = Media.addImage(doc, "./demo/images/cat.jpg"); const image3 = Media.addImage(doc, fs.readFileSync("./demo/images/cat.jpg"));
const image4 = Media.addImage(doc, "./demo/images/parrots.bmp"); const image4 = Media.addImage(doc, fs.readFileSync("./demo/images/parrots.bmp"));
const image5 = Media.addImage(doc, "./demo/images/pizza.gif"); const image5 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
const imageBase64Data = `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`; const imageBase64Data = `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`;
const image6 = Media.addImage(doc, Buffer.from(imageBase64Data, "base64"), 100, 100); const image6 = Media.addImage(doc, Buffer.from(imageBase64Data, "base64"), 100, 100);

View File

@ -8,7 +8,7 @@ const doc = new Document();
const table = doc.createTable(4, 4); const table = doc.createTable(4, 4);
table.getCell(2, 2).addContent(new Paragraph("Hello")); table.getCell(2, 2).addContent(new Paragraph("Hello"));
const image = Media.addImage(doc, "./demo/images/image1.jpeg"); const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
table.getCell(1, 1).addContent(image.Paragraph); table.getCell(1, 1).addContent(image.Paragraph);
const packer = new Packer(); const packer = new Packer();

View File

@ -30,5 +30,4 @@ const packer = new Packer();
packer.toBuffer(doc).then((buffer) => { packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer); fs.writeFileSync("My Document.docx", buffer);
console.log("Document created successfully at project root!");
}); });

29
demo/demo30.ts Normal file
View File

@ -0,0 +1,29 @@
import * as fs from "fs";
import { Document, ImportDotx, Packer, Paragraph } from "../build";
const importDotx = new ImportDotx();
const filePath = "./demo/dotx/template.dotx";
fs.readFile(filePath, (err, data) => {
if (err) {
throw new Error(`Failed to read file ${filePath}.`);
}
importDotx.extract(data).then((templateDocument) => {
// This any needs fixing
const sectionProps = {
titlePage: templateDocument.titlePageIsDefined,
} as any;
const doc = new Document(undefined, sectionProps, {
template: templateDocument,
});
const paragraph = new Paragraph("Hello World");
doc.addParagraph(paragraph);
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});
});
});

BIN
demo/dotx/template.dotx Normal file

Binary file not shown.

View File

@ -48,7 +48,7 @@
"types": "./build/index.d.ts", "types": "./build/index.d.ts",
"dependencies": { "dependencies": {
"@types/image-size": "0.0.29", "@types/image-size": "0.0.29",
"@types/jszip": "^3.1.3", "@types/jszip": "^3.1.4",
"fast-xml-parser": "^3.3.6", "fast-xml-parser": "^3.3.6",
"image-size": "^0.6.2", "image-size": "^0.6.2",
"jszip": "^3.1.5", "jszip": "^3.1.5",

View File

@ -2,6 +2,12 @@ import { BaseXmlComponent, IXmlableObject } from "file/xml-components";
export class Formatter { export class Formatter {
public format(input: BaseXmlComponent): IXmlableObject { public format(input: BaseXmlComponent): IXmlableObject {
return input.prepForXml(); const output = input.prepForXml();
if (output) {
return output;
} else {
throw Error("XMLComponent did not format correctly");
}
} }
} }

View File

@ -59,6 +59,18 @@ export class Compiler {
zip.file(`word/media/${data.fileName}`, mediaData); zip.file(`word/media/${data.fileName}`, mediaData);
} }
for (const header of file.Headers) {
for (const data of header.Media.Array) {
zip.file(`word/media/${data.fileName}`, data.stream);
}
}
for (const footer of file.Footers) {
for (const data of footer.Media.Array) {
zip.file(`word/media/${data.fileName}`, data.stream);
}
}
return zip; return zip;
} }
@ -128,4 +140,13 @@ export class Compiler {
}, },
}; };
} }
/* By default docx collapse empty tags. <a></a> -> <a/>. this function mimic it
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;
// }
} }

View File

@ -16,7 +16,7 @@ describe("Body", () => {
expect(formatted) expect(formatted)
.to.have.property("w:sectPr") .to.have.property("w:sectPr")
.and.to.be.an.instanceof(Array); .and.to.be.an.instanceof(Array);
expect(formatted["w:sectPr"]).to.have.length(7); expect(formatted["w:sectPr"]).to.have.length(5);
}); });
}); });

View File

@ -35,7 +35,7 @@ export class Body extends XmlComponent {
this.sections.push(new SectionProperties(params)); this.sections.push(new SectionProperties(params));
} }
} }
public prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
if (this.sections.length === 1) { if (this.sections.length === 1) {
this.root.push(this.sections[0]); this.root.push(this.sections[0]);
} else if (this.sections.length > 1) { } else if (this.sections.length > 1) {

View File

@ -8,9 +8,7 @@ describe("PageBorders", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create empty element when no options are passed", () => { it("should create empty element when no options are passed", () => {
const properties = new PageBorders(); const properties = new PageBorders();
const tree = new Formatter().format(properties); expect(() => new Formatter().format(properties)).to.throw();
expect(tree).to.equal("");
}); });
it("should create page borders with some configuration", () => { it("should create page borders with some configuration", () => {

View File

@ -98,7 +98,9 @@ export class PageBorders extends XmlComponent {
} }
} }
public prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
return this.root.length > 0 ? super.prepForXml() : ""; if (this.root.length > 0) {
return super.prepForXml();
}
} }
} }

View File

@ -1,12 +1,17 @@
import { expect } from "chai"; import { expect } from "chai";
import { Formatter } from "../../../../export/formatter"; import { Formatter } from "../../../../export/formatter";
import { FooterReferenceType, PageBorderOffsetFrom, PageNumberFormat } from "./"; import { FooterWrapper } from "../../../footer-wrapper";
import { HeaderWrapper } from "../../../header-wrapper";
import { Media } from "../../../media";
import { PageBorderOffsetFrom, PageNumberFormat } from "./";
import { SectionProperties } from "./section-properties"; import { SectionProperties } from "./section-properties";
describe("SectionProperties", () => { describe("SectionProperties", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create section properties with options", () => { it("should create section properties with options", () => {
const media = new Media();
const properties = new SectionProperties({ const properties = new SectionProperties({
width: 11906, width: 11906,
height: 16838, height: 16838,
@ -20,9 +25,12 @@ describe("SectionProperties", () => {
mirror: false, mirror: false,
space: 708, space: 708,
linePitch: 360, linePitch: 360,
headerId: 100, headers: {
footerId: 200, default: new HeaderWrapper(media, 100),
footerType: FooterReferenceType.EVEN, },
footers: {
even: new FooterWrapper(media, 200),
},
pageNumberStart: 10, pageNumberStart: 10,
pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT, pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT,
}); });
@ -78,9 +86,7 @@ describe("SectionProperties", () => {
}); });
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": [{ _attr: { "w:space": 708 } }] }); expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": [{ _attr: { "w:space": 708 } }] });
expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] }); expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] });
expect(tree["w:sectPr"][4]).to.deep.equal({ "w:headerReference": [{ _attr: { "r:id": "rId0", "w:type": "default" } }] }); expect(tree["w:sectPr"][4]).to.deep.equal({ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] });
expect(tree["w:sectPr"][5]).to.deep.equal({ "w:footerReference": [{ _attr: { "r:id": "rId0", "w:type": "default" } }] });
expect(tree["w:sectPr"][6]).to.deep.equal({ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] });
}); });
it("should create section properties with changed options", () => { it("should create section properties with changed options", () => {
@ -170,7 +176,8 @@ describe("SectionProperties", () => {
}); });
const tree = new Formatter().format(properties); const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);
expect(tree["w:sectPr"][7]).to.deep.equal({ const pgBorders = tree["w:sectPr"].find((item) => item["w:pgBorders"] !== undefined);
expect(pgBorders).to.deep.equal({
"w:pgBorders": [{ _attr: { "w:offsetFrom": "page" } }], "w:pgBorders": [{ _attr: { "w:offsetFrom": "page" } }],
}); });
}); });

View File

@ -1,115 +1,170 @@
// http://officeopenxml.com/WPsection.php // http://officeopenxml.com/WPsection.php
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { FooterReferenceType, IPageBordersOptions, IPageNumberTypeAttributes, PageBorders, PageNumberFormat, PageNumberType } from "./"; import { FooterWrapper } from "../../../footer-wrapper";
import { HeaderWrapper } from "../../../header-wrapper";
import { IPageBordersOptions, IPageNumberTypeAttributes, PageBorders, PageNumberFormat, PageNumberType } from "./";
import { Columns } from "./columns/columns"; import { Columns } from "./columns/columns";
import { IColumnsAttributes } from "./columns/columns-attributes"; import { IColumnsAttributes } from "./columns/columns-attributes";
import { DocumentGrid } from "./doc-grid/doc-grid"; import { DocumentGrid } from "./doc-grid/doc-grid";
import { IDocGridAttributesProperties } from "./doc-grid/doc-grid-attributes"; import { IDocGridAttributesProperties } from "./doc-grid/doc-grid-attributes";
import { FooterReference, IFooterOptions } from "./footer-reference/footer-reference"; import { FooterReferenceType } from "./footer-reference";
import { HeaderReference, IHeaderOptions } from "./header-reference/header-reference"; import { FooterReference } from "./footer-reference/footer-reference";
import { HeaderReferenceType } from "./header-reference/header-reference-attributes"; import { HeaderReferenceType } from "./header-reference";
import { HeaderReference } from "./header-reference/header-reference";
import { PageMargin } from "./page-margin/page-margin"; import { PageMargin } from "./page-margin/page-margin";
import { IPageMarginAttributes } from "./page-margin/page-margin-attributes"; import { IPageMarginAttributes } from "./page-margin/page-margin-attributes";
import { PageSize } from "./page-size/page-size"; import { PageSize } from "./page-size/page-size";
import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes"; import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes";
import { TitlePage } from "./title-page/title-page";
export interface IHeaderFooterGroup<T> {
default?: T;
first?: T;
even?: T;
}
interface IHeadersOptions {
headers?: IHeaderFooterGroup<HeaderWrapper>;
}
interface IFootersOptions {
footers?: IHeaderFooterGroup<FooterWrapper>;
}
interface ITitlePageOptions {
titlePage?: boolean;
}
export type SectionPropertiesOptions = IPageSizeAttributes & export type SectionPropertiesOptions = IPageSizeAttributes &
IPageMarginAttributes & IPageMarginAttributes &
IColumnsAttributes & IColumnsAttributes &
IDocGridAttributesProperties & IDocGridAttributesProperties &
IHeaderOptions & IHeadersOptions &
IFooterOptions & IFootersOptions &
IPageNumberTypeAttributes & IPageNumberTypeAttributes &
IPageBordersOptions; IPageBordersOptions &
ITitlePageOptions;
export class SectionProperties extends XmlComponent { export class SectionProperties extends XmlComponent {
private readonly options: SectionPropertiesOptions; private readonly options: SectionPropertiesOptions;
constructor(options?: SectionPropertiesOptions) { constructor(options: SectionPropertiesOptions = {}) {
super("w:sectPr"); super("w:sectPr");
const defaultOptions = { const {
width: 11906, width = 11906,
height: 16838, height = 16838,
top: 1440, top = 1440,
right: 1440, right = 1440,
bottom: 1440, bottom = 1440,
left: 1440, left = 1440,
header: 708, header = 708,
footer: 708, footer = 708,
gutter: 0, gutter = 0,
mirror: false, mirror = false,
space: 708, space = 708,
linePitch: 360, linePitch = 360,
orientation: PageOrientation.PORTRAIT, orientation = PageOrientation.PORTRAIT,
headerType: HeaderReferenceType.DEFAULT, headers,
headerId: 0, footers,
footerType: FooterReferenceType.DEFAULT, pageNumberFormatType = PageNumberFormat.DECIMAL,
footerId: 0, pageNumberStart,
pageNumberStart: undefined, pageBorders,
pageNumberFormatType: PageNumberFormat.DECIMAL, pageBorderTop,
pageBorders: undefined, pageBorderRight,
pageBorderTop: undefined, pageBorderBottom,
pageBorderRight: undefined, pageBorderLeft,
pageBorderBottom: undefined, titlePage = false,
pageBorderLeft: undefined, } = options;
};
const mergedOptions = { this.options = options;
...defaultOptions, this.root.push(new PageSize(width, height, orientation));
...options, this.root.push(new PageMargin(top, right, bottom, left, header, footer, gutter, mirror));
}; this.root.push(new Columns(space));
this.root.push(new DocumentGrid(linePitch));
this.root.push(new PageSize(mergedOptions.width, mergedOptions.height, mergedOptions.orientation)); this.addHeaders(headers);
this.root.push( this.addFooters(footers);
new PageMargin(
mergedOptions.top,
mergedOptions.right,
mergedOptions.bottom,
mergedOptions.left,
mergedOptions.header,
mergedOptions.footer,
mergedOptions.gutter,
mergedOptions.mirror,
),
);
this.root.push(new Columns(mergedOptions.space));
this.root.push(new DocumentGrid(mergedOptions.linePitch));
this.root.push( this.root.push(new PageNumberType(pageNumberStart, pageNumberFormatType));
new HeaderReference({
headerType: mergedOptions.headerType,
headerId: mergedOptions.headerId,
}),
);
this.root.push(
new FooterReference({
footerType: mergedOptions.footerType,
footerId: mergedOptions.footerId,
}),
);
this.root.push(new PageNumberType(mergedOptions.pageNumberStart, mergedOptions.pageNumberFormatType)); if (pageBorders || pageBorderTop || pageBorderRight || pageBorderBottom || pageBorderLeft) {
if (
mergedOptions.pageBorders ||
mergedOptions.pageBorderTop ||
mergedOptions.pageBorderRight ||
mergedOptions.pageBorderBottom ||
mergedOptions.pageBorderLeft
) {
this.root.push( this.root.push(
new PageBorders({ new PageBorders({
pageBorders: mergedOptions.pageBorders, pageBorders: pageBorders,
pageBorderTop: mergedOptions.pageBorderTop, pageBorderTop: pageBorderTop,
pageBorderRight: mergedOptions.pageBorderRight, pageBorderRight: pageBorderRight,
pageBorderBottom: mergedOptions.pageBorderBottom, pageBorderBottom: pageBorderBottom,
pageBorderLeft: mergedOptions.pageBorderLeft, pageBorderLeft: pageBorderLeft,
}), }),
); );
} }
this.options = mergedOptions; if (titlePage) {
this.root.push(new TitlePage());
}
}
private addHeaders(headers?: IHeaderFooterGroup<HeaderWrapper>): void {
if (headers) {
if (headers.default) {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.DEFAULT,
headerId: headers.default.Header.ReferenceId,
}),
);
}
if (headers.first) {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.FIRST,
headerId: headers.first.Header.ReferenceId,
}),
);
}
if (headers.even) {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.EVEN,
headerId: headers.even.Header.ReferenceId,
}),
);
}
}
}
private addFooters(footers?: IHeaderFooterGroup<FooterWrapper>): void {
if (footers) {
if (footers.default) {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.DEFAULT,
footerId: footers.default.Footer.ReferenceId,
}),
);
}
if (footers.first) {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.FIRST,
footerId: footers.first.Footer.ReferenceId,
}),
);
}
if (footers.even) {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.EVEN,
footerId: footers.even.Footer.ReferenceId,
}),
);
}
}
} }
public get Options(): SectionPropertiesOptions { public get Options(): SectionPropertiesOptions {

View File

@ -0,0 +1,11 @@
import { IDocumentTemplate } from "../import-dotx";
export interface IFileProperties {
template?: IDocumentTemplate;
}
// Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432
/**
* @ignore
*/
export const WORKAROUND = "";

View File

@ -2,10 +2,17 @@ import { AppProperties } from "./app-properties/app-properties";
import { ContentTypes } from "./content-types/content-types"; import { ContentTypes } from "./content-types/content-types";
import { CoreProperties, IPropertiesOptions } from "./core-properties"; import { CoreProperties, IPropertiesOptions } from "./core-properties";
import { Document } from "./document"; import { Document } from "./document";
import { FooterReferenceType, HeaderReference, HeaderReferenceType, SectionPropertiesOptions } from "./document/body/section-properties"; import {
import { FooterWrapper } from "./footer-wrapper"; FooterReferenceType,
HeaderReference,
HeaderReferenceType,
IHeaderFooterGroup,
SectionPropertiesOptions,
} from "./document/body/section-properties";
import { IFileProperties } from "./file-properties";
import { FooterWrapper, IDocumentFooter } from "./footer-wrapper";
import { FootNotes } from "./footnotes"; import { FootNotes } from "./footnotes";
import { HeaderWrapper } from "./header-wrapper"; import { HeaderWrapper, IDocumentHeader } from "./header-wrapper";
import { Image, Media } from "./media"; import { Image, Media } from "./media";
import { Numbering } from "./numbering"; import { Numbering } from "./numbering";
import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
@ -18,33 +25,51 @@ import { Table } from "./table";
import { TableOfContents } from "./table-of-contents"; import { TableOfContents } from "./table-of-contents";
export class File { export class File {
private currentRelationshipId: number = 1;
private readonly document: Document; private readonly document: Document;
private styles: Styles; private readonly headers: IDocumentHeader[] = [];
private readonly footers: IDocumentFooter[] = [];
private readonly docRelationships: Relationships;
private readonly coreProperties: CoreProperties; private readonly coreProperties: CoreProperties;
private readonly numbering: Numbering; private readonly numbering: Numbering;
private readonly media: Media; private readonly media: Media;
private readonly docRelationships: Relationships;
private readonly fileRelationships: Relationships; private readonly fileRelationships: Relationships;
private readonly headerWrapper: HeaderWrapper[] = [];
private readonly footerWrapper: FooterWrapper[] = [];
private readonly footNotes: FootNotes; private readonly footNotes: FootNotes;
private readonly settings: Settings; private readonly settings: Settings;
private readonly contentTypes: ContentTypes; private readonly contentTypes: ContentTypes;
private readonly appProperties: AppProperties; private readonly appProperties: AppProperties;
private styles: Styles;
private currentRelationshipId: number = 1; constructor(
options: IPropertiesOptions = {
constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) {
if (!options) {
options = {
creator: "Un-named", creator: "Un-named",
revision: "1", revision: "1",
lastModifiedBy: "Un-named", lastModifiedBy: "Un-named",
}; },
sectionPropertiesOptions: SectionPropertiesOptions = {},
fileProperties: IFileProperties = {},
) {
this.coreProperties = new CoreProperties(options);
this.numbering = new Numbering();
this.docRelationships = new Relationships();
this.media = new Media();
this.fileRelationships = new Relationships();
this.appProperties = new AppProperties();
this.footNotes = new FootNotes();
this.contentTypes = new ContentTypes();
if (fileProperties.template) {
this.currentRelationshipId = fileProperties.template.currentRelationshipId + 1;
} }
if (options.externalStyles) { // set up styles
if (fileProperties.template && options.externalStyles) {
throw Error("can not use both template and external styles");
}
if (fileProperties.template) {
this.styles = fileProperties.template.styles;
} else if (options.externalStyles) {
const stylesFactory = new ExternalStylesFactory(); const stylesFactory = new ExternalStylesFactory();
this.styles = stylesFactory.newInstance(options.externalStyles); this.styles = stylesFactory.newInstance(options.externalStyles);
} else { } else {
@ -52,61 +77,30 @@ export class File {
this.styles = stylesFactory.newInstance(); this.styles = stylesFactory.newInstance();
} }
this.coreProperties = new CoreProperties(options); this.addDefaultRelationships();
this.numbering = new Numbering();
this.docRelationships = new Relationships();
this.docRelationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
"styles.xml",
);
this.docRelationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",
"numbering.xml",
);
this.contentTypes = new ContentTypes();
this.docRelationships.createRelationship( if (fileProperties.template && fileProperties.template.headers) {
this.currentRelationshipId++, for (const templateHeader of fileProperties.template.headers) {
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes", this.addHeaderToDocument(templateHeader.header, templateHeader.type);
"footnotes.xml",
);
this.media = new Media();
const header = this.createHeader();
const footer = this.createFooter();
this.fileRelationships = new Relationships();
this.fileRelationships.createRelationship(
1,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
"word/document.xml",
);
this.fileRelationships.createRelationship(
2,
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties",
"docProps/core.xml",
);
this.fileRelationships.createRelationship(
3,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
"docProps/app.xml",
);
this.appProperties = new AppProperties();
this.footNotes = new FootNotes();
if (!sectionPropertiesOptions) {
sectionPropertiesOptions = {
footerType: FooterReferenceType.DEFAULT,
headerType: HeaderReferenceType.DEFAULT,
headerId: header.Header.ReferenceId,
footerId: footer.Footer.ReferenceId,
};
} else {
sectionPropertiesOptions.headerId = header.Header.ReferenceId;
sectionPropertiesOptions.footerId = footer.Footer.ReferenceId;
} }
} else {
this.createHeader();
}
if (fileProperties.template && fileProperties.template.footers) {
for (const templateFooter of fileProperties.template.footers) {
this.addFooterToDocument(templateFooter.footer, templateFooter.type);
}
} else {
this.createFooter();
}
sectionPropertiesOptions = {
...sectionPropertiesOptions,
headers: this.groupHeaders(this.headers, sectionPropertiesOptions.headers),
footers: this.groupFooters(this.footers, sectionPropertiesOptions.footers),
};
this.document = new Document(sectionPropertiesOptions); this.document = new Document(sectionPropertiesOptions);
this.settings = new Settings(); this.settings = new Settings();
} }
@ -115,8 +109,9 @@ export class File {
this.document.addTableOfContents(toc); this.document.addTableOfContents(toc);
} }
public addParagraph(paragraph: Paragraph): void { public addParagraph(paragraph: Paragraph): File {
this.document.addParagraph(paragraph); this.document.addParagraph(paragraph);
return this;
} }
public createParagraph(text?: string): Paragraph { public createParagraph(text?: string): Paragraph {
@ -179,25 +174,13 @@ export class File {
public createHeader(): HeaderWrapper { public createHeader(): HeaderWrapper {
const header = new HeaderWrapper(this.media, this.currentRelationshipId++); const header = new HeaderWrapper(this.media, this.currentRelationshipId++);
this.headerWrapper.push(header); this.addHeaderToDocument(header);
this.docRelationships.createRelationship(
header.Header.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",
`header${this.headerWrapper.length}.xml`,
);
this.contentTypes.addHeader(this.headerWrapper.length);
return header; return header;
} }
public createFooter(): FooterWrapper { public createFooter(): FooterWrapper {
const footer = new FooterWrapper(this.media, this.currentRelationshipId++); const footer = new FooterWrapper(this.media, this.currentRelationshipId++);
this.footerWrapper.push(footer); this.addFooterToDocument(footer);
this.docRelationships.createRelationship(
footer.Footer.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer",
`footer${this.footerWrapper.length}.xml`,
);
this.contentTypes.addFooter(this.footerWrapper.length);
return footer; return footer;
} }
@ -214,6 +197,152 @@ export class File {
return headerWrapper; return headerWrapper;
} }
public getFooterByReferenceNumber(refId: number): FooterWrapper {
const entry = this.footers.map((item) => item.footer).find((h) => h.Footer.ReferenceId === refId);
if (entry) {
return entry;
}
throw new Error(`There is no footer with given reference id ${refId}`);
}
public getHeaderByReferenceNumber(refId: number): HeaderWrapper {
const entry = this.headers.map((item) => item.header).find((h) => h.Header.ReferenceId === refId);
if (entry) {
return entry;
}
throw new Error(`There is no header with given reference id ${refId}`);
}
public verifyUpdateFields(): void {
if (this.document.getTablesOfContents().length) {
this.settings.addUpdateFields();
}
}
private addHeaderToDocument(header: HeaderWrapper, type: HeaderReferenceType = HeaderReferenceType.DEFAULT): void {
this.headers.push({ header, type });
this.docRelationships.createRelationship(
header.Header.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",
`header${this.headers.length}.xml`,
);
this.contentTypes.addHeader(this.headers.length);
}
private addFooterToDocument(footer: FooterWrapper, type: FooterReferenceType = FooterReferenceType.DEFAULT): void {
this.footers.push({ footer, type });
this.docRelationships.createRelationship(
footer.Footer.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer",
`footer${this.footers.length}.xml`,
);
this.contentTypes.addFooter(this.footers.length);
}
private addDefaultRelationships(): void {
this.fileRelationships.createRelationship(
1,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
"word/document.xml",
);
this.fileRelationships.createRelationship(
2,
"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties",
"docProps/core.xml",
);
this.fileRelationships.createRelationship(
3,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
"docProps/app.xml",
);
this.docRelationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
"styles.xml",
);
this.docRelationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",
"numbering.xml",
);
this.docRelationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes",
"footnotes.xml",
);
}
private groupHeaders(headers: IDocumentHeader[], group: IHeaderFooterGroup<HeaderWrapper> = {}): IHeaderFooterGroup<HeaderWrapper> {
let newGroup = group;
for (const header of headers) {
switch (header.type) {
case HeaderReferenceType.DEFAULT:
newGroup = {
...newGroup,
default: header.header,
};
break;
case HeaderReferenceType.FIRST:
newGroup = {
...newGroup,
first: header.header,
};
break;
case HeaderReferenceType.EVEN:
newGroup = {
...newGroup,
even: header.header,
};
break;
default:
newGroup = {
...newGroup,
default: header.header,
};
break;
}
}
return newGroup;
}
private groupFooters(footers: IDocumentFooter[], group: IHeaderFooterGroup<FooterWrapper> = {}): IHeaderFooterGroup<FooterWrapper> {
let newGroup = group;
for (const footer of footers) {
switch (footer.type) {
case FooterReferenceType.DEFAULT:
newGroup = {
...newGroup,
default: footer.footer,
};
break;
case FooterReferenceType.FIRST:
newGroup = {
...newGroup,
first: footer.footer,
};
break;
case FooterReferenceType.EVEN:
newGroup = {
...newGroup,
even: footer.footer,
};
break;
default:
newGroup = {
...newGroup,
default: footer.footer,
};
break;
}
}
return newGroup;
}
public get Document(): Document { public get Document(): Document {
return this.document; return this.document;
} }
@ -247,35 +376,19 @@ export class File {
} }
public get Header(): HeaderWrapper { public get Header(): HeaderWrapper {
return this.headerWrapper[0]; return this.headers[0].header;
} }
public get Headers(): HeaderWrapper[] { public get Headers(): HeaderWrapper[] {
return this.headerWrapper; return this.headers.map((item) => item.header);
}
public HeaderByRefNumber(refId: number): HeaderWrapper {
const entry = this.headerWrapper.find((h) => h.Header.ReferenceId === refId);
if (entry) {
return entry;
}
throw new Error(`There is no header with given reference id ${refId}`);
} }
public get Footer(): FooterWrapper { public get Footer(): FooterWrapper {
return this.footerWrapper[0]; return this.footers[0].footer;
} }
public get Footers(): FooterWrapper[] { public get Footers(): FooterWrapper[] {
return this.footerWrapper; return this.footers.map((item) => item.footer);
}
public FooterByRefNumber(refId: number): FooterWrapper {
const entry = this.footerWrapper.find((h) => h.Footer.ReferenceId === refId);
if (entry) {
return entry;
}
throw new Error(`There is no footer with given reference id ${refId}`);
} }
public get ContentTypes(): ContentTypes { public get ContentTypes(): ContentTypes {
@ -293,10 +406,4 @@ export class File {
public get Settings(): Settings { public get Settings(): Settings {
return this.settings; return this.settings;
} }
public verifyUpdateFields(): void {
if (this.document.getTablesOfContents().length) {
this.settings.addUpdateFields();
}
}
} }

View File

@ -1,16 +1,23 @@
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { FooterReferenceType } from "./document";
import { Footer } from "./footer/footer"; import { Footer } from "./footer/footer";
import { Image, Media } from "./media"; import { Image, IMediaData, Media } from "./media";
import { ImageParagraph, Paragraph } from "./paragraph"; import { ImageParagraph, Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
export interface IDocumentFooter {
footer: FooterWrapper;
type: FooterReferenceType;
}
export class FooterWrapper { export class FooterWrapper {
private readonly footer: Footer; private readonly footer: Footer;
private readonly relationships: Relationships; private readonly relationships: Relationships;
constructor(private readonly media: Media, referenceId: number) { constructor(private readonly media: Media, referenceId: number, initContent?: XmlComponent) {
this.footer = new Footer(referenceId); this.footer = new Footer(referenceId, initContent);
this.relationships = new Relationships(); this.relationships = new Relationships();
} }
@ -32,17 +39,33 @@ export class FooterWrapper {
return this.footer.createTable(rows, cols); return this.footer.createTable(rows, cols);
} }
public addChildElement(childElement: XmlComponent | string): void { public addChildElement(childElement: XmlComponent): void {
this.footer.addChildElement(childElement); this.footer.addChildElement(childElement);
} }
public createImage(image: Buffer, width?: number, height?: number): void { public addImageRelationship(image: Buffer, refId: number, width?: number, height?: number): IMediaData {
const mediaData = this.media.addMedia(image, this.relationships.RelationshipCount, width, height); const mediaData = this.media.addMedia(image, refId, width, height);
this.relationships.createRelationship( this.relationships.createRelationship(
mediaData.referenceId, mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`, `media/${mediaData.fileName}`,
); );
return mediaData;
}
public addHyperlinkRelationship(target: string, refId: number, targetMode?: "External" | undefined): void {
this.relationships.createRelationship(
refId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
target,
targetMode,
);
}
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
// TODO
// tslint:disable-next-line:no-any
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
this.addImage(new Image(new ImageParagraph(mediaData))); this.addImage(new Image(new ImageParagraph(mediaData)));
} }
@ -58,4 +81,8 @@ export class FooterWrapper {
public get Relationships(): Relationships { public get Relationships(): Relationships {
return this.relationships; return this.relationships;
} }
public get Media(): Media {
return this.media;
}
} }

View File

@ -1,14 +1,14 @@
// http://officeopenxml.com/WPfooters.php // http://officeopenxml.com/WPfooters.php
import { XmlComponent } from "file/xml-components"; import { InitializableXmlComponent, XmlComponent } from "file/xml-components";
import { Paragraph } from "../paragraph"; import { Paragraph } from "../paragraph";
import { Table } from "../table"; import { Table } from "../table";
import { FooterAttributes } from "./footer-attributes"; import { FooterAttributes } from "./footer-attributes";
export class Footer extends XmlComponent { export class Footer extends InitializableXmlComponent {
private readonly refId: number; private readonly refId: number;
constructor(referenceNumber: number) { constructor(referenceNumber: number, initContent?: XmlComponent) {
super("w:ftr"); super("w:ftr", initContent);
this.refId = referenceNumber; this.refId = referenceNumber;
this.root.push( this.root.push(
new FooterAttributes({ new FooterAttributes({

View File

@ -1,16 +1,23 @@
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { HeaderReferenceType } from "./document";
import { Header } from "./header/header"; import { Header } from "./header/header";
import { Image, Media } from "./media"; import { Image, IMediaData, Media } from "./media";
import { ImageParagraph, Paragraph } from "./paragraph"; import { ImageParagraph, Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
export interface IDocumentHeader {
header: HeaderWrapper;
type: HeaderReferenceType;
}
export class HeaderWrapper { export class HeaderWrapper {
private readonly header: Header; private readonly header: Header;
private readonly relationships: Relationships; private readonly relationships: Relationships;
constructor(private readonly media: Media, referenceId: number) { constructor(private readonly media: Media, referenceId: number, initContent?: XmlComponent) {
this.header = new Header(referenceId); this.header = new Header(referenceId, initContent);
this.relationships = new Relationships(); this.relationships = new Relationships();
} }
@ -36,13 +43,29 @@ export class HeaderWrapper {
this.header.addChildElement(childElement); this.header.addChildElement(childElement);
} }
public createImage(image: Buffer, width?: number, height?: number): void { public addImageRelationship(image: Buffer, refId: number, width?: number, height?: number): IMediaData {
const mediaData = this.media.addMedia(image, this.relationships.RelationshipCount, width, height); const mediaData = this.media.addMedia(image, refId, width, height);
this.relationships.createRelationship( this.relationships.createRelationship(
mediaData.referenceId, mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`, `media/${mediaData.fileName}`,
); );
return mediaData;
}
public addHyperlinkRelationship(target: string, refId: number, targetMode?: "External" | undefined): void {
this.relationships.createRelationship(
refId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
target,
targetMode,
);
}
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
// TODO
// tslint:disable-next-line:no-any
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
this.addImage(new Image(new ImageParagraph(mediaData))); this.addImage(new Image(new ImageParagraph(mediaData)));
} }
@ -58,4 +81,8 @@ export class HeaderWrapper {
public get Relationships(): Relationships { public get Relationships(): Relationships {
return this.relationships; return this.relationships;
} }
public get Media(): Media {
return this.media;
}
} }

View File

@ -23,6 +23,17 @@ export interface IHeaderAttributesProperties {
dcmitype?: string; dcmitype?: string;
xsi?: string; xsi?: string;
type?: string; type?: string;
cx?: string;
cx1?: string;
cx2?: string;
cx3?: string;
cx4?: string;
cx5?: string;
cx6?: string;
cx7?: string;
cx8?: string;
w16cid: string;
w16se: string;
} }
export class HeaderAttributes extends XmlAttributeComponent<IHeaderAttributesProperties> { export class HeaderAttributes extends XmlAttributeComponent<IHeaderAttributesProperties> {
@ -49,5 +60,16 @@ export class HeaderAttributes extends XmlAttributeComponent<IHeaderAttributesPro
dcmitype: "xmlns:dcmitype", dcmitype: "xmlns:dcmitype",
xsi: "xmlns:xsi", xsi: "xmlns:xsi",
type: "xsi:type", type: "xsi:type",
cx: "xmlns:cx",
cx1: "xmlns:cx1",
cx2: "xmlns:cx2",
cx3: "xmlns:cx3",
cx4: "xmlns:cx4",
cx5: "xmlns:cx5",
cx6: "xmlns:cx6",
cx7: "xmlns:cx7",
cx8: "xmlns:cx8",
w16cid: "xmlns:w16cid",
w16se: "xmlns:w16se",
}; };
} }

View File

@ -1,16 +1,17 @@
// http://officeopenxml.com/WPheaders.php // http://officeopenxml.com/WPheaders.php
import { XmlComponent } from "file/xml-components"; import { InitializableXmlComponent, XmlComponent } from "file/xml-components";
import { Paragraph } from "../paragraph"; import { Paragraph } from "../paragraph";
import { Table } from "../table"; import { Table } from "../table";
import { HeaderAttributes } from "./header-attributes"; import { HeaderAttributes } from "./header-attributes";
export class Header extends XmlComponent { export class Header extends InitializableXmlComponent {
private readonly refId: number; private readonly refId: number;
constructor(referenceNumber: number) { constructor(referenceNumber: number, initContent?: XmlComponent) {
super("w:hdr"); super("w:hdr", initContent);
this.refId = referenceNumber; this.refId = referenceNumber;
this.root.push( this.root.push(
new HeaderAttributes({ new HeaderAttributes({
wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas", wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
@ -29,6 +30,17 @@ export class Header extends XmlComponent {
wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk", wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
wne: "http://schemas.microsoft.com/office/word/2006/wordml", wne: "http://schemas.microsoft.com/office/word/2006/wordml",
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape", wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
cx: "http://schemas.microsoft.com/office/drawing/2014/chartex",
cx1: "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
cx2: "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
cx3: "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
cx4: "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
cx5: "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
cx6: "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
cx7: "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
cx8: "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
w16cid: "http://schemas.microsoft.com/office/word/2016/wordml/cid",
w16se: "http://schemas.microsoft.com/office/word/2015/wordml/symex",
}), }),
); );
} }

View File

@ -1,6 +1,7 @@
export * from "./paragraph"; export * from "./paragraph";
export * from "./table"; export * from "./table";
export * from "./file"; export * from "./file";
export * from "./file-properties";
export * from "./numbering"; export * from "./numbering";
export * from "./media"; export * from "./media";
export * from "./drawing"; export * from "./drawing";

View File

@ -87,7 +87,7 @@ export class Media {
} }
const imageData = { const imageData = {
referenceId: this.map.size + relationshipsCount + 1, referenceId: relationshipsCount,
stream: data, stream: data,
path: filePath, path: filePath,
fileName: key, fileName: key,

View File

@ -71,7 +71,7 @@ export class Numbering extends XmlComponent {
return num; return num;
} }
public prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
this.abstractNumbering.forEach((x) => this.root.push(x)); this.abstractNumbering.forEach((x) => this.root.push(x));
this.concreteNumbering.forEach((x) => this.root.push(x)); this.concreteNumbering.forEach((x) => this.root.push(x));
return super.prepForXml(); return super.prepForXml();

View File

@ -61,8 +61,6 @@ describe("External styles factory", () => {
it("should parse other child elements of w:styles", () => { it("should parse other child elements of w:styles", () => {
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any; const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any;
expect(importedStyle.root.length).to.equal(5);
expect(importedStyle.root[1]).to.eql({ expect(importedStyle.root[1]).to.eql({
deleted: false, deleted: false,
root: [ root: [

View File

@ -21,8 +21,10 @@ export class TableCellMargin extends XmlComponent {
super("w:tblCellMar"); super("w:tblCellMar");
} }
public prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
return this.root.length > 0 ? super.prepForXml() : ""; if (this.root.length > 0) {
return super.prepForXml();
}
} }
public addTopMargin(value: number, type: WidthType = WidthType.DXA): void { public addTopMargin(value: number, type: WidthType = WidthType.DXA): void {

View File

@ -8,8 +8,7 @@ describe("TableCellBorders", () => {
describe("#prepForXml", () => { describe("#prepForXml", () => {
it("should not add empty borders element if there are no borders defined", () => { it("should not add empty borders element if there are no borders defined", () => {
const tb = new TableCellBorders(); const tb = new TableCellBorders();
const tree = new Formatter().format(tb); expect(() => new Formatter().format(tb)).to.throw();
expect(tree).to.deep.equal("");
}); });
}); });

View File

@ -29,8 +29,10 @@ export class TableCellBorders extends XmlComponent {
super("w:tcBorders"); super("w:tcBorders");
} }
public prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
return this.root.length > 0 ? super.prepForXml() : ""; if (this.root.length > 0) {
return super.prepForXml();
}
} }
public addTopBorder(style: BorderStyle, size: number, color: string): TableCellBorders { public addTopBorder(style: BorderStyle, size: number, color: string): TableCellBorders {

View File

@ -145,9 +145,13 @@ export class TableCell extends XmlComponent {
return this; return this;
} }
public prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
// Cells must end with a paragraph // Cells must end with a paragraph
const retval = super.prepForXml(); const retval = super.prepForXml();
if (!retval) {
return undefined;
}
const content = retval["w:tc"]; const content = retval["w:tc"];
if (!content[content.length - 1]["w:p"]) { if (!content[content.length - 1]["w:p"]) {
content.push(new Paragraph().prepForXml()); content.push(new Paragraph().prepForXml());

View File

@ -8,7 +8,7 @@ export abstract class BaseXmlComponent {
this.rootKey = rootKey; this.rootKey = rootKey;
} }
public abstract prepForXml(): IXmlableObject; public abstract prepForXml(): IXmlableObject | undefined;
public get IsDeleted(): boolean { public get IsDeleted(): boolean {
return this.deleted; return this.deleted;

View File

@ -1,7 +1,7 @@
/* tslint:disable */ // tslint:disable:no-any
import { XmlComponent, IXmlableObject } from ".";
import * as fastXmlParser from "fast-xml-parser"; import * as fastXmlParser from "fast-xml-parser";
import { flatMap } from "lodash"; import { flatMap } from "lodash";
import { IXmlableObject, XmlComponent } from ".";
export const parseOptions = { export const parseOptions = {
ignoreAttributes: false, ignoreAttributes: false,
@ -54,8 +54,27 @@ export function convertToXmlComponent(elementName: string, element: any): Import
* Represents imported xml component from xml file. * Represents imported xml component from xml file.
*/ */
export class ImportedXmlComponent extends XmlComponent { export class ImportedXmlComponent extends XmlComponent {
private _attr: any; /**
* Converts the xml string to a XmlComponent tree.
*
* @param importedContent xml content of the imported component
*/
public static fromXmlString(importedContent: string): ImportedXmlComponent {
const imported = fastXmlParser.parse(importedContent, parseOptions);
const elementName = Object.keys(imported)[0];
const converted = convertToXmlComponent(elementName, imported[elementName]);
if (Array.isArray(converted) && converted.length > 1) {
throw new Error("Invalid conversion, input must be one element.");
}
return Array.isArray(converted) ? converted[0] : converted;
}
// tslint:disable-next-line:variable-name
private readonly _attr: any;
// tslint:disable-next-line:variable-name
constructor(rootKey: string, _attr?: any) { constructor(rootKey: string, _attr?: any) {
super(rootKey); super(rootKey);
if (_attr) { if (_attr) {
@ -89,8 +108,12 @@ export class ImportedXmlComponent extends XmlComponent {
* ] * ]
* } * }
*/ */
prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
const result = super.prepForXml(); const result = super.prepForXml();
if (!result) {
return undefined;
}
if (!!this._attr) { if (!!this._attr) {
if (!Array.isArray(result[this.rootKey])) { if (!Array.isArray(result[this.rootKey])) {
result[this.rootKey] = [result[this.rootKey]]; result[this.rootKey] = [result[this.rootKey]];
@ -100,33 +123,17 @@ export class ImportedXmlComponent extends XmlComponent {
return result; return result;
} }
push(xmlComponent: XmlComponent) { public push(xmlComponent: XmlComponent): void {
this.root.push(xmlComponent); this.root.push(xmlComponent);
} }
/**
* Converts the xml string to a XmlComponent tree.
*
* @param importedContent xml content of the imported component
*/
static fromXmlString(importedContent: string): ImportedXmlComponent {
const imported = fastXmlParser.parse(importedContent, parseOptions);
const elementName = Object.keys(imported)[0];
const converted = convertToXmlComponent(elementName, imported[elementName]);
if (Array.isArray(converted) && converted.length > 1) {
throw new Error("Invalid conversion, input must be one element.");
}
return Array.isArray(converted) ? converted[0] : converted;
}
} }
/** /**
* Used for the attributes of root element that is being imported. * Used for the attributes of root element that is being imported.
*/ */
export class ImportedRootElementAttributes extends XmlComponent { export class ImportedRootElementAttributes extends XmlComponent {
constructor(private _attr: any) { // tslint:disable-next-line:variable-name
constructor(private readonly _attr: any) {
super(""); super("");
} }

View File

@ -3,3 +3,4 @@ export * from "./attributes";
export * from "./default-attributes"; export * from "./default-attributes";
export * from "./imported-xml-component"; export * from "./imported-xml-component";
export * from "./xmlable-object"; export * from "./xmlable-object";
export * from "./initializable-xml-component";

View File

@ -0,0 +1,11 @@
import { XmlComponent } from "file/xml-components";
export abstract class InitializableXmlComponent extends XmlComponent {
constructor(rootKey: string, initComponent?: InitializableXmlComponent) {
super(rootKey);
if (initComponent) {
this.root = initComponent.root;
}
}
}

View File

@ -26,6 +26,11 @@ describe("XmlComponent", () => {
xmlComponent.addChildElement(child); xmlComponent.addChildElement(child);
const xml = xmlComponent.prepForXml(); const xml = xmlComponent.prepForXml();
if (!xml) {
return;
}
assert.equal(xml["w:test"].length, 0); assert.equal(xml["w:test"].length, 0);
}); });
}); });

View File

@ -7,10 +7,10 @@ export abstract class XmlComponent extends BaseXmlComponent {
constructor(rootKey: string) { constructor(rootKey: string) {
super(rootKey); super(rootKey);
this.root = new Array<BaseXmlComponent>(); this.root = new Array<BaseXmlComponent | string>();
} }
public prepForXml(): IXmlableObject { public prepForXml(): IXmlableObject | undefined {
const children = this.root const children = this.root
.filter((c) => { .filter((c) => {
if (c instanceof BaseXmlComponent) { if (c instanceof BaseXmlComponent) {
@ -24,7 +24,7 @@ export abstract class XmlComponent extends BaseXmlComponent {
} }
return comp; return comp;
}) })
.filter((comp) => comp); // Exclude null, undefined, and empty strings .filter((comp) => comp !== undefined); // Exclude undefined
return { return {
[this.rootKey]: children, [this.rootKey]: children,
}; };

View File

@ -0,0 +1,209 @@
import * as fastXmlParser from "fast-xml-parser";
import * as JSZip from "jszip";
import { FooterReferenceType } from "file/document/body/section-properties/footer-reference";
import { HeaderReferenceType } from "file/document/body/section-properties/header-reference";
import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper";
import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper";
import { Media } from "file/media";
import { Styles } from "file/styles";
import { ExternalStylesFactory } from "file/styles/external-styles-factory";
import { convertToXmlComponent, ImportedXmlComponent, parseOptions } from "file/xml-components";
const importParseOptions = {
...parseOptions,
textNodeName: "",
trimValues: false,
};
const schemeToType = {
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer": "footer",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image": "image",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink": "hyperlink",
};
interface IDocumentRefs {
headers: Array<{ id: number; type: HeaderReferenceType }>;
footers: Array<{ id: number; type: FooterReferenceType }>;
}
interface IRelationshipFileInfo {
id: number;
target: string;
type: "header" | "footer" | "image" | "hyperlink";
}
// Document Template
// https://fileinfo.com/extension/dotx
export interface IDocumentTemplate {
currentRelationshipId: number;
headers: IDocumentHeader[];
footers: IDocumentFooter[];
styles: Styles;
titlePageIsDefined: boolean;
}
export class ImportDotx {
private currentRelationshipId: number;
constructor() {
this.currentRelationshipId = 1;
}
public async extract(data: Buffer): Promise<IDocumentTemplate> {
const zipContent = await JSZip.loadAsync(data);
const stylesContent = await zipContent.files["word/styles.xml"].async("text");
const stylesFactory = new ExternalStylesFactory();
const styles = stylesFactory.newInstance(stylesContent);
const documentContent = zipContent.files["word/document.xml"];
const documentRefs: IDocumentRefs = this.extractDocumentRefs(await documentContent.async("text"));
const titlePageIsDefined = this.titlePageIsDefined(await documentContent.async("text"));
const relationshipContent = zipContent.files["word/_rels/document.xml.rels"];
const documentRelationships: IRelationshipFileInfo[] = this.findReferenceFiles(await relationshipContent.async("text"));
const media = new Media();
const headers: IDocumentHeader[] = [];
for (const headerRef of documentRefs.headers) {
const headerKey = "w:hdr";
const relationshipFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
if (!relationshipFileInfo) {
throw new Error(`Can not find target file for id ${headerRef.id}`);
}
const xmlData = await zipContent.files[`word/${relationshipFileInfo.target}`].async("text");
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
const importedComp = convertToXmlComponent(headerKey, xmlObj[headerKey]) as ImportedXmlComponent;
const header = new HeaderWrapper(media, this.currentRelationshipId++, importedComp);
await this.addRelationToWrapper(relationshipFileInfo, zipContent, header);
headers.push({ type: headerRef.type, header });
}
const footers: IDocumentFooter[] = [];
for (const footerRef of documentRefs.footers) {
const footerKey = "w:ftr";
const relationshipFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id);
if (!relationshipFileInfo) {
throw new Error(`Can not find target file for id ${footerRef.id}`);
}
const xmlData = await zipContent.files[`word/${relationshipFileInfo.target}`].async("text");
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
const importedComp = convertToXmlComponent(footerKey, xmlObj[footerKey]) as ImportedXmlComponent;
const footer = new FooterWrapper(media, this.currentRelationshipId++, importedComp);
await this.addRelationToWrapper(relationshipFileInfo, zipContent, footer);
footers.push({ type: footerRef.type, footer });
}
const templateDocument: IDocumentTemplate = {
headers,
footers,
currentRelationshipId: this.currentRelationshipId,
styles,
titlePageIsDefined,
};
return templateDocument;
}
public async addRelationToWrapper(
relationhipFile: IRelationshipFileInfo,
zipContent: JSZip,
wrapper: HeaderWrapper | FooterWrapper,
): Promise<void> {
let wrapperImagesReferences: IRelationshipFileInfo[] = [];
let hyperLinkReferences: IRelationshipFileInfo[] = [];
const refFile = zipContent.files[`word/_rels/${relationhipFile.target}.rels`];
if (refFile) {
const xmlRef = await refFile.async("text");
wrapperImagesReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "image");
hyperLinkReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "hyperlink");
}
for (const r of wrapperImagesReferences) {
const buffer = await zipContent.files[`word/${r.target}`].async("nodebuffer");
wrapper.addImageRelationship(buffer, r.id);
}
for (const r of hyperLinkReferences) {
wrapper.addHyperlinkRelationship(r.target, r.id, "External");
}
}
public findReferenceFiles(xmlData: string): IRelationshipFileInfo[] {
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
const relationshipXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
? xmlObj.Relationships.Relationship
: [xmlObj.Relationships.Relationship];
const relationships: IRelationshipFileInfo[] = relationshipXmlArray
.map((item) => {
return {
id: this.parseRefId(item._attr.Id),
type: schemeToType[item._attr.Type],
target: item._attr.Target,
};
})
.filter((item) => item.type !== null);
return relationships;
}
public extractDocumentRefs(xmlData: string): IDocumentRefs {
interface IAttributedXML {
_attr: object;
}
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
const headerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:headerReference"];
let headersXmlArray: IAttributedXML[];
if (headerProps === undefined) {
headersXmlArray = [];
} else if (Array.isArray(headerProps)) {
headersXmlArray = headerProps;
} else {
headersXmlArray = [headerProps];
}
const headers = headersXmlArray.map((item) => {
return {
type: item._attr["w:type"],
id: this.parseRefId(item._attr["r:id"]),
};
});
const footerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:footerReference"];
let footersXmlArray: IAttributedXML[];
if (footerProps === undefined) {
footersXmlArray = [];
} else if (Array.isArray(footerProps)) {
footersXmlArray = footerProps;
} else {
footersXmlArray = [footerProps];
}
const footers = footersXmlArray.map((item) => {
return {
type: item._attr["w:type"],
id: this.parseRefId(item._attr["r:id"]),
};
});
return { headers, footers };
}
public titlePageIsDefined(xmlData: string): boolean {
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
return sectionProp["w:titlePg"] !== undefined;
}
public parseRefId(str: string): number {
const match = /^rId(\d+)$/.exec(str);
if (match === null) {
throw new Error("Invalid ref id");
}
return parseInt(match[1], 10);
}
}

1
src/import-dotx/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./import-dotx";

View File

@ -3,3 +3,4 @@
export { File as Document } from "./file"; export { File as Document } from "./file";
export * from "./file"; export * from "./file";
export * from "./export"; export * from "./export";
export * from "./import-dotx";