diff --git a/MyDocument.docx b/MyDocument.docx index 3bc32bccfa..1cfaaf5a6a 100644 Binary files a/MyDocument.docx and b/MyDocument.docx differ diff --git a/demo/importDemo.ts b/demo/demo27.ts similarity index 52% rename from demo/importDemo.ts rename to demo/demo27.ts index 42fcf2a1ea..1d91734203 100644 --- a/demo/importDemo.ts +++ b/demo/demo27.ts @@ -1,26 +1,25 @@ import { Document, Packer, Paragraph, ImportDocx } from "../build"; import * as fs from "fs"; -console.log(process.cwd()); - let importDocx = new ImportDocx(); -fs.readFile("./src/importDocx/simple.dotx", (err, data) => { +const filePath = "./demo/dotx/template.dotx"; +fs.readFile(filePath, (err, data) => { if (err) { - console.log(err); + console.error(`failed to read file ${filePath}.`); } else { - importDocx.read(data).then(xmlComp => { - console.log(xmlComp); - const doc = new Document({templateHeader : xmlComp}); - // const doc = new Document(); + importDocx.extract(data).then(templateDocument => { + let options = {}; + options['templateDocument'] = templateDocument; + + const doc = new Document(options); const paragraph = new Paragraph("Hello World"); doc.addParagraph(paragraph); - // console.log(JSON.stringify(xmlComp, null, 2)); const packer = new Packer(); - packer.toBuffer(doc).then((buffer) => { fs.writeFileSync("MyDocument.docx", buffer); + console.log('done. open MyDocument.docx'); }); }); diff --git a/demo/dotx/simple.dotx b/demo/dotx/simple.dotx new file mode 100644 index 0000000000..8ad9309eb2 Binary files /dev/null and b/demo/dotx/simple.dotx differ diff --git a/demo/dotx/simple2.dotx b/demo/dotx/simple2.dotx new file mode 100644 index 0000000000..13ab583d96 Binary files /dev/null and b/demo/dotx/simple2.dotx differ diff --git a/src/importDocx/template.dotx b/demo/dotx/template.dotx similarity index 100% rename from src/importDocx/template.dotx rename to demo/dotx/template.dotx diff --git a/demo/importDemo.js b/demo/importDemo.js deleted file mode 100644 index c40eb0a6ae..0000000000 --- a/demo/importDemo.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; -exports.__esModule = true; -var build_1 = require("../build"); -var fs = require("fs"); -var importDocx = new build_1.ImportDocx(); -fs.readFile("./src/import/template.dotx", function (err, data) { - if (err) { - console.log(err); - } - else { - importDocx.read(data); - } -}); diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index f06c09e196..b1b80682c1 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -53,10 +53,21 @@ export class Compiler { } } + for (const data of file.Media.Array) { const mediaData = data.stream; zip.file(`word/media/${data.fileName}`, mediaData); } + for (let header of file.Headers) { + for (const data of header.media.Array) { + zip.file(`word/media/${data.fileName}`, data.stream); + } + } + for (let footer of file.Footers) { + for (const data of footer.media.Array) { + zip.file(`word/media/${data.fileName}`, data.stream); + } + } return zip; } @@ -122,4 +133,13 @@ export class Compiler { }, }; } + + + /* By default docx collapse empty tags. -> . this function mimic it + so comparing (diff) original docx file and the library output is easier */ + collapseEmptyTags(xmlData : string) : string { + const regEx = /<(([^ <>]+)[^<>]*)><\/\2>/g; + let collapsed = xmlData.replace(regEx, '<$1/>'); + return collapsed; + } } diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index a5a239635d..ce51fe2029 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -2,6 +2,8 @@ import { XmlComponent } from "file/xml-components"; import { DocumentAttributes } from "../document/document-attributes"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; +import { TemplateDocument } from 'importDocx/importDocx' + export interface IPropertiesOptions { title?: string; subject?: string; @@ -12,7 +14,7 @@ export interface IPropertiesOptions { revision?: string; externalStyles?: string; - templateHeader? : XmlComponent; + templateDocument? : TemplateDocument; } export class CoreProperties extends XmlComponent { diff --git a/src/file/document/body/section-properties/section-properties.spec.ts b/src/file/document/body/section-properties/section-properties.spec.ts index e80facb635..0bc2ac24c7 100644 --- a/src/file/document/body/section-properties/section-properties.spec.ts +++ b/src/file/document/body/section-properties/section-properties.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { Formatter } from "../../../../export/formatter"; -import { FooterReferenceType, PageBorderOffsetFrom, PageNumberFormat } from "./"; +import { PageBorderOffsetFrom, PageNumberFormat, FooterReferenceType, HeaderReferenceType } from "./"; import { SectionProperties } from "./section-properties"; describe("SectionProperties", () => { @@ -19,9 +19,8 @@ describe("SectionProperties", () => { gutter: 0, space: 708, linePitch: 360, - headerId: 100, - footerId: 200, - footerType: FooterReferenceType.EVEN, + headers: [{headerId: 100, headerType: HeaderReferenceType.DEFAULT}], + footers: [{footerId: 200, footerType: FooterReferenceType.EVEN}], pageNumberStart: 10, pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT, }); diff --git a/src/file/document/body/section-properties/section-properties.ts b/src/file/document/body/section-properties/section-properties.ts index c23964cd14..aa2cc9a664 100644 --- a/src/file/document/body/section-properties/section-properties.ts +++ b/src/file/document/body/section-properties/section-properties.ts @@ -1,24 +1,27 @@ // http://officeopenxml.com/WPsection.php import { XmlComponent } from "file/xml-components"; -import { FooterReferenceType, IPageBordersOptions, IPageNumberTypeAttributes, PageBorders, PageNumberFormat, PageNumberType } from "./"; +import { IPageBordersOptions, IPageNumberTypeAttributes, PageBorders, PageNumberFormat, PageNumberType } from "./"; import { Columns } from "./columns/columns"; import { IColumnsAttributes } from "./columns/columns-attributes"; import { DocumentGrid } from "./doc-grid/doc-grid"; import { IDocGridAttributesProperties } from "./doc-grid/doc-grid-attributes"; import { FooterReference, IFooterOptions } from "./footer-reference/footer-reference"; import { HeaderReference, IHeaderOptions } from "./header-reference/header-reference"; -import { HeaderReferenceType } from "./header-reference/header-reference-attributes"; import { PageMargin } from "./page-margin/page-margin"; import { IPageMarginAttributes } from "./page-margin/page-margin-attributes"; import { PageSize } from "./page-size/page-size"; import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes"; +import { TitlePage } from "./title-page/title-page"; + +type IHeadersOptions = {headers? : IHeaderOptions[]} +type IFootersOptions = {footers? : IFooterOptions[]} export type SectionPropertiesOptions = IPageSizeAttributes & IPageMarginAttributes & IColumnsAttributes & IDocGridAttributesProperties & - IHeaderOptions & - IFooterOptions & + IHeadersOptions & + IFootersOptions & IPageNumberTypeAttributes & IPageBordersOptions; @@ -41,10 +44,8 @@ export class SectionProperties extends XmlComponent { space: 708, linePitch: 360, orientation: PageOrientation.PORTRAIT, - headerType: HeaderReferenceType.DEFAULT, - headerId: 0, - footerType: FooterReferenceType.DEFAULT, - footerId: 0, + headers: [], + footers: [], pageNumberStart: undefined, pageNumberFormatType: PageNumberFormat.DECIMAL, pageBorders: undefined, @@ -52,6 +53,7 @@ export class SectionProperties extends XmlComponent { pageBorderRight: undefined, pageBorderBottom: undefined, pageBorderLeft: undefined, + titlePage : true }; const mergedOptions = { @@ -59,6 +61,7 @@ export class SectionProperties extends XmlComponent { ...options, }; + this.root.push(new PageSize(mergedOptions.width, mergedOptions.height, mergedOptions.orientation)); this.root.push( new PageMargin( @@ -74,19 +77,24 @@ export class SectionProperties extends XmlComponent { this.root.push(new Columns(mergedOptions.space)); this.root.push(new DocumentGrid(mergedOptions.linePitch)); - this.root.push( - new HeaderReference({ - headerType: mergedOptions.headerType, - headerId: mergedOptions.headerId, - }), - ); - this.root.push( - new FooterReference({ - footerType: mergedOptions.footerType, - footerId: mergedOptions.footerId, - }), - ); + for (let header of mergedOptions.headers) { + this.root.push( + new HeaderReference({ + headerType: header.headerType, + headerId: header.headerId, + }), + ); + } + for (let footer of mergedOptions.footers) { + this.root.push( + new FooterReference({ + footerType: footer.footerType, + footerId: footer.footerId, + }), + ); + } + this.root.push(new PageNumberType(mergedOptions.pageNumberStart, mergedOptions.pageNumberFormatType)); if ( @@ -107,6 +115,10 @@ export class SectionProperties extends XmlComponent { ); } + if (mergedOptions.titlePage) { + this.root.push(new TitlePage()); + } + this.options = mergedOptions; } diff --git a/src/file/file.ts b/src/file/file.ts index 2700c226b2..da9b289cc9 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -2,7 +2,14 @@ import { AppProperties } from "./app-properties/app-properties"; import { ContentTypes } from "./content-types/content-types"; import { CoreProperties, IPropertiesOptions } from "./core-properties"; import { Document } from "./document"; -import { FooterReferenceType, HeaderReference, HeaderReferenceType, SectionPropertiesOptions } from "./document/body/section-properties"; +import { + FooterReferenceType, + HeaderReference, + HeaderReferenceType, + SectionPropertiesOptions, + IHeaderOptions, + IFooterOptions, +} from "./document/body/section-properties"; import { FooterWrapper } from "./footer-wrapper"; import { FootNotes } from "./footnotes"; import { HeaderWrapper } from "./header-wrapper"; @@ -14,7 +21,9 @@ import { Styles } from "./styles"; import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { DefaultStylesFactory } from "./styles/factory"; import { Table } from "./table"; -import { XmlComponent } from "./xml-components"; + +type DocumentHeaders = { header: HeaderWrapper; type: HeaderReferenceType }[]; +type DocumentFooters = { footer: FooterWrapper; type: FooterReferenceType }[]; export class File { private readonly document: Document; @@ -24,8 +33,9 @@ export class File { private readonly media: Media; private readonly docRelationships: Relationships; private readonly fileRelationships: Relationships; - private readonly headerWrapper: HeaderWrapper[] = []; - private readonly footerWrapper: FooterWrapper[] = []; + private readonly documentHeaders: DocumentHeaders = []; + private readonly documentFooters: DocumentFooters = []; + private readonly footNotes: FootNotes; private readonly contentTypes: ContentTypes; @@ -42,6 +52,11 @@ export class File { }; } + const templateDocument = options.templateDocument; + if (templateDocument) { + this.currentRelationshipId = templateDocument.currentRelationshipId + 1; + } + if (options.externalStyles) { const stylesFactory = new ExternalStylesFactory(); this.styles = stylesFactory.newInstance(options.externalStyles); @@ -52,6 +67,7 @@ export class File { this.coreProperties = new CoreProperties(options); this.numbering = new Numbering(); + this.docRelationships = new Relationships(); this.docRelationships.createRelationship( this.currentRelationshipId++, @@ -70,10 +86,26 @@ export class File { "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes", "footnotes.xml", ); + this.media = new Media(); - const header = this.createHeader(options.templateHeader); - const footer = this.createFooter(); + const templateHeaders = templateDocument && templateDocument.headers; + if (!templateHeaders) { + this.createHeader(); + } else { + for (let templateHeader of templateHeaders) { + this.addHeaderToDocument(templateHeader.header, templateHeader.type); + } + } + + const templateFooters = templateDocument && templateDocument.footers; + if (!templateFooters) { + this.createFooter(); + } else { + for (let templateFooter of templateFooters) { + this.addFooterToDocument(templateFooter.footer, templateFooter.type); + } + } this.fileRelationships = new Relationships(); this.fileRelationships.createRelationship( @@ -94,17 +126,34 @@ export class File { this.appProperties = new AppProperties(); this.footNotes = new FootNotes(); + + let headersOptions: IHeaderOptions[] = []; + for (let documentHeader of this.documentHeaders) { + headersOptions.push({ + headerId: documentHeader.header.Header.ReferenceId, + headerType: documentHeader.type, + }); + } + + let footersOptions: IFooterOptions[] = []; + for (let documentFooter of this.documentFooters) { + footersOptions.push({ + footerId: documentFooter.footer.Footer.ReferenceId, + footerType: documentFooter.type, + }); + } + if (!sectionPropertiesOptions) { sectionPropertiesOptions = { - footerType: FooterReferenceType.DEFAULT, - headerType: HeaderReferenceType.DEFAULT, - headerId: header.Header.ReferenceId, - footerId: footer.Footer.ReferenceId, + headers: headersOptions, + footers: footersOptions, }; - } else { - sectionPropertiesOptions.headerId = header.Header.ReferenceId; - sectionPropertiesOptions.footerId = footer.Footer.ReferenceId; } + else { + sectionPropertiesOptions.headers = headersOptions; + sectionPropertiesOptions.footers = footersOptions; + } + this.document = new Document(sectionPropertiesOptions); } @@ -170,30 +219,36 @@ export class File { this.footNotes.createFootNote(paragraph); } - public createHeader(templateHeader? : XmlComponent): HeaderWrapper { - const header = new HeaderWrapper(this.media, this.currentRelationshipId++, templateHeader); - console.log('\n\n-------\n\n'); - console.log('header', JSON.stringify(header.Header, null, 2)); - this.headerWrapper.push(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); + public createHeader(): HeaderWrapper { + const header = new HeaderWrapper(this.currentRelationshipId++); + this.addHeaderToDocument(header); return header; } + private addHeaderToDocument(header: HeaderWrapper, type: HeaderReferenceType = HeaderReferenceType.DEFAULT) { + this.documentHeaders.push({ header, type }); + this.docRelationships.createRelationship( + header.Header.ReferenceId, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header", + `header${this.documentHeaders.length}.xml`, + ); + this.contentTypes.addHeader(this.documentHeaders.length); + } + public createFooter(): FooterWrapper { - const footer = new FooterWrapper(this.media, this.currentRelationshipId++); - this.footerWrapper.push(footer); + const footer = new FooterWrapper(this.currentRelationshipId++); + this.addFooterToDocument(footer); + return footer; + } + + private addFooterToDocument(footer: FooterWrapper, type: FooterReferenceType = FooterReferenceType.DEFAULT) { + this.documentFooters.push({ footer, type }); this.docRelationships.createRelationship( footer.Footer.ReferenceId, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer", - `footer${this.footerWrapper.length}.xml`, + `footer${this.documentFooters.length}.xml`, ); - this.contentTypes.addFooter(this.footerWrapper.length); - return footer; + this.contentTypes.addFooter(this.documentFooters.length); } public createFirstPageHeader(): HeaderWrapper { @@ -238,15 +293,15 @@ export class File { } public get Header(): HeaderWrapper { - return this.headerWrapper[0]; + return this.documentHeaders[0].header; } public get Headers(): HeaderWrapper[] { - return this.headerWrapper; + return this.documentHeaders.map((item) => item.header); } public HeaderByRefNumber(refId: number): HeaderWrapper { - const entry = this.headerWrapper.find((h) => h.Header.ReferenceId === refId); + const entry = this.documentHeaders.map((item) => item.header).find((h) => h.Header.ReferenceId === refId); if (entry) { return entry; } @@ -254,15 +309,15 @@ export class File { } public get Footer(): FooterWrapper { - return this.footerWrapper[0]; + return this.documentFooters[0].footer; } public get Footers(): FooterWrapper[] { - return this.footerWrapper; + return this.documentFooters.map((item) => item.footer); } public FooterByRefNumber(refId: number): FooterWrapper { - const entry = this.footerWrapper.find((h) => h.Footer.ReferenceId === refId); + const entry = this.documentFooters.map((item) => item.footer).find((h) => h.Footer.ReferenceId === refId); if (entry) { return entry; } diff --git a/src/file/footer-wrapper.ts b/src/file/footer-wrapper.ts index afd673c2b2..f738d92a27 100644 --- a/src/file/footer-wrapper.ts +++ b/src/file/footer-wrapper.ts @@ -4,14 +4,17 @@ import { Image, Media } from "./media"; import { ImageParagraph, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { Table } from "./table"; +import { IMediaData } from 'file/media'; export class FooterWrapper { private readonly footer: Footer; private readonly relationships: Relationships; + public readonly media = new Media(); - constructor(private readonly media: Media, referenceId: number) { - this.footer = new Footer(referenceId); + constructor(referenceId: number, initContent? : XmlComponent) { + this.footer = new Footer(referenceId, initContent); this.relationships = new Relationships(); + } public addParagraph(paragraph: Paragraph): void { @@ -36,16 +39,22 @@ export class FooterWrapper { this.footer.addChildElement(childElement); } - public createImage(image: Buffer, width?: number, height?: number): void { - const mediaData = this.media.addMedia(image, this.relationships.RelationshipCount, width, height); + public addImageRelation(image: Buffer, refId : number, width?: number, height?: number) : IMediaData { + const mediaData = this.media.addMedia(image, refId, width, height); this.relationships.createRelationship( - mediaData.referenceId, + refId, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", `media/${mediaData.fileName}`, ); + return mediaData; + } + + public createImage(image: Buffer, width?: number, height?: number): void { + let mediaData = this.addImageRelation(image, this.relationships.RelationshipCount, width, height); this.addImage(new Image(new ImageParagraph(mediaData))); } + public addImage(image: Image): FooterWrapper { this.footer.addParagraph(image.Paragraph); return this; diff --git a/src/file/footer/footer.ts b/src/file/footer/footer.ts index 532e662c25..959540d083 100644 --- a/src/file/footer/footer.ts +++ b/src/file/footer/footer.ts @@ -7,8 +7,8 @@ import { FooterAttributes } from "./footer-attributes"; export class Footer extends XmlComponent { private readonly refId: number; - constructor(referenceNumber: number) { - super("w:ftr"); + constructor(referenceNumber: number, initContent? : XmlComponent) { + super("w:ftr", initContent); this.refId = referenceNumber; this.root.push( new FooterAttributes({ diff --git a/src/file/header-wrapper.ts b/src/file/header-wrapper.ts index 1ba399aeca..37b7981848 100644 --- a/src/file/header-wrapper.ts +++ b/src/file/header-wrapper.ts @@ -4,13 +4,16 @@ import { Image, Media } from "./media"; import { ImageParagraph, Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { Table } from "./table"; +import { IMediaData } from 'file/media'; export class HeaderWrapper { private readonly header: Header; private readonly relationships: Relationships; + public readonly media = new Media(); - constructor(private readonly media: Media, referenceId: number, initContent? : XmlComponent) { + // constructor(private readonly media: Media, referenceId: number, initContent? : XmlComponent) { + constructor(referenceId: number, initContent? : XmlComponent) { this.header = new Header(referenceId, initContent); this.relationships = new Relationships(); } @@ -37,13 +40,18 @@ export class HeaderWrapper { this.header.addChildElement(childElement); } - public createImage(image: Buffer, width?: number, height?: number): void { - const mediaData = this.media.addMedia(image, this.relationships.RelationshipCount, width, height); + public addImageRelation(image: Buffer, refId : number, width?: number, height?: number) : IMediaData { + const mediaData = this.media.addMedia(image, refId, width, height); this.relationships.createRelationship( - mediaData.referenceId, + refId, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", `media/${mediaData.fileName}`, ); + return mediaData; + } + + public createImage(image: Buffer, width?: number, height?: number): void { + let mediaData = this.addImageRelation(image, this.relationships.RelationshipCount, width, height); this.addImage(new Image(new ImageParagraph(mediaData))); } diff --git a/src/file/header/header-attributes.ts b/src/file/header/header-attributes.ts index e47271841c..9d7f8a7801 100644 --- a/src/file/header/header-attributes.ts +++ b/src/file/header/header-attributes.ts @@ -23,6 +23,17 @@ export interface IHeaderAttributesProperties { dcmitype?: string; xsi?: 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 { @@ -49,5 +60,16 @@ export class HeaderAttributes extends XmlAttributeComponent(); - if (initContent) { - console.log('\n\n-------\n\n'); - console.log('new root', JSON.stringify(initContent, null,2)); - console.log('\n\n-------\n\n'); - } - } public prepForXml(): IXmlableObject { @@ -30,7 +24,7 @@ export abstract class XmlComponent extends BaseXmlComponent { } return comp; }) - .filter((comp) => comp); // Exclude null, undefined, and empty strings + .filter((comp) => comp !== null); // Exclude null, undefined, and empty strings return { [this.rootKey]: children, }; diff --git a/src/importDocx/importDocx.ts b/src/importDocx/importDocx.ts index 9370771a68..73a0114933 100644 --- a/src/importDocx/importDocx.ts +++ b/src/importDocx/importDocx.ts @@ -1,27 +1,152 @@ import * as JSZip from "jszip"; import * as fastXmlParser from "fast-xml-parser"; import { convertToXmlComponent, parseOptions, ImportedXmlComponent } from "file/xml-components"; +import { HeaderWrapper } from 'file/header-wrapper'; +import { FooterWrapper } from 'file/footer-wrapper'; +import { HeaderReferenceType } from 'file/document/body/section-properties/header-reference'; +import { FooterReferenceType } from 'file/document/body/section-properties/footer-reference'; +// import { RelationshipType } from 'file/relationships/relationship/relationship'; + +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', +} + +interface DocumentRefs { + headers : {id : number, type: HeaderReferenceType}[], + footers : {id : number, type: FooterReferenceType}[] +} + +type RelationFileInfo = {id : number, targetFile: string, type: 'header' | 'footer' | 'image'}; + +type DocumentHeaders = {type : HeaderReferenceType, header : HeaderWrapper}[]; +type DocumentFooters = {type : FooterReferenceType, footer : FooterWrapper}[]; + +export interface TemplateDocument { + currentRelationshipId : number; + headers : DocumentHeaders, + footers : DocumentFooters, +} export class ImportDocx { + private currentRelationshipId: number = 1; + constructor() { } - read(data) : Promise { - return new Promise((resolve) => { - JSZip.loadAsync(data).then((zipContent) => { - let headerContent = zipContent['files']['word/header2.xml']; - - headerContent.async('text').then((xmlData : string) => { - console.log('\n\n-------\n\n'); - console.log('headerContent', JSON.stringify(xmlData, null, 2)); - console.log('\n\n-------\n\n'); - const jsonObj = fastXmlParser.parse(xmlData, parseOptions); - let xmlComp = convertToXmlComponent('w:hdr', jsonObj['w:hdr']) as ImportedXmlComponent; - resolve(xmlComp); - }) - }); - }) + + + async extract(data : Buffer) : Promise { + let zipContent = await JSZip.loadAsync(data); + + let documentContent = zipContent['files']['word/document.xml']; + const documentRefs : DocumentRefs = this.extractDocumentRefs(await documentContent.async('text')) + + let relationshipContent = zipContent['files']['word/_rels/document.xml.rels']; + const documentRelations : RelationFileInfo[] = this.findReferenceFiles(await relationshipContent.async('text')); + + let headers : DocumentHeaders = []; + for(let headerRef of documentRefs.headers) { + const headerKey = 'w:hdr'; + const relationFileInfo = documentRelations.find(rel => rel.id === headerRef.id); + if (relationFileInfo == null) { + throw `can not find target file for id ${headerRef.id}`; + } + + const xmlData = await zipContent['files'][`word/${relationFileInfo.targetFile}`].async('text'); + const xmlObj = fastXmlParser.parse(xmlData, parseOptions); + + let importedComp = convertToXmlComponent(headerKey, xmlObj[headerKey]) as ImportedXmlComponent; + + let header = new HeaderWrapper(this.currentRelationshipId++, importedComp); + await this.addImagesToWrapper(relationFileInfo, zipContent, header); + headers.push({type : headerRef.type, header}) + } + + let footers : DocumentFooters = []; + for(let footerRef of documentRefs.footers) { + const footerKey = 'w:ftr' + const relationFileInfo = documentRelations.find(rel => rel.id === footerRef.id); + if (relationFileInfo == null) { + throw `can not find target file for id ${footerRef.id}`; + } + const xmlData = await zipContent['files'][`word/${relationFileInfo.targetFile}`].async('text'); + const xmlObj = fastXmlParser.parse(xmlData, parseOptions); + let importedComp = convertToXmlComponent(footerKey, xmlObj[footerKey]) as ImportedXmlComponent; + + let footer = new FooterWrapper(this.currentRelationshipId++, importedComp); + await this.addImagesToWrapper(relationFileInfo, zipContent, footer); + footers.push({type : footerRef.type, footer}) + } + + let templateDocument : TemplateDocument = {headers, footers, currentRelationshipId : this.currentRelationshipId} + return templateDocument; } + + async addImagesToWrapper(relationFile : RelationFileInfo, zipContent, wrapper : HeaderWrapper | FooterWrapper) { + let wrapperImagesReferences : RelationFileInfo[] = []; + const refFile = zipContent['files'][`word/_rels/${relationFile.targetFile}.rels`]; + if (refFile) { + const xmlRef = await refFile.async('text'); + wrapperImagesReferences = this.findReferenceFiles(xmlRef).filter(r => r.type === 'image'); + } + for (let r of wrapperImagesReferences) { + const buffer = await zipContent['files'][`word/${r.targetFile}`].async('nodebuffer'); + wrapper.addImageRelation(buffer, r.id); + } + } + + + findReferenceFiles(xmlData : string) : RelationFileInfo[] { + const xmlObj = fastXmlParser.parse(xmlData, parseOptions); + const relationXmlArray = Array.isArray(xmlObj['Relationships']['Relationship']) ? xmlObj['Relationships']['Relationship'] : [xmlObj['Relationships']['Relationship']]; + const relations : RelationFileInfo[] = relationXmlArray + .map(item => { + return { + id : this.parseRefId(item['_attr']['Id']), + type : schemeToType[item['_attr']['Type']], + targetFile : item['_attr']['Target'] + } + }) + .filter(item => item.type != null) + return relations; + } + + extractDocumentRefs(xmlData : string) : DocumentRefs { + + const xmlObj = fastXmlParser.parse(xmlData, parseOptions); + const sectionProp = xmlObj['w:document']['w:body']['w:sectPr']; + + const headersXmlArray = Array.isArray(sectionProp['w:headerReference']) ? sectionProp['w:headerReference'] : [sectionProp['w:headerReference']]; + const headers = headersXmlArray + .map(item => { + return { + type : item['_attr']['w:type'], + id : this.parseRefId(item['_attr']['r:id']) + } + }); + + const footersXmlArray = Array.isArray(sectionProp['w:footerReference']) ? sectionProp['w:footerReference'] : [sectionProp['w:footerReference']]; + const footers = footersXmlArray + .map(item => { + return { + type : item['_attr']['w:type'], + id : this.parseRefId(item['_attr']['r:id']) + } + }); + + return {headers, footers} + } + + parseRefId(str : string) : number { + let match = /^rId(\d+)$/.exec(str); + if (match == null) { + throw 'invalid ref id'; + } + return parseInt(match[1]); + } + } diff --git a/src/importDocx/simple.dotx b/src/importDocx/simple.dotx deleted file mode 100644 index cc25525149..0000000000 Binary files a/src/importDocx/simple.dotx and /dev/null differ