From 24f60df94d755689493b761340d16d090c8b1b16 Mon Sep 17 00:00:00 2001 From: privateOmega Date: Tue, 1 Dec 2020 11:09:00 +0530 Subject: [PATCH] feat: added footer support --- index.js | 13 ++++++- src/docx-document.js | 81 +++++++++++++++++++++++++++++++++++++++ src/helpers/namespaces.js | 1 + src/html-to-docx.js | 32 +++++++++++++++- 4 files changed, 123 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 68f79936..00ea92e0 100644 --- a/index.js +++ b/index.js @@ -23,19 +23,28 @@ const minifyHTMLString = (htmlString) => { } }; -async function generateContainer(htmlString, headerHTMLString, documentOptions = {}) { +async function generateContainer( + htmlString, + headerHTMLString, + documentOptions = {}, + footerHTMLString +) { const zip = new JSZip(); let contentHTML = htmlString; let headerHTML = headerHTMLString; + let footerHTML = footerHTMLString; if (htmlString) { contentHTML = minifyHTMLString(contentHTML); } if (headerHTMLString) { headerHTML = minifyHTMLString(headerHTML); } + if (footerHTMLString) { + footerHTML = minifyHTMLString(footerHTML); + } - addFilesToContainer(zip, contentHTML, documentOptions, headerHTML); + addFilesToContainer(zip, contentHTML, documentOptions, headerHTML, footerHTML); const buffer = await zip.generateAsync({ type: 'arraybuffer' }); if (Object.prototype.hasOwnProperty.call(global, 'Blob')) { diff --git a/src/docx-document.js b/src/docx-document.js index 22de39aa..f79438e7 100644 --- a/src/docx-document.js +++ b/src/docx-document.js @@ -53,6 +53,8 @@ class DocxDocument { modifiedAt, headerType, header, + footerType, + footer, font, fontSize, complexScriptFontSize, @@ -82,6 +84,8 @@ class DocxDocument { this.modifiedAt = modifiedAt || new Date(); this.headerType = headerType || 'default'; this.header = header || false; + this.footerType = footerType || 'default'; + this.footer = footer || false; this.font = font || 'Times New Roman'; this.fontSize = fontSize || 22; this.complexScriptFontSize = complexScriptFontSize || 22; @@ -90,12 +94,14 @@ class DocxDocument { this.lastNumberingId = 0; this.lastMediaId = 0; this.lastHeaderId = 0; + this.lastFooterId = 0; this.stylesObjects = []; this.numberingObjects = []; this.relationshipFilename = 'document'; this.relationships = [{ fileName: 'document', lastRelsId: 4, rels: [] }]; this.mediaFiles = []; this.headerObjects = []; + this.footerObjects = []; this.documentXML = null; this.generateContentTypesXML = this.generateContentTypesXML.bind(this); @@ -110,6 +116,7 @@ class DocxDocument { this.createMediaFile = this.createMediaFile.bind(this); this.createDocumentRelationships = this.createDocumentRelationships.bind(this); this.generateHeaderXML = this.generateHeaderXML.bind(this); + this.generateFooterXML = this.generateFooterXML.bind(this); } generateContentTypesXML() { @@ -135,6 +142,26 @@ class DocxDocument { } ); } + if (this.footerObjects && Array.isArray(this.footerObjects) && this.footerObjects.length) { + this.footerObjects.forEach( + // eslint-disable-next-line array-callback-return + ({ footerId }) => { + const contentTypesFragment = fragment({ + defaultNamespace: { + ele: 'http://schemas.openxmlformats.org/package/2006/content-types', + }, + }) + .ele('Override') + .att('PartName', `/word/footer${footerId}.xml`) + .att( + 'ContentType', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml' + ) + .up(); + contentTypesXML.root().import(contentTypesFragment); + } + ); + } return contentTypesXML.toString({ prettyPrint: true }); } @@ -192,6 +219,33 @@ class DocxDocument { documentXML.root().first().first().import(headerXmlFragment); } + if ( + this.footer && + this.footerObjects && + Array.isArray(this.footerObjects) && + this.footerObjects.length + ) { + const footerXmlFragment = fragment(); + + this.footerObjects.forEach( + // eslint-disable-next-line array-callback-return + ({ relationshipId, type }) => { + const footerFragment = fragment({ + namespaceAlias: { + w: namespaces.w, + r: namespaces.r, + }, + }) + .ele('@w', 'footerReference') + .att('@r', 'id', `rId${relationshipId}`) + .att('@w', 'type', type) + .up(); + footerXmlFragment.import(footerFragment); + } + ); + + documentXML.root().first().first().import(footerXmlFragment); + } return documentXML.toString({ prettyPrint: true }); } @@ -414,6 +468,9 @@ class DocxDocument { case 'header': relationshipType = namespaces.headers; break; + case 'footer': + relationshipType = namespaces.footers; + break; default: break; } @@ -451,6 +508,30 @@ class DocxDocument { return { headerId: this.lastHeaderId, headerXML }; } + + generateFooterXML(vTree) { + const footerXML = create({ + encoding: 'UTF-8', + standalone: true, + namespaceAlias: { + w: namespaces.w, + ve: namespaces.ve, + o: namespaces.o, + r: namespaces.r, + v: namespaces.v, + wp: namespaces.wp, + w10: namespaces.w10, + }, + }).ele('@w', 'ftr'); + + const XMLFragment = fragment(); + convertVTreeToXML(this, vTree, XMLFragment); + footerXML.root().import(XMLFragment); + + this.lastFooterId += 1; + + return { footerId: this.lastFooterId, footerXML }; + } } export default DocxDocument; diff --git a/src/helpers/namespaces.js b/src/helpers/namespaces.js index 2dd958b2..e4bfdf7f 100644 --- a/src/helpers/namespaces.js +++ b/src/helpers/namespaces.js @@ -22,6 +22,7 @@ const namespaces = { images: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', styles: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', headers: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header', + footers: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer', coreProperties: 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties', officeDocumentRelation: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', diff --git a/src/html-to-docx.js b/src/html-to-docx.js index ac73fb85..a9cb2b5f 100644 --- a/src/html-to-docx.js +++ b/src/html-to-docx.js @@ -43,6 +43,8 @@ const defaultDocumentOptions = { modifiedAt: new Date(), headerType: 'default', header: false, + footerType: 'default', + footer: false, font: 'Times New Roman', fontSize: 22, complexScriptFontSize: 22, @@ -120,7 +122,13 @@ const normalizeDocumentOptions = (documentOptions) => { // Ref: https://en.wikipedia.org/wiki/Office_Open_XML_file_formats // http://officeopenxml.com/anatomyofOOXML.php // eslint-disable-next-line import/prefer-default-export -export function addFilesToContainer(zip, htmlString, suppliedDocumentOptions, headerHTMLString) { +export function addFilesToContainer( + zip, + htmlString, + suppliedDocumentOptions, + headerHTMLString, + footerHTMLString +) { const normalizedDocumentOptions = normalizeDocumentOptions(suppliedDocumentOptions); const documentOptions = mergeOptions(defaultDocumentOptions, normalizedDocumentOptions); @@ -158,7 +166,27 @@ export function addFilesToContainer(zip, htmlString, suppliedDocumentOptions, he createFolders: false, }); - docxDocument.headerObjects.push({ headerId, relationshipId, type: 'default' }); + docxDocument.headerObjects.push({ headerId, relationshipId, type: docxDocument.headerType }); + } + if (docxDocument.footer && footerHTMLString) { + const vTree = convertHTML(footerHTMLString); + + docxDocument.relationshipFilename = 'footer1'; + const { footerId, footerXML } = docxDocument.generateFooterXML(vTree); + docxDocument.relationshipFilename = 'document'; + + const relationshipId = docxDocument.createDocumentRelationships( + docxDocument.relationshipFilename, + 'footer', + `footer${footerId}.xml`, + 'Internal' + ); + + zip.folder('word').file(`footer${footerId}.xml`, footerXML.toString({ prettyPrint: true }), { + createFolders: false, + }); + + docxDocument.footerObjects.push({ footerId, relationshipId, type: docxDocument.footerType }); } zip