make XmlAttributeComponent into a generic class

This change brings increased type safety to uses of
XmlAttributeComponent. Now the compiler is checkign for us that the
properties that get passed in to every subclass match the intended
interface, and also that the xmlKeys property -> xml attribute mapping
has all the right keys
This commit is contained in:
felipe
2017-03-10 10:42:24 +01:00
parent 766069c7cc
commit 62911d6e44
12 changed files with 100 additions and 154 deletions

View File

@ -26,39 +26,30 @@ interface IDocumentAttributesProperties {
type?: string;
}
export class DocumentAttributes extends XmlAttributeComponent {
constructor(properties?: IDocumentAttributesProperties) {
super({
wpc: "xmlns:wpc",
mc: "xmlns:mc",
o: "xmlns:o",
r: "xmlns:r",
m: "xmlns:m",
v: "xmlns:v",
wp14: "xmlns:wp14",
wp: "xmlns:wp",
w10: "xmlns:w10",
w: "xmlns:w",
w14: "xmlns:w14",
w15: "xmlns:w15",
wpg: "xmlns:wpg",
wpi: "xmlns:wpi",
wne: "xmlns:wne",
wps: "xmlns:wps",
Ignorable: "mc:Ignorable",
cp: "xmlns:cp",
dc: "xmlns:dc",
dcterms: "xmlns:dcterms",
dcmitype: "xmlns:dcmitype",
xsi: "xmlns:xsi",
type: "xsi:type",
}, properties);
this.root = properties;
if (!properties) {
this.root = {};
}
}
export class DocumentAttributes extends XmlAttributeComponent<IDocumentAttributesProperties> {
protected xmlKeys = {
wpc: "xmlns:wpc",
mc: "xmlns:mc",
o: "xmlns:o",
r: "xmlns:r",
m: "xmlns:m",
v: "xmlns:v",
wp14: "xmlns:wp14",
wp: "xmlns:wp",
w10: "xmlns:w10",
w: "xmlns:w",
w14: "xmlns:w14",
w15: "xmlns:w15",
wpg: "xmlns:wpg",
wpi: "xmlns:wpi",
wne: "xmlns:wne",
wps: "xmlns:wps",
Ignorable: "mc:Ignorable",
cp: "xmlns:cp",
dc: "xmlns:dc",
dcterms: "xmlns:dcterms",
dcmitype: "xmlns:dcmitype",
xsi: "xmlns:xsi",
type: "xsi:type",
};
}

View File

@ -1,18 +1,15 @@
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
interface IndentAttributesProperties {
interface IIndentAttributesProperties {
left: number;
hanging: number;
}
class IndentAttributes extends XmlAttributeComponent {
constructor(properties: IndentAttributesProperties) {
super({
left: "w:left",
hanging: "w:hanging",
}, properties);
}
class IndentAttributes extends XmlAttributeComponent<IIndentAttributesProperties> {
protected xmlKeys = {
left: "w:left",
hanging: "w:hanging",
};
}
export class Indent extends XmlComponent {

View File

@ -6,14 +6,12 @@ export interface ISpacingProperties {
line?: number;
}
class SpacingAttributes extends XmlAttributeComponent {
constructor(properties: ISpacingProperties) {
super({
after: "w:after",
before: "w:before",
line: "w:line",
}, properties);
}
class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
protected xmlKeys = {
after: "w:after",
before: "w:before",
line: "w:line",
};
}
export class Spacing extends XmlComponent {

View File

@ -6,15 +6,12 @@ interface IRunFontAttributesProperties {
hint?: string;
}
class RunFontAttributes extends XmlAttributeComponent {
constructor(properties: IRunFontAttributesProperties) {
super({
ascii: "w:ascii",
hAnsi: "w:hAnsi",
hint: "w:hint",
}, properties);
}
class RunFontAttributes extends XmlAttributeComponent<IRunFontAttributesProperties> {
protected xmlKeys = {
ascii: "w:ascii",
hAnsi: "w:hAnsi",
hint: "w:hint",
};
}
export class RunFonts extends XmlComponent {

View File

@ -22,29 +22,26 @@ interface IAttributesProperties {
pos?: string | number; // Little strange. Perhaps it is normal. Need to clarify in the spec.
}
export class Attributes extends XmlAttributeComponent {
constructor(properties?: IAttributesProperties) {
super({
val: "w:val",
color: "w:color",
space: "w:space",
sz: "w:sz",
type: "w:type",
rsidR: "w:rsidR",
rsidRPr: "w:rsidRPr",
rsidSect: "w:rsidSect",
w: "w:w",
h: "w:h",
top: "w:top",
right: "w:right",
bottom: "w:bottom",
left: "w:left",
header: "w:header",
footer: "w:footer",
gutter: "w:gutter",
linePitch: "w:linePitch",
pos: "w:pos",
}, properties);
}
export class Attributes extends XmlAttributeComponent<IAttributesProperties> {
protected xmlKeys = {
val: "w:val",
color: "w:color",
space: "w:space",
sz: "w:sz",
type: "w:type",
rsidR: "w:rsidR",
rsidRPr: "w:rsidRPr",
rsidSect: "w:rsidSect",
w: "w:w",
h: "w:h",
top: "w:top",
right: "w:right",
bottom: "w:bottom",
left: "w:left",
header: "w:header",
footer: "w:footer",
gutter: "w:gutter",
linePitch: "w:linePitch",
pos: "w:pos",
};
}

View File

@ -1,31 +1,25 @@
import * as _ from "lodash";
import { BaseXmlComponent } from "./base";
export abstract class XmlAttributeComponent extends BaseXmlComponent {
protected root: object;
private xmlKeys: object;
type AttributeMap<T> = {[P in keyof T]: string};
constructor(xmlKeys: object, properties: object) {
export abstract class XmlAttributeComponent<T> extends BaseXmlComponent {
protected root: T;
protected xmlKeys: AttributeMap<T>;
constructor(properties: T) {
super("_attr");
this.xmlKeys = xmlKeys;
this.root = properties;
if (!properties) {
this.root = {};
}
}
public prepForXml(): {_attr: {[key: string]: (string | number | boolean)}} {
const attrs = {};
if (this.root !== undefined) {
_.forOwn(this.root, (value, key) => {
if (value !== undefined) {
const newKey = this.xmlKeys[key];
attrs[newKey] = value;
}
});
}
Object.keys(this.root).forEach((key) => {
const value = this.root[key];
if (value !== undefined) {
const newKey = this.xmlKeys[key];
attrs[newKey] = value;
}
});
return {_attr: attrs};
}
}

View File

@ -7,14 +7,11 @@ interface IAbstractNumberingAttributesProperties {
restartNumberingAfterBreak?: number;
}
class AbstractNumberingAttributes extends XmlAttributeComponent {
constructor(properties: IAbstractNumberingAttributesProperties) {
super({
abstractNumId: "w:abstractNumId",
restartNumberingAfterBreak: "w15:restartNumberingAfterBreak",
}, properties);
}
class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberingAttributesProperties> {
protected xmlKeys = {
abstractNumId: "w:abstractNumId",
restartNumberingAfterBreak: "w15:restartNumberingAfterBreak",
};
}
export class AbstractNumbering extends XmlComponent {

View File

@ -7,14 +7,11 @@ interface ILevelAttributesProperties {
tentative?: number;
}
class LevelAttributes extends XmlAttributeComponent {
constructor(properties: ILevelAttributesProperties) {
super({
ilvl: "w:ilvl",
tentative: "w15:tentative",
}, properties);
}
class LevelAttributes extends XmlAttributeComponent<ILevelAttributesProperties> {
protected xmlKeys = {
ilvl: "w:ilvl",
tentative: "w15:tentative",
};
}
class Start extends XmlComponent {

View File

@ -14,13 +14,8 @@ interface INumAttributesProperties {
numId: number;
}
class NumAttributes extends XmlAttributeComponent {
constructor(properties: INumAttributesProperties) {
super({
numId: "w:numId",
}, properties);
}
class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
protected xmlKeys = {numId: "w:numId"};
}
export class Num extends XmlComponent {

View File

@ -4,10 +4,8 @@ interface IComponentAttributes {
val: string;
}
class ComponentAttributes extends XmlAttributeComponent {
constructor(properties: IComponentAttributes) {
super({val: "w:val"}, properties);
}
class ComponentAttributes extends XmlAttributeComponent<IComponentAttributes> {
protected xmlKeys = {val: "w:val"};
}
export class Name extends XmlComponent {

View File

@ -14,15 +14,13 @@ export interface IStyleAttributes {
customStyle?: string;
}
class StyleAttributes extends XmlAttributeComponent {
constructor(properties: IStyleAttributes) {
super({
type: "w:type",
styleId: "w:styleId",
default: "w:default",
customStyle: "w:customStyle",
}, properties);
}
class StyleAttributes extends XmlAttributeComponent<IStyleAttributes> {
protected xmlKeys = {
type: "w:type",
styleId: "w:styleId",
default: "w:default",
customStyle: "w:customStyle",
};
}
export class Style extends XmlComponent {

View File

@ -2,21 +2,8 @@ import { assert } from "chai";
import { Attributes } from "../../../docx/xml-components";
describe("Attribute", () => {
let attributes: Attributes;
beforeEach(() => {
attributes = new Attributes();
});
describe("#constructor()", () => {
it("should not add val with empty constructor", () => {
const newAttrs = new Attributes();
const stringifiedJson = JSON.stringify(newAttrs);
const newJson = JSON.parse(stringifiedJson);
assert.isUndefined(newJson.root.val);
});
it("should have val as defined with populated constructor", () => {
const newAttrs = new Attributes({
val: "test",