Merge branch 'master' of https://github.com/h4buli/docx into feat/h4buli-update
# Conflicts: # package.json # src/file/numbering/numbering.ts
This commit is contained in:
@ -45,6 +45,7 @@
|
|||||||
"@types/image-size": "0.0.29",
|
"@types/image-size": "0.0.29",
|
||||||
"@types/request-promise": "^4.1.41",
|
"@types/request-promise": "^4.1.41",
|
||||||
"archiver": "^2.1.1",
|
"archiver": "^2.1.1",
|
||||||
|
"fast-xml-parser": "^3.3.6",
|
||||||
"image-size": "^0.6.2",
|
"image-size": "^0.6.2",
|
||||||
"request": "^2.83.0",
|
"request": "^2.83.0",
|
||||||
"request-promise": "^4.2.2",
|
"request-promise": "^4.2.2",
|
||||||
|
@ -10,6 +10,7 @@ export interface IPropertiesOptions {
|
|||||||
description?: string;
|
description?: string;
|
||||||
lastModifiedBy?: string;
|
lastModifiedBy?: string;
|
||||||
revision?: string;
|
revision?: string;
|
||||||
|
externalStyles?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CoreProperties extends XmlComponent {
|
export class CoreProperties extends XmlComponent {
|
||||||
|
@ -11,7 +11,9 @@ import { Paragraph, PictureRun } from "./paragraph";
|
|||||||
import { Relationships } from "./relationships";
|
import { Relationships } from "./relationships";
|
||||||
import { Styles } from "./styles";
|
import { Styles } from "./styles";
|
||||||
import { DefaultStylesFactory } from "./styles/factory";
|
import { DefaultStylesFactory } from "./styles/factory";
|
||||||
|
import { ExternalStylesFactory } from "./styles/external-styles-factory";
|
||||||
import { Table } from "./table";
|
import { Table } from "./table";
|
||||||
|
import { IMediaData } from "index";
|
||||||
|
|
||||||
export class File {
|
export class File {
|
||||||
private readonly document: Document;
|
private readonly document: Document;
|
||||||
@ -28,8 +30,6 @@ export class File {
|
|||||||
|
|
||||||
constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) {
|
constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) {
|
||||||
this.document = new Document(sectionPropertiesOptions);
|
this.document = new Document(sectionPropertiesOptions);
|
||||||
const stylesFactory = new DefaultStylesFactory();
|
|
||||||
this.styles = stylesFactory.newInstance();
|
|
||||||
|
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {
|
options = {
|
||||||
@ -39,6 +39,14 @@ export class File {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.externalStyles) {
|
||||||
|
const stylesFactory = new ExternalStylesFactory();
|
||||||
|
this.styles = stylesFactory.newInstance(options.externalStyles);
|
||||||
|
} else {
|
||||||
|
const stylesFactory = new DefaultStylesFactory();
|
||||||
|
this.styles = stylesFactory.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
this.coreProperties = new CoreProperties(options);
|
this.coreProperties = new CoreProperties(options);
|
||||||
this.numbering = new Numbering();
|
this.numbering = new Numbering();
|
||||||
this.docRelationships = new Relationships();
|
this.docRelationships = new Relationships();
|
||||||
@ -111,6 +119,16 @@ export class File {
|
|||||||
return this.document.createDrawing(mediaData);
|
return this.document.createDrawing(mediaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createImageData(imageName: string, data: Buffer, width?: number, height?: number): IMediaData {
|
||||||
|
const mediaData = this.media.addMediaWithData(imageName, data, this.docRelationships.RelationshipCount, width, height);
|
||||||
|
this.docRelationships.createRelationship(
|
||||||
|
mediaData.referenceId,
|
||||||
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
||||||
|
`media/${mediaData.fileName}`,
|
||||||
|
);
|
||||||
|
return mediaData;
|
||||||
|
}
|
||||||
|
|
||||||
public get Document(): Document {
|
public get Document(): Document {
|
||||||
return this.document;
|
return this.document;
|
||||||
}
|
}
|
||||||
|
@ -5,3 +5,4 @@ export * from "./numbering";
|
|||||||
export * from "./media";
|
export * from "./media";
|
||||||
export * from "./drawing";
|
export * from "./drawing";
|
||||||
export * from "./styles";
|
export * from "./styles";
|
||||||
|
export * from "./xml-components";
|
@ -13,8 +13,8 @@ export interface IMediaDataDimensions {
|
|||||||
|
|
||||||
export interface IMediaData {
|
export interface IMediaData {
|
||||||
referenceId: number;
|
referenceId: number;
|
||||||
stream: fs.ReadStream;
|
stream: fs.ReadStream | Buffer;
|
||||||
path: string;
|
path?: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
dimensions: IMediaDataDimensions;
|
dimensions: IMediaDataDimensions;
|
||||||
}
|
}
|
||||||
|
@ -11,23 +11,10 @@ export class Media {
|
|||||||
this.map = new Map<string, IMediaData>();
|
this.map = new Map<string, IMediaData>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMedia(key: string): IMediaData {
|
private createMedia(key: string, relationshipsCount, dimensions, data: fs.ReadStream | Buffer, filePath?: string, ) {
|
||||||
const data = this.map.get(key);
|
|
||||||
|
|
||||||
if (data === undefined) {
|
|
||||||
throw new Error(`Cannot find image with the key ${key}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public addMedia(filePath: string, relationshipsCount: number): IMediaData {
|
|
||||||
const key = path.basename(filePath);
|
|
||||||
const dimensions = sizeOf(filePath);
|
|
||||||
|
|
||||||
const imageData = {
|
const imageData = {
|
||||||
referenceId: this.map.size + relationshipsCount + 1,
|
referenceId: this.map.size + relationshipsCount + 1,
|
||||||
stream: fs.createReadStream(filePath),
|
stream: data,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
fileName: key,
|
fileName: key,
|
||||||
dimensions: {
|
dimensions: {
|
||||||
@ -45,6 +32,36 @@ export class Media {
|
|||||||
|
|
||||||
return imageData;
|
return imageData;
|
||||||
}
|
}
|
||||||
|
public getMedia(key: string): IMediaData {
|
||||||
|
const data = this.map.get(key);
|
||||||
|
|
||||||
|
if (data === undefined) {
|
||||||
|
throw new Error(`Cannot find image with the key ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMedia(filePath: string, relationshipsCount: number): IMediaData {
|
||||||
|
const key = path.basename(filePath);
|
||||||
|
const dimensions = sizeOf(filePath);
|
||||||
|
return this.createMedia(key, relationshipsCount, dimensions, fs.createReadStream(filePath), filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMediaWithData(fileName: string, data: Buffer, relationshipsCount: number, width?, height?): IMediaData {
|
||||||
|
const key = fileName;
|
||||||
|
let dimensions;
|
||||||
|
if (width && height) {
|
||||||
|
dimensions = {
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dimensions = sizeOf(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.createMedia(key, relationshipsCount, dimensions, data);
|
||||||
|
}
|
||||||
|
|
||||||
public get array(): IMediaData[] {
|
public get array(): IMediaData[] {
|
||||||
const array = new Array<IMediaData>();
|
const array = new Array<IMediaData>();
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from "./numbering";
|
export * from "./numbering";
|
||||||
|
export * from "./abstract-numbering";
|
@ -1,12 +1,14 @@
|
|||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent, IXmlableObject } from "file/xml-components";
|
||||||
import { DocumentAttributes } from "../document/document-attributes";
|
import { DocumentAttributes } from "../document/document-attributes";
|
||||||
import { Indent } from "../paragraph/formatting";
|
|
||||||
import { AbstractNumbering } from "./abstract-numbering";
|
import { AbstractNumbering } from "./abstract-numbering";
|
||||||
import { Num } from "./num";
|
import { Num } from "./num";
|
||||||
|
|
||||||
export class Numbering extends XmlComponent {
|
export class Numbering extends XmlComponent {
|
||||||
private nextId: number;
|
private nextId: number;
|
||||||
|
|
||||||
|
private abstractNumbering: Array<XmlComponent> = [];
|
||||||
|
private concreteNumbering: Array<XmlComponent> = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("w:numbering");
|
super("w:numbering");
|
||||||
this.root.push(
|
this.root.push(
|
||||||
@ -32,39 +34,23 @@ export class Numbering extends XmlComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.nextId = 0;
|
this.nextId = 0;
|
||||||
|
|
||||||
const abstractNumbering = this.createAbstractNumbering();
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(0, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 720, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(1, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 1440, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(2, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 2160, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(3, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 2880, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(4, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 3600, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(5, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 4320, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(6, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 5040, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(7, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 5760, hanging: 360 }));
|
|
||||||
|
|
||||||
abstractNumbering.createLevel(8, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 6480, hanging: 360 }));
|
|
||||||
|
|
||||||
this.createConcreteNumbering(abstractNumbering);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public createAbstractNumbering(): AbstractNumbering {
|
public createAbstractNumbering(): AbstractNumbering {
|
||||||
const num = new AbstractNumbering(this.nextId++);
|
const num = new AbstractNumbering(this.nextId++);
|
||||||
this.root.push(num);
|
this.abstractNumbering.push(num);
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
|
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
|
||||||
const num = new Num(this.nextId++, abstractNumbering.id);
|
const num = new Num(this.nextId++, abstractNumbering.id);
|
||||||
this.root.push(num);
|
this.concreteNumbering.push(num);
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public prepForXml(): IXmlableObject {
|
||||||
|
this.abstractNumbering.forEach(x => this.root.push(x));
|
||||||
|
this.concreteNumbering.forEach(x => this.root.push(x));
|
||||||
|
return super.prepForXml();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,11 @@ export class Paragraph extends XmlComponent {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setCustomNumbering(numberId: number, indentLevel: number): Paragraph {
|
||||||
|
this.properties.push(new NumberProperties(numberId, indentLevel));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public style(styleId: string): Paragraph {
|
public style(styleId: string): Paragraph {
|
||||||
this.properties.push(new Style(styleId));
|
this.properties.push(new Style(styleId));
|
||||||
return this;
|
return this;
|
||||||
|
160
src/file/styles/external-styles-factory.spec.ts
Normal file
160
src/file/styles/external-styles-factory.spec.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { ExternalStylesFactory } from "./external-styles-factory";
|
||||||
|
|
||||||
|
describe("External styles factory", () => {
|
||||||
|
let externalStyles;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
externalStyles = `
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:styles xmlns:mc="first" xmlns:r="second">
|
||||||
|
<w:docDefaults>
|
||||||
|
</w:docDefaults>
|
||||||
|
|
||||||
|
<w:latentStyles w:defLockedState="1" w:defUIPriority="99">
|
||||||
|
</w:latentStyles>
|
||||||
|
|
||||||
|
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
|
||||||
|
<w:name w:val="Normal"/>
|
||||||
|
<w:qFormat/>
|
||||||
|
</w:style>
|
||||||
|
|
||||||
|
<w:style w:type="paragraph" w:styleId="Heading1">
|
||||||
|
<w:name w:val="heading 1"/>
|
||||||
|
<w:basedOn w:val="Normal"/>
|
||||||
|
<w:pPr>
|
||||||
|
<w:keepNext/>
|
||||||
|
<w:keepLines/>
|
||||||
|
|
||||||
|
<w:pBdr>
|
||||||
|
<w:bottom w:val="single" w:sz="4" w:space="1" w:color="auto"/>
|
||||||
|
</w:pBdr>
|
||||||
|
</w:pPr>
|
||||||
|
</w:style>
|
||||||
|
</w:styles>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#parse", () => {
|
||||||
|
it("should parse w:styles attributes", () => {
|
||||||
|
const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any;
|
||||||
|
|
||||||
|
expect(importedStyle.rootKey).to.equal("w:styles");
|
||||||
|
expect(importedStyle.root[0]._attr).to.eql({
|
||||||
|
"xmlns:mc": "first",
|
||||||
|
"xmlns:r": "second",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse other child elements of w:styles", () => {
|
||||||
|
const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any;
|
||||||
|
|
||||||
|
expect(importedStyle.root.length).to.equal(5);
|
||||||
|
expect(importedStyle.root[1]).to.eql({
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:docDefaults",
|
||||||
|
});
|
||||||
|
expect(importedStyle.root[2]).to.eql({
|
||||||
|
_attr: {
|
||||||
|
"w:defLockedState": "1",
|
||||||
|
"w:defUIPriority": "99",
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:latentStyles",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse styles elements", () => {
|
||||||
|
const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any;
|
||||||
|
|
||||||
|
expect(importedStyle.root.length).to.equal(5);
|
||||||
|
expect(importedStyle.root[3]).to.eql({
|
||||||
|
_attr: {
|
||||||
|
"w:default": "1",
|
||||||
|
"w:styleId": "Normal",
|
||||||
|
"w:type": "paragraph",
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
root: [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:val": "Normal",
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:qFormat",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rootKey: "w:style",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(importedStyle.root[4]).to.eql({
|
||||||
|
_attr: {
|
||||||
|
"w:styleId": "Heading1",
|
||||||
|
"w:type": "paragraph",
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
root: [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:val": "heading 1",
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:val": "Normal",
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:basedOn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: false,
|
||||||
|
root: [
|
||||||
|
{
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:keepNext",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:keepLines",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deleted: false,
|
||||||
|
root: [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "auto",
|
||||||
|
"w:space": "1",
|
||||||
|
"w:sz": "4",
|
||||||
|
"w:val": "single",
|
||||||
|
},
|
||||||
|
deleted: false,
|
||||||
|
root: [],
|
||||||
|
rootKey: "w:bottom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rootKey: "w:pBdr",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rootKey: "w:pPr",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rootKey: "w:style",
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
64
src/file/styles/external-styles-factory.ts
Normal file
64
src/file/styles/external-styles-factory.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Styles } from "./";
|
||||||
|
import * as fastXmlParser from "fast-xml-parser";
|
||||||
|
import { ImportedXmlComponent, ImportedRootElementAttributes } from "./../../file/xml-components";
|
||||||
|
|
||||||
|
const parseOptions = {
|
||||||
|
ignoreAttributes: false,
|
||||||
|
attributeNamePrefix: "",
|
||||||
|
attrNodeName: "_attr",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ExternalStylesFactory {
|
||||||
|
/**
|
||||||
|
* Creates new Style based on the given styles.
|
||||||
|
* Parses the styles and convert them to XmlComponent.
|
||||||
|
* Example content from styles.xml:
|
||||||
|
* <?xml version="1.0">
|
||||||
|
* <w:styles xmlns:mc="some schema" ...>
|
||||||
|
*
|
||||||
|
* <w:style w:type="paragraph" w:styleId="Heading1">
|
||||||
|
* <w:name w:val="heading 1"/>
|
||||||
|
* .....
|
||||||
|
* </w:style>
|
||||||
|
*
|
||||||
|
* <w:style w:type="paragraph" w:styleId="Heading2">
|
||||||
|
* <w:name w:val="heading 2"/>
|
||||||
|
* .....
|
||||||
|
* </w:style>
|
||||||
|
*
|
||||||
|
* <w:docDefaults>Or any other element will be parsed to</w:docDefaults>
|
||||||
|
*
|
||||||
|
* </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));
|
||||||
|
|
||||||
|
// convert other elements (not styles definitions, but default styles and so on ...)
|
||||||
|
Object.keys(xmlStyles)
|
||||||
|
.filter((element) => element !== "_attr" && element !== "w:style")
|
||||||
|
.forEach((element) => {
|
||||||
|
importedStyle.push(new ImportedXmlComponent(element, xmlStyles[element]._attr));
|
||||||
|
});
|
||||||
|
|
||||||
|
// convert the styles one by one
|
||||||
|
xmlStyles["w:style"]
|
||||||
|
.map((style) => this.convertElement("w:style", style))
|
||||||
|
.forEach(importedStyle.push.bind(importedStyle));
|
||||||
|
|
||||||
|
return importedStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertElement(elementName: string, element: any): ImportedXmlComponent {
|
||||||
|
const xmlElement = new ImportedXmlComponent(elementName, element._attr);
|
||||||
|
if (typeof element === "object") {
|
||||||
|
Object.keys(element)
|
||||||
|
.filter((key) => key !== "_attr")
|
||||||
|
.map((item) => this.convertElement(item, element[item]))
|
||||||
|
.forEach(xmlElement.push.bind(xmlElement));
|
||||||
|
}
|
||||||
|
return xmlElement;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { Color, Italics, Size } from "../paragraph/run/formatting";
|
import { Color, Italics, Size } from "../paragraph/run/formatting";
|
||||||
|
|
||||||
import { Styles } from "./";
|
import { Styles } from "./";
|
||||||
// import { DocumentDefaults } from "./defaults";
|
import { DocumentAttributes } from "../document/document-attributes";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Heading1Style,
|
Heading1Style,
|
||||||
Heading2Style,
|
Heading2Style,
|
||||||
@ -15,7 +16,15 @@ import {
|
|||||||
|
|
||||||
export class DefaultStylesFactory {
|
export class DefaultStylesFactory {
|
||||||
public newInstance(): Styles {
|
public newInstance(): Styles {
|
||||||
const styles = new Styles();
|
const documentAttributes = new DocumentAttributes({
|
||||||
|
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
||||||
|
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
||||||
|
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
||||||
|
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
|
||||||
|
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
|
||||||
|
Ignorable: "w14 w15",
|
||||||
|
});
|
||||||
|
const styles = new Styles(documentAttributes);
|
||||||
styles.createDocumentDefaults();
|
styles.createDocumentDefaults();
|
||||||
|
|
||||||
const titleStyle = new TitleStyle();
|
const titleStyle = new TitleStyle();
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent, BaseXmlComponent } from "file/xml-components";
|
||||||
import { DocumentAttributes } from "../document/document-attributes";
|
|
||||||
import { DocumentDefaults } from "./defaults";
|
import { DocumentDefaults } from "./defaults";
|
||||||
import { ParagraphStyle } from "./style";
|
import { ParagraphStyle } from "./style";
|
||||||
|
|
||||||
export class Styles extends XmlComponent {
|
export class Styles extends XmlComponent {
|
||||||
constructor() {
|
constructor(_initialStyles?: BaseXmlComponent) {
|
||||||
super("w:styles");
|
super("w:styles");
|
||||||
this.root.push(
|
if (_initialStyles) {
|
||||||
new DocumentAttributes({
|
this.root.push(_initialStyles);
|
||||||
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
}
|
||||||
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
||||||
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
||||||
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
|
|
||||||
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
|
|
||||||
Ignorable: "w14 w15",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public push(style: XmlComponent): Styles {
|
public push(style: XmlComponent): Styles {
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from "./table";
|
export * from "./table";
|
||||||
|
export * from './table-cell';
|
181
src/file/table/table-cell.spec.ts
Normal file
181
src/file/table/table-cell.spec.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { TableCellBorders, BorderStyle, TableCellWidth, WidthType } from "./table-cell";
|
||||||
|
import { Formatter } from "../../export/formatter";
|
||||||
|
|
||||||
|
describe("TableCellBorders", () => {
|
||||||
|
describe("#prepForXml", () => {
|
||||||
|
it("should not add empty borders element if there are no borders defined", () => {
|
||||||
|
const tb = new TableCellBorders();
|
||||||
|
const tree = new Formatter().format(tb);
|
||||||
|
expect(tree).to.deep.equal("");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#addingBorders", () => {
|
||||||
|
it("should add top border", () => {
|
||||||
|
const tb = new TableCellBorders();
|
||||||
|
tb.addTopBorder(BorderStyle.DOTTED, 1, "FF00FF");
|
||||||
|
|
||||||
|
const tree = new Formatter().format(tb);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:tcBorders": [
|
||||||
|
{
|
||||||
|
"w:top": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 1,
|
||||||
|
"w:val": "dotted",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add start(left) border", () => {
|
||||||
|
const tb = new TableCellBorders();
|
||||||
|
tb.addStartBorder(BorderStyle.SINGLE, 2, "FF00FF");
|
||||||
|
|
||||||
|
const tree = new Formatter().format(tb);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:tcBorders": [
|
||||||
|
{
|
||||||
|
"w:start": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 2,
|
||||||
|
"w:val": "single",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add bottom border", () => {
|
||||||
|
const tb = new TableCellBorders();
|
||||||
|
tb.addBottomBorder(BorderStyle.DOUBLE, 1, "FF00FF");
|
||||||
|
|
||||||
|
const tree = new Formatter().format(tb);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:tcBorders": [
|
||||||
|
{
|
||||||
|
"w:bottom": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 1,
|
||||||
|
"w:val": "double",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add end(right) border", () => {
|
||||||
|
const tb = new TableCellBorders();
|
||||||
|
tb.addEndBorder(BorderStyle.THICK, 3, "FF00FF");
|
||||||
|
|
||||||
|
const tree = new Formatter().format(tb);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:tcBorders": [
|
||||||
|
{
|
||||||
|
"w:end": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 3,
|
||||||
|
"w:val": "thick",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add multiple borders", () => {
|
||||||
|
const tb = new TableCellBorders();
|
||||||
|
tb.addTopBorder(BorderStyle.DOTTED, 1, "FF00FF");
|
||||||
|
tb.addEndBorder(BorderStyle.THICK, 3, "FF00FF");
|
||||||
|
tb.addBottomBorder(BorderStyle.DOUBLE, 1, "FF00FF");
|
||||||
|
tb.addStartBorder(BorderStyle.SINGLE, 2, "FF00FF");
|
||||||
|
|
||||||
|
const tree = new Formatter().format(tb);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:tcBorders": [
|
||||||
|
{
|
||||||
|
"w:top": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 1,
|
||||||
|
"w:val": "dotted",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:end": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 3,
|
||||||
|
"w:val": "thick",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:bottom": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 1,
|
||||||
|
"w:val": "double",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:start": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
"w:color": "FF00FF",
|
||||||
|
"w:sz": 2,
|
||||||
|
"w:val": "single",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("TableCellWidth", () => {
|
||||||
|
describe("#constructor", () => {
|
||||||
|
it("should create object", () => {
|
||||||
|
const tcWidth = new TableCellWidth(100, WidthType.DXA);
|
||||||
|
const tree = new Formatter().format(tcWidth);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:tcW": [
|
||||||
|
{
|
||||||
|
"_attr": {
|
||||||
|
"w:type": "dxa",
|
||||||
|
"w:w": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
197
src/file/table/table-cell.ts
Normal file
197
src/file/table/table-cell.ts
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import { XmlComponent, XmlAttributeComponent, IXmlableObject } from "file/xml-components";
|
||||||
|
|
||||||
|
export enum BorderStyle {
|
||||||
|
SINGLE = "single",
|
||||||
|
DASH_DOT_STROKED = "dashDotStroked",
|
||||||
|
DASHED = "dashed",
|
||||||
|
DASH_SMALL_GAP = "dashSmallGap",
|
||||||
|
DOT_DASH = "dotDash",
|
||||||
|
DOT_DOT_DASH = "dotDotDash",
|
||||||
|
DOTTED = "dotted",
|
||||||
|
DOUBLE = "double",
|
||||||
|
DOUBLE_WAVE = "doubleWave",
|
||||||
|
INSET = "inset",
|
||||||
|
NIL = "nil",
|
||||||
|
NONE = "none",
|
||||||
|
OUTSET = "outset",
|
||||||
|
THICK = "thick",
|
||||||
|
THICK_THIN_LARGE_GAP = "thickThinLargeGap",
|
||||||
|
THICK_THIN_MEDIUM_GAP = "thickThinMediumGap",
|
||||||
|
THICK_THIN_SMALL_GAP = "thickThinSmallGap",
|
||||||
|
THIN_THICK_LARGE_GAP = "thinThickLargeGap",
|
||||||
|
THIN_THICK_MEDIUM_GAP = "thinThickMediumGap",
|
||||||
|
THIN_THICK_SMALL_GAP = "thinThickSmallGap",
|
||||||
|
THIN_THICK_THIN_LARGE_GAP = "thinThickThinLargeGap",
|
||||||
|
THIN_THICK_THIN_MEDIUM_GAP = "thinThickThinMediumGap",
|
||||||
|
THIN_THICK_THIN_SMALL_GAP = "thinThickThinSmallGap",
|
||||||
|
THREE_D_EMBOSS = "threeDEmboss",
|
||||||
|
THREE_D_ENGRAVE = "threeDEngrave",
|
||||||
|
TRIPLE = "triple",
|
||||||
|
WAVE = "wave",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICellBorder {
|
||||||
|
style: BorderStyle;
|
||||||
|
size: number;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellBorderAttributes extends XmlAttributeComponent<ICellBorder> {
|
||||||
|
protected xmlKeys = { style: "w:val", size: "w:sz", color: "w:color" };
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseTableCellBorder extends XmlComponent {
|
||||||
|
setProperties(style: BorderStyle, size: number, color: string) {
|
||||||
|
let attrs = new CellBorderAttributes({
|
||||||
|
style: style,
|
||||||
|
size: size,
|
||||||
|
color: color,
|
||||||
|
});
|
||||||
|
this.root.push(attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TableCellBorders extends XmlComponent {
|
||||||
|
constructor() {
|
||||||
|
super("w:tcBorders");
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepForXml(): IXmlableObject {
|
||||||
|
return this.root.length > 0 ? super.prepForXml() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
addTopBorder(style: BorderStyle, size: number, color: string) {
|
||||||
|
const top = new BaseTableCellBorder("w:top");
|
||||||
|
top.setProperties(style, size, color);
|
||||||
|
this.root.push(top);
|
||||||
|
}
|
||||||
|
|
||||||
|
addStartBorder(style: BorderStyle, size: number, color: string) {
|
||||||
|
const start = new BaseTableCellBorder("w:start");
|
||||||
|
start.setProperties(style, size, color);
|
||||||
|
this.root.push(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
addBottomBorder(style: BorderStyle, size: number, color: string) {
|
||||||
|
const bottom = new BaseTableCellBorder("w:bottom");
|
||||||
|
bottom.setProperties(style, size, color);
|
||||||
|
this.root.push(bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
addEndBorder(style: BorderStyle, size: number, color: string) {
|
||||||
|
const end = new BaseTableCellBorder("w:end");
|
||||||
|
end.setProperties(style, size, color);
|
||||||
|
this.root.push(end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attributes fot the GridSpan element.
|
||||||
|
*/
|
||||||
|
class GridSpanAttributes extends XmlAttributeComponent<{ val: number }> {
|
||||||
|
protected xmlKeys = { val: "w:val" };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GridSpan element. Should be used in a table cell. Pass the number of columns that this cell need to span.
|
||||||
|
*/
|
||||||
|
export class GridSpan extends XmlComponent {
|
||||||
|
constructor(value: number) {
|
||||||
|
super("w:gridSpan");
|
||||||
|
|
||||||
|
this.root.push(
|
||||||
|
new GridSpanAttributes({
|
||||||
|
val: value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical merge types.
|
||||||
|
*/
|
||||||
|
export enum VMergeType {
|
||||||
|
/**
|
||||||
|
* Cell that is merged with upper one.
|
||||||
|
*/
|
||||||
|
CONTINUE = "continue",
|
||||||
|
/**
|
||||||
|
* Cell that is starting the vertical merge.
|
||||||
|
*/
|
||||||
|
RESTART = "restart",
|
||||||
|
}
|
||||||
|
|
||||||
|
class VMergeAttributes extends XmlAttributeComponent<{ val: VMergeType }> {
|
||||||
|
protected xmlKeys = { val: "w:val" };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical merge element. Should be used in a table cell.
|
||||||
|
*/
|
||||||
|
export class VMerge extends XmlComponent {
|
||||||
|
constructor(value: VMergeType) {
|
||||||
|
super("w:vMerge");
|
||||||
|
|
||||||
|
this.root.push(
|
||||||
|
new VMergeAttributes({
|
||||||
|
val: value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VerticalAlign {
|
||||||
|
BOTTOM = "bottom",
|
||||||
|
CENTER = "center",
|
||||||
|
TOP = "top",
|
||||||
|
}
|
||||||
|
|
||||||
|
class VAlignAttributes extends XmlAttributeComponent<{ val: VerticalAlign }> {
|
||||||
|
protected xmlKeys = { val: "w:val" };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical align element.
|
||||||
|
*/
|
||||||
|
export class VAlign extends XmlComponent {
|
||||||
|
constructor(value: VerticalAlign) {
|
||||||
|
super("w:vAlign");
|
||||||
|
|
||||||
|
this.root.push(
|
||||||
|
new VAlignAttributes({
|
||||||
|
val: value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WidthType {
|
||||||
|
/** Auto. */
|
||||||
|
AUTO = "auto",
|
||||||
|
/** Value is in twentieths of a point */
|
||||||
|
DXA = "dxa",
|
||||||
|
/** No (empty) value. */
|
||||||
|
NIL = "nil",
|
||||||
|
/** Value is in percentage. */
|
||||||
|
PERCENTAGE = "pct",
|
||||||
|
}
|
||||||
|
|
||||||
|
class TableCellWidthAttributes extends XmlAttributeComponent<{ type: WidthType; width: string | number }> {
|
||||||
|
protected xmlKeys = { width: "w:w", type: "w:type" };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table cell width element.
|
||||||
|
*/
|
||||||
|
export class TableCellWidth extends XmlComponent {
|
||||||
|
constructor(value: string | number, type: WidthType) {
|
||||||
|
super("w:tcW");
|
||||||
|
|
||||||
|
this.root.push(
|
||||||
|
new TableCellWidthAttributes({
|
||||||
|
width: value,
|
||||||
|
type: type,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,18 +2,22 @@ import { IXmlableObject, XmlComponent } from "file/xml-components";
|
|||||||
import { Paragraph } from "../paragraph";
|
import { Paragraph } from "../paragraph";
|
||||||
import { TableGrid } from "./grid";
|
import { TableGrid } from "./grid";
|
||||||
import { TableProperties, WidthTypes } from "./properties";
|
import { TableProperties, WidthTypes } from "./properties";
|
||||||
|
import { TableCellBorders, GridSpan, VMerge, VMergeType, VerticalAlign, VAlign, TableCellWidth, WidthType } from "file/table/table-cell";
|
||||||
|
|
||||||
export class Table extends XmlComponent {
|
export class Table extends XmlComponent {
|
||||||
private readonly properties: TableProperties;
|
private readonly properties: TableProperties;
|
||||||
private readonly rows: TableRow[];
|
private readonly rows: TableRow[];
|
||||||
private readonly grid: TableGrid;
|
private readonly grid: TableGrid;
|
||||||
|
|
||||||
constructor(rows: number, cols: number) {
|
constructor(rows: number, cols: number, colSizes?: number[]) {
|
||||||
super("w:tbl");
|
super("w:tbl");
|
||||||
this.properties = new TableProperties();
|
this.properties = new TableProperties();
|
||||||
this.root.push(this.properties);
|
this.root.push(this.properties);
|
||||||
this.properties.setBorder();
|
this.properties.setBorder();
|
||||||
|
|
||||||
|
if (colSizes && colSizes.length > 0) {
|
||||||
|
this.grid = new TableGrid(colSizes);
|
||||||
|
} else {
|
||||||
const gridCols: number[] = [];
|
const gridCols: number[] = [];
|
||||||
for (let i = 0; i < cols; i++) {
|
for (let i = 0; i < cols; i++) {
|
||||||
/*
|
/*
|
||||||
@ -29,6 +33,8 @@ export class Table extends XmlComponent {
|
|||||||
gridCols.push(1);
|
gridCols.push(1);
|
||||||
}
|
}
|
||||||
this.grid = new TableGrid(gridCols);
|
this.grid = new TableGrid(gridCols);
|
||||||
|
}
|
||||||
|
|
||||||
this.root.push(this.grid);
|
this.root.push(this.grid);
|
||||||
|
|
||||||
this.rows = [];
|
this.rows = [];
|
||||||
@ -112,10 +118,36 @@ export class TableCell extends XmlComponent {
|
|||||||
this.addContent(para);
|
this.addContent(para);
|
||||||
return para;
|
return para;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get cellProperties() {
|
||||||
|
return this.properties;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TableCellProperties extends XmlComponent {
|
export class TableCellProperties extends XmlComponent {
|
||||||
|
private cellBorder: TableCellBorders;
|
||||||
constructor() {
|
constructor() {
|
||||||
super("w:tcPr");
|
super("w:tcPr");
|
||||||
|
this.cellBorder = new TableCellBorders();
|
||||||
|
this.root.push(this.cellBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
get borders() {
|
||||||
|
return this.cellBorder;
|
||||||
|
}
|
||||||
|
addGridSpan(cellSpan: number) {
|
||||||
|
this.root.push(new GridSpan(cellSpan));
|
||||||
|
}
|
||||||
|
|
||||||
|
addVerticalMerge(type: VMergeType) {
|
||||||
|
this.root.push(new VMerge(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
setVerticalAlign(vAlignType: VerticalAlign) {
|
||||||
|
this.root.push(new VAlign(vAlignType));
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidth(width: string | number, type: WidthType) {
|
||||||
|
this.root.push(new TableCellWidth(width, type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,15 @@ import { IXmlableObject } from "./xmlable-object";
|
|||||||
|
|
||||||
export abstract class BaseXmlComponent {
|
export abstract class BaseXmlComponent {
|
||||||
protected rootKey: string;
|
protected rootKey: string;
|
||||||
|
protected deleted: boolean = false;
|
||||||
|
|
||||||
constructor(rootKey: string) {
|
constructor(rootKey: string) {
|
||||||
this.rootKey = rootKey;
|
this.rootKey = rootKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract prepForXml(): IXmlableObject;
|
public abstract prepForXml(): IXmlableObject;
|
||||||
|
|
||||||
|
get isDeleted() {
|
||||||
|
return this.deleted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
34
src/file/xml-components/imported-xml-component.spec.ts
Normal file
34
src/file/xml-components/imported-xml-component.spec.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
import { ImportedXmlComponent } from "./";
|
||||||
|
|
||||||
|
describe("ImportedXmlComponent", () => {
|
||||||
|
let importedXmlComponent: ImportedXmlComponent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const attributes = {
|
||||||
|
someAttr: "1",
|
||||||
|
otherAttr: "2",
|
||||||
|
};
|
||||||
|
importedXmlComponent = new ImportedXmlComponent("w:test", attributes);
|
||||||
|
importedXmlComponent.push(new ImportedXmlComponent("w:child"));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#prepForXml()", () => {
|
||||||
|
it("should transform for xml", () => {
|
||||||
|
const converted = importedXmlComponent.prepForXml();
|
||||||
|
expect(converted).to.eql({
|
||||||
|
"w:test": [
|
||||||
|
{
|
||||||
|
_attr: {
|
||||||
|
someAttr: "1",
|
||||||
|
otherAttr: "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:child": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
71
src/file/xml-components/imported-xml-component.ts
Normal file
71
src/file/xml-components/imported-xml-component.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { XmlComponent, IXmlableObject } from ".";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents imported xml component from xml file.
|
||||||
|
*/
|
||||||
|
export class ImportedXmlComponent extends XmlComponent {
|
||||||
|
private _attr: any;
|
||||||
|
|
||||||
|
constructor(rootKey: string, _attr?: any) {
|
||||||
|
super(rootKey);
|
||||||
|
if (_attr) {
|
||||||
|
this._attr = _attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the object so it can be converted to xml. Example:
|
||||||
|
* <w:someKey someAttr="1" otherAttr="11">
|
||||||
|
* <w:child childAttr="2">
|
||||||
|
* </w:child>
|
||||||
|
* </w:someKey>
|
||||||
|
* {
|
||||||
|
* 'w:someKey': [
|
||||||
|
* {
|
||||||
|
* _attr: {
|
||||||
|
* someAttr: "1",
|
||||||
|
* otherAttr: "11"
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* 'w:child': [
|
||||||
|
* {
|
||||||
|
* _attr: {
|
||||||
|
* childAttr: "2"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
prepForXml(): IXmlableObject {
|
||||||
|
const result = super.prepForXml();
|
||||||
|
if (!!this._attr) {
|
||||||
|
if (!Array.isArray(result[this.rootKey])) {
|
||||||
|
result[this.rootKey] = [result[this.rootKey]];
|
||||||
|
}
|
||||||
|
result[this.rootKey].unshift({ _attr: this._attr });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(xmlComponent: XmlComponent) {
|
||||||
|
this.root.push(xmlComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for the attributes of root element that is being imported.
|
||||||
|
*/
|
||||||
|
export class ImportedRootElementAttributes extends XmlComponent {
|
||||||
|
constructor(private _attr: any) {
|
||||||
|
super("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public prepForXml(): IXmlableObject {
|
||||||
|
return {
|
||||||
|
_attr: this._attr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export * from "./xml-component";
|
export * from "./xml-component";
|
||||||
export * from "./attributes";
|
export * from "./attributes";
|
||||||
export * from "./default-attributes";
|
export * from "./default-attributes";
|
||||||
|
export * from './imported-xml-component';
|
||||||
export * from "./xmlable-object";
|
export * from "./xmlable-object";
|
||||||
|
@ -18,4 +18,15 @@ describe("XmlComponent", () => {
|
|||||||
assert.equal(newJson.rootKey, "w:test");
|
assert.equal(newJson.rootKey, "w:test");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#prepForXml()", () => {
|
||||||
|
it("should skip deleted elements", () => {
|
||||||
|
const child = new TestComponent("w:test1");
|
||||||
|
child.delete();
|
||||||
|
xmlComponent.addChildElement(child);
|
||||||
|
|
||||||
|
const xml = xmlComponent.prepForXml();
|
||||||
|
assert.equal(xml['w:test'].length, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -12,6 +12,12 @@ export abstract class XmlComponent extends BaseXmlComponent {
|
|||||||
|
|
||||||
public prepForXml(): IXmlableObject {
|
public prepForXml(): IXmlableObject {
|
||||||
const children = this.root
|
const children = this.root
|
||||||
|
.filter(c => {
|
||||||
|
if (c instanceof BaseXmlComponent) {
|
||||||
|
return !c.isDeleted;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.map((comp) => {
|
.map((comp) => {
|
||||||
if (comp instanceof BaseXmlComponent) {
|
if (comp instanceof BaseXmlComponent) {
|
||||||
return comp.prepForXml();
|
return comp.prepForXml();
|
||||||
@ -23,4 +29,12 @@ export abstract class XmlComponent extends BaseXmlComponent {
|
|||||||
[this.rootKey]: children,
|
[this.rootKey]: children,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addChildElement(child: XmlComponent | string) {
|
||||||
|
this.root.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete() {
|
||||||
|
this.deleted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user