diff --git a/demo/dotx/a.dotx b/demo/dotx/a.dotx deleted file mode 100644 index 0d47cfd0fd..0000000000 Binary files a/demo/dotx/a.dotx and /dev/null differ diff --git a/demo/dotx/ab.dotx b/demo/dotx/ab.dotx deleted file mode 100644 index e38fafd70c..0000000000 Binary files a/demo/dotx/ab.dotx and /dev/null differ diff --git a/demo/dotx/c.dotx b/demo/dotx/c.dotx deleted file mode 100644 index 4bce9d6700..0000000000 Binary files a/demo/dotx/c.dotx and /dev/null differ diff --git a/demo/dotx/d.dotx b/demo/dotx/d.dotx deleted file mode 100644 index d352339103..0000000000 Binary files a/demo/dotx/d.dotx and /dev/null differ diff --git a/demo/dotx/e.dotx b/demo/dotx/e.dotx deleted file mode 100644 index bc1b6f68ff..0000000000 Binary files a/demo/dotx/e.dotx and /dev/null differ diff --git a/demo/dotx/f.dotx b/demo/dotx/f.dotx deleted file mode 100644 index 6a60190d6c..0000000000 Binary files a/demo/dotx/f.dotx and /dev/null differ diff --git a/package.json b/package.json index e2c9a731bc..56b5b1951c 100644 --- a/package.json +++ b/package.json @@ -49,10 +49,11 @@ "dependencies": { "@types/image-size": "0.0.29", "@types/jszip": "^3.1.4", - "fast-xml-parser": "^3.3.6", "image-size": "^0.6.2", "jszip": "^3.1.5", - "xml": "^1.0.1" + "npm": "^6.4.1", + "xml": "^1.0.1", + "xml-js": "^1.6.8" }, "author": "Dolan Miu", "license": "MIT", diff --git a/src/file/file.ts b/src/file/file.ts index addca15c8f..a4e43cb961 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -283,16 +283,19 @@ export class File { ...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, diff --git a/src/file/styles/external-styles-factory.ts b/src/file/styles/external-styles-factory.ts index 301211bdc7..cbfa4a3c90 100644 --- a/src/file/styles/external-styles-factory.ts +++ b/src/file/styles/external-styles-factory.ts @@ -1,5 +1,5 @@ -import * as fastXmlParser from "fast-xml-parser"; -import { convertToXmlComponent, ImportedRootElementAttributes, parseOptions } from "file/xml-components"; +import { convertToXmlComponent, ImportedRootElementAttributes, ImportedXmlComponent } from "file/xml-components"; +import { Element as XMLElement, xml2js } from "xml-js"; import { Styles } from "./"; export class ExternalStylesFactory { @@ -25,25 +25,24 @@ export class ExternalStylesFactory { * * @param externalStyles context from styles.xml */ - public newInstance(externalStyles: string): Styles { - const xmlStyles = fastXmlParser.parse(externalStyles, parseOptions)["w:styles"]; - // create styles with attributes from the parsed xml - const importedStyle = new Styles(new ImportedRootElementAttributes(xmlStyles._attr)); + public newInstance(xmlData: string): Styles { + const xmlObj = xml2js(xmlData, { compact: false }) as XMLElement; - // convert other elements (not styles definitions, but default styles and so on ...) - Object.keys(xmlStyles) - .filter((element) => element !== "_attr" && element !== "w:style") - .forEach((element) => { - const converted = convertToXmlComponent(element, xmlStyles[element]); - if (Array.isArray(converted)) { - converted.forEach((c) => importedStyle.push(c)); - } else { - importedStyle.push(converted); - } - }); + let stylesXmlElement: XMLElement | undefined; + for (const xmlElm of xmlObj.elements || []) { + if (xmlElm.name === "w:styles") { + stylesXmlElement = xmlElm; + } + } + if (stylesXmlElement === undefined) { + throw new Error("can not find styles element"); + } - // convert the styles one by one - xmlStyles["w:style"].map((style) => convertToXmlComponent("w:style", style)).forEach(importedStyle.push.bind(importedStyle)); + const importedStyle = new Styles(new ImportedRootElementAttributes(stylesXmlElement.attributes)); + const stylesElements = stylesXmlElement.elements || []; + for (const childElm of stylesElements) { + importedStyle.push(convertToXmlComponent(childElm) as ImportedXmlComponent); + } return importedStyle; } } diff --git a/src/file/xml-components/imported-xml-component.ts b/src/file/xml-components/imported-xml-component.ts index f600b062ea..8fa8f9746b 100644 --- a/src/file/xml-components/imported-xml-component.ts +++ b/src/file/xml-components/imported-xml-component.ts @@ -1,82 +1,30 @@ // tslint:disable:no-any -// import * as fastXmlParser from "fast-xml-parser"; -import { flatMap } from "lodash"; +import { Element as XmlElement } from "xml-js"; import { IXmlableObject, XmlComponent } from "."; -import { Element } from 'xml-js'; - -export const parseOptions = { - ignoreAttributes: false, - attributeNamePrefix: "", - attrNodeName: "_attr", -}; - - -export function convertToXmlComponentOld(elementName: string, element: any): ImportedXmlComponent | ImportedXmlComponent[] { - const xmlElement = new ImportedXmlComponent(elementName, element._attr); - if (Array.isArray(element)) { - const out: any[] = []; - element.forEach((itemInArray) => { - out.push(convertToXmlComponentOld(elementName, itemInArray)); - }); - return flatMap(out); - } else if (typeof element === "object") { - Object.keys(element) - .filter((key) => key !== "_attr") - .map((item) => convertToXmlComponentOld(item, element[item])) - .forEach((converted) => { - if (Array.isArray(converted)) { - converted.forEach(xmlElement.push.bind(xmlElement)); - } else { - xmlElement.push(converted); - } - }); - } else if (element !== "") { - xmlElement.push(element); - } - return xmlElement; -} - /** * Converts the given xml element (in json format) into XmlComponent. - * Note: If element is array, them it will return ImportedXmlComponent[]. Example for given: - * element = [ - * { w:t: "val 1"}, - * { w:t: "val 2"} - * ] - * will return - * [ - * ImportedXmlComponent { rootKey: "w:t", root: [ "val 1" ]}, - * ImportedXmlComponent { rootKey: "w:t", root: [ "val 2" ]} - * ] - * - * @param elementName name (rootKey) of the XmlComponent * @param element the xml element in json presentation */ -export function convertToXmlComponent(elementName: string, element: Element): ImportedXmlComponent { - const xmlComponent = new ImportedXmlComponent(elementName, element.attributes); - if (element.elements) { - for (const child of element.elements) { - if (child.type === undefined) { - continue; - } - switch (child.type) { - case 'element': - if (child.name === undefined) { - continue; - } - xmlComponent.push(convertToXmlComponent(child.name, child)); - break; - case 'text': - xmlComponent.push((child.text)); - break; - } - - } - } - return xmlComponent; -} +export function convertToXmlComponent(element: XmlElement): ImportedXmlComponent | string | undefined { + switch (element.type) { + case "element": + const xmlComponent = new ImportedXmlComponent(element.name as string, element.attributes); + const childElments = element.elements || []; + for (const childElm of childElments) { + const child = convertToXmlComponent(childElm); + if (child !== undefined) { + xmlComponent.push(child); + } + } + return xmlComponent; + case "text": + return element.text as string; + default: + return undefined; + } +} /** * Represents imported xml component from xml file. diff --git a/src/file/xml-components/xmlable-object.ts b/src/file/xml-components/xmlable-object.ts index 255df14fc2..33d41e2af1 100644 --- a/src/file/xml-components/xmlable-object.ts +++ b/src/file/xml-components/xmlable-object.ts @@ -1,5 +1,8 @@ +export interface IXmlAttribute { + [key: string]: string | number | boolean; +} export interface IXmlableObject extends Object { - _attr?: { [key: string]: string | number | boolean }; + _attr?: IXmlAttribute; } // Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432 diff --git a/src/import-dotx/import-dotx.ts b/src/import-dotx/import-dotx.ts index 72fb6c2a27..b170f15aec 100644 --- a/src/import-dotx/import-dotx.ts +++ b/src/import-dotx/import-dotx.ts @@ -1,24 +1,15 @@ -import * as fastXmlParser from "fast-xml-parser"; -import { xml2js, Element as XMLElement } from 'xml-js' -// var convertXmlJs = require('xml-js'); import * as JSZip from "jszip"; - +import { Element as XMLElement, ElementCompact as XMLElementCompact, xml2js } from "xml-js"; 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 { convertToXmlComponent, ImportedXmlComponent, parseOptions, convertToXmlComponentOld } from "file/xml-components"; +import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components"; import { Styles } from "file/styles"; import { ExternalStylesFactory } from "file/styles/external-styles-factory"; -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", @@ -70,50 +61,46 @@ export class ImportDotx { const headers: IDocumentHeader[] = []; for (const headerRef of documentRefs.headers) { - const headerKey = "w:hdr"; const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id); if (relationFileInfo === null || !relationFileInfo) { throw new Error(`Can not find target file for id ${headerRef.id}`); } const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text"); - const xmlObjOld = fastXmlParser.parse(xmlData, importParseOptions); - const xmlObj = xml2js(xmlData, {compact: false}) as XMLElement; - if (xmlObj.elements === undefined || xmlObj.elements[0].name !== headerKey) { + const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement; + let headerXmlElement: XMLElement | undefined; + for (const xmlElm of xmlObj.elements || []) { + if (xmlElm.name === "w:hdr") { + headerXmlElement = xmlElm; + } + } + if (headerXmlElement === undefined) { continue; } - - console.log('=========== fast-xml-parser header ======='); - console.log(JSON.stringify(xmlObjOld, null, 2)); - console.log('=========== xml-js header ======='); - console.log(JSON.stringify(xmlObj, null, 2)); - - const headerXmlElement = xmlObj.elements[0]; - const importedComp = convertToXmlComponent(headerKey, headerXmlElement) as ImportedXmlComponent; - console.log('=========== importedComp header ======='); - console.log(JSON.stringify(importedComp, null, 2)); - - const importedCompOld = convertToXmlComponentOld(headerKey, xmlObjOld[headerKey]) as ImportedXmlComponent; - console.log('=========== importedCompOld header ======='); - console.log(JSON.stringify(importedCompOld, null, 2)); - - const header = new HeaderWrapper(this.currentRelationshipId++, importedCompOld); - // const header = new HeaderWrapper(this.currentRelationshipId++, importedComp); + const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent; + const header = new HeaderWrapper(this.currentRelationshipId++, importedComp); await this.addRelationToWrapper(relationFileInfo, zipContent, header); headers.push({ type: headerRef.type, header }); } const footers: IDocumentFooter[] = []; for (const footerRef of documentRefs.footers) { - const footerKey = "w:ftr"; const relationFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id); if (relationFileInfo === null || !relationFileInfo) { throw new Error(`Can not find target file for id ${footerRef.id}`); } 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 xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement; + let footerXmlElement: XMLElement | undefined; + for (const xmlElm of xmlObj.elements || []) { + if (xmlElm.name === "w:ftr") { + footerXmlElement = xmlElm; + } + } + if (footerXmlElement === undefined) { + continue; + } + const importedComp = convertToXmlComponent(footerXmlElement) as ImportedXmlComponent; const footer = new FooterWrapper(this.currentRelationshipId++, importedComp); await this.addRelationToWrapper(relationFileInfo, zipContent, footer); footers.push({ type: footerRef.type, footer }); @@ -152,16 +139,19 @@ export class ImportDotx { } public findReferenceFiles(xmlData: string): IRelationshipFileInfo[] { - const xmlObj = fastXmlParser.parse(xmlData, importParseOptions); + const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact; const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship) ? xmlObj.Relationships.Relationship : [xmlObj.Relationships.Relationship]; const relations: IRelationshipFileInfo[] = relationXmlArray - .map((item) => { + .map((item: XMLElementCompact) => { + if (item._attributes === undefined) { + throw Error("relationship element has no attributes"); + } return { - id: this.parseRefId(item._attr.Id), - type: schemeToType[item._attr.Type], - target: item._attr.Target, + id: this.parseRefId(item._attributes.Id as string), + type: schemeToType[item._attributes.Type as string], + target: item._attributes.Target as string, }; }) .filter((item) => item.type !== null); @@ -169,14 +159,11 @@ export class ImportDotx { } public extractDocumentRefs(xmlData: string): IDocumentRefs { - interface IAttributedXML { - _attr: object; - } - const xmlObj = fastXmlParser.parse(xmlData, importParseOptions); + const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact; const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"]; - const headerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:headerReference"]; - let headersXmlArray: IAttributedXML[]; + const headerProps: XMLElementCompact = sectionProp["w:headerReference"]; + let headersXmlArray: XMLElementCompact[]; if (headerProps === undefined) { headersXmlArray = []; } else if (Array.isArray(headerProps)) { @@ -185,14 +172,17 @@ export class ImportDotx { headersXmlArray = [headerProps]; } const headers = headersXmlArray.map((item) => { + if (item._attributes === undefined) { + throw Error("header referecne element has no attributes"); + } return { - type: item._attr["w:type"], - id: this.parseRefId(item._attr["r:id"]), + type: item._attributes["w:type"] as HeaderReferenceType, + id: this.parseRefId(item._attributes["r:id"] as string), }; }); - const footerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:footerReference"]; - let footersXmlArray: IAttributedXML[]; + const footerProps: XMLElementCompact = sectionProp["w:footerReference"]; + let footersXmlArray: XMLElementCompact[]; if (footerProps === undefined) { footersXmlArray = []; } else if (Array.isArray(footerProps)) { @@ -202,9 +192,12 @@ export class ImportDotx { } const footers = footersXmlArray.map((item) => { + if (item._attributes === undefined) { + throw Error("footer referecne element has no attributes"); + } return { - type: item._attr["w:type"], - id: this.parseRefId(item._attr["r:id"]), + type: item._attributes["w:type"] as FooterReferenceType, + id: this.parseRefId(item._attributes["r:id"] as string), }; }); @@ -212,7 +205,7 @@ export class ImportDotx { } public titlePageIsDefined(xmlData: string): boolean { - const xmlObj = fastXmlParser.parse(xmlData, importParseOptions); + const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact; const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"]; return sectionProp["w:titlePg"] !== undefined; }