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": {
"@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",

View File

@ -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,

View File

@ -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 {
* </w:styles>
* @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;
}
}

View File

@ -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(<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.

View File

@ -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

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 { 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;
}