works
This commit is contained in:
BIN
demo/dotx/a.dotx
BIN
demo/dotx/a.dotx
Binary file not shown.
Binary file not shown.
BIN
demo/dotx/c.dotx
BIN
demo/dotx/c.dotx
Binary file not shown.
BIN
demo/dotx/d.dotx
BIN
demo/dotx/d.dotx
Binary file not shown.
BIN
demo/dotx/e.dotx
BIN
demo/dotx/e.dotx
Binary file not shown.
BIN
demo/dotx/f.dotx
BIN
demo/dotx/f.dotx
Binary file not shown.
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user