From 8eff6bd0cfb7e890b0b8520122266f74365e6853 Mon Sep 17 00:00:00 2001 From: Dolan Date: Fri, 1 Nov 2019 01:57:01 +0000 Subject: [PATCH 1/4] Declarative numbering work --- docs/usage/paragraph.md | 51 +- src/file/core-properties/properties.ts | 2 + src/file/file.ts | 4 +- src/file/numbering/abstract-numbering.spec.ts | 665 ++++++++++++++++++ src/file/numbering/abstract-numbering.ts | 21 +- src/file/numbering/concrete-numbering.spec.ts | 80 +++ src/file/numbering/level.ts | 235 +++---- src/file/numbering/num.ts | 2 +- src/file/numbering/numbering.spec.ts | 431 +----------- src/file/numbering/numbering.ts | 157 ++++- src/file/paragraph/paragraph.spec.ts | 24 +- src/file/paragraph/paragraph.ts | 4 +- src/file/styles/style-options.ts | 39 + src/file/styles/style/paragraph-style.ts | 55 +- 14 files changed, 1071 insertions(+), 699 deletions(-) create mode 100644 src/file/numbering/abstract-numbering.spec.ts create mode 100644 src/file/numbering/concrete-numbering.spec.ts create mode 100644 src/file/styles/style-options.ts diff --git a/docs/usage/paragraph.md b/docs/usage/paragraph.md index f681a09148..f6da3ec963 100644 --- a/docs/usage/paragraph.md +++ b/docs/usage/paragraph.md @@ -20,11 +20,7 @@ This method is useful for adding different [text](text.md) with different styles ```ts const paragraph = new Paragraph({ - children: [ - new TextRun("Lorem Ipsum Foo Bar"), - new TextRun("Hello World"), - new SymbolRun("F071"), - ], + children: [new TextRun("Lorem Ipsum Foo Bar"), new TextRun("Hello World"), new SymbolRun("F071")], }); ``` @@ -60,27 +56,27 @@ doc.addSection({ This is the list of options for a paragraph. A detailed explanation is below: -| Property | Type | Mandatory? | Possible Values | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | -| [text](#text) | `string` | Optional | | -| [heading](#heading) | `HeadingLevel` | Optional | `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`, `TITLE` | -| [border](#border) | `IBorderOptions` | Optional | `top`, `bottom`, `left`, `right`. Each of these are of type IBorderPropertyOptions. Click here for Example | -| [spacing](#spacing) | `ISpacingProperties` | Optional | See below for ISpacingProperties | +| Property | Type | Mandatory? | Possible Values | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | +| [text](#text) | `string` | Optional | | +| [heading](#heading) | `HeadingLevel` | Optional | `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`, `TITLE` | +| [border](#border) | `IBorderOptions` | Optional | `top`, `bottom`, `left`, `right`. Each of these are of type IBorderPropertyOptions. Click here for Example | +| [spacing](#spacing) | `ISpacingProperties` | Optional | See below for ISpacingProperties | | [outlineLevel](#outline-level) | `number` | Optional | | -| alignment | `AlignmentType` | Optional | | -| heading | `HeadingLevel` | Optional | | -| bidirectional | `boolean` | Optional | | -| thematicBreak | `boolean` | Optional | | -| pageBreakBefore | `boolean` | Optional | | -| contextualSpacing | `boolean` | Optional | | -| indent | `IIndentAttributesProperties` | Optional | | -| keepLines | `boolean` | Optional | | -| keepNext | `boolean` | Optional | | -| children | `(TextRun or PictureRun or Hyperlink)[]` | Optional | | -| style | `string` | Optional | | -| tabStop | `{ left?: ITabStopOptions; right?: ITabStopOptions; maxRight?: { leader: LeaderType; }; center?: ITabStopOptions }` | Optional | | -| bullet | `{ level: number }` | Optional | | -| numbering | `{ num: Num; level: number; custom?: boolean }` | Optional | | +| alignment | `AlignmentType` | Optional | | +| heading | `HeadingLevel` | Optional | | +| bidirectional | `boolean` | Optional | | +| thematicBreak | `boolean` | Optional | | +| pageBreakBefore | `boolean` | Optional | | +| contextualSpacing | `boolean` | Optional | | +| indent | `IIndentAttributesProperties` | Optional | | +| keepLines | `boolean` | Optional | | +| keepNext | `boolean` | Optional | | +| children | `(TextRun or PictureRun or Hyperlink)[]` | Optional | | +| style | `string` | Optional | | +| tabStop | `{ left?: ITabStopOptions; right?: ITabStopOptions; maxRight?: { leader: LeaderType; }; center?: ITabStopOptions }` | Optional | | +| bullet | `{ level: number }` | Optional | | +| numbering | `{ num: ConcreteNumbering; level: number; custom?: boolean }` | Optional | | ## Text @@ -252,10 +248,7 @@ To move to a new page (insert a page break): ```ts const paragraph = new docx.Paragraph({ - children: [ - new TextRun("Amazing Heading"), - new PageBreak(), - ] + children: [new TextRun("Amazing Heading"), new PageBreak()], }); ``` diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index a99c74d98f..e86a0732d0 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,6 +1,7 @@ import { XmlComponent } from "file/xml-components"; import { DocumentAttributes } from "../document/document-attributes"; +import { IAbstractNumberingOptions } from "../numbering"; import { IStylesOptions } from "../styles"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; @@ -14,6 +15,7 @@ export interface IPropertiesOptions { readonly revision?: string; readonly externalStyles?: string; readonly styles?: IStylesOptions; + readonly numbering?: IAbstractNumberingOptions; } export class CoreProperties extends XmlComponent { diff --git a/src/file/file.ts b/src/file/file.ts index ccf5fe374c..63bb272596 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -71,7 +71,9 @@ export class File { sections: ISectionOptions[] = [], ) { this.coreProperties = new CoreProperties(options); - this.numbering = new Numbering(); + this.numbering = new Numbering({ + levels: options.numbering ? options.numbering.levels : [], + }); this.docRelationships = new Relationships(); this.fileRelationships = new Relationships(); this.appProperties = new AppProperties(); diff --git a/src/file/numbering/abstract-numbering.spec.ts b/src/file/numbering/abstract-numbering.spec.ts new file mode 100644 index 0000000000..45d7f25f0e --- /dev/null +++ b/src/file/numbering/abstract-numbering.spec.ts @@ -0,0 +1,665 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { EMPTY_OBJECT } from "file/xml-components"; + +import { AlignmentType, TabStopPosition } from "../paragraph"; +import { UnderlineType } from "../paragraph/run/underline"; +import { ShadingType } from "../table"; +import { AbstractNumbering } from "./abstract-numbering"; + +describe.only("AbstractNumbering", () => { + it("stores its ID at its .id property", () => { + const abstractNumbering = new AbstractNumbering(5, { + levels: [], + }); + expect(abstractNumbering.id).to.equal(5); + }); + + describe("#createLevel", () => { + it("creates a level with the given characteristics", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 3, + format: "lowerLetter", + text: "%1)", + alignment: AlignmentType.END, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); + expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); + expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } }); + expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); + expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); + }); + + it("uses 'start' as the default alignment", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 3, + format: "lowerLetter", + text: "%1)", + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); + expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); + expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } }); + expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); + expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); + }); + + describe("formatting methods: paragraph properties", () => { + it("#indent", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + indent: { left: 720 }, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }], + }); + }); + + it("#spacing", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + spacing: { before: 50, after: 150 }, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }], + }); + }); + + it("#center", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.CENTER, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }], + }); + }); + + it("#left", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.LEFT, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }], + }); + }); + + it("#right", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.RIGHT, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }], + }); + }); + + it("#justified", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.JUSTIFIED, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }], + }); + }); + + it("#thematicBreak", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + thematicBreak: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [ + { + "w:pBdr": [ + { + "w:bottom": { + _attr: { + "w:color": "auto", + "w:space": 1, + "w:val": "single", + "w:sz": 6, + }, + }, + }, + ], + }, + ], + }); + }); + + it("#leftTabStop", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + leftTabStop: 1200, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [ + { + "w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }], + }, + ], + }); + }); + + it("#maxRightTabStop", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + rightTabStop: TabStopPosition.MAX, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [ + { + "w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }], + }, + ], + }); + }); + + it("#keepLines", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + keepLines: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }], + }); + }); + + it("#keepNext", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + keepNext: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }], + }); + }); + }); + + describe("formatting methods: run properties", () => { + it("#size", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + size: 24, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }], + }); + }); + + it("#smallCaps", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + smallCaps: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], + }); + }); + + it("#allCaps", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + allCaps: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], + }); + }); + + it("#strike", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + strike: true, + }, + }, + }, + ], + }); + + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], + }); + }); + + it("#doubleStrike", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + doubleStrike: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], + }); + }); + + it("#subScript", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + subScript: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }], + }); + }); + + it("#superScript", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + superScript: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }], + }); + }); + + it("#font", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + font: "Times", + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [ + { "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } }, + ], + }); + }); + + it("#bold", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + bold: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:b": { _attr: { "w:val": true } } }], + }); + }); + + it("#italics", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + italics: true, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:i": { _attr: { "w:val": true } } }], + }); + }); + + it("#highlight", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + highlight: "005599", + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }], + }); + }); + + it("#shadow", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + shadow: { + type: ShadingType.PERCENT_10, + fill: "00FFFF", + color: "FF0000", + }, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], + }); + }); + + describe("#underline", () => { + it("should set underline to 'single' if no arguments are given", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + underline: {}, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }], + }); + }); + + it("should set the style if given", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + underline: { + type: UnderlineType.DOUBLE, + }, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }], + }); + }); + + it("should set the style and color if given", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + underline: { + type: UnderlineType.DOUBLE, + color: "005599", + }, + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }], + }); + }); + }); + + it("#color", () => { + const abstractNumbering = new AbstractNumbering(1, { + levels: [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + color: "123456", + }, + }, + }, + ], + }); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:lvl"]).to.include({ + "w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }], + }); + }); + }); + }); +}); diff --git a/src/file/numbering/abstract-numbering.ts b/src/file/numbering/abstract-numbering.ts index e42e2e4fc8..49b1175812 100644 --- a/src/file/numbering/abstract-numbering.ts +++ b/src/file/numbering/abstract-numbering.ts @@ -1,5 +1,6 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -import { Level } from "./level"; + +import { ILevelsOptions, Level } from "./level"; import { MultiLevelType } from "./multi-level-type"; interface IAbstractNumberingAttributesProperties { @@ -14,10 +15,14 @@ class AbstractNumberingAttributes extends XmlAttributeComponent { + describe("#overrideLevel", () => { + let concreteNumbering; + beforeEach(() => { + concreteNumbering = new ConcreteNumbering(0, 1); + }); + + it("sets a new override level for the given level number", () => { + concreteNumbering.overrideLevel(3); + const tree = new Formatter().format(concreteNumbering); + expect(tree["w:num"]).to.include({ + "w:lvlOverride": [ + { + _attr: { + "w:ilvl": 3, + }, + }, + { + "w:lvl": { + _attr: { + "w:ilvl": 3, + "w15:tentative": 1, + }, + }, + }, + ], + }); + }); + + it("sets the startOverride element if start is given", () => { + concreteNumbering.overrideLevel(1, 9); + const tree = new Formatter().format(concreteNumbering); + expect(tree["w:num"]).to.include({ + "w:lvlOverride": [ + { + _attr: { + "w:ilvl": 1, + }, + }, + { + "w:startOverride": { + _attr: { + "w:val": 9, + }, + }, + }, + { + "w:lvl": { + _attr: { + "w:ilvl": 1, + "w15:tentative": 1, + }, + }, + }, + ], + }); + }); + + it("sets the lvl element if overrideLevel.Level is accessed", () => { + const ol = concreteNumbering.overrideLevel(1); + expect(ol.Level).to.be.instanceof(LevelForOverride); + const tree = new Formatter().format(concreteNumbering); + expect(tree["w:num"]).to.include({ + "w:lvlOverride": [ + { _attr: { "w:ilvl": 1 } }, + { + "w:lvl": { _attr: { "w15:tentative": 1, "w:ilvl": 1 } }, + }, + ], + }); + }); + }); +}); diff --git a/src/file/numbering/level.ts b/src/file/numbering/level.ts index bdc65fbba4..b9f66a89c0 100644 --- a/src/file/numbering/level.ts +++ b/src/file/numbering/level.ts @@ -2,9 +2,7 @@ import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-compon import { Alignment, AlignmentType, - IIndentAttributesProperties, Indent, - ISpacingProperties, KeepLines, KeepNext, Spacing, @@ -15,7 +13,7 @@ import { import { ParagraphProperties } from "../paragraph/properties"; import * as formatting from "../paragraph/run/formatting"; import { RunProperties } from "../paragraph/run/properties"; -import { UnderlineType } from "../paragraph/run/underline"; +import { IParagraphStyleOptions2, IRunStyleOptions } from "../styles/style-options"; interface ILevelAttributesProperties { readonly ilvl?: number; @@ -63,7 +61,7 @@ class LevelText extends XmlComponent { } class LevelJc extends XmlComponent { - constructor(value: string) { + constructor(value: AlignmentType) { super("w:lvlJc"); this.root.push( new Attributes({ @@ -79,6 +77,19 @@ export enum LevelSuffix { TAB = "tab", } +export interface ILevelsOptions { + readonly level: number; + readonly format: string; + readonly text: string; + readonly alignment?: AlignmentType; + readonly start?: number; + readonly suffix?: LevelSuffix; + readonly style?: { + readonly run?: IRunStyleOptions; + readonly paragraph?: IParagraphStyleOptions2; + }; +} + class Suffix extends XmlComponent { constructor(value: LevelSuffix) { super("w:suff"); @@ -94,7 +105,7 @@ export class LevelBase extends XmlComponent { private readonly paragraphProperties: ParagraphProperties; private readonly runProperties: RunProperties; - constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) { + constructor({ level, format, text, alignment = AlignmentType.START, start = 1, style, suffix }: ILevelsOptions) { super("w:lvl"); this.root.push( new LevelAttributes({ @@ -103,174 +114,122 @@ export class LevelBase extends XmlComponent { }), ); - if (start !== undefined) { - this.root.push(new Start(start)); - } - if (numberFormat !== undefined) { - this.root.push(new NumberFormat(numberFormat)); - } - if (levelText !== undefined) { - this.root.push(new LevelText(levelText)); - } - if (lvlJc !== undefined) { - this.root.push(new LevelJc(lvlJc)); - } + this.root.push(new Start(start)); + this.root.push(new NumberFormat(format)); + this.root.push(new LevelText(text)); + this.root.push(new LevelJc(alignment)); this.paragraphProperties = new ParagraphProperties({}); this.runProperties = new RunProperties(); this.root.push(this.paragraphProperties); this.root.push(this.runProperties); - } - public setSuffix(value: LevelSuffix): LevelBase { - this.root.push(new Suffix(value)); - return this; - } + if (suffix) { + this.root.push(new Suffix(suffix)); + } - public addParagraphProperty(property: XmlComponent): Level { - this.paragraphProperties.push(property); - return this; - } + if (style) { + if (style.run) { + if (style.run.size) { + this.runProperties.push(new formatting.Size(style.run.size)); + } - public addRunProperty(property: XmlComponent): Level { - this.runProperties.push(property); - return this; - } + if (style.run.bold) { + this.runProperties.push(new formatting.Bold()); + } - // ---------- Run formatting ---------------------- // + if (style.run.italics) { + this.runProperties.push(new formatting.Italics()); + } - public size(twips: number): Level { - this.addRunProperty(new formatting.Size(twips)); - return this; - } + if (style.run.smallCaps) { + this.runProperties.push(new formatting.SmallCaps()); + } - public bold(): Level { - this.addRunProperty(new formatting.Bold()); - return this; - } + if (style.run.allCaps) { + this.runProperties.push(new formatting.Caps()); + } - public italics(): Level { - this.addRunProperty(new formatting.Italics()); - return this; - } + if (style.run.strike) { + this.runProperties.push(new formatting.Strike()); + } - public smallCaps(): Level { - this.addRunProperty(new formatting.SmallCaps()); - return this; - } + if (style.run.doubleStrike) { + this.runProperties.push(new formatting.DoubleStrike()); + } - public allCaps(): Level { - this.addRunProperty(new formatting.Caps()); - return this; - } + if (style.run.subScript) { + this.runProperties.push(new formatting.SubScript()); + } - public strike(): Level { - this.addRunProperty(new formatting.Strike()); - return this; - } + if (style.run.superScript) { + this.runProperties.push(new formatting.SuperScript()); + } - public doubleStrike(): Level { - this.addRunProperty(new formatting.DoubleStrike()); - return this; - } + if (style.run.underline) { + this.runProperties.push(new formatting.Underline(style.run.underline.type, style.run.underline.color)); + } - public subScript(): Level { - this.addRunProperty(new formatting.SubScript()); - return this; - } + if (style.run.color) { + this.runProperties.push(new formatting.Color(style.run.color)); + } - public superScript(): Level { - this.addRunProperty(new formatting.SuperScript()); - return this; - } + if (style.run.font) { + this.runProperties.push(new formatting.RunFonts(style.run.font)); + } - public underline(underlineType?: UnderlineType, color?: string): Level { - this.addRunProperty(new formatting.Underline(underlineType, color)); - return this; - } + if (style.run.highlight) { + this.runProperties.push(new formatting.Highlight(style.run.highlight)); + } - public color(color: string): Level { - this.addRunProperty(new formatting.Color(color)); - return this; - } + if (style.run.shadow) { + this.runProperties.push(new formatting.Shading(style.run.shadow.type, style.run.shadow.fill, style.run.shadow.color)); + } + } - public font(fontName: string): Level { - this.addRunProperty(new formatting.RunFonts(fontName)); - return this; - } + if (style.paragraph) { + if (style.paragraph.alignment) { + this.paragraphProperties.push(new Alignment(style.paragraph.alignment)); + } - public highlight(color: string): Level { - this.addRunProperty(new formatting.Highlight(color)); - return this; - } + if (style.paragraph.thematicBreak) { + this.paragraphProperties.push(new ThematicBreak()); + } - public shadow(value: string, fill: string, color: string): Level { - this.addRunProperty(new formatting.Shading(value, fill, color)); - return this; - } - // --------------------- Paragraph formatting ------------------------ // + if (style.paragraph.rightTabStop) { + this.paragraphProperties.push(new TabStop(TabStopType.RIGHT, style.paragraph.rightTabStop)); + } - public center(): Level { - this.addParagraphProperty(new Alignment(AlignmentType.CENTER)); - return this; - } + if (style.paragraph.leftTabStop) { + this.paragraphProperties.push(new TabStop(TabStopType.LEFT, style.paragraph.leftTabStop)); + } - public left(): Level { - this.addParagraphProperty(new Alignment(AlignmentType.LEFT)); - return this; - } + if (style.paragraph.indent) { + this.paragraphProperties.push(new Indent(style.paragraph.indent)); + } - public right(): Level { - this.addParagraphProperty(new Alignment(AlignmentType.RIGHT)); - return this; - } + if (style.paragraph.spacing) { + this.paragraphProperties.push(new Spacing(style.paragraph.spacing)); + } - public justified(): Level { - this.addParagraphProperty(new Alignment(AlignmentType.BOTH)); - return this; - } + if (style.paragraph.keepNext) { + this.paragraphProperties.push(new KeepNext()); + } - public thematicBreak(): Level { - this.addParagraphProperty(new ThematicBreak()); - return this; - } - - public rightTabStop(position: number): Level { - return this.addParagraphProperty(new TabStop(TabStopType.RIGHT, position)); - } - - public leftTabStop(position: number): Level { - this.addParagraphProperty(new TabStop(TabStopType.LEFT, position)); - return this; - } - - public indent(attrs: IIndentAttributesProperties): Level { - this.addParagraphProperty(new Indent(attrs)); - return this; - } - - public spacing(params: ISpacingProperties): Level { - this.addParagraphProperty(new Spacing(params)); - return this; - } - - public keepNext(): Level { - this.addParagraphProperty(new KeepNext()); - return this; - } - - public keepLines(): Level { - this.addParagraphProperty(new KeepLines()); - return this; + if (style.paragraph.keepLines) { + this.paragraphProperties.push(new KeepLines()); + } + } + } } } export class Level extends LevelBase { // This is the level that sits under abstractNum. We make a // handful of properties required - constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) { - super(level, 1, numberFormat, levelText, lvlJc); + constructor(options: ILevelsOptions) { + super(options); } } diff --git a/src/file/numbering/num.ts b/src/file/numbering/num.ts index bf552593aa..c19c81a39b 100644 --- a/src/file/numbering/num.ts +++ b/src/file/numbering/num.ts @@ -20,7 +20,7 @@ class NumAttributes extends XmlAttributeComponent { protected readonly xmlKeys = { numId: "w:numId" }; } -export class Num extends XmlComponent { +export class ConcreteNumbering extends XmlComponent { public readonly id: number; constructor(numId: number, abstractNumId: number) { diff --git a/src/file/numbering/numbering.spec.ts b/src/file/numbering/numbering.spec.ts index d5f2f9e168..0c16884300 100644 --- a/src/file/numbering/numbering.spec.ts +++ b/src/file/numbering/numbering.spec.ts @@ -2,24 +2,15 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { AbstractNumbering } from "./abstract-numbering"; -import { LevelForOverride } from "./level"; -import { Num } from "./num"; import { Numbering } from "./numbering"; -import { EMPTY_OBJECT } from "file/xml-components"; -import { TabStopPosition } from "../paragraph"; -import { UnderlineType } from "../paragraph/run/underline"; - describe("Numbering", () => { - let numbering: Numbering; - - beforeEach(() => { - numbering = new Numbering(); - }); - describe("#constructor", () => { it("creates a default numbering with one abstract and one concrete instance", () => { + const numbering = new Numbering({ + levels: [], + }); + const tree = new Formatter().format(numbering); expect(Object.keys(tree)).to.deep.equal(["w:numbering"]); const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]); @@ -48,418 +39,4 @@ describe("Numbering", () => { }); }); }); - - describe("#createAbstractNumbering", () => { - it("returns a new AbstractNumbering instance", () => { - const a2 = numbering.createAbstractNumbering(); - expect(a2).to.be.instanceof(AbstractNumbering); - }); - - it("assigns a unique ID to each abstract numbering it creates", () => { - const a2 = numbering.createAbstractNumbering(); - const a3 = numbering.createAbstractNumbering(); - expect(a2.id).not.to.equal(a3.id); - }); - }); - - describe("#createConcreteNumbering", () => { - it("returns a new Num instance with its abstract ID set to the AbstractNumbering's ID", () => { - const a2 = numbering.createAbstractNumbering(); - const n = numbering.createConcreteNumbering(a2); - expect(n).to.be.instanceof(Num); - const tree = new Formatter().format(numbering); - const serializedN = tree["w:numbering"].find((obj) => obj["w:num"] && obj["w:num"][0]._attr["w:numId"] === n.id); - expect(serializedN["w:num"][1]["w:abstractNumId"]._attr["w:val"]).to.equal(a2.id); - }); - - it("assigns a unique ID to each concrete numbering it creates", () => { - const a2 = numbering.createAbstractNumbering(); - const n = numbering.createConcreteNumbering(a2); - const n2 = numbering.createConcreteNumbering(a2); - expect(n.id).not.to.equal(n2.id); - }); - }); -}); - -describe("AbstractNumbering", () => { - it("stores its ID at its .id property", () => { - const abstractNumbering = new AbstractNumbering(5); - expect(abstractNumbering.id).to.equal(5); - }); - - describe("#createLevel", () => { - it("creates a level with the given characteristics", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)", "end"); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); - expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } }); - expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); - }); - - it("uses 'start' as the default alignment", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)"); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); - expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } }); - expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); - }); - - describe("formatting methods: paragraph properties", () => { - it("#indent", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").indent({ left: 720 }); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }], - }); - }); - - it("#spacing", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").spacing({ before: 50, after: 150 }); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }], - }); - }); - - it("#center", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").center(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }], - }); - }); - - it("#left", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.", "left").left(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }], - }); - }); - - it("#right", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").right(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }], - }); - }); - - it("#justified", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").justified(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }], - }); - }); - - it("#thematicBreak", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").thematicBreak(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [ - { - "w:pBdr": [ - { - "w:bottom": { - _attr: { - "w:color": "auto", - "w:space": 1, - "w:val": "single", - "w:sz": 6, - }, - }, - }, - ], - }, - ], - }); - }); - - it("#leftTabStop", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").leftTabStop(1200); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [ - { - "w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }], - }, - ], - }); - }); - - it("#maxRightTabStop", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").rightTabStop(TabStopPosition.MAX); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [ - { - "w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }], - }, - ], - }); - }); - - it("#keepLines", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepLines(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }], - }); - }); - - it("#keepNext", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepNext(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }], - }); - }); - }); - - describe("formatting methods: run properties", () => { - it("#size", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").size(24); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }], - }); - }); - - it("#smallCaps", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").smallCaps(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], - }); - }); - - it("#allCaps", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").allCaps(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], - }); - }); - - it("#strike", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").strike(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], - }); - }); - - it("#doubleStrike", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").doubleStrike(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], - }); - }); - - it("#subScript", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").subScript(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }], - }); - }); - - it("#superScript", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").superScript(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }], - }); - }); - - it("#font", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").font("Times"); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [ - { "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } }, - ], - }); - }); - - it("#bold", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").bold(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:b": { _attr: { "w:val": true } } }], - }); - }); - - it("#italics", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").italics(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:i": { _attr: { "w:val": true } } }], - }); - }); - - it("#highlight", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").highlight("005599"); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }], - }); - }); - - it("#shadow", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").shadow("pct10", "00FFFF", "FF0000"); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }); - }); - - describe("#underline", () => { - it("should set underline to 'single' if no arguments are given", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }], - }); - }); - - it("should set the style if given", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }], - }); - }); - - it("should set the style and color if given", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE, "005599"); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }], - }); - }); - }); - - it("#color", () => { - const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").color("123456"); - const tree = new Formatter().format(level); - expect(tree["w:lvl"]).to.include({ - "w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }], - }); - }); - }); - }); -}); - -describe("concrete numbering", () => { - describe("#overrideLevel", () => { - let numbering; - let abstractNumbering; - let concreteNumbering; - beforeEach(() => { - numbering = new Numbering(); - abstractNumbering = numbering.createAbstractNumbering(); - concreteNumbering = numbering.createConcreteNumbering(abstractNumbering); - }); - - it("sets a new override level for the given level number", () => { - concreteNumbering.overrideLevel(3); - const tree = new Formatter().format(concreteNumbering); - expect(tree["w:num"]).to.include({ - "w:lvlOverride": [ - { - _attr: { - "w:ilvl": 3, - }, - }, - { - "w:lvl": { - _attr: { - "w:ilvl": 3, - "w15:tentative": 1, - }, - }, - }, - ], - }); - }); - - it("sets the startOverride element if start is given", () => { - concreteNumbering.overrideLevel(1, 9); - const tree = new Formatter().format(concreteNumbering); - expect(tree["w:num"]).to.include({ - "w:lvlOverride": [ - { - _attr: { - "w:ilvl": 1, - }, - }, - { - "w:startOverride": { - _attr: { - "w:val": 9, - }, - }, - }, - { - "w:lvl": { - _attr: { - "w:ilvl": 1, - "w15:tentative": 1, - }, - }, - }, - ], - }); - }); - - it("sets the lvl element if overrideLevel.Level is accessed", () => { - const ol = concreteNumbering.overrideLevel(1); - expect(ol.Level).to.be.instanceof(LevelForOverride); - const tree = new Formatter().format(concreteNumbering); - expect(tree["w:num"]).to.include({ - "w:lvlOverride": [ - { _attr: { "w:ilvl": 1 } }, - { - "w:lvl": { _attr: { "w15:tentative": 1, "w:ilvl": 1 } }, - }, - ], - }); - }); - }); }); diff --git a/src/file/numbering/numbering.ts b/src/file/numbering/numbering.ts index 0080b6b349..891aa2ab3a 100644 --- a/src/file/numbering/numbering.ts +++ b/src/file/numbering/numbering.ts @@ -1,8 +1,10 @@ -import { Indent } from "file/paragraph"; +// http://officeopenxml.com/WPnumbering.php +import { AlignmentType } from "file/paragraph"; import { IXmlableObject, XmlComponent } from "file/xml-components"; + import { DocumentAttributes } from "../document/document-attributes"; -import { AbstractNumbering } from "./abstract-numbering"; -import { Num } from "./num"; +import { AbstractNumbering, IAbstractNumberingOptions } from "./abstract-numbering"; +import { ConcreteNumbering } from "./num"; export class Numbering extends XmlComponent { // tslint:disable-next-line:readonly-keyword @@ -11,7 +13,7 @@ export class Numbering extends XmlComponent { private readonly abstractNumbering: XmlComponent[] = []; private readonly concreteNumbering: XmlComponent[] = []; - constructor() { + constructor(options: IAbstractNumberingOptions) { super("w:numbering"); this.root.push( new DocumentAttributes({ @@ -37,39 +39,114 @@ export class Numbering extends XmlComponent { this.nextId = 0; - const abstractNumbering = this.createAbstractNumbering(); - - abstractNumbering.createLevel(0, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 720, hanging: 360 })); - - abstractNumbering.createLevel(1, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 1440, hanging: 360 })); - - abstractNumbering.createLevel(2, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 2160, hanging: 360 })); - - abstractNumbering.createLevel(3, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 2880, hanging: 360 })); - - abstractNumbering.createLevel(4, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 3600, hanging: 360 })); - - abstractNumbering.createLevel(5, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 4320, hanging: 360 })); - - abstractNumbering.createLevel(6, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 5040, hanging: 360 })); - - abstractNumbering.createLevel(7, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 5760, hanging: 360 })); - - abstractNumbering.createLevel(8, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 6480, hanging: 360 })); + const abstractNumbering = this.createAbstractNumbering({ + levels: [ + { + level: 0, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 720, hanging: 360 }, + }, + }, + }, + { + level: 1, + format: "bullet", + text: "\u25CB", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 1440, hanging: 360 }, + }, + }, + }, + { + level: 2, + format: "bullet", + text: "\u25A0", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 2160, hanging: 360 }, + }, + }, + }, + { + level: 3, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 2880, hanging: 360 }, + }, + }, + }, + { + level: 4, + format: "bullet", + text: "\u25CB", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 3600, hanging: 360 }, + }, + }, + }, + { + level: 5, + format: "bullet", + text: "\u25A0", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 4320, hanging: 360 }, + }, + }, + }, + { + level: 6, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 5040, hanging: 360 }, + }, + }, + }, + { + level: 7, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 5760, hanging: 360 }, + }, + }, + }, + { + level: 8, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 6480, hanging: 360 }, + }, + }, + }, + ], + }); this.createConcreteNumbering(abstractNumbering); - } - public createAbstractNumbering(): AbstractNumbering { - const num = new AbstractNumbering(this.nextId++); - this.abstractNumbering.push(num); - return num; - } - - public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num { - const num = new Num(this.nextId++, abstractNumbering.id); - this.concreteNumbering.push(num); - return num; + const currentAbstractNumbering = this.createAbstractNumbering(options); + this.createConcreteNumbering(currentAbstractNumbering); } public prepForXml(): IXmlableObject | undefined { @@ -77,4 +154,16 @@ export class Numbering extends XmlComponent { this.concreteNumbering.forEach((x) => this.root.push(x)); return super.prepForXml(); } + + private createConcreteNumbering(abstractNumbering: AbstractNumbering): ConcreteNumbering { + const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id); + this.concreteNumbering.push(num); + return num; + } + + private createAbstractNumbering(options: IAbstractNumberingOptions): AbstractNumbering { + const num = new AbstractNumbering(this.nextId++, options); + this.abstractNumbering.push(num); + return num; + } } diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index e8e5f6c8c9..013bf59f5d 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -596,10 +596,15 @@ describe("Paragraph", () => { describe("#setNumbering", () => { it("should add list paragraph style to JSON", () => { - const numbering = new Numbering(); - const numberedAbstract = numbering.createAbstractNumbering(); - numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start"); - const letterNumbering = numbering.createConcreteNumbering(numberedAbstract); + const numbering = new Numbering({ + levels: [ + { + level: 0, + format: "lowerLetter", + text: "%1)", + }, + ], + }); const paragraph = new Paragraph({ numbering: { @@ -622,9 +627,16 @@ describe("Paragraph", () => { }); it("it should add numbered properties", () => { - const numbering = new Numbering(); + const numbering = new Numbering({ + levels: [ + { + level: 0, + format: "lowerLetter", + text: "%1)", + }, + ], + }); const numberedAbstract = numbering.createAbstractNumbering(); - numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start"); const letterNumbering = numbering.createConcreteNumbering(numberedAbstract); const paragraph = new Paragraph({ diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index b11393ee37..1a26a7c666 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,6 +1,6 @@ // http://officeopenxml.com/WPparagraph.php import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; -import { Num } from "file/numbering/num"; +import { ConcreteNumbering } from "file/numbering/num"; import { XmlComponent } from "file/xml-components"; import { Alignment, AlignmentType } from "./formatting/alignment"; @@ -41,7 +41,7 @@ export interface IParagraphOptions { readonly level: number; }; readonly numbering?: { - readonly num: Num; + readonly num: ConcreteNumbering; readonly level: number; readonly custom?: boolean; }; diff --git a/src/file/styles/style-options.ts b/src/file/styles/style-options.ts new file mode 100644 index 0000000000..dcedadbec5 --- /dev/null +++ b/src/file/styles/style-options.ts @@ -0,0 +1,39 @@ +import { AlignmentType, IIndentAttributesProperties, ISpacingProperties, UnderlineType } from "../paragraph"; +import { ShadingType } from "../table"; + +export interface IRunStyleOptions { + readonly size?: number; + readonly bold?: boolean; + readonly italics?: boolean; + readonly smallCaps?: boolean; + readonly allCaps?: boolean; + readonly strike?: boolean; + readonly doubleStrike?: boolean; + readonly subScript?: boolean; + readonly superScript?: boolean; + readonly underline?: { + readonly type?: UnderlineType; + readonly color?: string; + }; + readonly color?: string; + readonly font?: string; + readonly characterSpacing?: number; + readonly highlight?: string; + readonly shadow?: { + readonly type: ShadingType; + readonly fill: string; + readonly color: string; + }; +} + +export interface IParagraphStyleOptions2 { + readonly alignment?: AlignmentType; + readonly thematicBreak?: boolean; + readonly rightTabStop?: number; + readonly leftTabStop?: number; + readonly indent?: IIndentAttributesProperties; + readonly spacing?: ISpacingProperties; + readonly keepNext?: boolean; + readonly keepLines?: boolean; + readonly outlineLevel?: number; +} diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index ab592a9fc6..a47a33f492 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -1,21 +1,9 @@ -import { - Alignment, - AlignmentType, - Indent, - ISpacingProperties, - KeepLines, - KeepNext, - OutlineLevel, - ParagraphProperties, - Spacing, - ThematicBreak, -} from "file/paragraph"; -import { IIndentAttributesProperties, TabStop, TabStopType } from "file/paragraph/formatting"; +import { Alignment, Indent, KeepLines, KeepNext, OutlineLevel, ParagraphProperties, Spacing, ThematicBreak } from "file/paragraph"; +import { TabStop, TabStopType } from "file/paragraph/formatting"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; -import { UnderlineType } from "file/paragraph/run/underline"; -import { ShadingType } from "file/table"; +import { IParagraphStyleOptions2, IRunStyleOptions } from "../style-options"; import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; import { Style } from "./style"; @@ -27,41 +15,8 @@ export interface IBaseParagraphStyleOptions { readonly semiHidden?: boolean; readonly uiPriority?: number; readonly unhideWhenUsed?: boolean; - readonly run?: { - readonly size?: number; - readonly bold?: boolean; - readonly italics?: boolean; - readonly smallCaps?: boolean; - readonly allCaps?: boolean; - readonly strike?: boolean; - readonly doubleStrike?: boolean; - readonly subScript?: boolean; - readonly superScript?: boolean; - readonly underline?: { - readonly type?: UnderlineType; - readonly color?: string; - }; - readonly color?: string; - readonly font?: string; - readonly characterSpacing?: number; - readonly highlight?: string; - readonly shadow?: { - readonly type: ShadingType; - readonly fill: string; - readonly color: string; - }; - }; - readonly paragraph?: { - readonly alignment?: AlignmentType; - readonly thematicBreak?: boolean; - readonly rightTabStop?: number; - readonly leftTabStop?: number; - readonly indent?: IIndentAttributesProperties; - readonly spacing?: ISpacingProperties; - readonly keepNext?: boolean; - readonly keepLines?: boolean; - readonly outlineLevel?: number; - }; + readonly run?: IRunStyleOptions; + readonly paragraph?: IParagraphStyleOptions2; } export interface IParagraphStyleOptions extends IBaseParagraphStyleOptions { From a622c210efc701a8356db9db9986851de77b8f48 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Mon, 4 Nov 2019 20:19:09 +0000 Subject: [PATCH 2/4] Progress on declaritive numbering --- src/export/packer/numbering-replacer.ts | 0 src/file/paragraph/paragraph.spec.ts | 2 -- 2 files changed, 2 deletions(-) create mode 100644 src/export/packer/numbering-replacer.ts diff --git a/src/export/packer/numbering-replacer.ts b/src/export/packer/numbering-replacer.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 013bf59f5d..d4c7fc81e9 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -636,8 +636,6 @@ describe("Paragraph", () => { }, ], }); - const numberedAbstract = numbering.createAbstractNumbering(); - const letterNumbering = numbering.createConcreteNumbering(numberedAbstract); const paragraph = new Paragraph({ numbering: { From 9b40b5e55e9bdfa6bffe4d3e856c76a90b02f375 Mon Sep 17 00:00:00 2001 From: Dolan Date: Wed, 6 Nov 2019 20:54:39 +0000 Subject: [PATCH 3/4] Add work for custom level id --- src/file/numbering/abstract-numbering.spec.ts | 76 +++++++++---------- src/file/numbering/concrete-numbering.spec.ts | 37 +++++---- src/file/numbering/level.ts | 14 +++- src/file/numbering/num.ts | 4 +- .../paragraph/formatting/unordered-list.ts | 6 +- src/file/paragraph/paragraph.spec.ts | 30 +------- src/file/paragraph/paragraph.ts | 5 +- 7 files changed, 77 insertions(+), 95 deletions(-) diff --git a/src/file/numbering/abstract-numbering.spec.ts b/src/file/numbering/abstract-numbering.spec.ts index 45d7f25f0e..6552775cd5 100644 --- a/src/file/numbering/abstract-numbering.spec.ts +++ b/src/file/numbering/abstract-numbering.spec.ts @@ -8,7 +8,7 @@ import { UnderlineType } from "../paragraph/run/underline"; import { ShadingType } from "../table"; import { AbstractNumbering } from "./abstract-numbering"; -describe.only("AbstractNumbering", () => { +describe("AbstractNumbering", () => { it("stores its ID at its .id property", () => { const abstractNumbering = new AbstractNumbering(5, { levels: [], @@ -29,11 +29,11 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); - expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } }); - expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); }); it("uses 'start' as the default alignment", () => { @@ -47,11 +47,11 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); - expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } }); - expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); - expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } }); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } }); }); describe("formatting methods: paragraph properties", () => { @@ -71,7 +71,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }], }); }); @@ -92,7 +92,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }], }); }); @@ -113,7 +113,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }], }); }); @@ -134,7 +134,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }], }); }); @@ -155,7 +155,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }], }); }); @@ -176,7 +176,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }], }); }); @@ -197,7 +197,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [ { "w:pBdr": [ @@ -233,7 +233,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [ { "w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }], @@ -258,7 +258,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [ { "w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }], @@ -283,7 +283,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }], }); }); @@ -304,7 +304,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }], }); }); @@ -327,7 +327,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }], }); }); @@ -348,7 +348,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], }); }); @@ -369,7 +369,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], }); }); @@ -391,7 +391,7 @@ describe.only("AbstractNumbering", () => { }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], }); }); @@ -412,7 +412,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], }); }); @@ -433,7 +433,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }], }); }); @@ -454,7 +454,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }], }); }); @@ -475,7 +475,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [ { "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } }, ], @@ -498,7 +498,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:b": { _attr: { "w:val": true } } }], }); }); @@ -519,7 +519,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:i": { _attr: { "w:val": true } } }], }); }); @@ -540,7 +540,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }], }); }); @@ -565,7 +565,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], }); }); @@ -587,7 +587,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }], }); }); @@ -610,7 +610,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }], }); }); @@ -634,7 +634,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }], }); }); @@ -656,7 +656,7 @@ describe.only("AbstractNumbering", () => { ], }); const tree = new Formatter().format(abstractNumbering); - expect(tree["w:lvl"]).to.include({ + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }], }); }); diff --git a/src/file/numbering/concrete-numbering.spec.ts b/src/file/numbering/concrete-numbering.spec.ts index dd15fb5431..42ccc87f2b 100644 --- a/src/file/numbering/concrete-numbering.spec.ts +++ b/src/file/numbering/concrete-numbering.spec.ts @@ -7,7 +7,7 @@ import { ConcreteNumbering } from "./num"; describe("ConcreteNumbering", () => { describe("#overrideLevel", () => { - let concreteNumbering; + let concreteNumbering: ConcreteNumbering; beforeEach(() => { concreteNumbering = new ConcreteNumbering(0, 1); }); @@ -17,18 +17,13 @@ describe("ConcreteNumbering", () => { const tree = new Formatter().format(concreteNumbering); expect(tree["w:num"]).to.include({ "w:lvlOverride": [ + { _attr: { "w:ilvl": 3 } }, { - _attr: { - "w:ilvl": 3, - }, - }, - { - "w:lvl": { - _attr: { - "w:ilvl": 3, - "w15:tentative": 1, - }, - }, + "w:lvl": [ + { _attr: { "w:ilvl": 3, "w15:tentative": 1 } }, + { "w:start": { _attr: { "w:val": 1 } } }, + { "w:lvlJc": { _attr: { "w:val": "start" } } }, + ], }, ], }); @@ -52,12 +47,11 @@ describe("ConcreteNumbering", () => { }, }, { - "w:lvl": { - _attr: { - "w:ilvl": 1, - "w15:tentative": 1, - }, - }, + "w:lvl": [ + { _attr: { "w:ilvl": 1, "w15:tentative": 1 } }, + { "w:start": { _attr: { "w:val": 1 } } }, + { "w:lvlJc": { _attr: { "w:val": "start" } } }, + ], }, ], }); @@ -67,11 +61,16 @@ describe("ConcreteNumbering", () => { const ol = concreteNumbering.overrideLevel(1); expect(ol.Level).to.be.instanceof(LevelForOverride); const tree = new Formatter().format(concreteNumbering); + expect(tree["w:num"]).to.include({ "w:lvlOverride": [ { _attr: { "w:ilvl": 1 } }, { - "w:lvl": { _attr: { "w15:tentative": 1, "w:ilvl": 1 } }, + "w:lvl": [ + { _attr: { "w:ilvl": 1, "w15:tentative": 1 } }, + { "w:start": { _attr: { "w:val": 1 } } }, + { "w:lvlJc": { _attr: { "w:val": "start" } } }, + ], }, ], }); diff --git a/src/file/numbering/level.ts b/src/file/numbering/level.ts index b9f66a89c0..cbef58dfc0 100644 --- a/src/file/numbering/level.ts +++ b/src/file/numbering/level.ts @@ -79,8 +79,8 @@ export enum LevelSuffix { export interface ILevelsOptions { readonly level: number; - readonly format: string; - readonly text: string; + readonly format?: string; + readonly text?: string; readonly alignment?: AlignmentType; readonly start?: number; readonly suffix?: LevelSuffix; @@ -115,10 +115,16 @@ export class LevelBase extends XmlComponent { ); this.root.push(new Start(start)); - this.root.push(new NumberFormat(format)); - this.root.push(new LevelText(text)); this.root.push(new LevelJc(alignment)); + if (format) { + this.root.push(new NumberFormat(format)); + } + + if (text) { + this.root.push(new LevelText(text)); + } + this.paragraphProperties = new ParagraphProperties({}); this.runProperties = new RunProperties(); diff --git a/src/file/numbering/num.ts b/src/file/numbering/num.ts index c19c81a39b..dac54fe668 100644 --- a/src/file/numbering/num.ts +++ b/src/file/numbering/num.ts @@ -55,7 +55,9 @@ export class LevelOverride extends XmlComponent { this.root.push(new StartOverride(start)); } - this.lvl = new LevelForOverride(this.levelNum); + this.lvl = new LevelForOverride({ + level: this.levelNum, + }); this.root.push(this.lvl); } diff --git a/src/file/paragraph/formatting/unordered-list.ts b/src/file/paragraph/formatting/unordered-list.ts index 0b6c4daae0..f7650fc844 100644 --- a/src/file/paragraph/formatting/unordered-list.ts +++ b/src/file/paragraph/formatting/unordered-list.ts @@ -1,7 +1,7 @@ import { Attributes, XmlComponent } from "file/xml-components"; export class NumberProperties extends XmlComponent { - constructor(numberId: number, indentLevel: number) { + constructor(numberId: number | string, indentLevel: number) { super("w:numPr"); this.root.push(new IndentLevel(indentLevel)); this.root.push(new NumberId(numberId)); @@ -20,11 +20,11 @@ class IndentLevel extends XmlComponent { } class NumberId extends XmlComponent { - constructor(id: number) { + constructor(id: number | string) { super("w:numId"); this.root.push( new Attributes({ - val: id, + val: typeof id === "string" ? `__${id}__` : id, }), ); } diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index d4c7fc81e9..99c8efb0c0 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -3,7 +3,6 @@ import { assert, expect } from "chai"; import { Formatter } from "export/formatter"; import { EMPTY_OBJECT } from "file/xml-components"; -import { Numbering } from "../numbering"; import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; import { Paragraph } from "./paragraph"; @@ -596,19 +595,9 @@ describe("Paragraph", () => { describe("#setNumbering", () => { it("should add list paragraph style to JSON", () => { - const numbering = new Numbering({ - levels: [ - { - level: 0, - format: "lowerLetter", - text: "%1)", - }, - ], - }); - const paragraph = new Paragraph({ numbering: { - num: letterNumbering, + reference: "test id", level: 0, }, }); @@ -627,19 +616,9 @@ describe("Paragraph", () => { }); it("it should add numbered properties", () => { - const numbering = new Numbering({ - levels: [ - { - level: 0, - format: "lowerLetter", - text: "%1)", - }, - ], - }); - const paragraph = new Paragraph({ numbering: { - num: letterNumbering, + reference: "test id", level: 0, }, }); @@ -650,10 +629,7 @@ describe("Paragraph", () => { "w:pPr": [ { "w:pStyle": { _attr: { "w:val": "ListParagraph" } } }, { - "w:numPr": [ - { "w:ilvl": { _attr: { "w:val": 0 } } }, - { "w:numId": { _attr: { "w:val": letterNumbering.id } } }, - ], + "w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "__test id__" } } }], }, ], }, diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 1a26a7c666..1defd01779 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -1,6 +1,5 @@ // http://officeopenxml.com/WPparagraph.php import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; -import { ConcreteNumbering } from "file/numbering/num"; import { XmlComponent } from "file/xml-components"; import { Alignment, AlignmentType } from "./formatting/alignment"; @@ -41,7 +40,7 @@ export interface IParagraphOptions { readonly level: number; }; readonly numbering?: { - readonly num: ConcreteNumbering; + readonly reference: string; readonly level: number; readonly custom?: boolean; }; @@ -141,7 +140,7 @@ export class Paragraph extends XmlComponent { if (!options.numbering.custom) { this.properties.push(new Style("ListParagraph")); } - this.properties.push(new NumberProperties(options.numbering.num.id, options.numbering.level)); + this.properties.push(new NumberProperties(options.numbering.reference, options.numbering.level)); } if (options.children) { From 643e3c2f8493c2e199dd70cfb84ac49ff59e8986 Mon Sep 17 00:00:00 2001 From: Dolan Date: Fri, 8 Nov 2019 03:11:19 +0000 Subject: [PATCH 4/4] Finish making numbering declarative --- demo/2-declaritive-styles.ts | 30 +- demo/29-numbered-lists.ts | 40 +- demo/3-numbering-and-bullet-points.ts | 64 +- src/export/packer/next-compiler.ts | 6 +- src/export/packer/numbering-replacer.ts | 17 + src/file/core-properties/properties.ts | 4 +- src/file/file.ts | 10 +- src/file/numbering/abstract-numbering.spec.ts | 644 ++++++++---------- src/file/numbering/abstract-numbering.ts | 8 +- src/file/numbering/num.ts | 2 +- src/file/numbering/numbering.spec.ts | 2 +- src/file/numbering/numbering.ts | 200 +++--- .../paragraph/formatting/unordered-list.ts | 2 +- src/file/paragraph/paragraph.spec.ts | 2 +- 14 files changed, 530 insertions(+), 501 deletions(-) diff --git a/demo/2-declaritive-styles.ts b/demo/2-declaritive-styles.ts index 34d7e19e70..472b916091 100644 --- a/demo/2-declaritive-styles.ts +++ b/demo/2-declaritive-styles.ts @@ -1,7 +1,7 @@ // Example on how to customise the look at feel using Styles // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build"; +import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build"; const doc = new Document({ creator: "Clippy", @@ -83,15 +83,23 @@ const doc = new Document({ }, ], }, + numbering: { + config: [ + { + reference: "my-crazy-numbering", + levels: [ + { + level: 0, + format: "lowerLetter", + text: "%1)", + alignment: AlignmentType.LEFT, + }, + ], + }, + ], + }, }); -const numberedAbstract = doc.Numbering.createAbstractNumbering(); -numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left"); - -const letterNumbering = doc.Numbering.createConcreteNumbering(numberedAbstract); -const letterNumbering5 = doc.Numbering.createConcreteNumbering(numberedAbstract); -letterNumbering5.overrideLevel(0, 5); - doc.addSection({ children: [ new Paragraph({ @@ -106,21 +114,21 @@ doc.addSection({ new Paragraph({ text: "Option1", numbering: { - num: letterNumbering, + reference: "my-crazy-numbering", level: 0, }, }), new Paragraph({ text: "Option5 -- override 2 to 5", numbering: { - num: letterNumbering, + reference: "my-crazy-numbering", level: 0, }, }), new Paragraph({ text: "Option3", numbering: { - num: letterNumbering, + reference: "my-crazy-numbering", level: 0, }, }), diff --git a/demo/29-numbered-lists.ts b/demo/29-numbered-lists.ts index 740320e5f8..18da5cc359 100644 --- a/demo/29-numbered-lists.ts +++ b/demo/29-numbered-lists.ts @@ -1,23 +1,37 @@ // Numbered lists // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Numbering, Packer, Paragraph } from "../build"; +import { AlignmentType, Document, Packer, Paragraph } from "../build"; -const doc = new Document(); - -const numbering = new Numbering(); - -const abstractNum = numbering.createAbstractNumbering(); -abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 }); - -const concrete = numbering.createConcreteNumbering(abstractNum); +const doc = new Document({ + numbering: { + config: [ + { + levels: [ + { + level: 0, + format: "upperRoman", + text: "%1", + alignment: AlignmentType.START, + style: { + paragraph: { + indent: { left: 720, hanging: 260 }, + }, + }, + }, + ], + reference: "my-crazy-reference", + }, + ], + }, +}); doc.addSection({ children: [ new Paragraph({ text: "line with contextual spacing", numbering: { - num: concrete, + reference: "my-crazy-reference", level: 0, }, contextualSpacing: true, @@ -28,7 +42,7 @@ doc.addSection({ new Paragraph({ text: "line with contextual spacing", numbering: { - num: concrete, + reference: "my-crazy-reference", level: 0, }, contextualSpacing: true, @@ -39,7 +53,7 @@ doc.addSection({ new Paragraph({ text: "line without contextual spacing", numbering: { - num: concrete, + reference: "my-crazy-reference", level: 0, }, contextualSpacing: false, @@ -50,7 +64,7 @@ doc.addSection({ new Paragraph({ text: "line without contextual spacing", numbering: { - num: concrete, + reference: "my-crazy-reference", level: 0, }, contextualSpacing: false, diff --git a/demo/3-numbering-and-bullet-points.ts b/demo/3-numbering-and-bullet-points.ts index e87e6616d2..72b33d2e8e 100644 --- a/demo/3-numbering-and-bullet-points.ts +++ b/demo/3-numbering-and-bullet-points.ts @@ -1,46 +1,80 @@ // Numbering and bullet points example // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Numbering, Packer, Paragraph } from "../build"; +import { AlignmentType, Document, Packer, Paragraph } from "../build"; -const doc = new Document(); - -const numbering = new Numbering(); - -const abstractNum = numbering.createAbstractNumbering(); -abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 }); -abstractNum.createLevel(1, "decimal", "%2.", "start").indent({ left: 1440, hanging: 980 }); -abstractNum.createLevel(2, "lowerLetter", "%3)", "start").indent({ left: 2160, hanging: 1700 }); - -const concrete = numbering.createConcreteNumbering(abstractNum); +const doc = new Document({ + numbering: { + config: [ + { + reference: "my-crazy-numbering", + levels: [ + { + level: 0, + format: "upperRoman", + text: "%1", + alignment: AlignmentType.START, + style: { + paragraph: { + indent: { left: 720, hanging: 260 }, + }, + }, + }, + { + level: 1, + format: "decimal", + text: "%2.", + alignment: AlignmentType.START, + style: { + paragraph: { + indent: { left: 1440, hanging: 980 }, + }, + }, + }, + { + level: 2, + format: "lowerLetter", + text: "%3)", + alignment: AlignmentType.START, + style: { + paragraph: { + indent: { left: 2160, hanging: 1700 }, + }, + }, + }, + ], + }, + ], + }, +}); doc.addSection({ children: [ new Paragraph({ text: "Hey you", numbering: { - num: concrete, + reference: "my-crazy-numbering", level: 0, }, }), new Paragraph({ text: "What's up fam", numbering: { - num: concrete, + reference: "my-crazy-numbering", level: 1, }, }), new Paragraph({ text: "Hello World 2", numbering: { - num: concrete, + reference: "my-crazy-numbering", level: 1, }, }), new Paragraph({ text: "Yeah boi", numbering: { - num: concrete, + reference: "my-crazy-numbering", level: 2, }, }), diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 6a3e752098..bff24a8ef7 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -4,6 +4,7 @@ import * as xml from "xml"; import { File } from "file"; import { Formatter } from "../formatter"; import { ImageReplacer } from "./image-replacer"; +import { NumberingReplacer } from "./numbering-replacer"; interface IXmlifyedFile { readonly data: string; @@ -30,10 +31,12 @@ interface IXmlifyedFileMapping { export class Compiler { private readonly formatter: Formatter; private readonly imageReplacer: ImageReplacer; + private readonly numberingReplacer: NumberingReplacer; constructor() { this.formatter = new Formatter(); this.imageReplacer = new ImageReplacer(); + this.numberingReplacer = new NumberingReplacer(); } public compile(file: File, prettifyXml?: boolean): JSZip { @@ -89,8 +92,9 @@ export class Compiler { Document: { data: (() => { const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount); + const referenedXmlData = this.numberingReplacer.replace(xmlData, file.Numbering.ConcreteNumbering); - return xmlData; + return referenedXmlData; })(), path: "word/document.xml", }, diff --git a/src/export/packer/numbering-replacer.ts b/src/export/packer/numbering-replacer.ts index e69de29bb2..4ac5dcbf1d 100644 --- a/src/export/packer/numbering-replacer.ts +++ b/src/export/packer/numbering-replacer.ts @@ -0,0 +1,17 @@ +import { ConcreteNumbering } from "file"; + +export class NumberingReplacer { + public replace(xmlData: string, concreteNumberings: ConcreteNumbering[]): string { + let currentXmlData = xmlData; + + for (const concreteNumbering of concreteNumberings) { + if (!concreteNumbering.reference) { + continue; + } + + currentXmlData = currentXmlData.replace(new RegExp(`{${concreteNumbering.reference}}`, "g"), concreteNumbering.id.toString()); + } + + return currentXmlData; + } +} diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index e86a0732d0..8ac2130310 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,7 +1,7 @@ import { XmlComponent } from "file/xml-components"; import { DocumentAttributes } from "../document/document-attributes"; -import { IAbstractNumberingOptions } from "../numbering"; +import { INumberingOptions } from "../numbering"; import { IStylesOptions } from "../styles"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; @@ -15,7 +15,7 @@ export interface IPropertiesOptions { readonly revision?: string; readonly externalStyles?: string; readonly styles?: IStylesOptions; - readonly numbering?: IAbstractNumberingOptions; + readonly numbering?: INumberingOptions; } export class CoreProperties extends XmlComponent { diff --git a/src/file/file.ts b/src/file/file.ts index 63bb272596..6d46fdb338 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -71,9 +71,13 @@ export class File { sections: ISectionOptions[] = [], ) { this.coreProperties = new CoreProperties(options); - this.numbering = new Numbering({ - levels: options.numbering ? options.numbering.levels : [], - }); + this.numbering = new Numbering( + options.numbering + ? options.numbering + : { + config: [], + }, + ); this.docRelationships = new Relationships(); this.fileRelationships = new Relationships(); this.appProperties = new AppProperties(); diff --git a/src/file/numbering/abstract-numbering.spec.ts b/src/file/numbering/abstract-numbering.spec.ts index 6552775cd5..59655aa40e 100644 --- a/src/file/numbering/abstract-numbering.spec.ts +++ b/src/file/numbering/abstract-numbering.spec.ts @@ -10,24 +10,20 @@ import { AbstractNumbering } from "./abstract-numbering"; describe("AbstractNumbering", () => { it("stores its ID at its .id property", () => { - const abstractNumbering = new AbstractNumbering(5, { - levels: [], - }); + const abstractNumbering = new AbstractNumbering(5, []); expect(abstractNumbering.id).to.equal(5); }); describe("#createLevel", () => { it("creates a level with the given characteristics", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 3, - format: "lowerLetter", - text: "%1)", - alignment: AlignmentType.END, - }, - ], - }); + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 3, + format: "lowerLetter", + text: "%1)", + alignment: AlignmentType.END, + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); @@ -37,15 +33,13 @@ describe("AbstractNumbering", () => { }); it("uses 'start' as the default alignment", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 3, - format: "lowerLetter", - text: "%1)", - }, - ], - }); + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 3, + format: "lowerLetter", + text: "%1)", + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } }); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } }); @@ -56,20 +50,18 @@ describe("AbstractNumbering", () => { describe("formatting methods: paragraph properties", () => { it("#indent", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - indent: { left: 720 }, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + indent: { left: 720 }, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }], @@ -77,20 +69,18 @@ describe("AbstractNumbering", () => { }); it("#spacing", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - spacing: { before: 50, after: 150 }, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + spacing: { before: 50, after: 150 }, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }], @@ -98,20 +88,18 @@ describe("AbstractNumbering", () => { }); it("#center", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - alignment: AlignmentType.CENTER, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.CENTER, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }], @@ -119,20 +107,18 @@ describe("AbstractNumbering", () => { }); it("#left", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - alignment: AlignmentType.LEFT, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.LEFT, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }], @@ -140,20 +126,18 @@ describe("AbstractNumbering", () => { }); it("#right", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - alignment: AlignmentType.RIGHT, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.RIGHT, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }], @@ -161,20 +145,18 @@ describe("AbstractNumbering", () => { }); it("#justified", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - alignment: AlignmentType.JUSTIFIED, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + alignment: AlignmentType.JUSTIFIED, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }], @@ -182,20 +164,18 @@ describe("AbstractNumbering", () => { }); it("#thematicBreak", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - thematicBreak: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + thematicBreak: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [ @@ -218,20 +198,18 @@ describe("AbstractNumbering", () => { }); it("#leftTabStop", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - leftTabStop: 1200, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + leftTabStop: 1200, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [ @@ -243,20 +221,18 @@ describe("AbstractNumbering", () => { }); it("#maxRightTabStop", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - rightTabStop: TabStopPosition.MAX, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + rightTabStop: TabStopPosition.MAX, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [ @@ -268,20 +244,18 @@ describe("AbstractNumbering", () => { }); it("#keepLines", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - keepLines: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + keepLines: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }], @@ -289,20 +263,18 @@ describe("AbstractNumbering", () => { }); it("#keepNext", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - paragraph: { - keepNext: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + paragraph: { + keepNext: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }], @@ -312,20 +284,18 @@ describe("AbstractNumbering", () => { describe("formatting methods: run properties", () => { it("#size", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - size: 24, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + size: 24, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }], @@ -333,20 +303,18 @@ describe("AbstractNumbering", () => { }); it("#smallCaps", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - smallCaps: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + smallCaps: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], @@ -354,20 +322,18 @@ describe("AbstractNumbering", () => { }); it("#allCaps", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - allCaps: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + allCaps: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], @@ -375,20 +341,18 @@ describe("AbstractNumbering", () => { }); it("#strike", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - strike: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + strike: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ @@ -397,20 +361,18 @@ describe("AbstractNumbering", () => { }); it("#doubleStrike", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - doubleStrike: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + doubleStrike: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], @@ -418,20 +380,18 @@ describe("AbstractNumbering", () => { }); it("#subScript", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - subScript: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + subScript: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }], @@ -439,20 +399,18 @@ describe("AbstractNumbering", () => { }); it("#superScript", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - superScript: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + superScript: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }], @@ -460,20 +418,18 @@ describe("AbstractNumbering", () => { }); it("#font", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - font: "Times", - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + font: "Times", }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [ @@ -483,20 +439,18 @@ describe("AbstractNumbering", () => { }); it("#bold", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - bold: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + bold: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:b": { _attr: { "w:val": true } } }], @@ -504,20 +458,18 @@ describe("AbstractNumbering", () => { }); it("#italics", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - italics: true, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + italics: true, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:i": { _attr: { "w:val": true } } }], @@ -525,20 +477,18 @@ describe("AbstractNumbering", () => { }); it("#highlight", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - highlight: "005599", - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + highlight: "005599", }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }], @@ -546,24 +496,22 @@ describe("AbstractNumbering", () => { }); it("#shadow", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - shadow: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + shadow: { + type: ShadingType.PERCENT_10, + fill: "00FFFF", + color: "FF0000", }, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], @@ -572,20 +520,18 @@ describe("AbstractNumbering", () => { describe("#underline", () => { it("should set underline to 'single' if no arguments are given", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - underline: {}, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + underline: {}, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }], @@ -593,22 +539,20 @@ describe("AbstractNumbering", () => { }); it("should set the style if given", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - underline: { - type: UnderlineType.DOUBLE, - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + underline: { + type: UnderlineType.DOUBLE, }, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }], @@ -616,23 +560,21 @@ describe("AbstractNumbering", () => { }); it("should set the style and color if given", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - underline: { - type: UnderlineType.DOUBLE, - color: "005599", - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + underline: { + type: UnderlineType.DOUBLE, + color: "005599", }, }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }], @@ -641,20 +583,18 @@ describe("AbstractNumbering", () => { }); it("#color", () => { - const abstractNumbering = new AbstractNumbering(1, { - levels: [ - { - level: 0, - format: "lowerRoman", - text: "%0.", - style: { - run: { - color: "123456", - }, + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + color: "123456", }, }, - ], - }); + }, + ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }], diff --git a/src/file/numbering/abstract-numbering.ts b/src/file/numbering/abstract-numbering.ts index 49b1175812..2eed73fe39 100644 --- a/src/file/numbering/abstract-numbering.ts +++ b/src/file/numbering/abstract-numbering.ts @@ -15,14 +15,10 @@ class AbstractNumberingAttributes extends XmlAttributeComponent { export class ConcreteNumbering extends XmlComponent { public readonly id: number; - constructor(numId: number, abstractNumId: number) { + constructor(numId: number, abstractNumId: number, public readonly reference?: string) { super("w:num"); this.root.push( new NumAttributes({ diff --git a/src/file/numbering/numbering.spec.ts b/src/file/numbering/numbering.spec.ts index 0c16884300..79a824b67a 100644 --- a/src/file/numbering/numbering.spec.ts +++ b/src/file/numbering/numbering.spec.ts @@ -8,7 +8,7 @@ describe("Numbering", () => { describe("#constructor", () => { it("creates a default numbering with one abstract and one concrete instance", () => { const numbering = new Numbering({ - levels: [], + config: [], }); const tree = new Formatter().format(numbering); diff --git a/src/file/numbering/numbering.ts b/src/file/numbering/numbering.ts index 891aa2ab3a..b8747991d0 100644 --- a/src/file/numbering/numbering.ts +++ b/src/file/numbering/numbering.ts @@ -3,17 +3,25 @@ import { AlignmentType } from "file/paragraph"; import { IXmlableObject, XmlComponent } from "file/xml-components"; import { DocumentAttributes } from "../document/document-attributes"; -import { AbstractNumbering, IAbstractNumberingOptions } from "./abstract-numbering"; +import { AbstractNumbering } from "./abstract-numbering"; +import { ILevelsOptions } from "./level"; import { ConcreteNumbering } from "./num"; +export interface INumberingOptions { + readonly config: Array<{ + readonly levels: ILevelsOptions[]; + readonly reference: string; + }>; +} + export class Numbering extends XmlComponent { // tslint:disable-next-line:readonly-keyword private nextId: number; - private readonly abstractNumbering: XmlComponent[] = []; - private readonly concreteNumbering: XmlComponent[] = []; + private readonly abstractNumbering: AbstractNumbering[] = []; + private readonly concreteNumbering: ConcreteNumbering[] = []; - constructor(options: IAbstractNumberingOptions) { + constructor(options: INumberingOptions) { super("w:numbering"); this.root.push( new DocumentAttributes({ @@ -39,114 +47,114 @@ export class Numbering extends XmlComponent { this.nextId = 0; - const abstractNumbering = this.createAbstractNumbering({ - levels: [ - { - level: 0, - format: "bullet", - text: "\u25CF", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 720, hanging: 360 }, - }, + const abstractNumbering = this.createAbstractNumbering([ + { + level: 0, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 720, hanging: 360 }, }, }, - { - level: 1, - format: "bullet", - text: "\u25CB", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 1440, hanging: 360 }, - }, + }, + { + level: 1, + format: "bullet", + text: "\u25CB", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 1440, hanging: 360 }, }, }, - { - level: 2, - format: "bullet", - text: "\u25A0", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 2160, hanging: 360 }, - }, + }, + { + level: 2, + format: "bullet", + text: "\u25A0", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 2160, hanging: 360 }, }, }, - { - level: 3, - format: "bullet", - text: "\u25CF", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 2880, hanging: 360 }, - }, + }, + { + level: 3, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 2880, hanging: 360 }, }, }, - { - level: 4, - format: "bullet", - text: "\u25CB", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 3600, hanging: 360 }, - }, + }, + { + level: 4, + format: "bullet", + text: "\u25CB", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 3600, hanging: 360 }, }, }, - { - level: 5, - format: "bullet", - text: "\u25A0", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 4320, hanging: 360 }, - }, + }, + { + level: 5, + format: "bullet", + text: "\u25A0", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 4320, hanging: 360 }, }, }, - { - level: 6, - format: "bullet", - text: "\u25CF", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 5040, hanging: 360 }, - }, + }, + { + level: 6, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 5040, hanging: 360 }, }, }, - { - level: 7, - format: "bullet", - text: "\u25CF", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 5760, hanging: 360 }, - }, + }, + { + level: 7, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 5760, hanging: 360 }, }, }, - { - level: 8, - format: "bullet", - text: "\u25CF", - alignment: AlignmentType.LEFT, - style: { - paragraph: { - indent: { left: 6480, hanging: 360 }, - }, + }, + { + level: 8, + format: "bullet", + text: "\u25CF", + alignment: AlignmentType.LEFT, + style: { + paragraph: { + indent: { left: 6480, hanging: 360 }, }, }, - ], - }); + }, + ]); this.createConcreteNumbering(abstractNumbering); - const currentAbstractNumbering = this.createAbstractNumbering(options); - this.createConcreteNumbering(currentAbstractNumbering); + for (const con of options.config) { + const currentAbstractNumbering = this.createAbstractNumbering(con.levels); + this.createConcreteNumbering(currentAbstractNumbering, con.reference); + } } public prepForXml(): IXmlableObject | undefined { @@ -155,15 +163,19 @@ export class Numbering extends XmlComponent { return super.prepForXml(); } - private createConcreteNumbering(abstractNumbering: AbstractNumbering): ConcreteNumbering { - const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id); + private createConcreteNumbering(abstractNumbering: AbstractNumbering, reference?: string): ConcreteNumbering { + const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id, reference); this.concreteNumbering.push(num); return num; } - private createAbstractNumbering(options: IAbstractNumberingOptions): AbstractNumbering { + private createAbstractNumbering(options: ILevelsOptions[]): AbstractNumbering { const num = new AbstractNumbering(this.nextId++, options); this.abstractNumbering.push(num); return num; } + + public get ConcreteNumbering(): ConcreteNumbering[] { + return this.concreteNumbering; + } } diff --git a/src/file/paragraph/formatting/unordered-list.ts b/src/file/paragraph/formatting/unordered-list.ts index f7650fc844..bd1bcb562a 100644 --- a/src/file/paragraph/formatting/unordered-list.ts +++ b/src/file/paragraph/formatting/unordered-list.ts @@ -24,7 +24,7 @@ class NumberId extends XmlComponent { super("w:numId"); this.root.push( new Attributes({ - val: typeof id === "string" ? `__${id}__` : id, + val: typeof id === "string" ? `{${id}}` : id, }), ); } diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 99c8efb0c0..b4a797a8c2 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -629,7 +629,7 @@ describe("Paragraph", () => { "w:pPr": [ { "w:pStyle": { _attr: { "w:val": "ListParagraph" } } }, { - "w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "__test id__" } } }], + "w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }], }, ], },