remove more duplicate classes; add additional values functions; clean up tests

This commit is contained in:
Tom Hunkapiller
2021-05-24 11:28:10 +03:00
parent a56119e7cd
commit ce2a0fb864
38 changed files with 311 additions and 362 deletions

View File

@ -45,18 +45,10 @@ describe("Formatter", () => {
{ {
"w:rPr": [ "w:rPr": [
{ {
"w:b": { "w:b": {},
_attr: {
"w:val": true,
},
},
}, },
{ {
"w:bCs": { "w:bCs": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}, },

View File

@ -33,7 +33,7 @@ export class BorderElement extends XmlComponent {
constructor(elementName: string, { color, ...options }: IBorderOptions) { constructor(elementName: string, { color, ...options }: IBorderOptions) {
super(elementName); super(elementName);
this.root.push( this.root.push(
new TableBordersAttributes({ new BordersAttributes({
...options, ...options,
color: color === undefined ? color : hexColorValue(color), color: color === undefined ? color : hexColorValue(color),
}), }),
@ -41,7 +41,7 @@ export class BorderElement extends XmlComponent {
} }
} }
class TableBordersAttributes extends XmlAttributeComponent<IBorderOptions> { class BordersAttributes extends XmlAttributeComponent<IBorderOptions> {
protected readonly xmlKeys = { protected readonly xmlKeys = {
style: "w:val", style: "w:val",
color: "w:color", color: "w:color",

View File

@ -351,7 +351,7 @@ describe("AbstractNumbering", () => {
]); ]);
const tree = new Formatter().format(abstractNumbering); const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:smallCaps": {} }],
}); });
}); });
@ -370,7 +370,7 @@ describe("AbstractNumbering", () => {
]); ]);
const tree = new Formatter().format(abstractNumbering); const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:caps": {} }],
}); });
}); });
@ -390,7 +390,7 @@ describe("AbstractNumbering", () => {
const tree = new Formatter().format(abstractNumbering); const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:strike": {} }],
}); });
}); });
@ -409,7 +409,7 @@ describe("AbstractNumbering", () => {
]); ]);
const tree = new Formatter().format(abstractNumbering); const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:dstrike": {} }],
}); });
}); });
@ -515,17 +515,17 @@ describe("AbstractNumbering", () => {
const boldTests = [ const boldTests = [
{ {
bold: true, bold: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }, { "w:bCs": {} }],
}, },
{ {
bold: true, bold: true,
boldComplexScript: true, boldComplexScript: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }, { "w:bCs": {} }],
}, },
{ {
bold: true, bold: true,
boldComplexScript: false, boldComplexScript: false,
expected: [{ "w:b": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }],
}, },
]; ];
boldTests.forEach(({ bold, boldComplexScript, expected }) => { boldTests.forEach(({ bold, boldComplexScript, expected }) => {
@ -548,17 +548,17 @@ describe("AbstractNumbering", () => {
const italicsTests = [ const italicsTests = [
{ {
italics: true, italics: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }, { "w:iCs": {} }],
}, },
{ {
italics: true, italics: true,
italicsComplexScript: true, italicsComplexScript: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }, { "w:iCs": {} }],
}, },
{ {
italics: true, italics: true,
italicsComplexScript: false, italicsComplexScript: false,
expected: [{ "w:i": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }],
}, },
]; ];
italicsTests.forEach(({ italics, italicsComplexScript, expected }) => { italicsTests.forEach(({ italics, italicsComplexScript, expected }) => {

View File

@ -1,15 +0,0 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Bidirectional } from "./bidirectional";
describe("Bidirectional", () => {
it("should create", () => {
const bidirectional = new Bidirectional();
const tree = new Formatter().format(bidirectional);
expect(tree).to.deep.equal({
"w:bidi": {},
});
});
});

View File

@ -1,7 +0,0 @@
import { XmlComponent } from "file/xml-components";
export class Bidirectional extends XmlComponent {
constructor() {
super("w:bidi");
}
}

View File

@ -1,7 +1,6 @@
export * from "./alignment"; export * from "./alignment";
export * from "./border"; export * from "./border";
export * from "./indent"; export * from "./indent";
export * from "./keep";
export * from "./page-break"; export * from "./page-break";
export * from "./spacing"; export * from "./spacing";
export * from "./style"; export * from "./style";

View File

@ -1,13 +0,0 @@
import { XmlComponent } from "file/xml-components";
export class KeepLines extends XmlComponent {
constructor() {
super("w:keepLines");
}
}
export class KeepNext extends XmlComponent {
constructor() {
super("w:keepNext");
}
}

View File

@ -2,7 +2,7 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { ContextualSpacing, Spacing } from "./spacing"; import { Spacing } from "./spacing";
describe("Spacing", () => { describe("Spacing", () => {
describe("#constructor", () => { describe("#constructor", () => {
@ -23,23 +23,3 @@ describe("Spacing", () => {
}); });
}); });
}); });
describe("ContextualSpacing", () => {
describe("#constructor", () => {
it("should create", () => {
const spacing = new ContextualSpacing(true);
const tree = new Formatter().format(spacing);
expect(tree).to.deep.equal({
"w:contextualSpacing": { _attr: { "w:val": 1 } },
});
});
it("should create with value of 0 if param is false", () => {
const spacing = new ContextualSpacing(false);
const tree = new Formatter().format(spacing);
expect(tree).to.deep.equal({
"w:contextualSpacing": { _attr: { "w:val": 0 } },
});
});
});
});

View File

@ -1,5 +1,5 @@
// http://officeopenxml.com/WPspacing.php // http://officeopenxml.com/WPspacing.php
import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export enum LineRuleType { export enum LineRuleType {
AT_LEAST = "atLeast", AT_LEAST = "atLeast",
@ -30,14 +30,3 @@ export class Spacing extends XmlComponent {
this.root.push(new SpacingAttributes(options)); this.root.push(new SpacingAttributes(options));
} }
} }
export class ContextualSpacing extends XmlComponent {
constructor(value: boolean) {
super("w:contextualSpacing");
this.root.push(
new Attributes({
val: value === false ? 0 : 1,
}),
);
}
}

View File

@ -1,20 +0,0 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { WidowControl } from "./widow-control";
describe("WidowControl", () => {
it("should create", () => {
const widowControl = new WidowControl(true);
const tree = new Formatter().format(widowControl);
expect(tree).to.deep.equal({
"w:widowControl": {
_attr: {
"w:val": true,
},
},
});
});
});

View File

@ -1,13 +0,0 @@
// http://www.datypic.com/sc/ooxml/e-w_widowControl-1.html
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export class WidowControlAttributes extends XmlAttributeComponent<{ readonly val: boolean }> {
protected readonly xmlKeys = { val: "w:val" };
}
export class WidowControl extends XmlComponent {
constructor(value: boolean) {
super("w:widowControl");
this.root.push(new WidowControlAttributes({ val: value }));
}
}

View File

@ -416,7 +416,7 @@ describe("Paragraph", () => {
}); });
describe("#contextualSpacing()", () => { describe("#contextualSpacing()", () => {
it("should add contextualSpacing to JSON, and set 1 if true", () => { it("should add contextualSpacing", () => {
const paragraph = new Paragraph({ const paragraph = new Paragraph({
contextualSpacing: true, contextualSpacing: true,
}); });
@ -424,7 +424,20 @@ describe("Paragraph", () => {
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:p": [ "w:p": [
{ {
"w:pPr": [{ "w:contextualSpacing": { _attr: { "w:val": 1 } } }], "w:pPr": [{ "w:contextualSpacing": {} }],
},
],
});
});
it("should remove contextualSpacing", () => {
const paragraph = new Paragraph({
contextualSpacing: false,
});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [{ "w:contextualSpacing": { _attr: { "w:val": false } } }],
}, },
], ],
}); });

View File

@ -86,11 +86,7 @@ describe("ParagraphProperties", () => {
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:pPr": [ "w:pPr": [
{ {
"w:widowControl": { "w:widowControl": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}); });

View File

@ -1,18 +1,15 @@
// http://officeopenxml.com/WPparagraphProperties.php // http://officeopenxml.com/WPparagraphProperties.php
import { IContext, IgnoreIfEmptyXmlComponent, IXmlableObject, XmlComponent } from "file/xml-components"; import { IContext, IgnoreIfEmptyXmlComponent, IXmlableObject, OnOffElement, XmlComponent } from "file/xml-components";
import { DocumentWrapper } from "../document-wrapper"; import { DocumentWrapper } from "../document-wrapper";
import { IShadingAttributesProperties, Shading } from "../shading"; import { IShadingAttributesProperties, Shading } from "../shading";
import { Alignment, AlignmentType } from "./formatting/alignment"; import { Alignment, AlignmentType } from "./formatting/alignment";
import { Bidirectional } from "./formatting/bidirectional";
import { Border, IBordersOptions, ThematicBreak } from "./formatting/border"; import { Border, IBordersOptions, ThematicBreak } from "./formatting/border";
import { IIndentAttributesProperties, Indent } from "./formatting/indent"; import { IIndentAttributesProperties, Indent } from "./formatting/indent";
import { KeepLines, KeepNext } from "./formatting/keep";
import { PageBreakBefore } from "./formatting/page-break"; import { PageBreakBefore } from "./formatting/page-break";
import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spacing"; import { ISpacingProperties, Spacing } from "./formatting/spacing";
import { HeadingLevel, Style } from "./formatting/style"; import { HeadingLevel, Style } from "./formatting/style";
import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop"; import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop";
import { NumberProperties } from "./formatting/unordered-list"; import { NumberProperties } from "./formatting/unordered-list";
import { WidowControl } from "./formatting/widow-control";
import { FrameProperties, IFrameOptions } from "./frame/frame-properties"; import { FrameProperties, IFrameOptions } from "./frame/frame-properties";
import { OutlineLevel } from "./links"; import { OutlineLevel } from "./links";
@ -84,12 +81,12 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
this.push(new Style(options.style)); this.push(new Style(options.style));
} }
if (options.keepNext) { if (options.keepNext !== undefined) {
this.push(new KeepNext()); this.push(new OnOffElement("w:keepNext", options.keepNext));
} }
if (options.keepLines) { if (options.keepLines !== undefined) {
this.push(new KeepLines()); this.push(new OnOffElement("w:keepLines", options.keepLines));
} }
if (options.pageBreakBefore) { if (options.pageBreakBefore) {
@ -100,8 +97,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
this.push(new FrameProperties(options.frame)); this.push(new FrameProperties(options.frame));
} }
if (options.widowControl) { if (options.widowControl !== undefined) {
this.push(new WidowControl(options.widowControl)); this.push(new OnOffElement("w:widowControl", options.widowControl));
} }
if (options.bullet) { if (options.bullet) {
@ -143,8 +140,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
this.push(new TabStop(TabStopType.LEFT, options.leftTabStop)); this.push(new TabStop(TabStopType.LEFT, options.leftTabStop));
} }
if (options.bidirectional) { if (options.bidirectional !== undefined) {
this.push(new Bidirectional()); this.push(new OnOffElement("w:bidi", options.contextualSpacing));
} }
if (options.spacing) { if (options.spacing) {
@ -155,8 +152,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
this.push(new Indent(options.indent)); this.push(new Indent(options.indent));
} }
if (options.contextualSpacing) { if (options.contextualSpacing !== undefined) {
this.push(new ContextualSpacing(options.contextualSpacing)); this.push(new OnOffElement("w:contextualSpacing", options.contextualSpacing));
} }
if (options.alignment) { if (options.alignment) {

View File

@ -98,18 +98,19 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
return; return;
} }
if (options.bold) { if (options.bold !== undefined) {
this.push(new OnOffElement("w:b")); this.push(new OnOffElement("w:b", options.bold));
} }
if ((options.boldComplexScript === undefined && options.bold) || options.boldComplexScript) { if ((options.boldComplexScript === undefined && options.bold !== undefined) || options.boldComplexScript) {
this.push(new OnOffElement("w:bCs")); this.push(new OnOffElement("w:bCs", options.boldComplexScript ?? options.bold));
} }
if (options.italics) { if (options.italics !== undefined) {
this.push(new OnOffElement("w:i")); this.push(new OnOffElement("w:i", options.italics));
} }
if ((options.italicsComplexScript === undefined && options.italics) || options.italicsComplexScript) {
this.push(new OnOffElement("w:iCs")); if ((options.italicsComplexScript === undefined && options.italics !== undefined) || options.italicsComplexScript) {
this.push(new OnOffElement("w:iCs", options.italicsComplexScript ?? options.italics));
} }
if (options.underline) { if (options.underline) {
@ -124,7 +125,7 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
this.push(new Color(options.color)); this.push(new Color(options.color));
} }
if (options.size) { if (options.size !== undefined) {
this.push(new HpsMeasureElement("w:sz", options.size)); this.push(new HpsMeasureElement("w:sz", options.size));
} }
const szCs = const szCs =
@ -133,23 +134,23 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
this.push(new HpsMeasureElement("w:szCs", szCs)); this.push(new HpsMeasureElement("w:szCs", szCs));
} }
if (options.rightToLeft) { if (options.rightToLeft !== undefined) {
this.push(new OnOffElement("w:rtl")); this.push(new OnOffElement("w:rtl", options.rightToLeft));
} }
// These two are mutually exclusive // These two are mutually exclusive
if (options.smallCaps) { if (options.smallCaps !== undefined) {
this.push(new OnOffElement("w:smallCaps")); this.push(new OnOffElement("w:smallCaps", options.smallCaps));
} else if (options.allCaps) { } else if (options.allCaps !== undefined) {
this.push(new OnOffElement("w:caps")); this.push(new OnOffElement("w:caps", options.allCaps));
} }
if (options.strike) { if (options.strike !== undefined) {
this.push(new OnOffElement("w:strike")); this.push(new OnOffElement("w:strike", options.strike));
} }
if (options.doubleStrike) { if (options.doubleStrike !== undefined) {
this.push(new OnOffElement("w:dstrike")); this.push(new OnOffElement("w:dstrike", options.doubleStrike));
} }
if (options.subScript) { if (options.subScript) {
@ -189,12 +190,12 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent {
this.push(new CharacterSpacing(options.characterSpacing)); this.push(new CharacterSpacing(options.characterSpacing));
} }
if (options.emboss) { if (options.emboss !== undefined) {
this.push(new OnOffElement("w:emboss")); this.push(new OnOffElement("w:emboss", options.emboss));
} }
if (options.imprint) { if (options.imprint !== undefined) {
this.push(new OnOffElement("w:imprint")); this.push(new OnOffElement("w:imprint", options.imprint));
} }
if (options.shading) { if (options.shading) {

View File

@ -20,13 +20,9 @@ describe("Run", () => {
"w:r": [ "w:r": [
{ {
"w:rPr": [ "w:rPr": [
{ "w:b": { _attr: { "w:val": true } } }, { "w:b": {} },
{ {
"w:bCs": { "w:bCs": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}, },
@ -45,13 +41,9 @@ describe("Run", () => {
"w:r": [ "w:r": [
{ {
"w:rPr": [ "w:rPr": [
{ "w:i": { _attr: { "w:val": true } } }, { "w:i": {} },
{ {
"w:iCs": { "w:iCs": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}, },
@ -116,7 +108,7 @@ describe("Run", () => {
}); });
const tree = new Formatter().format(run); const tree = new Formatter().format(run);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }] }], "w:r": [{ "w:rPr": [{ "w:smallCaps": {} }] }],
}); });
}); });
}); });
@ -128,7 +120,7 @@ describe("Run", () => {
}); });
const tree = new Formatter().format(run); const tree = new Formatter().format(run);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }] }], "w:r": [{ "w:rPr": [{ "w:caps": {} }] }],
}); });
}); });
}); });
@ -140,7 +132,7 @@ describe("Run", () => {
}); });
const tree = new Formatter().format(run); const tree = new Formatter().format(run);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }] }], "w:r": [{ "w:rPr": [{ "w:strike": {} }] }],
}); });
}); });
}); });
@ -152,7 +144,7 @@ describe("Run", () => {
}); });
const tree = new Formatter().format(run); const tree = new Formatter().format(run);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }] }], "w:r": [{ "w:rPr": [{ "w:dstrike": {} }] }],
}); });
}); });
}); });
@ -164,7 +156,7 @@ describe("Run", () => {
}); });
const tree = new Formatter().format(run); const tree = new Formatter().format(run);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:emboss": { _attr: { "w:val": true } } }] }], "w:r": [{ "w:rPr": [{ "w:emboss": {} }] }],
}); });
}); });
}); });
@ -176,7 +168,7 @@ describe("Run", () => {
}); });
const tree = new Formatter().format(run); const tree = new Formatter().format(run);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:imprint": { _attr: { "w:val": true } } }] }], "w:r": [{ "w:rPr": [{ "w:imprint": {} }] }],
}); });
}); });
}); });
@ -367,7 +359,7 @@ describe("Run", () => {
}); });
const tree = new Formatter().format(run); const tree = new Formatter().format(run);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:rtl": { _attr: { "w:val": true } } }] }], "w:r": [{ "w:rPr": [{ "w:rtl": {} }] }],
}); });
}); });
}); });

View File

@ -59,10 +59,10 @@ describe("SymbolRun", () => {
"w:r": [ "w:r": [
{ {
"w:rPr": [ "w:rPr": [
{ "w:b": { _attr: { "w:val": true } } }, { "w:b": {} },
{ "w:bCs": { _attr: { "w:val": true } } }, { "w:bCs": {} },
{ "w:i": { _attr: { "w:val": true } } }, { "w:i": {} },
{ "w:iCs": { _attr: { "w:val": true } } }, { "w:iCs": {} },
{ "w:u": { _attr: { "w:val": "double", "w:color": "ff0000" } } }, { "w:u": { _attr: { "w:val": "double", "w:color": "ff0000" } } },
{ "w:em": { _attr: { "w:val": "dot" } } }, { "w:em": { _attr: { "w:val": "dot" } } },
{ "w:color": { _attr: { "w:val": "00FF00" } } }, { "w:color": { _attr: { "w:val": "00FF00" } } },

View File

@ -66,7 +66,7 @@ describe("CharacterStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:smallCaps": {} }],
}, },
{ {
"w:uiPriority": { "w:uiPriority": {
@ -94,7 +94,7 @@ describe("CharacterStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:caps": {} }],
}, },
{ {
"w:uiPriority": { "w:uiPriority": {
@ -122,7 +122,7 @@ describe("CharacterStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:strike": {} }],
}, },
{ {
"w:uiPriority": { "w:uiPriority": {
@ -150,7 +150,7 @@ describe("CharacterStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:dstrike": {} }],
}, },
{ {
"w:uiPriority": { "w:uiPriority": {
@ -601,17 +601,17 @@ describe("CharacterStyle", () => {
const boldTests = [ const boldTests = [
{ {
bold: true, bold: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }, { "w:bCs": {} }],
}, },
{ {
bold: true, bold: true,
boldComplexScript: true, boldComplexScript: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }, { "w:bCs": {} }],
}, },
{ {
bold: true, bold: true,
boldComplexScript: false, boldComplexScript: false,
expected: [{ "w:b": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }],
}, },
]; ];
boldTests.forEach(({ bold, boldComplexScript, expected }) => { boldTests.forEach(({ bold, boldComplexScript, expected }) => {
@ -645,17 +645,17 @@ describe("CharacterStyle", () => {
const italicsTests = [ const italicsTests = [
{ {
italics: true, italics: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }, { "w:iCs": {} }],
}, },
{ {
italics: true, italics: true,
italicsComplexScript: true, italicsComplexScript: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }, { "w:iCs": {} }],
}, },
{ {
italics: true, italics: true,
italicsComplexScript: false, italicsComplexScript: false,
expected: [{ "w:i": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }],
}, },
]; ];
italicsTests.forEach(({ italics, italicsComplexScript, expected }) => { italicsTests.forEach(({ italics, italicsComplexScript, expected }) => {

View File

@ -242,11 +242,7 @@ describe("ParagraphStyle", () => {
{ {
"w:pPr": [ "w:pPr": [
{ {
"w:contextualSpacing": { "w:contextualSpacing": {},
_attr: {
"w:val": 1,
},
},
}, },
], ],
}, },
@ -404,7 +400,7 @@ describe("ParagraphStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:smallCaps": {} }],
}, },
], ],
}); });
@ -422,7 +418,7 @@ describe("ParagraphStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:caps": {} }],
}, },
], ],
}); });
@ -440,7 +436,7 @@ describe("ParagraphStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:strike": {} }],
}, },
], ],
}); });
@ -458,7 +454,7 @@ describe("ParagraphStyle", () => {
"w:style": [ "w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ {
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], "w:rPr": [{ "w:dstrike": {} }],
}, },
], ],
}); });
@ -562,17 +558,17 @@ describe("ParagraphStyle", () => {
const boldTests = [ const boldTests = [
{ {
bold: true, bold: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }, { "w:bCs": {} }],
}, },
{ {
bold: true, bold: true,
boldComplexScript: true, boldComplexScript: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }, { "w:bCs": {} }],
}, },
{ {
bold: true, bold: true,
boldComplexScript: false, boldComplexScript: false,
expected: [{ "w:b": { _attr: { "w:val": true } } }], expected: [{ "w:b": {} }],
}, },
]; ];
boldTests.forEach(({ bold, boldComplexScript, expected }) => { boldTests.forEach(({ bold, boldComplexScript, expected }) => {
@ -591,17 +587,17 @@ describe("ParagraphStyle", () => {
const italicsTests = [ const italicsTests = [
{ {
italics: true, italics: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }, { "w:iCs": {} }],
}, },
{ {
italics: true, italics: true,
italicsComplexScript: true, italicsComplexScript: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }, { "w:iCs": {} }],
}, },
{ {
italics: true, italics: true,
italicsComplexScript: false, italicsComplexScript: false,
expected: [{ "w:i": { _attr: { "w:val": true } } }], expected: [{ "w:i": {} }],
}, },
]; ];
italicsTests.forEach(({ italics, italicsComplexScript, expected }) => { italicsTests.forEach(({ italics, italicsComplexScript, expected }) => {

View File

@ -1,8 +1,19 @@
// http://officeopenxml.com/WPtableGrid.php // http://officeopenxml.com/WPtableGrid.php
// <xsd:complexType name="CT_TblGridCol">
// <xsd:attribute name="w" type="s:ST_TwipsMeasure"/>
// </xsd:complexType>
// <xsd:complexType name="CT_TblGridBase">
// <xsd:sequence>
// <xsd:element name="gridCol" type="CT_TblGridCol" minOccurs="0" maxOccurs="unbounded"/>
// </xsd:sequence>
// </xsd:complexType>
import { twipsMeasureValue } from "file/values";
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export class TableGrid extends XmlComponent { export class TableGrid extends XmlComponent {
constructor(widths: number[]) { constructor(widths: number[] | string[]) {
super("w:tblGrid"); super("w:tblGrid");
for (const width of widths) { for (const width of widths) {
this.root.push(new GridCol(width)); this.root.push(new GridCol(width));
@ -10,15 +21,15 @@ export class TableGrid extends XmlComponent {
} }
} }
class GridColAttributes extends XmlAttributeComponent<{ readonly w: number }> { class GridColAttributes extends XmlAttributeComponent<{ readonly w: number | string }> {
protected readonly xmlKeys = { w: "w:w" }; protected readonly xmlKeys = { w: "w:w" };
} }
export class GridCol extends XmlComponent { export class GridCol extends XmlComponent {
constructor(width?: number) { constructor(width?: number | string) {
super("w:gridCol"); super("w:gridCol");
if (width !== undefined) { if (width !== undefined) {
this.root.push(new GridColAttributes({ w: width })); this.root.push(new GridColAttributes({ w: twipsMeasureValue(width) }));
} }
} }
} }

View File

@ -129,6 +129,19 @@ export class TableFloatOptionsAttributes extends XmlAttributeComponent<ITableFlo
}; };
} }
// <xsd:complexType name="CT_TblPPr">
// <xsd:attribute name="leftFromText" type="s:ST_TwipsMeasure"/>
// <xsd:attribute name="rightFromText" type="s:ST_TwipsMeasure"/>
// <xsd:attribute name="topFromText" type="s:ST_TwipsMeasure"/>
// <xsd:attribute name="bottomFromText" type="s:ST_TwipsMeasure"/>
// <xsd:attribute name="vertAnchor" type="ST_VAnchor"/>
// <xsd:attribute name="horzAnchor" type="ST_HAnchor"/>
// <xsd:attribute name="tblpXSpec" type="s:ST_XAlign"/>
// <xsd:attribute name="tblpX" type="ST_SignedTwipsMeasure"/>
// <xsd:attribute name="tblpYSpec" type="s:ST_YAlign"/>
// <xsd:attribute name="tblpY" type="ST_SignedTwipsMeasure"/>
// </xsd:complexType>
export class TableFloatProperties extends XmlComponent { export class TableFloatProperties extends XmlComponent {
constructor(options: ITableFloatOptions) { constructor(options: ITableFloatOptions) {
super("w:tblpPr"); super("w:tblpPr");

View File

@ -9,6 +9,15 @@ class TableLayoutAttributes extends XmlAttributeComponent<{ readonly type: Table
protected readonly xmlKeys = { type: "w:type" }; protected readonly xmlKeys = { type: "w:type" };
} }
// <xsd:complexType name="CT_TblLayoutType">
// <xsd:attribute name="type" type="ST_TblLayoutType"/>
// </xsd:complexType>
// <xsd:simpleType name="ST_TblLayoutType">
// <xsd:restriction base="xsd:string">
// <xsd:enumeration value="fixed"/>
// <xsd:enumeration value="autofit"/>
// </xsd:restriction>
// </xsd:simpleType>
export class TableLayout extends XmlComponent { export class TableLayout extends XmlComponent {
constructor(type: TableLayoutType) { constructor(type: TableLayoutType) {
super("w:tblLayout"); super("w:tblLayout");

View File

@ -5,6 +5,16 @@ export enum OverlapType {
OVERLAP = "overlap", OVERLAP = "overlap",
} }
// <xsd:complexType name="CT_TblOverlap">
// <xsd:attribute name="val" type="ST_TblOverlap" use="required"/>
// </xsd:complexType>
// <xsd:simpleType name="ST_TblOverlap">
// <xsd:restriction base="xsd:string">
// <xsd:enumeration value="never"/>
// <xsd:enumeration value="overlap"/>
// </xsd:restriction>
// </xsd:simpleType>
class TableOverlapAttributes extends XmlAttributeComponent<{ readonly val: OverlapType }> { class TableOverlapAttributes extends XmlAttributeComponent<{ readonly val: OverlapType }> {
protected readonly xmlKeys = { val: "w:val" }; protected readonly xmlKeys = { val: "w:val" };
} }

View File

@ -21,7 +21,7 @@
// <xsd:element name="tblDescription" type="CT_String" minOccurs="0" maxOccurs="1"/> // <xsd:element name="tblDescription" type="CT_String" minOccurs="0" maxOccurs="1"/>
// </xsd:sequence> // </xsd:sequence>
// </xsd:complexType> // </xsd:complexType>
import { IgnoreIfEmptyXmlComponent } from "file/xml-components"; import { IgnoreIfEmptyXmlComponent, OnOffElement, StringValueElement } from "file/xml-components";
import { Alignment, AlignmentType } from "../../paragraph"; import { Alignment, AlignmentType } from "../../paragraph";
import { IShadingAttributesProperties, Shading } from "../../shading"; import { IShadingAttributesProperties, Shading } from "../../shading";
@ -30,8 +30,6 @@ import { ITableBordersOptions, TableBorders } from "./table-borders";
import { ITableCellMarginOptions, TableCellMargin, TableCellMarginElementType } from "./table-cell-margin"; import { ITableCellMarginOptions, TableCellMargin, TableCellMarginElementType } from "./table-cell-margin";
import { ITableFloatOptions, TableFloatProperties } from "./table-float-properties"; import { ITableFloatOptions, TableFloatProperties } from "./table-float-properties";
import { TableLayout, TableLayoutType } from "./table-layout"; import { TableLayout, TableLayoutType } from "./table-layout";
import { TableStyle } from "./table-style";
import { VisuallyRightToLeft } from "./visually-right-to-left";
export interface ITablePropertiesOptions { export interface ITablePropertiesOptions {
readonly width?: ITableWidthProperties; readonly width?: ITableWidthProperties;
@ -51,15 +49,15 @@ export class TableProperties extends IgnoreIfEmptyXmlComponent {
super("w:tblPr"); super("w:tblPr");
if (options.style) { if (options.style) {
this.root.push(new TableStyle(options.style)); this.root.push(new StringValueElement("w:tblStyle", options.style));
} }
if (options.float) { if (options.float) {
this.root.push(new TableFloatProperties(options.float)); this.root.push(new TableFloatProperties(options.float));
} }
if (options.visuallyRightToLeft) { if (options.visuallyRightToLeft !== undefined) {
this.root.push(new VisuallyRightToLeft()); this.root.push(new OnOffElement("w:bidiVisual", options.visuallyRightToLeft));
} }
if (options.width) { if (options.width) {

View File

@ -1,22 +0,0 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { TableStyle } from "./table-style";
describe("TableStyle", () => {
describe("#constructor", () => {
it("should create", () => {
const tableStyle = new TableStyle("test-id");
const tree = new Formatter().format(tableStyle);
expect(tree).to.deep.equal({
"w:tblStyle": {
_attr: {
"w:val": "test-id",
},
},
});
});
});
});

View File

@ -1,13 +0,0 @@
import { Attributes, XmlComponent } from "file/xml-components";
export class TableStyle extends XmlComponent {
constructor(styleId: string) {
super("w:tblStyle");
this.root.push(
new Attributes({
val: styleId,
}),
);
}
}

View File

@ -1,14 +0,0 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { VisuallyRightToLeft } from "./visually-right-to-left";
describe("VisuallyRightToLeft", () => {
it("should create", () => {
const visuallyRightToLeft = new VisuallyRightToLeft();
const tree = new Formatter().format(visuallyRightToLeft);
expect(tree).to.deep.equal({
"w:bidiVisual": {},
});
});
});

View File

@ -1,8 +0,0 @@
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_bidiVisual_topic_ID0EOXIQ.html
import { XmlComponent } from "file/xml-components";
export class VisuallyRightToLeft extends XmlComponent {
constructor() {
super("w:bidiVisual");
}
}

View File

@ -1,5 +1,18 @@
import { twipsMeasureValue } from "file/values";
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
// <xsd:complexType name="CT_Height">
// <xsd:attribute name="val" type="s:ST_TwipsMeasure"/>
// <xsd:attribute name="hRule" type="ST_HeightRule"/>
// </xsd:complexType>
// <xsd:simpleType name="ST_HeightRule">
// <xsd:restriction base="xsd:string">
// <xsd:enumeration value="auto"/>
// <xsd:enumeration value="exact"/>
// <xsd:enumeration value="atLeast"/>
// </xsd:restriction>
// </xsd:simpleType>
export enum HeightRule { export enum HeightRule {
/** Height is determined based on the content, so value is ignored. */ /** Height is determined based on the content, so value is ignored. */
AUTO = "auto", AUTO = "auto",
@ -10,19 +23,19 @@ export enum HeightRule {
} }
export class TableRowHeightAttributes extends XmlAttributeComponent<{ export class TableRowHeightAttributes extends XmlAttributeComponent<{
readonly value: number; readonly value: number | string;
readonly rule: HeightRule; readonly rule: HeightRule;
}> { }> {
protected readonly xmlKeys = { value: "w:val", rule: "w:hRule" }; protected readonly xmlKeys = { value: "w:val", rule: "w:hRule" };
} }
export class TableRowHeight extends XmlComponent { export class TableRowHeight extends XmlComponent {
constructor(value: number, rule: HeightRule) { constructor(value: number | string, rule: HeightRule) {
super("w:trHeight"); super("w:trHeight");
this.root.push( this.root.push(
new TableRowHeightAttributes({ new TableRowHeightAttributes({
value: value, value: twipsMeasureValue(value),
rule: rule, rule: rule,
}), }),
); );

View File

@ -17,13 +17,13 @@ describe("TableRowProperties", () => {
it("sets cantSplit to avoid row been paginated", () => { it("sets cantSplit to avoid row been paginated", () => {
const rowProperties = new TableRowProperties({ cantSplit: true }); const rowProperties = new TableRowProperties({ cantSplit: true });
const tree = new Formatter().format(rowProperties); const tree = new Formatter().format(rowProperties);
expect(tree).to.deep.equal({ "w:trPr": [{ "w:cantSplit": { _attr: { "w:val": true } } }] }); expect(tree).to.deep.equal({ "w:trPr": [{ "w:cantSplit": {} }] });
}); });
it("sets row as table header (repeat row on each page of table)", () => { it("sets row as table header (repeat row on each page of table)", () => {
const rowProperties = new TableRowProperties({ tableHeader: true }); const rowProperties = new TableRowProperties({ tableHeader: true });
const tree = new Formatter().format(rowProperties); const tree = new Formatter().format(rowProperties);
expect(tree).to.deep.equal({ "w:trPr": [{ "w:tblHeader": { _attr: { "w:val": true } } }] }); expect(tree).to.deep.equal({ "w:trPr": [{ "w:tblHeader": {} }] });
}); });
it("sets row height exact", () => { it("sets row height exact", () => {

View File

@ -1,5 +1,33 @@
// http://officeopenxml.com/WPtableRowProperties.php // http://officeopenxml.com/WPtableRowProperties.php
import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "file/xml-components";
// <xsd:complexType name="CT_TrPrBase">
// <xsd:choice maxOccurs="unbounded">
// <xsd:element name="cnfStyle" type="CT_Cnf" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="divId" type="CT_DecimalNumber" minOccurs="0"/>
// <xsd:element name="gridBefore" type="CT_DecimalNumber" minOccurs="0"/>
// <xsd:element name="gridAfter" type="CT_DecimalNumber" minOccurs="0"/>
// <xsd:element name="wBefore" type="CT_TblWidth" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="wAfter" type="CT_TblWidth" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="cantSplit" type="CT_OnOff" minOccurs="0"/>
// <xsd:element name="trHeight" type="CT_Height" minOccurs="0"/>
// <xsd:element name="tblHeader" type="CT_OnOff" minOccurs="0"/>
// <xsd:element name="tblCellSpacing" type="CT_TblWidth" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="jc" type="CT_JcTable" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="hidden" type="CT_OnOff" minOccurs="0"/>
// </xsd:choice>
// </xsd:complexType>
// <xsd:complexType name="CT_TrPr">
// <xsd:complexContent>
// <xsd:extension base="CT_TrPrBase">
// <xsd:sequence>
// <xsd:element name="ins" type="CT_TrackChange" minOccurs="0"/>
// <xsd:element name="del" type="CT_TrackChange" minOccurs="0"/>
// <xsd:element name="trPrChange" type="CT_TrPrChange" minOccurs="0"/>
// </xsd:sequence>
// </xsd:extension>
// </xsd:complexContent>
// </xsd:complexType>
import { IgnoreIfEmptyXmlComponent, OnOffElement } from "file/xml-components";
import { HeightRule, TableRowHeight } from "./table-row-height"; import { HeightRule, TableRowHeight } from "./table-row-height";
@ -7,7 +35,7 @@ export interface ITableRowPropertiesOptions {
readonly cantSplit?: boolean; readonly cantSplit?: boolean;
readonly tableHeader?: boolean; readonly tableHeader?: boolean;
readonly height?: { readonly height?: {
readonly value: number; readonly value: number | string;
readonly rule: HeightRule; readonly rule: HeightRule;
}; };
} }
@ -16,12 +44,12 @@ export class TableRowProperties extends IgnoreIfEmptyXmlComponent {
constructor(options: ITableRowPropertiesOptions) { constructor(options: ITableRowPropertiesOptions) {
super("w:trPr"); super("w:trPr");
if (options.cantSplit) { if (options.cantSplit !== undefined) {
this.root.push(new CantSplit()); this.root.push(new OnOffElement("w:cantSplit", options.cantSplit));
} }
if (options.tableHeader) { if (options.tableHeader !== undefined) {
this.root.push(new TableHeader()); this.root.push(new OnOffElement("w:tblHeader", options.tableHeader));
} }
if (options.height) { if (options.height) {
@ -29,25 +57,3 @@ export class TableRowProperties extends IgnoreIfEmptyXmlComponent {
} }
} }
} }
class CantSplitAttributes extends XmlAttributeComponent<{ readonly val: boolean }> {
protected readonly xmlKeys = { val: "w:val" };
}
export class CantSplit extends XmlComponent {
constructor() {
super("w:cantSplit");
this.root.push(new CantSplitAttributes({ val: true }));
}
}
class TableHeaderAttributes extends XmlAttributeComponent<{ readonly val: boolean }> {
protected readonly xmlKeys = { val: "w:val" };
}
export class TableHeader extends XmlComponent {
constructor() {
super("w:tblHeader");
this.root.push(new TableHeaderAttributes({ val: true }));
}
}

View File

@ -53,11 +53,7 @@ describe("TableRow", () => {
{ {
"w:trPr": [ "w:trPr": [
{ {
"w:cantSplit": { "w:cantSplit": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}, },
@ -76,11 +72,7 @@ describe("TableRow", () => {
{ {
"w:trPr": [ "w:trPr": [
{ {
"w:tblHeader": { "w:tblHeader": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}, },
@ -141,11 +133,7 @@ describe("TableRow", () => {
{ {
"w:trPr": [ "w:trPr": [
{ {
"w:tblHeader": { "w:tblHeader": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}, },

View File

@ -1,4 +1,5 @@
// http://officeopenxml.com/WPtableWidth.php // http://officeopenxml.com/WPtableWidth.php
import { measurementOrPercentValue } from "file/values";
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
// <xsd:simpleType name="ST_TblWidth"> // <xsd:simpleType name="ST_TblWidth">
@ -37,6 +38,6 @@ export class TableWidthElement extends XmlComponent {
constructor(name: string, { type = WidthType.AUTO, size }: ITableWidthProperties) { constructor(name: string, { type = WidthType.AUTO, size }: ITableWidthProperties) {
super(name); super(name);
// super("w:tblW"); // super("w:tblW");
this.root.push(new TableWidthAttributes({ type: type, size: type === WidthType.PERCENTAGE ? `${size}%` : size })); this.root.push(new TableWidthAttributes({ type: type, size: measurementOrPercentValue(size) }));
} }
} }

View File

@ -282,7 +282,7 @@ describe("Table", () => {
"w:tblW": { "w:tblW": {
_attr: { _attr: {
"w:type": "pct", "w:type": "pct",
"w:w": "100%", "w:w": 100,
}, },
}, },
}, },

View File

@ -54,18 +54,10 @@ describe("DeletedTextRun", () => {
{ {
"w:rPr": [ "w:rPr": [
{ {
"w:b": { "w:b": {},
_attr: {
"w:val": true,
},
},
}, },
{ {
"w:bCs": { "w:bCs": {},
_attr: {
"w:val": true,
},
},
}, },
], ],
}, },

View File

@ -4,6 +4,7 @@ import {
hpsMeasureValue, hpsMeasureValue,
positiveUniversalMeasureValue, positiveUniversalMeasureValue,
signedTwipsMeasureValue, signedTwipsMeasureValue,
twipsMeasureValue,
universalMeasureValue, universalMeasureValue,
unsignedDecimalNumber, unsignedDecimalNumber,
} from "./values"; } from "./values";
@ -89,6 +90,20 @@ describe("values", () => {
}); });
}); });
describe("twipsMeasureValue", () => {
it("should allow valid values", () => {
expect(twipsMeasureValue(1243)).to.eq(1243);
expect(twipsMeasureValue("5mm")).to.eq("5mm");
expect(twipsMeasureValue("10.in")).to.eq("10in");
});
it("should throw on invalid values", () => {
expect(() => twipsMeasureValue(-12)).to.throw();
expect(() => twipsMeasureValue(NaN)).to.throw();
expect(() => twipsMeasureValue("foo")).to.throw();
expect(() => twipsMeasureValue("-5mm")).to.throw();
});
});
describe("hpsMeasureValue", () => { describe("hpsMeasureValue", () => {
it("should allow valid values", () => { it("should allow valid values", () => {
expect(hpsMeasureValue(1243)).to.eq(1243); expect(hpsMeasureValue(1243)).to.eq(1243);

View File

@ -1,6 +1,27 @@
// Runtime checks and cleanup for value types in the spec that aren't easily expressed through our type system. // Runtime checks and cleanup for value types in the spec that aren't easily expressed through our type system.
// These will help us to prevent silent failures and corrupted documents. // These will help us to prevent silent failures and corrupted documents.
// <xsd:simpleType name="ST_DecimalNumber">
// <xsd:restriction base="xsd:integer"/>
// </xsd:simpleType>
export function decimalNumber(val: number): number {
if (isNaN(val)) {
throw new Error(`Invalid value '${val}' specified. Must be an integer.`);
}
return Math.floor(val);
}
// <xsd:simpleType name="ST_UnsignedDecimalNumber">
// <xsd:restriction base="xsd:unsignedLong"/>
// </xsd:simpleType>
export function unsignedDecimalNumber(val: number): number {
const value = decimalNumber(val);
if (value < 0) {
throw new Error(`Invalid value '${val}' specified. Must be a positive integer.`);
}
return value;
}
// <xsd:simpleType name="ST_UniversalMeasure"> // <xsd:simpleType name="ST_UniversalMeasure">
// <xsd:restriction base="xsd:string"> // <xsd:restriction base="xsd:string">
// <xsd:pattern value="-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/> // <xsd:pattern value="-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
@ -63,27 +84,11 @@ export function hexColorValue(val: string): string {
return color; return color;
} }
// <xsd:simpleType name="ST_UnsignedDecimalNumber">
// <xsd:restriction base="xsd:unsignedLong"/>
// </xsd:simpleType>
export function unsignedDecimalNumber(val: number): number {
if (isNaN(val) || val < 0) {
throw new Error(`Invalid value '${val}' specified. Must be a positive base10 integer.`);
}
return Math.floor(val);
}
// <xsd:simpleType name="ST_SignedTwipsMeasure"> // <xsd:simpleType name="ST_SignedTwipsMeasure">
// <xsd:union memberTypes="xsd:integer s:ST_UniversalMeasure"/> // <xsd:union memberTypes="xsd:integer s:ST_UniversalMeasure"/>
// </xsd:simpleType> // </xsd:simpleType>
export function signedTwipsMeasureValue(val: string | number): string | number { export function signedTwipsMeasureValue(val: string | number): string | number {
if (typeof val === "string") { return typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val);
return universalMeasureValue(val);
}
if (isNaN(val)) {
throw new Error(`Invalid value '${val}' specified. Expected a valid number.`);
}
return Math.floor(val);
} }
// <xsd:simpleType name="ST_HpsMeasure"> // <xsd:simpleType name="ST_HpsMeasure">
@ -92,3 +97,42 @@ export function signedTwipsMeasureValue(val: string | number): string | number {
export function hpsMeasureValue(val: string | number): string | number { export function hpsMeasureValue(val: string | number): string | number {
return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val);
} }
// <xsd:simpleType name="ST_TwipsMeasure">
// <xsd:union memberTypes="ST_UnsignedDecimalNumber ST_PositiveUniversalMeasure"/>
// </xsd:simpleType>
export function twipsMeasureValue(val: string | number): string | number {
return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val);
}
// <xsd:simpleType name="ST_Percentage">
// <xsd:restriction base="xsd:string">
// <xsd:pattern value="-?[0-9]+(\.[0-9]+)?%"/>
// </xsd:restriction>
// </xsd:simpleType>
export function percentageValue(val: string): string {
if (val.slice(-1) !== "%") {
throw new Error(`Invalid value '${val}'. Expected percentage value (eg '55%')`);
}
const percent = val.substring(0, val.length - 1);
if (isNaN(Number(percent))) {
throw new Error(`Invalid value '${percent}' specified. Expected a valid number.`);
}
return `${Number(percent)}%`;
}
// <xsd:simpleType name="ST_MeasurementOrPercent">
// <xsd:union memberTypes="ST_DecimalNumberOrPercent s:ST_UniversalMeasure"/>
// </xsd:simpleType>
// <xsd:simpleType name="ST_DecimalNumberOrPercent">
// <xsd:union memberTypes="ST_UnqualifiedPercentage s:ST_Percentage"/>
// </xsd:simpleType>
// <xsd:simpleType name="ST_UnqualifiedPercentage">
// <xsd:restriction base="xsd:integer"/>
// </xsd:simpleType>
export function measurementOrPercentValue(val: number | string): number | string {
return typeof val === "number" ? decimalNumber(val) : percentageValue(val);
}

View File

@ -4,15 +4,22 @@ import { hpsMeasureValue } from "../values";
// This represents element type CT_OnOff, which indicate a boolean value. // This represents element type CT_OnOff, which indicate a boolean value.
// //
// A value of 1 or true specifies that the property shall be explicitly applied.
// This is the default value for this attribute, and is implied when the parent
// element is present, but this attribute is omitted.
// A value of 0 or false specifies that the property shall be explicitly turned off.
//
// <xsd:complexType name="CT_OnOff"> // <xsd:complexType name="CT_OnOff">
// <xsd:attribute name="val" type="s:ST_OnOff"/> // <xsd:attribute name="val" type="s:ST_OnOff"/>
// </xsd:complexType> // </xsd:complexType>
export class OnOffElement extends XmlComponent { export class OnOffElement extends XmlComponent {
constructor(name: string, val: boolean | undefined = true) { constructor(name: string, val: boolean | undefined = true) {
super(name); super(name);
if (val !== true) {
this.root.push(new Attributes({ val })); this.root.push(new Attributes({ val }));
} }
} }
}
// This represents element type CT_HpsMeasure, which indicate an unsigned int or a measurement with unit. // This represents element type CT_HpsMeasure, which indicate an unsigned int or a measurement with unit.
// //
@ -25,3 +32,15 @@ export class HpsMeasureElement extends XmlComponent {
this.root.push(new Attributes({ val: hpsMeasureValue(val) })); this.root.push(new Attributes({ val: hpsMeasureValue(val) }));
} }
} }
// This represents element type CT_String, which indicate a string value.
//
// <xsd:complexType name="CT_String">
// <xsd:attribute name="val" type="s:ST_String" use="required"/>
// </xsd:complexType>
export class StringValueElement extends XmlComponent {
constructor(name: string, val: string) {
super(name);
this.root.push(new Attributes({ val }));
}
}