diff --git a/demo/demo30.ts b/demo/demo30.ts index d5d2300c4c..a587e42b7a 100644 --- a/demo/demo30.ts +++ b/demo/demo30.ts @@ -12,7 +12,7 @@ fs.readFile(filePath, (err, data) => { importDotx.extract(data).then((templateDocument) => { // This any needs fixing const sectionProps = { - titlePage: true, + titlePage: templateDocument.titlePageIsDefined, } as any; const doc = new Document(undefined, sectionProps, { diff --git a/src/file/footer-wrapper.ts b/src/file/footer-wrapper.ts index 99cff08f20..785f2724e4 100644 --- a/src/file/footer-wrapper.ts +++ b/src/file/footer-wrapper.ts @@ -55,6 +55,15 @@ export class FooterWrapper { 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, width?: number, height?: number): void { const mediaData = this.addImageRelationship(image, this.relationships.RelationshipCount, width, height); this.addImage(new Image(new ImageParagraph(mediaData))); diff --git a/src/file/header-wrapper.ts b/src/file/header-wrapper.ts index c2ed4841f1..beb7903cf5 100644 --- a/src/file/header-wrapper.ts +++ b/src/file/header-wrapper.ts @@ -55,6 +55,15 @@ export class HeaderWrapper { 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, width?: number, height?: number): void { const mediaData = this.addImageRelationship(image, this.relationships.RelationshipCount, width, height); this.addImage(new Image(new ImageParagraph(mediaData))); diff --git a/src/import-dotx/import-dotx.ts b/src/import-dotx/import-dotx.ts index 940ecd2f10..4dbe01be56 100644 --- a/src/import-dotx/import-dotx.ts +++ b/src/import-dotx/import-dotx.ts @@ -20,6 +20,7 @@ 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 { @@ -29,8 +30,8 @@ interface IDocumentRefs { interface IRelationshipFileInfo { id: number; - targetFile: string; - type: "header" | "footer" | "image"; + target: string; + type: "header" | "footer" | "image" | "hyperlink"; } // Document Template @@ -40,6 +41,7 @@ export interface IDocumentTemplate { headers: IDocumentHeader[]; footers: IDocumentFooter[]; styles: Styles; + titlePageIsDefined: boolean; } export class ImportDotx { @@ -58,6 +60,7 @@ export class ImportDotx { 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")); @@ -70,13 +73,13 @@ export class ImportDotx { throw new Error(`Can not find target file for id ${headerRef.id}`); } - const xmlData = await zipContent.files[`word/${relationFileInfo.targetFile}`].async("text"); + const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text"); const xmlObj = fastXmlParser.parse(xmlData, importParseOptions); const importedComp = convertToXmlComponent(headerKey, xmlObj[headerKey]) as ImportedXmlComponent; const header = new HeaderWrapper(this.currentRelationshipId++, importedComp); - await this.addImagesToWrapper(relationFileInfo, zipContent, header); + await this.addRelationToWrapper(relationFileInfo, zipContent, header); headers.push({ type: headerRef.type, header }); } @@ -87,34 +90,45 @@ export class ImportDotx { if (relationFileInfo === null || !relationFileInfo) { throw new Error(`Can not find target file for id ${footerRef.id}`); } - const xmlData = await zipContent.files[`word/${relationFileInfo.targetFile}`].async("text"); + const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text"); const xmlObj = fastXmlParser.parse(xmlData, importParseOptions); const importedComp = convertToXmlComponent(footerKey, xmlObj[footerKey]) as ImportedXmlComponent; const footer = new FooterWrapper(this.currentRelationshipId++, importedComp); - await this.addImagesToWrapper(relationFileInfo, zipContent, footer); + await this.addRelationToWrapper(relationFileInfo, zipContent, footer); footers.push({ type: footerRef.type, footer }); } - const templateDocument: IDocumentTemplate = { headers, footers, currentRelationshipId: this.currentRelationshipId, styles }; + const templateDocument: IDocumentTemplate = { + headers, + footers, + currentRelationshipId: this.currentRelationshipId, + styles, + titlePageIsDefined, + }; return templateDocument; } - public async addImagesToWrapper( + public async addRelationToWrapper( relationFile: IRelationshipFileInfo, zipContent: JSZip, wrapper: HeaderWrapper | FooterWrapper, ): Promise { let wrapperImagesReferences: IRelationshipFileInfo[] = []; - const refFile = zipContent.files[`word/_rels/${relationFile.targetFile}.rels`]; + let hyperLinkReferences: IRelationshipFileInfo[] = []; + const refFile = zipContent.files[`word/_rels/${relationFile.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.targetFile}`].async("nodebuffer"); + 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[] { @@ -127,7 +141,7 @@ export class ImportDotx { return { id: this.parseRefId(item._attr.Id), type: schemeToType[item._attr.Type], - targetFile: item._attr.Target, + target: item._attr.Target, }; }) .filter((item) => item.type !== null); @@ -135,12 +149,21 @@ export class ImportDotx { } 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 headersXmlArray = Array.isArray(sectionProp["w:headerReference"]) - ? sectionProp["w:headerReference"] - : [sectionProp["w:headerReference"]]; + 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"], @@ -148,9 +171,16 @@ export class ImportDotx { }; }); - const footersXmlArray = Array.isArray(sectionProp["w:footerReference"]) - ? sectionProp["w:footerReference"] - : [sectionProp["w:footerReference"]]; + 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"], @@ -161,6 +191,12 @@ export class ImportDotx { 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) {