This commit is contained in:
amitm02
2018-10-17 09:15:32 +03:00
parent 9d9dd62f00
commit 3f3fd05cb1
12 changed files with 94 additions and 147 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -49,10 +49,11 @@
"dependencies": { "dependencies": {
"@types/image-size": "0.0.29", "@types/image-size": "0.0.29",
"@types/jszip": "^3.1.4", "@types/jszip": "^3.1.4",
"fast-xml-parser": "^3.3.6",
"image-size": "^0.6.2", "image-size": "^0.6.2",
"jszip": "^3.1.5", "jszip": "^3.1.5",
"xml": "^1.0.1" "npm": "^6.4.1",
"xml": "^1.0.1",
"xml-js": "^1.6.8"
}, },
"author": "Dolan Miu", "author": "Dolan Miu",
"license": "MIT", "license": "MIT",

View File

@ -283,16 +283,19 @@ export class File {
...newGroup, ...newGroup,
default: header.header, default: header.header,
}; };
break;
case HeaderReferenceType.FIRST: case HeaderReferenceType.FIRST:
newGroup = { newGroup = {
...newGroup, ...newGroup,
first: header.header, first: header.header,
}; };
break;
case HeaderReferenceType.EVEN: case HeaderReferenceType.EVEN:
newGroup = { newGroup = {
...newGroup, ...newGroup,
even: header.header, even: header.header,
}; };
break;
default: default:
newGroup = { newGroup = {
...newGroup, ...newGroup,

View File

@ -1,5 +1,5 @@
import * as fastXmlParser from "fast-xml-parser"; import { convertToXmlComponent, ImportedRootElementAttributes, ImportedXmlComponent } from "file/xml-components";
import { convertToXmlComponent, ImportedRootElementAttributes, parseOptions } from "file/xml-components"; import { Element as XMLElement, xml2js } from "xml-js";
import { Styles } from "./"; import { Styles } from "./";
export class ExternalStylesFactory { export class ExternalStylesFactory {
@ -25,25 +25,24 @@ export class ExternalStylesFactory {
* </w:styles> * </w:styles>
* @param externalStyles context from styles.xml * @param externalStyles context from styles.xml
*/ */
public newInstance(externalStyles: string): Styles { public newInstance(xmlData: string): Styles {
const xmlStyles = fastXmlParser.parse(externalStyles, parseOptions)["w:styles"]; const xmlObj = xml2js(xmlData, { compact: false }) as XMLElement;
// create styles with attributes from the parsed xml
const importedStyle = new Styles(new ImportedRootElementAttributes(xmlStyles._attr));
// convert other elements (not styles definitions, but default styles and so on ...) let stylesXmlElement: XMLElement | undefined;
Object.keys(xmlStyles) for (const xmlElm of xmlObj.elements || []) {
.filter((element) => element !== "_attr" && element !== "w:style") if (xmlElm.name === "w:styles") {
.forEach((element) => { stylesXmlElement = xmlElm;
const converted = convertToXmlComponent(element, xmlStyles[element]); }
if (Array.isArray(converted)) { }
converted.forEach((c) => importedStyle.push(c)); if (stylesXmlElement === undefined) {
} else { throw new Error("can not find styles element");
importedStyle.push(converted); }
}
});
// convert the styles one by one const importedStyle = new Styles(new ImportedRootElementAttributes(stylesXmlElement.attributes));
xmlStyles["w:style"].map((style) => convertToXmlComponent("w:style", style)).forEach(importedStyle.push.bind(importedStyle)); const stylesElements = stylesXmlElement.elements || [];
for (const childElm of stylesElements) {
importedStyle.push(convertToXmlComponent(childElm) as ImportedXmlComponent);
}
return importedStyle; return importedStyle;
} }
} }

View File

@ -1,82 +1,30 @@
// tslint:disable:no-any // tslint:disable:no-any
// import * as fastXmlParser from "fast-xml-parser"; import { Element as XmlElement } from "xml-js";
import { flatMap } from "lodash";
import { IXmlableObject, XmlComponent } from "."; 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. * 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 * @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(<string>(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. * Represents imported xml component from xml file.

View File

@ -1,5 +1,8 @@
export interface IXmlAttribute {
[key: string]: string | number | boolean;
}
export interface IXmlableObject extends Object { 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 // Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432

View File

@ -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 * 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 { FooterReferenceType } from "file/document/body/section-properties/footer-reference";
import { HeaderReferenceType } from "file/document/body/section-properties/header-reference"; import { HeaderReferenceType } from "file/document/body/section-properties/header-reference";
import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper"; import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper";
import { HeaderWrapper, IDocumentHeader } from "file/header-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 { Styles } from "file/styles";
import { ExternalStylesFactory } from "file/styles/external-styles-factory"; import { ExternalStylesFactory } from "file/styles/external-styles-factory";
const importParseOptions = {
...parseOptions,
textNodeName: "",
trimValues: false,
};
const schemeToType = { const schemeToType = {
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header", "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/footer": "footer",
@ -70,50 +61,46 @@ export class ImportDotx {
const headers: IDocumentHeader[] = []; const headers: IDocumentHeader[] = [];
for (const headerRef of documentRefs.headers) { for (const headerRef of documentRefs.headers) {
const headerKey = "w:hdr";
const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id); const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
if (relationFileInfo === null || !relationFileInfo) { if (relationFileInfo === null || !relationFileInfo) {
throw new Error(`Can not find target file for id ${headerRef.id}`); throw new Error(`Can not find target file for id ${headerRef.id}`);
} }
const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text"); const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text");
const xmlObjOld = fastXmlParser.parse(xmlData, importParseOptions); const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
const xmlObj = xml2js(xmlData, {compact: false}) as XMLElement; let headerXmlElement: XMLElement | undefined;
if (xmlObj.elements === undefined || xmlObj.elements[0].name !== headerKey) { for (const xmlElm of xmlObj.elements || []) {
if (xmlElm.name === "w:hdr") {
headerXmlElement = xmlElm;
}
}
if (headerXmlElement === undefined) {
continue; continue;
} }
const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent;
console.log('=========== fast-xml-parser header ======='); const header = new HeaderWrapper(this.currentRelationshipId++, importedComp);
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);
await this.addRelationToWrapper(relationFileInfo, zipContent, header); await this.addRelationToWrapper(relationFileInfo, zipContent, header);
headers.push({ type: headerRef.type, header }); headers.push({ type: headerRef.type, header });
} }
const footers: IDocumentFooter[] = []; const footers: IDocumentFooter[] = [];
for (const footerRef of documentRefs.footers) { for (const footerRef of documentRefs.footers) {
const footerKey = "w:ftr";
const relationFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id); const relationFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id);
if (relationFileInfo === null || !relationFileInfo) { if (relationFileInfo === null || !relationFileInfo) {
throw new Error(`Can not find target file for id ${footerRef.id}`); throw new Error(`Can not find target file for id ${footerRef.id}`);
} }
const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text"); const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text");
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions); const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
const importedComp = convertToXmlComponent(footerKey, xmlObj[footerKey]) as ImportedXmlComponent; 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); const footer = new FooterWrapper(this.currentRelationshipId++, importedComp);
await this.addRelationToWrapper(relationFileInfo, zipContent, footer); await this.addRelationToWrapper(relationFileInfo, zipContent, footer);
footers.push({ type: footerRef.type, footer }); footers.push({ type: footerRef.type, footer });
@ -152,16 +139,19 @@ export class ImportDotx {
} }
public findReferenceFiles(xmlData: string): IRelationshipFileInfo[] { 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) const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
? xmlObj.Relationships.Relationship ? xmlObj.Relationships.Relationship
: [xmlObj.Relationships.Relationship]; : [xmlObj.Relationships.Relationship];
const relations: IRelationshipFileInfo[] = relationXmlArray const relations: IRelationshipFileInfo[] = relationXmlArray
.map((item) => { .map((item: XMLElementCompact) => {
if (item._attributes === undefined) {
throw Error("relationship element has no attributes");
}
return { return {
id: this.parseRefId(item._attr.Id), id: this.parseRefId(item._attributes.Id as string),
type: schemeToType[item._attr.Type], type: schemeToType[item._attributes.Type as string],
target: item._attr.Target, target: item._attributes.Target as string,
}; };
}) })
.filter((item) => item.type !== null); .filter((item) => item.type !== null);
@ -169,14 +159,11 @@ export class ImportDotx {
} }
public extractDocumentRefs(xmlData: string): IDocumentRefs { public extractDocumentRefs(xmlData: string): IDocumentRefs {
interface IAttributedXML { const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
_attr: object;
}
const xmlObj = fastXmlParser.parse(xmlData, importParseOptions);
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"]; const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
const headerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:headerReference"]; const headerProps: XMLElementCompact = sectionProp["w:headerReference"];
let headersXmlArray: IAttributedXML[]; let headersXmlArray: XMLElementCompact[];
if (headerProps === undefined) { if (headerProps === undefined) {
headersXmlArray = []; headersXmlArray = [];
} else if (Array.isArray(headerProps)) { } else if (Array.isArray(headerProps)) {
@ -185,14 +172,17 @@ export class ImportDotx {
headersXmlArray = [headerProps]; headersXmlArray = [headerProps];
} }
const headers = headersXmlArray.map((item) => { const headers = headersXmlArray.map((item) => {
if (item._attributes === undefined) {
throw Error("header referecne element has no attributes");
}
return { return {
type: item._attr["w:type"], type: item._attributes["w:type"] as HeaderReferenceType,
id: this.parseRefId(item._attr["r:id"]), id: this.parseRefId(item._attributes["r:id"] as string),
}; };
}); });
const footerProps: undefined | IAttributedXML | IAttributedXML[] = sectionProp["w:footerReference"]; const footerProps: XMLElementCompact = sectionProp["w:footerReference"];
let footersXmlArray: IAttributedXML[]; let footersXmlArray: XMLElementCompact[];
if (footerProps === undefined) { if (footerProps === undefined) {
footersXmlArray = []; footersXmlArray = [];
} else if (Array.isArray(footerProps)) { } else if (Array.isArray(footerProps)) {
@ -202,9 +192,12 @@ export class ImportDotx {
} }
const footers = footersXmlArray.map((item) => { const footers = footersXmlArray.map((item) => {
if (item._attributes === undefined) {
throw Error("footer referecne element has no attributes");
}
return { return {
type: item._attr["w:type"], type: item._attributes["w:type"] as FooterReferenceType,
id: this.parseRefId(item._attr["r:id"]), id: this.parseRefId(item._attributes["r:id"] as string),
}; };
}); });
@ -212,7 +205,7 @@ export class ImportDotx {
} }
public titlePageIsDefined(xmlData: string): boolean { 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"]; const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
return sectionProp["w:titlePg"] !== undefined; return sectionProp["w:titlePg"] !== undefined;
} }