From 7639b60b156adb7a45eb7a22cfeab6fd076a3acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 13 Nov 2018 11:04:03 -0200 Subject: [PATCH 1/2] Refactor to separate classes in their specific files an tests improuvements for styles --- src/file/numbering/numbering.spec.ts | 32 +- src/file/styles/style/character-style.spec.ts | 296 +++++++++ src/file/styles/style/character-style.ts | 53 ++ src/file/styles/style/components.spec.ts | 53 ++ src/file/styles/style/default-styles.spec.ts | 331 ++++++++++ src/file/styles/style/default-styles.ts | 106 ++++ src/file/styles/style/index.ts | 339 +---------- src/file/styles/style/paragraph-style.spec.ts | 524 ++++++++++++++++ src/file/styles/style/paragraph-style.ts | 178 ++++++ src/file/styles/style/style.spec.ts | 36 ++ src/file/styles/style/style.ts | 32 + src/file/styles/styles.spec.ts | 569 ++---------------- src/file/styles/styles.ts | 14 +- 13 files changed, 1689 insertions(+), 874 deletions(-) create mode 100644 src/file/styles/style/character-style.spec.ts create mode 100644 src/file/styles/style/character-style.ts create mode 100644 src/file/styles/style/components.spec.ts create mode 100644 src/file/styles/style/default-styles.spec.ts create mode 100644 src/file/styles/style/default-styles.ts create mode 100644 src/file/styles/style/paragraph-style.spec.ts create mode 100644 src/file/styles/style/paragraph-style.ts create mode 100644 src/file/styles/style/style.spec.ts create mode 100644 src/file/styles/style/style.ts diff --git a/src/file/numbering/numbering.spec.ts b/src/file/numbering/numbering.spec.ts index cebf780b0c..50643b1e07 100644 --- a/src/file/numbering/numbering.spec.ts +++ b/src/file/numbering/numbering.spec.ts @@ -25,23 +25,21 @@ describe("Numbering", () => { { "w:multiLevelType": [{ _attr: { "w:val": "hybridMultilevel" } }] }, ]); - abstractNums - .filter((el) => el["w:lvl"]) - .forEach((el, ix) => { - expect(Object.keys(el)).to.have.lengthOf(1); - expect(Object.keys(el["w:lvl"]).sort()).to.deep.equal(["_attr", "w:start", "w:lvlJc", "w:numFmt", "w:pPr", "w:rPr"]); - expect(el["w:lvl"]).to.have.deep.members([ - { _attr: { "w:ilvl": ix, "w15:tentative": 1 } }, - { "w:start": [{ _attr: { "w:val": 1 } }] }, - { "w:lvlJc": [{ _attr: { "w:val": "left" } }] }, - { "w:numFmt": [{ _attr: { "w:val": "bullet" } }] }, - ]); - // Once chai 4.0.0 lands and #644 is resolved, we can add the following to the test: - // {"w:lvlText": [{"_attr": {"w:val": "•"}}]}, - // {"w:rPr": [{"w:rFonts": [{"_attr": {"w:ascii": "Symbol", "w:cs": "Symbol", "w:eastAsia": "Symbol", "w:hAnsi": "Symbol", "w:hint": "default"}}]}]}, - // {"w:pPr": [{"_attr": {}}, - // {"w:ind": [{"_attr": {"w:left": 720, "w:hanging": 360}}]}]}, - }); + abstractNums.filter((el) => el["w:lvl"]).forEach((el, ix) => { + expect(Object.keys(el)).to.have.lengthOf(1); + expect(Object.keys(el["w:lvl"]).sort()).to.deep.equal(["_attr", "w:start", "w:lvlJc", "w:numFmt", "w:pPr", "w:rPr"]); + expect(el["w:lvl"]).to.have.deep.members([ + { _attr: { "w:ilvl": ix, "w15:tentative": 1 } }, + { "w:start": [{ _attr: { "w:val": 1 } }] }, + { "w:lvlJc": [{ _attr: { "w:val": "left" } }] }, + { "w:numFmt": [{ _attr: { "w:val": "bullet" } }] }, + ]); + // Once chai 4.0.0 lands and #644 is resolved, we can add the following to the test: + // {"w:lvlText": [{"_attr": {"w:val": "•"}}]}, + // {"w:rPr": [{"w:rFonts": [{"_attr": {"w:ascii": "Symbol", "w:cs": "Symbol", "w:eastAsia": "Symbol", "w:hAnsi": "Symbol", "w:hint": "default"}}]}]}, + // {"w:pPr": [{"_attr": {}}, + // {"w:ind": [{"_attr": {"w:left": 720, "w:hanging": 360}}]}]}, + }); }); }); diff --git a/src/file/styles/style/character-style.spec.ts b/src/file/styles/style/character-style.spec.ts new file mode 100644 index 0000000000..1556aae109 --- /dev/null +++ b/src/file/styles/style/character-style.spec.ts @@ -0,0 +1,296 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; + +import { CharacterStyle } from "./character-style"; + +describe("CharacterStyle", () => { + describe("#constructor", () => { + it("should set the style type to character and use the given style id", () => { + const style = new CharacterStyle("myStyleId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { "w:rPr": [] }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + + it("should set the name of the style, if given", () => { + const style = new CharacterStyle("myStyleId", "Style Name"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { "w:name": [{ _attr: { "w:val": "Style Name" } }] }, + { "w:rPr": [] }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + }); + + describe("formatting methods: style attributes", () => { + it("#basedOn", () => { + const style = new CharacterStyle("myStyleId").basedOn("otherId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { "w:rPr": [] }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + { "w:basedOn": [{ _attr: { "w:val": "otherId" } }] }, + ], + }); + }); + }); + + describe("formatting methods: run properties", () => { + it("#size", () => { + const style = new CharacterStyle("myStyleId").size(24); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:sz": [{ _attr: { "w:val": 24 } }] }, { "w:szCs": [{ _attr: { "w:val": 24 } }] }], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + + describe("#underline", () => { + it("should set underline to 'single' if no arguments are given", () => { + const style = new CharacterStyle("myStyleId").underline(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:u": [{ _attr: { "w:val": "single" } }] }], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + + it("should set the style if given", () => { + const style = new CharacterStyle("myStyleId").underline("double"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:u": [{ _attr: { "w:val": "double" } }] }], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + + it("should set the style and color if given", () => { + const style = new CharacterStyle("myStyleId").underline("double", "005599"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:u": [{ _attr: { "w:val": "double", "w:color": "005599" } }] }], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + }); + + it("#superScript", () => { + const style = new CharacterStyle("myStyleId").superScript(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [ + { + "w:vertAlign": [ + { + _attr: { + "w:val": "superscript", + }, + }, + ], + }, + ], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + + it("#color", () => { + const style = new CharacterStyle("myStyleId").color("123456"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:color": [{ _attr: { "w:val": "123456" } }] }], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + + it("#link", () => { + const style = new CharacterStyle("myStyleId").link("MyLink"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + { "w:link": [{ _attr: { "w:val": "MyLink" } }] }, + ], + }); + }); + + it("#semiHidden", () => { + const style = new CharacterStyle("myStyleId").semiHidden(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { "w:unhideWhenUsed": [] }, + { "w:semiHidden": [] }, + ], + }); + }); + }); +}); diff --git a/src/file/styles/style/character-style.ts b/src/file/styles/style/character-style.ts new file mode 100644 index 0000000000..ffeb4d6739 --- /dev/null +++ b/src/file/styles/style/character-style.ts @@ -0,0 +1,53 @@ +import { XmlComponent } from "file/xml-components"; +import { BasedOn, UiPriority, UnhideWhenUsed, Link, SemiHidden } from "./components"; +import * as formatting from "file/paragraph/run/formatting"; +import { RunProperties } from "file/paragraph/run/properties"; +import { Style } from "./style"; + +export class CharacterStyle extends Style { + private readonly runProperties: RunProperties; + + constructor(styleId: string, name?: string) { + super({ type: "character", styleId: styleId }, name); + this.runProperties = new RunProperties(); + this.root.push(this.runProperties); + this.root.push(new UiPriority("99")); + this.root.push(new UnhideWhenUsed()); + } + + public basedOn(parentId: string): CharacterStyle { + this.root.push(new BasedOn(parentId)); + return this; + } + + public addRunProperty(property: XmlComponent): CharacterStyle { + this.runProperties.push(property); + return this; + } + + public color(color: string): CharacterStyle { + return this.addRunProperty(new formatting.Color(color)); + } + + public underline(underlineType?: string, color?: string): CharacterStyle { + return this.addRunProperty(new formatting.Underline(underlineType, color)); + } + + public superScript(): CharacterStyle { + return this.addRunProperty(new formatting.SuperScript()); + } + + public size(twips: number): CharacterStyle { + return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); + } + + public link(link: string): CharacterStyle { + this.root.push(new Link(link)); + return this; + } + + public semiHidden(): CharacterStyle { + this.root.push(new SemiHidden()); + return this; + } +} diff --git a/src/file/styles/style/components.spec.ts b/src/file/styles/style/components.spec.ts new file mode 100644 index 0000000000..4ac3ca998f --- /dev/null +++ b/src/file/styles/style/components.spec.ts @@ -0,0 +1,53 @@ +import { expect } from "chai"; +import { Formatter } from "export/formatter"; +import * as components from "./components"; + +describe("Style components", () => { + it("Name#constructor", () => { + const style = new components.Name("Style Name"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:name": [{ _attr: { "w:val": "Style Name" } }] }); + }); + + it("BasedOn#constructor", () => { + const style = new components.BasedOn("otherId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:basedOn": [{ _attr: { "w:val": "otherId" } }] }); + }); + + it("Next#constructor", () => { + const style = new components.Next("otherId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:next": [{ _attr: { "w:val": "otherId" } }] }); + }); + + it("Link#constructor", () => { + const style = new components.Link("otherId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:link": [{ _attr: { "w:val": "otherId" } }] }); + }); + + it("UiPriority#constructor", () => { + const style = new components.UiPriority("123"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:uiPriority": [{ _attr: { "w:val": "123" } }] }); + }); + + it("UnhideWhenUsed#constructor", () => { + const style = new components.UnhideWhenUsed(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:unhideWhenUsed": [] }); + }); + + it("QuickFormat#constructor", () => { + const style = new components.QuickFormat(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:qFormat": [] }); + }); + + it("SemiHidden#constructor", () => { + const style = new components.SemiHidden(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ "w:semiHidden": [] }); + }); +}); diff --git a/src/file/styles/style/default-styles.spec.ts b/src/file/styles/style/default-styles.spec.ts new file mode 100644 index 0000000000..a3144f186b --- /dev/null +++ b/src/file/styles/style/default-styles.spec.ts @@ -0,0 +1,331 @@ +import { expect } from "chai"; +import { Formatter } from "export/formatter"; +import * as defaultStyels from "./default-styles"; + +describe("Default Styles", () => { + it("HeadingStyle#constructor", () => { + const style = new defaultStyels.HeadingStyle("Heading1", "Heading 1"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Heading1" } }, + { "w:name": [{ _attr: { "w:val": "Heading 1" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("TitleStyle#constructor", () => { + const style = new defaultStyels.TitleStyle(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Title" } }, + { "w:name": [{ _attr: { "w:val": "Title" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("Heading1Style#constructor", () => { + const style = new defaultStyels.Heading1Style(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Heading1" } }, + { "w:name": [{ _attr: { "w:val": "Heading 1" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("Heading2Style#constructor", () => { + const style = new defaultStyels.Heading2Style(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Heading2" } }, + { "w:name": [{ _attr: { "w:val": "Heading 2" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("Heading3Style#constructor", () => { + const style = new defaultStyels.Heading3Style(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Heading3" } }, + { "w:name": [{ _attr: { "w:val": "Heading 3" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("Heading4Style#constructor", () => { + const style = new defaultStyels.Heading4Style(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Heading4" } }, + { "w:name": [{ _attr: { "w:val": "Heading 4" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("Heading5Style#constructor", () => { + const style = new defaultStyels.Heading5Style(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Heading5" } }, + { "w:name": [{ _attr: { "w:val": "Heading 5" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("Heading6Style#constructor", () => { + const style = new defaultStyels.Heading6Style(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "Heading6" } }, + { "w:name": [{ _attr: { "w:val": "Heading 6" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:next": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("ListParagraph#constructor", () => { + const style = new defaultStyels.ListParagraph(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "ListParagraph" } }, + { "w:name": [{ _attr: { "w:val": "List Paragraph" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("FootnoteText#constructor", () => { + const style = new defaultStyels.FootnoteText(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "FootnoteText" } }, + { "w:name": [{ _attr: { "w:val": "footnote text" } }] }, + { + "w:pPr": [ + { + "w:spacing": [ + { + _attr: { + "w:after": 0, + "w:line": 240, + "w:lineRule": "auto", + }, + }, + ], + }, + ], + }, + { + "w:rPr": [ + { + "w:sz": [ + { + _attr: { + "w:val": 20, + }, + }, + ], + }, + { + "w:szCs": [ + { + _attr: { + "w:val": 20, + }, + }, + ], + }, + ], + }, + { "w:basedOn": [{ _attr: { "w:val": "Normal" } }] }, + { "w:link": [{ _attr: { "w:val": "FootnoteTextChar" } }] }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:semiHidden": [], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }); + }); + + it("FootnoteReferenceStyle#constructor", () => { + const style = new defaultStyels.FootnoteReferenceStyle(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "FootnoteReference" } }, + { "w:name": [{ _attr: { "w:val": "footnote reference" } }] }, + { + "w:rPr": [ + { + "w:vertAlign": [ + { + _attr: { + "w:val": "superscript", + }, + }, + ], + }, + ], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + { "w:basedOn": [{ _attr: { "w:val": "DefaultParagraphFont" } }] }, + + { + "w:semiHidden": [], + }, + ], + }); + }); + + it("FootnoteTextChar#constructor", () => { + const style = new defaultStyels.FootnoteTextChar(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "FootnoteTextChar" } }, + { "w:name": [{ _attr: { "w:val": "Footnote Text Char" } }] }, + { + "w:rPr": [ + { + "w:sz": [ + { + _attr: { + "w:val": 20, + }, + }, + ], + }, + { + "w:szCs": [ + { + _attr: { + "w:val": 20, + }, + }, + ], + }, + ], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + { "w:basedOn": [{ _attr: { "w:val": "DefaultParagraphFont" } }] }, + { "w:link": [{ _attr: { "w:val": "FootnoteText" } }] }, + { + "w:semiHidden": [], + }, + ], + }); + }); + + it("HyperlinkStyle#constructor", () => { + const style = new defaultStyels.HyperlinkStyle(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "Hyperlink" } }, + { "w:name": [{ _attr: { "w:val": "Hyperlink" } }] }, + { + "w:rPr": [{ "w:color": [{ _attr: { "w:val": "0563C1" } }] }, { "w:u": [{ _attr: { "w:val": "single" } }] }], + }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + { "w:basedOn": [{ _attr: { "w:val": "DefaultParagraphFont" } }] }, + ], + }); + }); +}); diff --git a/src/file/styles/style/default-styles.ts b/src/file/styles/style/default-styles.ts new file mode 100644 index 0000000000..d9572499c2 --- /dev/null +++ b/src/file/styles/style/default-styles.ts @@ -0,0 +1,106 @@ +import { CharacterStyle } from "./character-style"; +import { ParagraphStyle } from "./paragraph-style"; + +export class HeadingStyle extends ParagraphStyle { + constructor(styleId: string, name: string) { + super(styleId, name); + this.basedOn("Normal"); + this.next("Normal"); + this.quickFormat(); + } +} + +export class TitleStyle extends HeadingStyle { + constructor() { + super("Title", "Title"); + } +} + +export class Heading1Style extends HeadingStyle { + constructor() { + super("Heading1", "Heading 1"); + } +} + +export class Heading2Style extends HeadingStyle { + constructor() { + super("Heading2", "Heading 2"); + } +} + +export class Heading3Style extends HeadingStyle { + constructor() { + super("Heading3", "Heading 3"); + } +} + +export class Heading4Style extends HeadingStyle { + constructor() { + super("Heading4", "Heading 4"); + } +} + +export class Heading5Style extends HeadingStyle { + constructor() { + super("Heading5", "Heading 5"); + } +} + +export class Heading6Style extends HeadingStyle { + constructor() { + super("Heading6", "Heading 6"); + } +} + +export class ListParagraph extends ParagraphStyle { + constructor() { + super("ListParagraph", "List Paragraph"); + this.basedOn("Normal"); + this.quickFormat(); + } +} + +export class FootnoteText extends ParagraphStyle { + constructor() { + super("FootnoteText", "footnote text"); + this.basedOn("Normal") + .link("FootnoteTextChar") + .uiPriority("99") + .semiHidden() + .unhideWhenUsed() + .spacing({ + after: 0, + line: 240, + lineRule: "auto", + }) + .size(20); + } +} + +export class FootnoteReferenceStyle extends CharacterStyle { + constructor() { + super("FootnoteReference", "footnote reference"); + this.basedOn("DefaultParagraphFont") + .semiHidden() + .superScript(); + } +} + +export class FootnoteTextChar extends CharacterStyle { + constructor() { + super("FootnoteTextChar", "Footnote Text Char"); + this.basedOn("DefaultParagraphFont") + .link("FootnoteText") + .semiHidden() + .size(20); + } +} + +export class HyperlinkStyle extends CharacterStyle { + constructor() { + super("Hyperlink", "Hyperlink"); + this.basedOn("DefaultParagraphFont") + .color("0563C1") + .underline("single"); + } +} diff --git a/src/file/styles/style/index.ts b/src/file/styles/style/index.ts index cf14ae6c5e..abe53aa61e 100644 --- a/src/file/styles/style/index.ts +++ b/src/file/styles/style/index.ts @@ -1,335 +1,4 @@ -import { - Alignment, - AlignmentOptions, - Indent, - ISpacingProperties, - KeepLines, - KeepNext, - LeftTabStop, - MaxRightTabStop, - ParagraphProperties, - Spacing, - ThematicBreak, -} from "file/paragraph"; -import * as formatting from "file/paragraph/run/formatting"; -import { RunProperties } from "file/paragraph/run/properties"; -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -import { BasedOn, Link, Name, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; - -export interface IStyleAttributes { - readonly type?: string; - readonly styleId?: string; - readonly default?: boolean; - readonly customStyle?: string; -} - -class StyleAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - type: "w:type", - styleId: "w:styleId", - default: "w:default", - customStyle: "w:customStyle", - }; -} - -export class Style extends XmlComponent { - constructor(attributes: IStyleAttributes, name?: string) { - super("w:style"); - this.root.push(new StyleAttributes(attributes)); - if (name) { - this.root.push(new Name(name)); - } - } - - public push(styleSegment: XmlComponent): void { - this.root.push(styleSegment); - } -} - -export class ParagraphStyle extends Style { - private readonly paragraphProperties: ParagraphProperties; - private readonly runProperties: RunProperties; - - constructor(styleId: string, name?: string) { - super({ type: "paragraph", styleId: styleId }, name); - this.paragraphProperties = new ParagraphProperties(); - this.runProperties = new RunProperties(); - this.root.push(this.paragraphProperties); - this.root.push(this.runProperties); - } - - public addParagraphProperty(property: XmlComponent): ParagraphStyle { - this.paragraphProperties.push(property); - return this; - } - - public addRunProperty(property: XmlComponent): ParagraphStyle { - this.runProperties.push(property); - return this; - } - - public basedOn(parentId: string): ParagraphStyle { - this.root.push(new BasedOn(parentId)); - return this; - } - - public quickFormat(): ParagraphStyle { - this.root.push(new QuickFormat()); - return this; - } - - public next(nextId: string): ParagraphStyle { - this.root.push(new Next(nextId)); - return this; - } - - // ---------- Run formatting ---------------------- // - - public size(twips: number): ParagraphStyle { - return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); - } - - public bold(): ParagraphStyle { - return this.addRunProperty(new formatting.Bold()); - } - - public italics(): ParagraphStyle { - return this.addRunProperty(new formatting.Italics()); - } - - public smallCaps(): ParagraphStyle { - return this.addRunProperty(new formatting.SmallCaps()); - } - - public allCaps(): ParagraphStyle { - return this.addRunProperty(new formatting.Caps()); - } - - public strike(): ParagraphStyle { - return this.addRunProperty(new formatting.Strike()); - } - - public doubleStrike(): ParagraphStyle { - return this.addRunProperty(new formatting.DoubleStrike()); - } - - public subScript(): ParagraphStyle { - return this.addRunProperty(new formatting.SubScript()); - } - - public superScript(): ParagraphStyle { - return this.addRunProperty(new formatting.SuperScript()); - } - - public underline(underlineType?: string, color?: string): ParagraphStyle { - return this.addRunProperty(new formatting.Underline(underlineType, color)); - } - - public color(color: string): ParagraphStyle { - return this.addRunProperty(new formatting.Color(color)); - } - - public font(fontName: string): ParagraphStyle { - return this.addRunProperty(new formatting.RunFonts(fontName)); - } - - public characterSpacing(value: number): ParagraphStyle { - return this.addRunProperty(new formatting.CharacterSpacing(value)); - } - - // --------------------- Paragraph formatting ------------------------ // - - public center(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentOptions.CENTER)); - } - - public left(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentOptions.LEFT)); - } - - public right(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentOptions.RIGHT)); - } - - public justified(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentOptions.BOTH)); - } - - public thematicBreak(): ParagraphStyle { - return this.addParagraphProperty(new ThematicBreak()); - } - - public maxRightTabStop(): ParagraphStyle { - return this.addParagraphProperty(new MaxRightTabStop()); - } - - public leftTabStop(position: number): ParagraphStyle { - return this.addParagraphProperty(new LeftTabStop(position)); - } - - public indent(attrs: object): ParagraphStyle { - return this.addParagraphProperty(new Indent(attrs)); - } - - public spacing(params: ISpacingProperties): ParagraphStyle { - return this.addParagraphProperty(new Spacing(params)); - } - - public keepNext(): ParagraphStyle { - return this.addParagraphProperty(new KeepNext()); - } - - public keepLines(): ParagraphStyle { - return this.addParagraphProperty(new KeepLines()); - } -} - -export class HeadingStyle extends ParagraphStyle { - constructor(styleId: string, name: string) { - super(styleId, name); - this.basedOn("Normal"); - this.next("Normal"); - this.quickFormat(); - } -} - -export class TitleStyle extends HeadingStyle { - constructor() { - super("Title", "Title"); - } -} - -export class Heading1Style extends HeadingStyle { - constructor() { - super("Heading1", "Heading 1"); - } -} - -export class Heading2Style extends HeadingStyle { - constructor() { - super("Heading2", "Heading 2"); - } -} - -export class Heading3Style extends HeadingStyle { - constructor() { - super("Heading3", "Heading 3"); - } -} - -export class Heading4Style extends HeadingStyle { - constructor() { - super("Heading4", "Heading 4"); - } -} - -export class Heading5Style extends HeadingStyle { - constructor() { - super("Heading5", "Heading 5"); - } -} - -export class Heading6Style extends HeadingStyle { - constructor() { - super("Heading6", "Heading 6"); - } -} - -export class ListParagraph extends ParagraphStyle { - constructor() { - super("ListParagraph"); - this.root.push(new Name("List Paragraph")); - this.root.push(new BasedOn("Normal")); - this.root.push(new QuickFormat()); - } -} - -export class CharacterStyle extends Style { - private readonly runProperties: RunProperties; - - constructor(styleId: string, name?: string) { - super({ type: "character", styleId: styleId }, name); - this.runProperties = new RunProperties(); - this.root.push(this.runProperties); - this.root.push(new UiPriority("99")); - this.root.push(new UnhideWhenUsed()); - } - - public basedOn(parentId: string): CharacterStyle { - this.root.push(new BasedOn(parentId)); - return this; - } - - public addRunProperty(property: XmlComponent): CharacterStyle { - this.runProperties.push(property); - return this; - } - - public color(color: string): CharacterStyle { - return this.addRunProperty(new formatting.Color(color)); - } - - public underline(underlineType?: string, color?: string): CharacterStyle { - return this.addRunProperty(new formatting.Underline(underlineType, color)); - } - - public size(twips: number): CharacterStyle { - return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); - } -} - -export class HyperlinkStyle extends CharacterStyle { - constructor() { - super("Hyperlink", "Hyperlink"); - this.basedOn("DefaultParagraphFont") - .color("0563C1") - .underline("single"); - } -} - -export class FootnoteReferenceStyle extends Style { - private readonly runProperties: RunProperties; - - constructor() { - super({ type: "character", styleId: "FootnoteReference" }); - this.root.push(new Name("footnote reference")); - this.root.push(new BasedOn("DefaultParagraphFont")); - this.root.push(new UiPriority("99")); - this.root.push(new SemiHidden()); - this.root.push(new UnhideWhenUsed()); - - this.runProperties = new RunProperties(); - this.runProperties.addChildElement(new formatting.SuperScript()); - this.root.push(this.runProperties); - } -} - -export class FootnoteText extends ParagraphStyle { - constructor() { - super("FootnoteText"); - this.root.push(new Name("footnote text")); - this.root.push(new BasedOn("Normal")); - this.root.push(new Link("FootnoteTextChar")); - this.root.push(new UiPriority("99")); - this.root.push(new SemiHidden()); - this.root.push(new UnhideWhenUsed()); - this.spacing({ - after: 0, - line: 240, - lineRule: "auto", - }); - this.size(20); - } -} - -export class FootnoteTextChar extends CharacterStyle { - constructor() { - super("FootnoteTextChar", "Footnote Text Char"); - this.basedOn("DefaultParagraphFont"); - this.root.push(new Link("FootnoteText")); - this.root.push(new UiPriority("99")); - this.root.push(new SemiHidden()); - this.size(20); - } -} +export * from "./style"; +export * from "./paragraph-style"; +export * from "./character-style"; +export * from "./default-styles"; diff --git a/src/file/styles/style/paragraph-style.spec.ts b/src/file/styles/style/paragraph-style.spec.ts new file mode 100644 index 0000000000..0fb854ce22 --- /dev/null +++ b/src/file/styles/style/paragraph-style.spec.ts @@ -0,0 +1,524 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; + +import { ParagraphStyle } from "./paragraph-style"; + +describe("ParagraphStyle", () => { + describe("#constructor", () => { + it("should set the style type to paragraph and use the given style id", () => { + const style = new ParagraphStyle("myStyleId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [] }, { "w:rPr": [] }], + }); + }); + + it("should set the name of the style, if given", () => { + const style = new ParagraphStyle("myStyleId", "Style Name"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:name": [{ _attr: { "w:val": "Style Name" } }] }, + { "w:pPr": [] }, + { "w:rPr": [] }, + ], + }); + }); + }); + + describe("formatting methods: style attributes", () => { + it("#basedOn", () => { + const style = new ParagraphStyle("myStyleId").basedOn("otherId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:basedOn": [{ _attr: { "w:val": "otherId" } }] }, + ], + }); + }); + + it("#quickFormat", () => { + const style = new ParagraphStyle("myStyleId").quickFormat(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:qFormat": [] }, + ], + }); + }); + + it("#next", () => { + const style = new ParagraphStyle("myStyleId").next("otherId"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:next": [{ _attr: { "w:val": "otherId" } }] }, + ], + }); + }); + }); + + describe("formatting methods: paragraph properties", () => { + it("#indent", () => { + const style = new ParagraphStyle("myStyleId").indent({ left: 720 }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [{ "w:ind": [{ _attr: { "w:left": 720 } }] }], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#spacing", () => { + const style = new ParagraphStyle("myStyleId").spacing({ before: 50, after: 150 }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [{ "w:spacing": [{ _attr: { "w:before": 50, "w:after": 150 } }] }], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#center", () => { + const style = new ParagraphStyle("myStyleId").center(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "center" } }] }], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#character spacing", () => { + const style = new ParagraphStyle("myStyleId").characterSpacing(24); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:spacing": [{ _attr: { "w:val": 24 } }] }], + }, + ], + }); + }); + + it("#left", () => { + const style = new ParagraphStyle("myStyleId").left(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "left" } }] }], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#right", () => { + const style = new ParagraphStyle("myStyleId").right(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "right" } }] }], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#justified", () => { + const style = new ParagraphStyle("myStyleId").justified(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "both" } }] }], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#thematicBreak", () => { + const style = new ParagraphStyle("myStyleId").thematicBreak(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [ + { + "w:pBdr": [ + { + "w:bottom": [ + { + _attr: { + "w:color": "auto", + "w:space": "1", + "w:val": "single", + "w:sz": "6", + }, + }, + ], + }, + ], + }, + ], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#leftTabStop", () => { + const style = new ParagraphStyle("myStyleId").leftTabStop(1200); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [ + { + "w:tabs": [{ "w:tab": [{ _attr: { "w:val": "left", "w:pos": 1200 } }] }], + }, + ], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#maxRightTabStop", () => { + const style = new ParagraphStyle("myStyleId").maxRightTabStop(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:pPr": [ + { + "w:tabs": [{ "w:tab": [{ _attr: { "w:val": "right", "w:pos": 9026 } }] }], + }, + ], + }, + { "w:rPr": [] }, + ], + }); + }); + + it("#keepLines", () => { + const style = new ParagraphStyle("myStyleId").keepLines(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [{ "w:keepLines": [] }] }, + { "w:rPr": [] }, + ], + }); + }); + + it("#keepNext", () => { + const style = new ParagraphStyle("myStyleId").keepNext(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [{ "w:keepNext": [] }] }, + { "w:rPr": [] }, + ], + }); + }); + }); + + describe("formatting methods: run properties", () => { + it("#size", () => { + const style = new ParagraphStyle("myStyleId").size(24); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:sz": [{ _attr: { "w:val": 24 } }] }, { "w:szCs": [{ _attr: { "w:val": 24 } }] }], + }, + ], + }); + }); + + it("#smallCaps", () => { + const style = new ParagraphStyle("myStyleId").smallCaps(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:smallCaps": [{ _attr: { "w:val": true } }] }], + }, + ], + }); + }); + + it("#allCaps", () => { + const style = new ParagraphStyle("myStyleId").allCaps(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:caps": [{ _attr: { "w:val": true } }] }], + }, + ], + }); + }); + + it("#strike", () => { + const style = new ParagraphStyle("myStyleId").strike(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:strike": [{ _attr: { "w:val": true } }] }], + }, + ], + }); + }); + + it("#doubleStrike", () => { + const style = new ParagraphStyle("myStyleId").doubleStrike(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:dstrike": [{ _attr: { "w:val": true } }] }], + }, + ], + }); + }); + + it("#subScript", () => { + const style = new ParagraphStyle("myStyleId").subScript(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "subscript" } }] }], + }, + ], + }); + }); + + it("#superScript", () => { + const style = new ParagraphStyle("myStyleId").superScript(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "superscript" } }] }], + }, + ], + }); + }); + + it("#font", () => { + const style = new ParagraphStyle("myStyleId").font("Times"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [ + { "w:rFonts": [{ _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } }] }, + ], + }, + ], + }); + }); + + it("#bold", () => { + const style = new ParagraphStyle("myStyleId").bold(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:b": [{ _attr: { "w:val": true } }] }], + }, + ], + }); + }); + + it("#italics", () => { + const style = new ParagraphStyle("myStyleId").italics(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:i": [{ _attr: { "w:val": true } }] }], + }, + ], + }); + }); + + describe("#underline", () => { + it("should set underline to 'single' if no arguments are given", () => { + const style = new ParagraphStyle("myStyleId").underline(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:u": [{ _attr: { "w:val": "single" } }] }], + }, + ], + }); + }); + + it("should set the style if given", () => { + const style = new ParagraphStyle("myStyleId").underline("double"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:u": [{ _attr: { "w:val": "double" } }] }], + }, + ], + }); + }); + + it("should set the style and color if given", () => { + const style = new ParagraphStyle("myStyleId").underline("double", "005599"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:u": [{ _attr: { "w:val": "double", "w:color": "005599" } }] }], + }, + ], + }); + }); + }); + + it("#color", () => { + const style = new ParagraphStyle("myStyleId").color("123456"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:color": [{ _attr: { "w:val": "123456" } }] }], + }, + ], + }); + }); + + it("#link", () => { + const style = new ParagraphStyle("myStyleId").link("MyLink"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:link": [{ _attr: { "w:val": "MyLink" } }] }, + ], + }); + }); + + it("#semiHidden", () => { + const style = new ParagraphStyle("myStyleId").semiHidden(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:semiHidden": [] }, + ], + }); + }); + + it("#uiPriority", () => { + const style = new ParagraphStyle("myStyleId").uiPriority("99"); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + ], + }); + }); + + it("#unhideWhenUsed", () => { + const style = new ParagraphStyle("myStyleId").unhideWhenUsed(); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { "w:rPr": [] }, + { "w:unhideWhenUsed": [] }, + ], + }); + }); + }); +}); diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts new file mode 100644 index 0000000000..caa2d2777e --- /dev/null +++ b/src/file/styles/style/paragraph-style.ts @@ -0,0 +1,178 @@ +import { XmlComponent } from "file/xml-components"; +import { + Alignment, + AlignmentOptions, + Indent, + ISpacingProperties, + KeepLines, + KeepNext, + LeftTabStop, + MaxRightTabStop, + ParagraphProperties, + Spacing, + ThematicBreak, +} from "file/paragraph"; +import * as formatting from "file/paragraph/run/formatting"; +import { RunProperties } from "file/paragraph/run/properties"; +import { BasedOn, QuickFormat, Next, Link, SemiHidden, UnhideWhenUsed, UiPriority } from "./components"; +import { Style } from "./style"; + +export class ParagraphStyle extends Style { + private readonly paragraphProperties: ParagraphProperties; + private readonly runProperties: RunProperties; + + constructor(styleId: string, name?: string) { + super({ type: "paragraph", styleId: styleId }, name); + this.paragraphProperties = new ParagraphProperties(); + this.runProperties = new RunProperties(); + this.root.push(this.paragraphProperties); + this.root.push(this.runProperties); + } + + public addParagraphProperty(property: XmlComponent): ParagraphStyle { + this.paragraphProperties.push(property); + return this; + } + + public addRunProperty(property: XmlComponent): ParagraphStyle { + this.runProperties.push(property); + return this; + } + + public basedOn(parentId: string): ParagraphStyle { + this.root.push(new BasedOn(parentId)); + return this; + } + + public quickFormat(): ParagraphStyle { + this.root.push(new QuickFormat()); + return this; + } + + public next(nextId: string): ParagraphStyle { + this.root.push(new Next(nextId)); + return this; + } + + // ---------- Run formatting ---------------------- // + + public size(twips: number): ParagraphStyle { + return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); + } + + public bold(): ParagraphStyle { + return this.addRunProperty(new formatting.Bold()); + } + + public italics(): ParagraphStyle { + return this.addRunProperty(new formatting.Italics()); + } + + public smallCaps(): ParagraphStyle { + return this.addRunProperty(new formatting.SmallCaps()); + } + + public allCaps(): ParagraphStyle { + return this.addRunProperty(new formatting.Caps()); + } + + public strike(): ParagraphStyle { + return this.addRunProperty(new formatting.Strike()); + } + + public doubleStrike(): ParagraphStyle { + return this.addRunProperty(new formatting.DoubleStrike()); + } + + public subScript(): ParagraphStyle { + return this.addRunProperty(new formatting.SubScript()); + } + + public superScript(): ParagraphStyle { + return this.addRunProperty(new formatting.SuperScript()); + } + + public underline(underlineType?: string, color?: string): ParagraphStyle { + return this.addRunProperty(new formatting.Underline(underlineType, color)); + } + + public color(color: string): ParagraphStyle { + return this.addRunProperty(new formatting.Color(color)); + } + + public font(fontName: string): ParagraphStyle { + return this.addRunProperty(new formatting.RunFonts(fontName)); + } + + public characterSpacing(value: number): ParagraphStyle { + return this.addRunProperty(new formatting.CharacterSpacing(value)); + } + + // --------------------- Paragraph formatting ------------------------ // + + public center(): ParagraphStyle { + return this.addParagraphProperty(new Alignment(AlignmentOptions.CENTER)); + } + + public left(): ParagraphStyle { + return this.addParagraphProperty(new Alignment(AlignmentOptions.LEFT)); + } + + public right(): ParagraphStyle { + return this.addParagraphProperty(new Alignment(AlignmentOptions.RIGHT)); + } + + public justified(): ParagraphStyle { + return this.addParagraphProperty(new Alignment(AlignmentOptions.BOTH)); + } + + public thematicBreak(): ParagraphStyle { + return this.addParagraphProperty(new ThematicBreak()); + } + + public maxRightTabStop(): ParagraphStyle { + return this.addParagraphProperty(new MaxRightTabStop()); + } + + public leftTabStop(position: number): ParagraphStyle { + return this.addParagraphProperty(new LeftTabStop(position)); + } + + public indent(attrs: object): ParagraphStyle { + return this.addParagraphProperty(new Indent(attrs)); + } + + public spacing(params: ISpacingProperties): ParagraphStyle { + return this.addParagraphProperty(new Spacing(params)); + } + + public keepNext(): ParagraphStyle { + return this.addParagraphProperty(new KeepNext()); + } + + public keepLines(): ParagraphStyle { + return this.addParagraphProperty(new KeepLines()); + } + + /*-------------- Style Properties -----------------*/ + + public link(link: string): ParagraphStyle { + this.root.push(new Link(link)); + return this; + } + + public semiHidden(): ParagraphStyle { + this.root.push(new SemiHidden()); + return this; + } + + public uiPriority(priority: string): ParagraphStyle { + this.root.push(new UiPriority(priority)); + return this; + } + + public unhideWhenUsed(): ParagraphStyle { + this.root.push(new UnhideWhenUsed()); + return this; + } +} diff --git a/src/file/styles/style/style.spec.ts b/src/file/styles/style/style.spec.ts new file mode 100644 index 0000000000..13b12ca021 --- /dev/null +++ b/src/file/styles/style/style.spec.ts @@ -0,0 +1,36 @@ +import { expect } from "chai"; +import { Formatter } from "export/formatter"; +import { Style } from "./style"; + +describe("Style", () => { + describe("#constructor()", () => { + it("should set the given properties", () => { + const style = new Style({ + type: "paragraph", + styleId: "myStyleId", + default: true, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId", "w:default": true } }], + }); + }); + + it("should set the name of the style, if given", () => { + const style = new Style( + { + type: "paragraph", + styleId: "myStyleId", + }, + "Style Name", + ); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:name": [{ _attr: { "w:val": "Style Name" } }] }, + ], + }); + }); + }); +}); diff --git a/src/file/styles/style/style.ts b/src/file/styles/style/style.ts new file mode 100644 index 0000000000..92df610e0d --- /dev/null +++ b/src/file/styles/style/style.ts @@ -0,0 +1,32 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { Name } from "./components"; + +export interface IStyleAttributes { + readonly type?: string; + readonly styleId?: string; + readonly default?: boolean; + readonly customStyle?: string; +} + +class StyleAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + type: "w:type", + styleId: "w:styleId", + default: "w:default", + customStyle: "w:customStyle", + }; +} + +export class Style extends XmlComponent { + constructor(attributes: IStyleAttributes, name?: string) { + super("w:style"); + this.root.push(new StyleAttributes(attributes)); + if (name) { + this.root.push(new Name(name)); + } + } + + public push(styleSegment: XmlComponent): void { + this.root.push(styleSegment); + } +} diff --git a/src/file/styles/styles.spec.ts b/src/file/styles/styles.spec.ts index 8b7a8f793f..5476c0742d 100644 --- a/src/file/styles/styles.spec.ts +++ b/src/file/styles/styles.spec.ts @@ -2,8 +2,8 @@ import { assert, expect } from "chai"; import { Formatter } from "export/formatter"; -import { ParagraphStyle, Style } from "./style"; -import * as components from "./style/components"; +import { CharacterStyle, ParagraphStyle } from "./style"; + import { Styles } from "./styles"; describe("Styles", () => { @@ -22,7 +22,8 @@ describe("Styles", () => { describe("#createParagraphStyle", () => { it("should create a new paragraph style and push it onto this collection", () => { - styles.createParagraphStyle("pStyleId"); + const pStyle = styles.createParagraphStyle("pStyleId"); + expect(pStyle).to.instanceOf(ParagraphStyle); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); expect(tree).to.deep.equal([ { @@ -32,7 +33,8 @@ describe("Styles", () => { }); it("should set the paragraph name if given", () => { - styles.createParagraphStyle("pStyleId", "Paragraph Style"); + const pStyle = styles.createParagraphStyle("pStyleId", "Paragraph Style"); + expect(pStyle).to.instanceOf(ParagraphStyle); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); expect(tree).to.deep.equal([ { @@ -46,528 +48,59 @@ describe("Styles", () => { ]); }); }); -}); -describe("Style", () => { - describe("#constructor()", () => { - it("should set the given properties", () => { - const style = new Style({ - type: "paragraph", - styleId: "myStyleId", - default: true, - }); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId", "w:default": true } }], - }); - }); - - it("should set the name of the style, if given", () => { - const style = new Style( + describe("#createCharacterStyle", () => { + it("should create a new character style and push it onto this collection", () => { + const cStyle = styles.createCharacterStyle("pStyleId"); + expect(cStyle).to.instanceOf(CharacterStyle); + const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); + expect(tree).to.deep.equal([ { - type: "paragraph", - styleId: "myStyleId", - }, - "Style Name", - ); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:name": [{ _attr: { "w:val": "Style Name" } }] }, - ], - }); - }); - }); -}); - -describe("Style components", () => { - it("Name#constructor", () => { - const style = new components.Name("Style Name"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:name": [{ _attr: { "w:val": "Style Name" } }] }); - }); - - it("BasedOn#constructor", () => { - const style = new components.BasedOn("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:basedOn": [{ _attr: { "w:val": "otherId" } }] }); - }); - - it("Next#constructor", () => { - const style = new components.Next("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:next": [{ _attr: { "w:val": "otherId" } }] }); - }); - - it("Link#constructor", () => { - const style = new components.Link("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:link": [{ _attr: { "w:val": "otherId" } }] }); - }); - - it("UiPriority#constructor", () => { - const style = new components.UiPriority("123"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:uiPriority": [{ _attr: { "w:val": "123" } }] }); - }); -}); - -describe("ParagraphStyle", () => { - describe("#constructor", () => { - it("should set the style type to paragraph and use the given style id", () => { - const style = new ParagraphStyle("myStyleId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [] }, { "w:rPr": [] }], - }); - }); - - it("should set the name of the style, if given", () => { - const style = new ParagraphStyle("myStyleId", "Style Name"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:name": [{ _attr: { "w:val": "Style Name" } }] }, - { "w:pPr": [] }, - { "w:rPr": [] }, - ], - }); - }); - }); - - describe("formatting methods: style attributes", () => { - it("#basedOn", () => { - const style = new ParagraphStyle("myStyleId").basedOn("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { "w:rPr": [] }, - { "w:basedOn": [{ _attr: { "w:val": "otherId" } }] }, - ], - }); - }); - - it("#quickFormat", () => { - const style = new ParagraphStyle("myStyleId").quickFormat(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { "w:rPr": [] }, - { "w:qFormat": [] }, - ], - }); - }); - - it("#next", () => { - const style = new ParagraphStyle("myStyleId").next("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { "w:rPr": [] }, - { "w:next": [{ _attr: { "w:val": "otherId" } }] }, - ], - }); - }); - }); - - describe("formatting methods: paragraph properties", () => { - it("#indent", () => { - const style = new ParagraphStyle("myStyleId").indent({ left: 720 }); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [{ "w:ind": [{ _attr: { "w:left": 720 } }] }], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#spacing", () => { - const style = new ParagraphStyle("myStyleId").spacing({ before: 50, after: 150 }); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [{ "w:spacing": [{ _attr: { "w:before": 50, "w:after": 150 } }] }], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#center", () => { - const style = new ParagraphStyle("myStyleId").center(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "center" } }] }], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#character spacing", () => { - const style = new ParagraphStyle("myStyleId").characterSpacing(24); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:spacing": [{ _attr: { "w:val": 24 } }] }], - }, - ], - }); - }); - - it("#left", () => { - const style = new ParagraphStyle("myStyleId").left(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "left" } }] }], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#right", () => { - const style = new ParagraphStyle("myStyleId").right(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "right" } }] }], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#justified", () => { - const style = new ParagraphStyle("myStyleId").justified(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [{ "w:jc": [{ _attr: { "w:val": "both" } }] }], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#thematicBreak", () => { - const style = new ParagraphStyle("myStyleId").thematicBreak(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [ - { - "w:pBdr": [ - { - "w:bottom": [ - { - _attr: { - "w:color": "auto", - "w:space": "1", - "w:val": "single", - "w:sz": "6", - }, - }, - ], + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "pStyleId" } }, + { "w:rPr": [] }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", }, - ], - }, - ], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#leftTabStop", () => { - const style = new ParagraphStyle("myStyleId").leftTabStop(1200); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [ - { - "w:tabs": [{ "w:tab": [{ _attr: { "w:val": "left", "w:pos": 1200 } }] }], - }, - ], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#maxRightTabStop", () => { - const style = new ParagraphStyle("myStyleId").maxRightTabStop(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { - "w:pPr": [ - { - "w:tabs": [{ "w:tab": [{ _attr: { "w:val": "right", "w:pos": 9026 } }] }], - }, - ], - }, - { "w:rPr": [] }, - ], - }); - }); - - it("#keepLines", () => { - const style = new ParagraphStyle("myStyleId").keepLines(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [{ "w:keepLines": [] }] }, - { "w:rPr": [] }, - ], - }); - }); - - it("#keepNext", () => { - const style = new ParagraphStyle("myStyleId").keepNext(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [{ "w:keepNext": [] }] }, - { "w:rPr": [] }, - ], - }); - }); - }); - - describe("formatting methods: run properties", () => { - it("#size", () => { - const style = new ParagraphStyle("myStyleId").size(24); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:sz": [{ _attr: { "w:val": 24 } }] }, { "w:szCs": [{ _attr: { "w:val": 24 } }] }], - }, - ], - }); - }); - - it("#smallCaps", () => { - const style = new ParagraphStyle("myStyleId").smallCaps(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:smallCaps": [{ _attr: { "w:val": true } }] }], - }, - ], - }); - }); - - it("#allCaps", () => { - const style = new ParagraphStyle("myStyleId").allCaps(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:caps": [{ _attr: { "w:val": true } }] }], - }, - ], - }); - }); - - it("#strike", () => { - const style = new ParagraphStyle("myStyleId").strike(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:strike": [{ _attr: { "w:val": true } }] }], - }, - ], - }); - }); - - it("#doubleStrike", () => { - const style = new ParagraphStyle("myStyleId").doubleStrike(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:dstrike": [{ _attr: { "w:val": true } }] }], - }, - ], - }); - }); - - it("#subScript", () => { - const style = new ParagraphStyle("myStyleId").subScript(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "subscript" } }] }], - }, - ], - }); - }); - - it("#superScript", () => { - const style = new ParagraphStyle("myStyleId").superScript(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "superscript" } }] }], - }, - ], - }); - }); - - it("#font", () => { - const style = new ParagraphStyle("myStyleId").font("Times"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [ - { "w:rFonts": [{ _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } }] }, - ], - }, - ], - }); - }); - - it("#bold", () => { - const style = new ParagraphStyle("myStyleId").bold(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:b": [{ _attr: { "w:val": true } }] }], - }, - ], - }); - }); - - it("#italics", () => { - const style = new ParagraphStyle("myStyleId").italics(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:i": [{ _attr: { "w:val": true } }] }], - }, - ], - }); - }); - - describe("#underline", () => { - it("should set underline to 'single' if no arguments are given", () => { - const style = new ParagraphStyle("myStyleId").underline(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, + }, + ], + }, { - "w:rPr": [{ "w:u": [{ _attr: { "w:val": "single" } }] }], + "w:unhideWhenUsed": [], }, ], - }); - }); - - it("should set the style if given", () => { - const style = new ParagraphStyle("myStyleId").underline("double"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:u": [{ _attr: { "w:val": "double" } }] }], - }, - ], - }); - }); - - it("should set the style and color if given", () => { - const style = new ParagraphStyle("myStyleId").underline("double", "005599"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:u": [{ _attr: { "w:val": "double", "w:color": "005599" } }] }], - }, - ], - }); - }); + }, + ]); }); - it("#color", () => { - const style = new ParagraphStyle("myStyleId").color("123456"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ - "w:style": [ - { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, - { "w:pPr": [] }, - { - "w:rPr": [{ "w:color": [{ _attr: { "w:val": "123456" } }] }], - }, - ], - }); + it("should set the character name if given", () => { + const cStyle = styles.createCharacterStyle("pStyleId", "Character Style"); + expect(cStyle).to.instanceOf(CharacterStyle); + const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); + expect(tree).to.deep.equal([ + { + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "pStyleId" } }, + { "w:name": [{ _attr: { "w:val": "Character Style" } }] }, + { "w:rPr": [] }, + { + "w:uiPriority": [ + { + _attr: { + "w:val": "99", + }, + }, + ], + }, + { + "w:unhideWhenUsed": [], + }, + ], + }, + ]); }); }); }); diff --git a/src/file/styles/styles.ts b/src/file/styles/styles.ts index d6cdbfc6f9..ff38f5ec6e 100644 --- a/src/file/styles/styles.ts +++ b/src/file/styles/styles.ts @@ -1,6 +1,6 @@ import { BaseXmlComponent, XmlComponent } from "file/xml-components"; import { DocumentDefaults } from "./defaults"; -import { ParagraphStyle } from "./style"; +import { CharacterStyle, ParagraphStyle } from "./style"; export * from "./border"; export class Styles extends XmlComponent { @@ -23,8 +23,14 @@ export class Styles extends XmlComponent { } public createParagraphStyle(styleId: string, name?: string): ParagraphStyle { - const para = new ParagraphStyle(styleId, name); - this.push(para); - return para; + const paragraphStyle = new ParagraphStyle(styleId, name); + this.push(paragraphStyle); + return paragraphStyle; + } + + public createCharacterStyle(styleId: string, name?: string): CharacterStyle { + const characterStyle = new CharacterStyle(styleId, name); + this.push(characterStyle); + return characterStyle; } } From 50e08f198c71b22ca189ef6d1fe0cd4c30ade1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Mendon=C3=A7a?= Date: Tue, 13 Nov 2018 11:04:29 -0200 Subject: [PATCH 2/2] Linter ajusts --- src/file/styles/style/character-style.ts | 4 ++-- src/file/styles/style/paragraph-style.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/file/styles/style/character-style.ts b/src/file/styles/style/character-style.ts index ffeb4d6739..f7354ae788 100644 --- a/src/file/styles/style/character-style.ts +++ b/src/file/styles/style/character-style.ts @@ -1,7 +1,7 @@ -import { XmlComponent } from "file/xml-components"; -import { BasedOn, UiPriority, UnhideWhenUsed, Link, SemiHidden } from "./components"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; +import { XmlComponent } from "file/xml-components"; +import { BasedOn, Link, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; import { Style } from "./style"; export class CharacterStyle extends Style { diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index caa2d2777e..60f0035529 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -1,4 +1,3 @@ -import { XmlComponent } from "file/xml-components"; import { Alignment, AlignmentOptions, @@ -14,7 +13,8 @@ import { } from "file/paragraph"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; -import { BasedOn, QuickFormat, Next, Link, SemiHidden, UnhideWhenUsed, UiPriority } from "./components"; +import { XmlComponent } from "file/xml-components"; +import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; import { Style } from "./style"; export class ParagraphStyle extends Style {