import { CustomTagStyleMap, HTMLString, IExportDoc, IExportOption, Node, } from './types'; import { D_Layout, D_TagStyleMap } from './default'; import { Document, Footer, Header, Packer, Paragraph } from 'docx'; import { isFilledArray, trimHtml, calcMargin } from './utils'; import { tableNodeToITableOptions } from './builder/table'; import JSZip from 'jszip'; import { parse } from 'html-to-ast'; import { saveAs } from 'file-saver'; import { contentBuilder } from './builder'; export const getInnerTextNode = (node: Node) => { let inner = node; while (inner && inner.children && inner.children.length === 1) { inner = inner.children[0]; } return inner; }; // recursion chain style export const chainStyle = ( nodeList: Node[], style: string[] = [], tagStyleMap: CustomTagStyleMap = D_TagStyleMap ) => { if (!nodeList || !isFilledArray(nodeList)) return; nodeList.forEach((node) => { const { attrs, children, name } = node; let STYLE: string[] = typeof attrs?.style === 'string' ? [attrs.style, ...style] : style; const shape = name ? [name, ...STYLE] : [...STYLE]; node.shape = shape; if (isFilledArray(children)) { chainStyle(children, shape, tagStyleMap); } }); }; // style builder export const StyleBuilder = ( list: Node[], tagStyleMap: CustomTagStyleMap = D_TagStyleMap ) => { const nList = [...list]; chainStyle(nList, [], tagStyleMap); return nList; }; // element creator export const ElementCreator = async ( astList: Node[], tagStyleMap: CustomTagStyleMap = D_TagStyleMap ): Promise => { if (!astList || astList.length === 0) return []; const tags = StyleBuilder( astList.filter((n: Node) => n.type === 'tag'), tagStyleMap ); if (!tags) return []; const ps: Paragraph[] = []; for (let node of tags) { const p = await contentBuilder(node, tagStyleMap); if (p) { ps.push(p as Paragraph); } } // const ps = tags // .map((node: Node) => { // return contentBuilder(node, tagStyleMap); // }) // .filter(Boolean); return [...ps] as Paragraph[]; }; // parse html string into Node list export const htmlToAST = (html: string): Node[] => { return parse(html) as Node[]; }; // generate Document export const genDocument = async ( html: HTMLString, options?: IExportOption ) => { const layoutOp = options?.layout || {}; const layout = { ...D_Layout, ...layoutOp }; const styleMap = options?.tagStyleMap || D_TagStyleMap; const ast: Node[] = htmlToAST(html); const paragraphs = await ElementCreator(ast, styleMap); const { orientation, topMargin, leftMargin, rightMargin, bottomMargin, header, footer, } = layout; const margin = { top: calcMargin(topMargin), left: calcMargin(leftMargin), right: calcMargin(rightMargin), bottom: calcMargin(bottomMargin), }; const page = { margin, size: { orientation, }, }; const section = { properties: { page, }, children: paragraphs, headers: {}, footers: {}, }; if (header) { const ast = parse(header) as Node[]; section.headers = { default: new Header({ children: await ElementCreator(ast, styleMap), }), }; } if (footer) { const ast = parse(footer) as Node[]; section.footers = { default: new Footer({ children: await ElementCreator(ast, styleMap), }), }; } const doc = new Document({ styles: { paragraphStyles: [], }, sections: [section], }); return doc; }; // export html as docx file export const exportAsDocx = async (doc: Document, docName = '') => { Packer.toBlob(doc).then((blob) => { saveAs(blob, `${docName}.docx`); }); }; // html -> docx export const exportHtmlToDocx = async ( html: HTMLString, docName = 'doc', options?: IExportOption ) => { const doc = await genDocument(trimHtml(html), options); exportAsDocx(doc, docName); return doc; }; // export multi files as .zip export const exportMultiDocsAsZip = async ( docList: IExportDoc[], fileName = 'docs', export_option?: IExportOption ) => { const zip = new JSZip(); const len = docList.length; if (len === 1) { const d = docList[0]; const { html, name, option } = d; const file = await genDocument(trimHtml(html), option || export_option); exportAsDocx(file, name); return; } for (let docFile of docList) { const { html, name, option } = docFile; const doc = await genDocument(trimHtml(html), option || export_option); const file = await Packer.toBlob(doc); zip.file(`${name}.docx`, file); } zip.generateAsync({ type: 'blob' }).then((content) => { saveAs(content, `${fileName}.zip`); }); }; export const exportAsZip = exportMultiDocsAsZip; export { IExportDoc, IExportOption }; export { parse, tableNodeToITableOptions, D_Layout, D_TagStyleMap };