diff --git a/demo/16-multiple-sections.ts b/demo/16-multiple-sections.ts index 87c076cf66..db4bf4aac1 100644 --- a/demo/16-multiple-sections.ts +++ b/demo/16-multiple-sections.ts @@ -1,7 +1,7 @@ // Multiple sections and headers // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Footer, Header, Packer, PageNumber, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build"; +import { Document, Footer, Header, Packer, PageNumber, NumberFormat, PageOrientation, Paragraph, TextRun } from "../build"; const doc = new Document({ sections: [ @@ -13,7 +13,7 @@ const doc = new Document({ page: { pageNumbers: { start: 1, - formatType: PageNumberFormat.DECIMAL, + formatType: NumberFormat.DECIMAL, }, }, }, @@ -38,7 +38,7 @@ const doc = new Document({ }, pageNumbers: { start: 1, - formatType: PageNumberFormat.DECIMAL, + formatType: NumberFormat.DECIMAL, }, }, }, @@ -85,7 +85,7 @@ const doc = new Document({ orientation: PageOrientation.PORTRAIT, }, pageNumbers: { - formatType: PageNumberFormat.UPPER_ROMAN, + formatType: NumberFormat.UPPER_ROMAN, }, }, }, @@ -116,7 +116,7 @@ const doc = new Document({ }, pageNumbers: { start: 25, - formatType: PageNumberFormat.DECIMAL, + formatType: NumberFormat.DECIMAL, }, }, }, diff --git a/demo/2-declaritive-styles.ts b/demo/2-declaritive-styles.ts index 5795ebf6e1..6af4120574 100644 --- a/demo/2-declaritive-styles.ts +++ b/demo/2-declaritive-styles.ts @@ -24,7 +24,7 @@ const doc = new Document({ size: 28, bold: true, italics: true, - color: "red", + color: "FF0000", }, paragraph: { spacing: { diff --git a/demo/20-table-cell-borders.ts b/demo/20-table-cell-borders.ts index 4dbc9192b9..9174fa28fa 100644 --- a/demo/20-table-cell-borders.ts +++ b/demo/20-table-cell-borders.ts @@ -36,17 +36,17 @@ const doc = new Document({ top: { style: BorderStyle.DASH_DOT_STROKED, size: 3, - color: "red", + color: "FF0000", }, bottom: { style: BorderStyle.DOUBLE, size: 3, - color: "blue", + color: "0000FF", }, left: { style: BorderStyle.DASH_DOT_STROKED, size: 3, - color: "green", + color: "00FF00", }, right: { style: BorderStyle.DASH_DOT_STROKED, diff --git a/demo/26-paragraph-borders.ts b/demo/26-paragraph-borders.ts index c64be50429..788bbfafcf 100644 --- a/demo/26-paragraph-borders.ts +++ b/demo/26-paragraph-borders.ts @@ -1,7 +1,7 @@ // Creates two paragraphs, one with a border and one without // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph } from "../build"; +import { BorderStyle, Document, Packer, Paragraph } from "../build"; const doc = new Document({ sections: [ @@ -14,13 +14,13 @@ const doc = new Document({ top: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, bottom: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, }, diff --git a/demo/28-table-of-contents.ts b/demo/28-table-of-contents.ts index 472ade8f67..aa5abd3986 100644 --- a/demo/28-table-of-contents.ts +++ b/demo/28-table-of-contents.ts @@ -9,6 +9,9 @@ import { File, HeadingLevel, Packer, Paragraph, StyleLevel, TableOfContents } fr // Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, // making the entries be hyperlinks for the paragraph const doc = new File({ + features: { + updateFields: true, + }, styles: { paragraphStyles: [ { diff --git a/demo/32-merge-and-shade-table-cells.ts b/demo/32-merge-and-shade-table-cells.ts index 9ca432dc2d..a3e2451f12 100644 --- a/demo/32-merge-and-shade-table-cells.ts +++ b/demo/32-merge-and-shade-table-cells.ts @@ -98,7 +98,7 @@ const table3 = new Table({ children: [new Paragraph("Bar1")], shading: { fill: "b79c2f", - val: ShadingType.REVERSE_DIAGONAL_STRIPE, + type: ShadingType.REVERSE_DIAGONAL_STRIPE, color: "auto", }, }), @@ -106,7 +106,7 @@ const table3 = new Table({ children: [new Paragraph("Bar2")], shading: { fill: "42c5f4", - val: ShadingType.PERCENT_95, + type: ShadingType.PERCENT_95, color: "auto", }, }), @@ -114,7 +114,7 @@ const table3 = new Table({ children: [new Paragraph("Bar3")], shading: { fill: "880aa8", - val: ShadingType.PERCENT_10, + type: ShadingType.PERCENT_10, color: "e2df0b", }, }), @@ -122,7 +122,7 @@ const table3 = new Table({ children: [new Paragraph("Bar4")], shading: { fill: "FF0000", - val: ShadingType.CLEAR, + type: ShadingType.CLEAR, color: "auto", }, }), @@ -224,22 +224,22 @@ const borders = { top: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "FF0000", }, bottom: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "FF0000", }, left: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "FF0000", }, right: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "FF0000", }, }; diff --git a/demo/39-page-numbers.ts b/demo/39-page-numbers.ts index 44dd3719e4..4b526a5a74 100644 --- a/demo/39-page-numbers.ts +++ b/demo/39-page-numbers.ts @@ -1,7 +1,7 @@ // Example how to display page numbers // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, PageNumberFormat, Paragraph, TextRun } from "../build"; +import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, NumberFormat, Paragraph, TextRun } from "../build"; const doc = new Document({ sections: [ @@ -10,7 +10,7 @@ const doc = new Document({ page: { pageNumbers: { start: 1, - formatType: PageNumberFormat.DECIMAL, + formatType: NumberFormat.DECIMAL, }, }, }, diff --git a/demo/45-highlighting-text.ts b/demo/45-highlighting-text.ts index 373e9b4b17..bc48ebb342 100644 --- a/demo/45-highlighting-text.ts +++ b/demo/45-highlighting-text.ts @@ -14,7 +14,7 @@ const doc = new Document({ children: [ new TextRun({ text: "Hello World", - color: "red", + color: "FF0000", bold: true, size: 24, font: { diff --git a/demo/46-shading-text.ts b/demo/46-shading-text.ts index 39f4f461ac..289691bd25 100644 --- a/demo/46-shading-text.ts +++ b/demo/46-shading-text.ts @@ -14,7 +14,7 @@ const doc = new Document({ children: [ new TextRun({ text: "Hello World", - color: "red", + color: "FF0000", bold: true, size: 24, font: { diff --git a/demo/47-number-of-total-pages-section.ts b/demo/47-number-of-total-pages-section.ts index b19cc0b158..8d335337e4 100644 --- a/demo/47-number-of-total-pages-section.ts +++ b/demo/47-number-of-total-pages-section.ts @@ -1,7 +1,7 @@ // Multiple sections with total number of pages in each section // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, PageNumberFormat, Paragraph, TextRun } from "../build"; +import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, NumberFormat, Paragraph, TextRun } from "../build"; const header = new Header({ children: [ @@ -31,7 +31,7 @@ const doc = new Document({ page: { pageNumbers: { start: 1, - formatType: PageNumberFormat.DECIMAL, + formatType: NumberFormat.DECIMAL, }, }, }, @@ -52,7 +52,7 @@ const doc = new Document({ page: { pageNumbers: { start: 1, - formatType: PageNumberFormat.DECIMAL, + formatType: NumberFormat.DECIMAL, }, }, }, diff --git a/demo/48-vertical-align.ts b/demo/48-vertical-align.ts index 123a784993..36400de154 100644 --- a/demo/48-vertical-align.ts +++ b/demo/48-vertical-align.ts @@ -1,13 +1,13 @@ // Example of making content of section vertically aligned // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph, SectionVerticalAlignValue, TextRun } from "../build"; +import { Document, Packer, Paragraph, VerticalAlign, TextRun } from "../build"; const doc = new Document({ sections: [ { properties: { - verticalAlign: SectionVerticalAlignValue.CENTER, + verticalAlign: VerticalAlign.CENTER, }, children: [ new Paragraph({ diff --git a/demo/49-table-borders.ts b/demo/49-table-borders.ts index aeccd73dd2..1a3943a947 100644 --- a/demo/49-table-borders.ts +++ b/demo/49-table-borders.ts @@ -24,22 +24,22 @@ const table = new Table({ top: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "ff0000", }, bottom: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "ff0000", }, left: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "ff0000", }, right: { style: BorderStyle.DASH_SMALL_GAP, size: 1, - color: "red", + color: "ff0000", }, }, children: [new Paragraph("Hello")], diff --git a/demo/60-track-revisions.ts b/demo/60-track-revisions.ts index 81a68d4f7d..ced597806c 100644 --- a/demo/60-track-revisions.ts +++ b/demo/60-track-revisions.ts @@ -20,10 +20,12 @@ import { - https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.insertedrun - https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.deletedrun - The method `addTrackRevisions()` adds an element `` to the `settings.xml` file. This specifies that the application shall track *new* revisions made to the existing document. + The setting `features: { trackRevisions: true }` adds an element `` to the `settings.xml` file. + This specifies that the application shall track *new* revisions made to the existing document. See also https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.trackrevisions - Note that this setting enables to track *new changes* after teh file is generated, so this example will still show inserted and deleted text runs when you remove it. + Note that this setting enables to track *new changes* after teh file is generated, so this example will still + show inserted and deleted text runs when you remove it. */ const paragraph = new Paragraph({ @@ -85,7 +87,7 @@ const doc = new Document({ new DeletedTextRun({ break: 1, text: "in order", - color: "red", + color: "ff0000", bold: true, size: 24, font: { diff --git a/demo/61-text-frame.ts b/demo/61-text-frame.ts index e10e3b6f3c..d6563418cf 100644 --- a/demo/61-text-frame.ts +++ b/demo/61-text-frame.ts @@ -1,7 +1,16 @@ // Text Frame (Text Box) example // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, FrameAnchorType, HorizontalPositionAlign, Packer, Paragraph, TextRun, VerticalPositionAlign } from "../build"; +import { + BorderStyle, + Document, + FrameAnchorType, + HorizontalPositionAlign, + Packer, + Paragraph, + TextRun, + VerticalPositionAlign, +} from "../build"; const doc = new Document({ sections: [ @@ -29,25 +38,25 @@ const doc = new Document({ top: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, bottom: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, left: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, right: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, }, diff --git a/docs/usage/change-tracking.md b/docs/usage/change-tracking.md index f4c486eca2..96d10799ca 100644 --- a/docs/usage/change-tracking.md +++ b/docs/usage/change-tracking.md @@ -37,7 +37,7 @@ const paragraph = new Paragraph({ new TextRun("This is a simple demo"), new DeletedTextRun({ text: "with a deletion.", - color: "red", + color: "ff0000", bold: true, size: 24, id: 0, diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md index db2a6b321f..4a88508bc8 100644 --- a/docs/usage/table-of-contents.md +++ b/docs/usage/table-of-contents.md @@ -12,14 +12,29 @@ The complete documentation can be found [here](https://www.ecma-international.or All you need to do is create a `TableOfContents` object and assign it to the document. -```ts -const toc = new TableOfContents("Summary", { - hyperlink: true, - headingStyleRange: "1-5", - stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)], -}); +**Note**: updateFields feature must be enabled for TableOfContents to update correctly. -doc.addTableOfContents(toc); +```ts +const doc = new Document({ + features: { + updateFields: true, + }, + sections: [ + { + children: [ + new TableOfContents("Summary", { + hyperlink: true, + headingStyleRange: "1-5", + }), + new Paragraph({ + text: "Header #1", + heading: HeadingLevel.HEADING_1, + pageBreakBefore: true, + }), + ] + } + ] +}); ``` ## Table of Contents Options diff --git a/docs/usage/tables.md b/docs/usage/tables.md index 450d05e9b7..a13b7f5a02 100644 --- a/docs/usage/tables.md +++ b/docs/usage/tables.md @@ -53,6 +53,18 @@ const table = new Table({ }); ``` +### Set Indent + +```ts +const table = new Table({ + ..., + indent: { + size: 600, + type: WidthType.DXA, + } +}); +``` + ## Table Row A table consists of multiple `table rows`. Table rows have a list of `children` which accepts a list of `table cells` explained below. You can create a simple `table row` like so: @@ -145,7 +157,7 @@ const tableRow = new TableRow({ | Property | Type | Notes | | ------------- | ----------------------------------- | ----------------------------------------------------------- | | children | `Array` | Required. You can nest tables by adding a table into a cell | -| shading | `ITableShadingAttributesProperties` | Optional | +| shading | `IShadingAttributesProperties` | Optional | | margins | `ITableCellMarginOptions` | Optional | | verticalAlign | `VerticalAlign` | Optional | | columnSpan | `number` | Optional | @@ -171,7 +183,7 @@ const cell = new TableCell({ top: { style: BorderStyle.DASH_DOT_STROKED, size: 1, - color: "red", + color: "ff0000", }, bottom: { style: BorderStyle.THICK_THIN_MEDIUM_GAP, @@ -190,12 +202,12 @@ Google DOCS does not support start and end borders, instead they use left and ri const cell = new TableCell({ ..., borders: { - top: { + left: { style: BorderStyle.DOT_DOT_DASH, size: 3, - color: "green", + color: "00FF00", }, - bottom: { + right: { style: BorderStyle.DOT_DOT_DASH, size: 3, color: "ff8000", diff --git a/src/convenience-functions.spec.ts b/src/convenience-functions.spec.ts index 29ea76736c..9a437adc73 100644 --- a/src/convenience-functions.spec.ts +++ b/src/convenience-functions.spec.ts @@ -18,14 +18,14 @@ describe("Utility", () => { }); describe("#uniqueNumericId", () => { - it("should generate a unique ID", () => { + it("should generate a unique incrementing ID", () => { // tslint:disable-next-line: no-unused-expression expect(uniqueNumericId()).to.not.be.undefined; }); }); describe("#uniqueId", () => { - it("should call the underlying header's addChildElement", () => { + it("should generate a unique pseudorandom ID", () => { // tslint:disable-next-line: no-unused-expression expect(uniqueId()).to.not.be.empty; }); diff --git a/src/convenience-functions.ts b/src/convenience-functions.ts index 4f1b9844ea..2443fb9f88 100644 --- a/src/convenience-functions.ts +++ b/src/convenience-functions.ts @@ -12,7 +12,7 @@ export const convertInchesToTwip = (inches: number): number => { }; export const uniqueNumericId = (): number => { - return currentCount++; + return ++currentCount; }; export const uniqueId = (): string => { diff --git a/src/export/formatter.spec.ts b/src/export/formatter.spec.ts index e79cf7a9cc..463ec38c7f 100644 --- a/src/export/formatter.spec.ts +++ b/src/export/formatter.spec.ts @@ -45,18 +45,10 @@ describe("Formatter", () => { { "w:rPr": [ { - "w:b": { - _attr: { - "w:val": true, - }, - }, + "w:b": {}, }, { - "w:bCs": { - _attr: { - "w:val": true, - }, - }, + "w:bCs": {}, }, ], }, diff --git a/src/export/packer/next-compiler.ts b/src/export/packer/next-compiler.ts index 3d99f93c64..3311301a61 100644 --- a/src/export/packer/next-compiler.ts +++ b/src/export/packer/next-compiler.ts @@ -70,7 +70,6 @@ export class Compiler { } private xmlifyFile(file: File, prettify?: boolean): IXmlifyedFileMapping { - file.verifyUpdateFields(); const documentRelationshipCount = file.Document.Relationships.RelationshipCount + 1; const documentXmlData = xml( @@ -78,7 +77,13 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, ); const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media); @@ -98,7 +103,12 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ); })(), path: "word/_rels/document.xml.rels", @@ -118,7 +128,13 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, ), path: "word/styles.xml", }, @@ -129,6 +145,7 @@ export class Compiler { file, }), { + indent: prettify, declaration: { standalone: "yes", encoding: "UTF-8", @@ -143,7 +160,13 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, ), path: "word/numbering.xml", }, @@ -153,7 +176,12 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ), path: "_rels/.rels", }, @@ -163,7 +191,12 @@ export class Compiler { viewWrapper: headerWrapper, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); @@ -181,7 +214,12 @@ export class Compiler { viewWrapper: headerWrapper, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ), path: `word/_rels/header${index + 1}.xml.rels`, }; @@ -192,7 +230,12 @@ export class Compiler { viewWrapper: footerWrapper, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); @@ -210,7 +253,12 @@ export class Compiler { viewWrapper: footerWrapper, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ), path: `word/_rels/footer${index + 1}.xml.rels`, }; @@ -221,7 +269,12 @@ export class Compiler { viewWrapper: headerWrapper, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); // TODO: 0 needs to be changed when headers get relationships of their own @@ -238,7 +291,12 @@ export class Compiler { viewWrapper: footerWrapper, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); // TODO: 0 needs to be changed when headers get relationships of their own @@ -255,7 +313,12 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ), path: "[Content_Types].xml", }, @@ -265,7 +328,13 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, ), path: "docProps/custom.xml", }, @@ -275,7 +344,13 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, ), path: "docProps/app.xml", }, @@ -285,7 +360,12 @@ export class Compiler { viewWrapper: file.FootNotes, file: file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ), path: "word/footnotes.xml", }, @@ -295,7 +375,12 @@ export class Compiler { viewWrapper: file.FootNotes, file: file, }), - prettify, + { + indent: prettify, + declaration: { + encoding: "UTF-8", + }, + }, ), path: "word/_rels/footnotes.xml.rels", }, @@ -305,7 +390,13 @@ export class Compiler { viewWrapper: file.Document, file, }), - prettify, + { + indent: prettify, + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }, ), path: "word/settings.xml", }, diff --git a/src/file/border/border.spec.ts b/src/file/border/border.spec.ts new file mode 100644 index 0000000000..e652c50422 --- /dev/null +++ b/src/file/border/border.spec.ts @@ -0,0 +1,54 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { BorderStyle } from "file/border"; + +import { BorderElement } from "./border"; + +describe("BorderElement", () => { + describe("#constructor", () => { + it("should create a simple border element", () => { + const border = new BorderElement("w:top", { + style: BorderStyle.SINGLE, + }); + const tree = new Formatter().format(border); + expect(tree).to.deep.equal({ + "w:top": { + _attr: { + "w:val": "single", + }, + }, + }); + }); + it("should create a simple border element with a size", () => { + const border = new BorderElement("w:top", { + style: BorderStyle.SINGLE, + size: 22, + }); + const tree = new Formatter().format(border); + expect(tree).to.deep.equal({ + "w:top": { + _attr: { + "w:val": "single", + "w:sz": 22, + }, + }, + }); + }); + it("should create a simple border element with space", () => { + const border = new BorderElement("w:top", { + style: BorderStyle.SINGLE, + space: 22, + }); + const tree = new Formatter().format(border); + expect(tree).to.deep.equal({ + "w:top": { + _attr: { + "w:val": "single", + "w:space": 22, + }, + }, + }); + }); + }); +}); diff --git a/src/file/border/border.ts b/src/file/border/border.ts new file mode 100644 index 0000000000..04e487ff61 --- /dev/null +++ b/src/file/border/border.ts @@ -0,0 +1,86 @@ +// Note that the border type is identical in all places, +// regardless of where it's used like paragraph/table/etc. +// PageBorders are a superset, but we're not using any of those extras. +// +// http://officeopenxml.com/WPborders.php +// http://officeopenxml.com/WPtableBorders.php +// http://officeopenxml.com/WPtableCellProperties-Borders.php +// http://officeopenxml.com/WPsectionBorders.php +// +// This describes the CT_Border type. +// +// +// +// +// +// +// +// +// +// +// +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { eighthPointMeasureValue, hexColorValue, pointMeasureValue } from "../values"; + +export interface IBorderOptions { + readonly style: BorderStyle; + /** Border color, in hex (eg 'FF00AA') */ + readonly color?: string; + /** Size of the border in 1/8 pt */ + readonly size?: number; + /** Spacing offset. Values are specified in pt */ + readonly space?: number; +} + +export class BorderElement extends XmlComponent { + constructor(elementName: string, { color, size, space, style }: IBorderOptions) { + super(elementName); + this.root.push( + new BordersAttributes({ + style, + color: color === undefined ? undefined : hexColorValue(color), + size: size === undefined ? undefined : eighthPointMeasureValue(size), + space: space === undefined ? undefined : pointMeasureValue(space), + }), + ); + } +} + +class BordersAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + style: "w:val", + color: "w:color", + size: "w:sz", + space: "w:space", + }; +} + +export enum BorderStyle { + SINGLE = "single", + DASH_DOT_STROKED = "dashDotStroked", + DASHED = "dashed", + DASH_SMALL_GAP = "dashSmallGap", + DOT_DASH = "dotDash", + DOT_DOT_DASH = "dotDotDash", + DOTTED = "dotted", + DOUBLE = "double", + DOUBLE_WAVE = "doubleWave", + INSET = "inset", + NIL = "nil", + NONE = "none", + OUTSET = "outset", + THICK = "thick", + THICK_THIN_LARGE_GAP = "thickThinLargeGap", + THICK_THIN_MEDIUM_GAP = "thickThinMediumGap", + THICK_THIN_SMALL_GAP = "thickThinSmallGap", + THIN_THICK_LARGE_GAP = "thinThickLargeGap", + THIN_THICK_MEDIUM_GAP = "thinThickMediumGap", + THIN_THICK_SMALL_GAP = "thinThickSmallGap", + THIN_THICK_THIN_LARGE_GAP = "thinThickThinLargeGap", + THIN_THICK_THIN_MEDIUM_GAP = "thinThickThinMediumGap", + THIN_THICK_THIN_SMALL_GAP = "thinThickThinSmallGap", + THREE_D_EMBOSS = "threeDEmboss", + THREE_D_ENGRAVE = "threeDEngrave", + TRIPLE = "triple", + WAVE = "wave", +} diff --git a/src/file/border/index.ts b/src/file/border/index.ts new file mode 100644 index 0000000000..cfae779c98 --- /dev/null +++ b/src/file/border/index.ts @@ -0,0 +1 @@ +export * from "./border"; diff --git a/src/file/core-properties/components.ts b/src/file/core-properties/components.ts deleted file mode 100644 index 4a7d0a2795..0000000000 --- a/src/file/core-properties/components.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { DocumentAttributes } from "../document/document-attributes"; - -export class Title extends XmlComponent { - constructor(value: string) { - super("dc:title"); - this.root.push(value); - } -} - -export class Subject extends XmlComponent { - constructor(value: string) { - super("dc:subject"); - this.root.push(value); - } -} - -export class Creator extends XmlComponent { - constructor(value: string) { - super("dc:creator"); - this.root.push(value); - } -} - -export class Keywords extends XmlComponent { - constructor(value: string) { - super("cp:keywords"); - this.root.push(value); - } -} - -export class Description extends XmlComponent { - constructor(value: string) { - super("dc:description"); - this.root.push(value); - } -} - -export class LastModifiedBy extends XmlComponent { - constructor(value: string) { - super("cp:lastModifiedBy"); - this.root.push(value); - } -} - -export class Revision extends XmlComponent { - constructor(value: string) { - super("cp:revision"); - this.root.push(value); - } -} - -export abstract class DateComponent extends XmlComponent { - protected getCurrentDate(): string { - const date = new Date(); - const year = date.getFullYear(); - const month = ("0" + (date.getMonth() + 1)).slice(-2); - const day = ("0" + date.getDate()).slice(-2); - const hours = ("0" + date.getHours()).slice(-2); - const minutes = ("0" + date.getMinutes()).slice(-2); - const seconds = ("0" + date.getSeconds()).slice(-2); - - return year + "-" + month + "-" + day + "T" + hours + ":" + minutes + ":" + seconds + "Z"; - } -} - -export class Created extends DateComponent { - constructor() { - super("dcterms:created"); - this.root.push( - new DocumentAttributes({ - type: "dcterms:W3CDTF", - }), - ); - this.root.push(this.getCurrentDate()); - } -} - -export class Modified extends DateComponent { - constructor() { - super("dcterms:modified"); - this.root.push( - new DocumentAttributes({ - type: "dcterms:W3CDTF", - }), - ); - this.root.push(this.getCurrentDate()); - } -} diff --git a/src/file/core-properties/properties.spec.ts b/src/file/core-properties/properties.spec.ts index 92b2cd6139..4d0b57f0f4 100644 --- a/src/file/core-properties/properties.spec.ts +++ b/src/file/core-properties/properties.spec.ts @@ -27,8 +27,7 @@ describe("Properties", () => { const tree = new Formatter().format(properties); expect(Object.keys(tree)).to.deep.equal(["cp:coreProperties"]); expect(tree["cp:coreProperties"]).to.be.an.instanceof(Array); - expect(Object.keys(tree["cp:coreProperties"][0])).to.deep.equal(["_attr"]); - expect(tree["cp:coreProperties"][1]).to.deep.equal({ "dc:title": ["test document"] }); + expect(tree["cp:coreProperties"]).to.deep.include({ "dc:title": ["test document"] }); }); it("should create properties with all the attributes given", () => { @@ -44,9 +43,9 @@ describe("Properties", () => { const tree = new Formatter().format(properties); expect(Object.keys(tree)).to.deep.equal(["cp:coreProperties"]); expect(tree["cp:coreProperties"]).to.be.an.instanceof(Array); + const key = (obj) => Object.keys(obj)[0]; - const props = tree["cp:coreProperties"].map(key).sort(); - expect(props).to.deep.equal([ + expect(tree["cp:coreProperties"].map(key)).to.include.members([ "_attr", "cp:keywords", "cp:lastModifiedBy", @@ -58,7 +57,7 @@ describe("Properties", () => { "dcterms:created", "dcterms:modified", ]); - expect(tree["cp:coreProperties"].slice(1, -2).sort((a, b) => (key(a) < key(b) ? -1 : 1))).to.deep.equal([ + expect(tree["cp:coreProperties"]).to.deep.include.members([ { "cp:keywords": ["test docx"] }, { "cp:lastModifiedBy": ["the author"] }, { "cp:revision": ["123"] }, diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index 3128b08517..57a869a167 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,4 +1,4 @@ -import { XmlComponent } from "file/xml-components"; +import { StringContainer, XmlComponent } from "file/xml-components"; import { ICustomPropertyOptions } from "../custom-properties"; import { IDocumentBackgroundOptions } from "../document"; @@ -7,7 +7,7 @@ import { ISectionOptions } from "../file"; import { INumberingOptions } from "../numbering"; import { Paragraph } from "../paragraph"; import { IStylesOptions } from "../styles"; -import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; +import { dateTimeValue } from "../values"; export interface IPropertiesOptions { readonly sections: ISectionOptions[]; @@ -29,12 +29,35 @@ export interface IPropertiesOptions { readonly background?: IDocumentBackgroundOptions; readonly features?: { readonly trackRevisions?: boolean; + readonly updateFields?: boolean; }; readonly compatabilityModeVersion?: number; readonly customProperties?: ICustomPropertyOptions[]; readonly evenAndOddHeaderAndFooters?: boolean; } +// + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + export class CoreProperties extends XmlComponent { constructor(options: Omit) { super("cp:coreProperties"); @@ -48,27 +71,39 @@ export class CoreProperties extends XmlComponent { }), ); if (options.title) { - this.root.push(new Title(options.title)); + this.root.push(new StringContainer("dc:title", options.title)); } if (options.subject) { - this.root.push(new Subject(options.subject)); + this.root.push(new StringContainer("dc:subject", options.subject)); } if (options.creator) { - this.root.push(new Creator(options.creator)); + this.root.push(new StringContainer("dc:creator", options.creator)); } if (options.keywords) { - this.root.push(new Keywords(options.keywords)); + this.root.push(new StringContainer("cp:keywords", options.keywords)); } if (options.description) { - this.root.push(new Description(options.description)); + this.root.push(new StringContainer("dc:description", options.description)); } if (options.lastModifiedBy) { - this.root.push(new LastModifiedBy(options.lastModifiedBy)); + this.root.push(new StringContainer("cp:lastModifiedBy", options.lastModifiedBy)); } if (options.revision) { - this.root.push(new Revision(options.revision)); + this.root.push(new StringContainer("cp:revision", options.revision)); } - this.root.push(new Created()); - this.root.push(new Modified()); + this.root.push(new TimestampElement("dcterms:created")); + this.root.push(new TimestampElement("dcterms:modified")); + } +} + +class TimestampElement extends XmlComponent { + constructor(name: string) { + super(name); + this.root.push( + new DocumentAttributes({ + type: "dcterms:W3CDTF", + }), + ); + this.root.push(dateTimeValue(new Date())); } } diff --git a/src/file/document/body/body.spec.ts b/src/file/document/body/body.spec.ts index bae759266c..a65f86b69e 100644 --- a/src/file/document/body/body.spec.ts +++ b/src/file/document/body/body.spec.ts @@ -3,6 +3,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import { Body } from "./body"; +import { sectionMarginDefaults } from "./section-properties"; describe("Body", () => { let body: Body; @@ -32,13 +33,13 @@ describe("Body", () => { { "w:pgMar": { _attr: { - "w:top": 1440, - "w:right": 1440, - "w:bottom": 1440, - "w:left": 1440, - "w:header": 708, - "w:footer": 708, - "w:gutter": 0, + "w:top": sectionMarginDefaults.TOP, + "w:right": sectionMarginDefaults.RIGHT, + "w:bottom": sectionMarginDefaults.BOTTOM, + "w:left": sectionMarginDefaults.LEFT, + "w:header": sectionMarginDefaults.HEADER, + "w:footer": sectionMarginDefaults.FOOTER, + "w:gutter": sectionMarginDefaults.GUTTER, }, }, }, @@ -47,7 +48,7 @@ describe("Body", () => { _attr: {}, }, }, - { "w:cols": { _attr: { "w:space": 708, "w:sep": false, "w:num": 1 } } }, + // { "w:cols": { _attr: { "w:space": 708, "w:sep": false, "w:num": 1 } } }, { "w:docGrid": { _attr: { "w:linePitch": 360 } } }, ], }, diff --git a/src/file/document/body/body.ts b/src/file/document/body/body.ts index ff56962d12..deb2e573cf 100644 --- a/src/file/document/body/body.ts +++ b/src/file/document/body/body.ts @@ -1,6 +1,6 @@ import { IContext, IXmlableObject, XmlComponent } from "file/xml-components"; -import { Paragraph, ParagraphProperties, TableOfContents } from "../.."; +import { Paragraph, ParagraphProperties } from "../.."; import { ISectionPropertiesOptions, SectionProperties } from "./section-properties/section-properties"; export class Body extends XmlComponent { @@ -38,10 +38,6 @@ export class Body extends XmlComponent { this.root.push(component); } - public getTablesOfContents(): TableOfContents[] { - return this.root.filter((child) => child instanceof TableOfContents) as TableOfContents[]; - } - private createSectionParagraph(section: SectionProperties): Paragraph { const paragraph = new Paragraph({}); const properties = new ParagraphProperties({}); diff --git a/src/file/document/body/section-properties/columns/columns-attributes.ts b/src/file/document/body/section-properties/columns/columns-attributes.ts deleted file mode 100644 index 40140bd63f..0000000000 --- a/src/file/document/body/section-properties/columns/columns-attributes.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export class ColumnsAttributes extends XmlAttributeComponent<{ - readonly space?: number; - readonly num?: number; - readonly separate?: boolean; -}> { - protected readonly xmlKeys = { - space: "w:space", - num: "w:num", - separate: "w:sep", - }; -} diff --git a/src/file/document/body/section-properties/columns/columns.ts b/src/file/document/body/section-properties/columns/columns.ts deleted file mode 100644 index c074f189d9..0000000000 --- a/src/file/document/body/section-properties/columns/columns.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { ColumnsAttributes } from "./columns-attributes"; - -export class Columns extends XmlComponent { - constructor(space: number, num: number, separate: boolean) { - super("w:cols"); - this.root.push( - new ColumnsAttributes({ - space: space, - num: num, - separate: separate, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/doc-grid/doc-grid-attributes.ts b/src/file/document/body/section-properties/doc-grid/doc-grid-attributes.ts deleted file mode 100644 index 3fb2a5030f..0000000000 --- a/src/file/document/body/section-properties/doc-grid/doc-grid-attributes.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export interface IDocGridAttributesProperties { - readonly linePitch?: number; -} - -export class DocGridAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - linePitch: "w:linePitch", - }; -} diff --git a/src/file/document/body/section-properties/doc-grid/doc-grid.ts b/src/file/document/body/section-properties/doc-grid/doc-grid.ts deleted file mode 100644 index e4df527443..0000000000 --- a/src/file/document/body/section-properties/doc-grid/doc-grid.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { DocGridAttributes } from "./doc-grid-attributes"; - -export class DocumentGrid extends XmlComponent { - constructor(linePitch: number) { - super("w:docGrid"); - this.root.push( - new DocGridAttributes({ - linePitch: linePitch, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/footer-reference/footer-reference-attributes.ts b/src/file/document/body/section-properties/footer-reference/footer-reference-attributes.ts deleted file mode 100644 index 861cc86a11..0000000000 --- a/src/file/document/body/section-properties/footer-reference/footer-reference-attributes.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export enum FooterReferenceType { - DEFAULT = "default", - FIRST = "first", - EVEN = "even", -} - -export class FooterReferenceAttributes extends XmlAttributeComponent<{ - readonly type: string; - readonly id: string; -}> { - protected readonly xmlKeys = { - type: "w:type", - id: "r:id", - }; -} diff --git a/src/file/document/body/section-properties/footer-reference/footer-reference.spec.ts b/src/file/document/body/section-properties/footer-reference/footer-reference.spec.ts deleted file mode 100644 index 50570b4b77..0000000000 --- a/src/file/document/body/section-properties/footer-reference/footer-reference.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; -import { FooterReference } from "./footer-reference"; -import { FooterReferenceType } from "./footer-reference-attributes"; - -describe("footerReference", () => { - it("should create", () => { - const footer = new FooterReference({ - footerType: FooterReferenceType.DEFAULT, - footerId: 1, - }); - - const tree = new Formatter().format(footer); - - expect(tree).to.deep.equal({ - "w:footerReference": { - _attr: { - "r:id": "rId1", - "w:type": "default", - }, - }, - }); - }); - - it("should create without a footer type", () => { - const footer = new FooterReference({ - footerId: 1, - }); - - const tree = new Formatter().format(footer); - - expect(tree).to.deep.equal({ - "w:footerReference": { - _attr: { - "r:id": "rId1", - "w:type": "default", - }, - }, - }); - }); -}); diff --git a/src/file/document/body/section-properties/footer-reference/footer-reference.ts b/src/file/document/body/section-properties/footer-reference/footer-reference.ts deleted file mode 100644 index d23e99aba1..0000000000 --- a/src/file/document/body/section-properties/footer-reference/footer-reference.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { FooterReferenceAttributes, FooterReferenceType } from "./footer-reference-attributes"; - -export interface IFooterOptions { - readonly footerType?: FooterReferenceType; - readonly footerId?: number; -} - -export class FooterReference extends XmlComponent { - constructor(options: IFooterOptions) { - super("w:footerReference"); - - this.root.push( - new FooterReferenceAttributes({ - type: options.footerType || FooterReferenceType.DEFAULT, - id: `rId${options.footerId}`, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/footer-reference/index.ts b/src/file/document/body/section-properties/footer-reference/index.ts deleted file mode 100644 index 9673319fba..0000000000 --- a/src/file/document/body/section-properties/footer-reference/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./footer-reference"; -export * from "./footer-reference-attributes"; diff --git a/src/file/document/body/section-properties/header-reference/header-reference-attributes.ts b/src/file/document/body/section-properties/header-reference/header-reference-attributes.ts deleted file mode 100644 index b46c993999..0000000000 --- a/src/file/document/body/section-properties/header-reference/header-reference-attributes.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export enum HeaderReferenceType { - DEFAULT = "default", - FIRST = "first", - EVEN = "even", -} - -export class HeaderReferenceAttributes extends XmlAttributeComponent<{ - readonly type: string; - readonly id: string; -}> { - protected readonly xmlKeys = { - type: "w:type", - id: "r:id", - }; -} diff --git a/src/file/document/body/section-properties/header-reference/header-reference.spec.ts b/src/file/document/body/section-properties/header-reference/header-reference.spec.ts deleted file mode 100644 index 2d6b39e553..0000000000 --- a/src/file/document/body/section-properties/header-reference/header-reference.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; -import { HeaderReference } from "./header-reference"; -import { HeaderReferenceType } from "./header-reference-attributes"; - -describe("HeaderReference", () => { - it("should create", () => { - const footer = new HeaderReference({ - headerType: HeaderReferenceType.DEFAULT, - headerId: 1, - }); - - const tree = new Formatter().format(footer); - - expect(tree).to.deep.equal({ - "w:headerReference": { - _attr: { - "r:id": "rId1", - "w:type": "default", - }, - }, - }); - }); - - it("should create without a header type", () => { - const footer = new HeaderReference({ - headerId: 1, - }); - - const tree = new Formatter().format(footer); - - expect(tree).to.deep.equal({ - "w:headerReference": { - _attr: { - "r:id": "rId1", - "w:type": "default", - }, - }, - }); - }); -}); diff --git a/src/file/document/body/section-properties/header-reference/header-reference.ts b/src/file/document/body/section-properties/header-reference/header-reference.ts deleted file mode 100644 index b1bc4ef4f4..0000000000 --- a/src/file/document/body/section-properties/header-reference/header-reference.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { HeaderReferenceAttributes, HeaderReferenceType } from "./header-reference-attributes"; - -export interface IHeaderReferenceOptions { - readonly headerType?: HeaderReferenceType; - readonly headerId?: number; -} - -export class HeaderReference extends XmlComponent { - constructor(options: IHeaderReferenceOptions) { - super("w:headerReference"); - this.root.push( - new HeaderReferenceAttributes({ - type: options.headerType || HeaderReferenceType.DEFAULT, - id: `rId${options.headerId}`, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/header-reference/index.ts b/src/file/document/body/section-properties/header-reference/index.ts deleted file mode 100644 index 80239ad98e..0000000000 --- a/src/file/document/body/section-properties/header-reference/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./header-reference"; -export * from "./header-reference-attributes"; diff --git a/src/file/document/body/section-properties/index.ts b/src/file/document/body/section-properties/index.ts index ee0820c640..bb4ce998e3 100644 --- a/src/file/document/body/section-properties/index.ts +++ b/src/file/document/body/section-properties/index.ts @@ -1,9 +1,2 @@ export * from "./section-properties"; -export * from "./footer-reference"; -export * from "./header-reference"; -export * from "./page-size"; -export * from "./page-number"; -export * from "./page-border"; -export * from "./line-number"; -export * from "./vertical-align"; -export * from "./type"; +export * from "./properties"; diff --git a/src/file/document/body/section-properties/line-number/index.ts b/src/file/document/body/section-properties/line-number/index.ts deleted file mode 100644 index 5808fe7f7b..0000000000 --- a/src/file/document/body/section-properties/line-number/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./line-number"; diff --git a/src/file/document/body/section-properties/line-number/line-number.ts b/src/file/document/body/section-properties/line-number/line-number.ts deleted file mode 100644 index fdc0515244..0000000000 --- a/src/file/document/body/section-properties/line-number/line-number.ts +++ /dev/null @@ -1,38 +0,0 @@ -// http://officeopenxml.com/WPsectionLineNumbering.php -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -export enum LineNumberRestartFormat { - CONTINUOUS = "continuous", - NEW_SECTION = "newSection", - NEW_PAGE = "newPage", -} - -export interface ILineNumberAttributes { - readonly countBy?: number; - readonly start?: number; - readonly restart?: LineNumberRestartFormat; - readonly distance?: number; -} - -export class LineNumberAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - countBy: "w:countBy", - start: "w:start", - restart: "w:restart", - distance: "w:distance", - }; -} - -export class LineNumberType extends XmlComponent { - constructor(countBy?: number, start?: number, restart?: LineNumberRestartFormat, dist?: number) { - super("w:lnNumType"); - this.root.push( - new LineNumberAttributes({ - countBy: countBy, - start: start, - restart: restart, - distance: dist, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/page-border/index.ts b/src/file/document/body/section-properties/page-border/index.ts deleted file mode 100644 index 53d8b8ce4b..0000000000 --- a/src/file/document/body/section-properties/page-border/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./page-borders"; diff --git a/src/file/document/body/section-properties/page-border/page-borders.ts b/src/file/document/body/section-properties/page-border/page-borders.ts deleted file mode 100644 index 4a042ccf8a..0000000000 --- a/src/file/document/body/section-properties/page-border/page-borders.ts +++ /dev/null @@ -1,100 +0,0 @@ -// http://officeopenxml.com/WPsectionBorders.php -import { BorderStyle } from "file/styles"; -import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -export enum PageBorderDisplay { - ALL_PAGES = "allPages", - FIRST_PAGE = "firstPage", - NOT_FIRST_PAGE = "notFirstPage", -} - -export enum PageBorderOffsetFrom { - PAGE = "page", - TEXT = "text", -} - -export enum PageBorderZOrder { - BACK = "back", - FRONT = "front", -} - -export interface IPageBorderAttributes { - readonly display?: PageBorderDisplay; - readonly offsetFrom?: PageBorderOffsetFrom; - readonly zOrder?: PageBorderZOrder; -} - -export interface IPageBorderConfiguration { - readonly style?: BorderStyle; - readonly size?: number; - readonly color?: string; - readonly space?: number; -} - -export interface IPageBordersOptions { - readonly pageBorders?: IPageBorderAttributes; - readonly pageBorderTop?: IPageBorderConfiguration; - readonly pageBorderRight?: IPageBorderConfiguration; - readonly pageBorderBottom?: IPageBorderConfiguration; - readonly pageBorderLeft?: IPageBorderConfiguration; -} - -class PageBordeAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - style: "w:val", - size: "w:size", - color: "w:color", - space: "w:space", - }; -} - -class PageBorder extends XmlComponent { - constructor(key: string, options: IPageBorderConfiguration) { - super(key); - - this.root.push(new PageBordeAttributes(options)); - } -} - -class PageBordersAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - display: "w:display", - offsetFrom: "w:offsetFrom", - zOrder: "w:zOrder", - }; -} - -export class PageBorders extends IgnoreIfEmptyXmlComponent { - constructor(options?: IPageBordersOptions) { - super("w:pgBorders"); - - if (!options) { - return; - } - - if (options.pageBorders) { - this.root.push( - new PageBordersAttributes({ - display: options.pageBorders.display, - offsetFrom: options.pageBorders.offsetFrom, - zOrder: options.pageBorders.zOrder, - }), - ); - } else { - this.root.push(new PageBordersAttributes({})); - } - - if (options.pageBorderTop) { - this.root.push(new PageBorder("w:top", options.pageBorderTop)); - } - if (options.pageBorderRight) { - this.root.push(new PageBorder("w:right", options.pageBorderRight)); - } - if (options.pageBorderBottom) { - this.root.push(new PageBorder("w:bottom", options.pageBorderBottom)); - } - if (options.pageBorderLeft) { - this.root.push(new PageBorder("w:left", options.pageBorderLeft)); - } - } -} diff --git a/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts b/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts deleted file mode 100644 index 7d7df6565c..0000000000 --- a/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export interface IPageMarginAttributes { - readonly top?: number; - readonly right?: number; - readonly bottom?: number; - readonly left?: number; - readonly header?: number; - readonly footer?: number; - readonly gutter?: number; -} - -export class PageMarginAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - top: "w:top", - right: "w:right", - bottom: "w:bottom", - left: "w:left", - header: "w:header", - footer: "w:footer", - gutter: "w:gutter", - }; -} diff --git a/src/file/document/body/section-properties/page-margin/page-margin.ts b/src/file/document/body/section-properties/page-margin/page-margin.ts deleted file mode 100644 index bd9a8e5fa6..0000000000 --- a/src/file/document/body/section-properties/page-margin/page-margin.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { PageMarginAttributes } from "./page-margin-attributes"; - -export class PageMargin extends XmlComponent { - constructor(top: number, right: number, bottom: number, left: number, header: number, footer: number, gutter: number) { - super("w:pgMar"); - this.root.push( - new PageMarginAttributes({ - top: top, - right: right, - bottom: bottom, - left: left, - header: header, - footer: footer, - gutter: gutter, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/page-number/index.ts b/src/file/document/body/section-properties/page-number/index.ts deleted file mode 100644 index 57e81d8724..0000000000 --- a/src/file/document/body/section-properties/page-number/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./page-number"; diff --git a/src/file/document/body/section-properties/page-number/page-number.ts b/src/file/document/body/section-properties/page-number/page-number.ts deleted file mode 100644 index e6a12101a7..0000000000 --- a/src/file/document/body/section-properties/page-number/page-number.ts +++ /dev/null @@ -1,53 +0,0 @@ -// http://officeopenxml.com/WPSectionPgNumType.php -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -export enum PageNumberFormat { - CARDINAL_TEXT = "cardinalText", - DECIMAL = "decimal", - DECIMAL_ENCLOSED_CIRCLE = "decimalEnclosedCircle", - DECIMAL_ENCLOSED_FULL_STOP = "decimalEnclosedFullstop", - DECIMAL_ENCLOSED_PAREN = "decimalEnclosedParen", - DECIMAL_ZERO = "decimalZero", - LOWER_LETTER = "lowerLetter", - LOWER_ROMAN = "lowerRoman", - NONE = "none", - ORDINAL_TEXT = "ordinalText", - UPPER_LETTER = "upperLetter", - UPPER_ROMAN = "upperRoman", - DECIMAL_FULL_WIDTH = "decimalFullWidth", -} - -export enum PageNumberSeparator { - COLON = "colon", - EM_DASH = "emDash", - EN_DASH = "endash", - HYPHEN = "hyphen", - PERIOD = "period", -} - -export interface IPageNumberTypeAttributes { - readonly start?: number; - readonly formatType?: PageNumberFormat; - readonly separator?: PageNumberSeparator; -} - -export class PageNumberTypeAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - start: "w:start", - formatType: "w:fmt", - separator: "w:chapSep", - }; -} - -export class PageNumberType extends XmlComponent { - constructor(start?: number, numberFormat?: PageNumberFormat, separator?: PageNumberSeparator) { - super("w:pgNumType"); - this.root.push( - new PageNumberTypeAttributes({ - start: start, - formatType: numberFormat, - separator: separator, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/page-size/index.ts b/src/file/document/body/section-properties/page-size/index.ts deleted file mode 100644 index 567f7c2d58..0000000000 --- a/src/file/document/body/section-properties/page-size/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./page-size"; -export * from "./page-size-attributes"; diff --git a/src/file/document/body/section-properties/page-size/page-size-attributes.ts b/src/file/document/body/section-properties/page-size/page-size-attributes.ts deleted file mode 100644 index 9406de635a..0000000000 --- a/src/file/document/body/section-properties/page-size/page-size-attributes.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export enum PageOrientation { - PORTRAIT = "portrait", - LANDSCAPE = "landscape", -} - -export interface IPageSizeAttributes { - readonly width?: number; - readonly height?: number; - readonly orientation?: PageOrientation; -} - -export class PageSizeAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - width: "w:w", - height: "w:h", - orientation: "w:orient", - }; -} diff --git a/src/file/document/body/section-properties/page-size/page-size.ts b/src/file/document/body/section-properties/page-size/page-size.ts deleted file mode 100644 index 6aa400bea2..0000000000 --- a/src/file/document/body/section-properties/page-size/page-size.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { PageOrientation, PageSizeAttributes } from "./page-size-attributes"; - -export class PageSize extends XmlComponent { - constructor(width: number, height: number, orientation: PageOrientation) { - super("w:pgSz"); - - const flip = orientation === PageOrientation.LANDSCAPE; - - this.root.push( - new PageSizeAttributes({ - width: flip ? height : width, - height: flip ? width : height, - orientation: orientation, - }), - ); - } -} diff --git a/src/file/document/body/section-properties/properties/columns.ts b/src/file/document/body/section-properties/properties/columns.ts new file mode 100644 index 0000000000..395df4ecae --- /dev/null +++ b/src/file/document/body/section-properties/properties/columns.ts @@ -0,0 +1,41 @@ +import { decimalNumber, twipsMeasureValue } from "file/values"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +// +// +export interface IColumnsAttributes { + readonly space?: number | string; + readonly count?: number; + readonly separate?: boolean; + readonly equalWidth?: boolean; +} + +export class ColumnsAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + space: "w:space", + count: "w:num", + separate: "w:sep", + equalWidth: "w:equalWidth", + }; +} + +export class Columns extends XmlComponent { + constructor({ space, count, separate, equalWidth }: IColumnsAttributes) { + super("w:cols"); + this.root.push( + new ColumnsAttributes({ + space: space === undefined ? undefined : twipsMeasureValue(space), + count: count === undefined ? undefined : decimalNumber(count), + separate, + equalWidth, + }), + ); + } +} diff --git a/src/file/document/body/section-properties/properties/doc-grid.ts b/src/file/document/body/section-properties/properties/doc-grid.ts new file mode 100644 index 0000000000..c8048452b8 --- /dev/null +++ b/src/file/document/body/section-properties/properties/doc-grid.ts @@ -0,0 +1,38 @@ +import { decimalNumber } from "file/values"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// not implemented +// +// +// +// +// +// +// +// + +// +// +// +// +// +export interface IDocGridAttributesProperties { + readonly linePitch?: number; +} + +export class DocGridAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + linePitch: "w:linePitch", + }; +} + +export class DocumentGrid extends XmlComponent { + constructor(linePitch: number) { + super("w:docGrid"); + this.root.push( + new DocGridAttributes({ + linePitch: decimalNumber(linePitch), + }), + ); + } +} diff --git a/src/file/document/body/section-properties/properties/header-footer-reference.spec.ts b/src/file/document/body/section-properties/properties/header-footer-reference.spec.ts new file mode 100644 index 0000000000..adb0bc70b6 --- /dev/null +++ b/src/file/document/body/section-properties/properties/header-footer-reference.spec.ts @@ -0,0 +1,56 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { HeaderFooterReference, HeaderFooterReferenceType, HeaderFooterType } from "./header-footer-reference"; + +describe("HeaderFooterReference", () => { + it("#constructor (footer)", () => { + const footer = new HeaderFooterReference(HeaderFooterType.FOOTER, { + type: HeaderFooterReferenceType.DEFAULT, + id: 1, + }); + + const tree = new Formatter().format(footer); + expect(tree).to.deep.equal({ + "w:footerReference": { + _attr: { + "r:id": "rId1", + "w:type": "default", + }, + }, + }); + }); + + it("#constructor (header)", () => { + const header = new HeaderFooterReference(HeaderFooterType.HEADER, { + type: HeaderFooterReferenceType.DEFAULT, + id: 1, + }); + + const tree = new Formatter().format(header); + expect(tree).to.deep.equal({ + "w:headerReference": { + _attr: { + "r:id": "rId1", + "w:type": "default", + }, + }, + }); + }); + + it("should create without a type", () => { + const footer = new HeaderFooterReference(HeaderFooterType.FOOTER, { + id: 1, + }); + + const tree = new Formatter().format(footer); + expect(tree).to.deep.equal({ + "w:footerReference": { + _attr: { + "r:id": "rId1", + "w:type": "default", + }, + }, + }); + }); +}); diff --git a/src/file/document/body/section-properties/properties/header-footer-reference.ts b/src/file/document/body/section-properties/properties/header-footer-reference.ts new file mode 100644 index 0000000000..1966790da9 --- /dev/null +++ b/src/file/document/body/section-properties/properties/header-footer-reference.ts @@ -0,0 +1,65 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +export enum HeaderFooterReferenceType { + DEFAULT = "default", + FIRST = "first", + EVEN = "even", +} + +// +// +// +// +// +// +// + +// +// +// +// +// +// + +// +// +// + +export interface IHeaderFooterOptions { + readonly type?: HeaderFooterReferenceType; + readonly id?: number; +} + +class FooterReferenceAttributes extends XmlAttributeComponent<{ + readonly type: HeaderFooterReferenceType; + readonly id: string; +}> { + protected readonly xmlKeys = { + type: "w:type", + id: "r:id", + }; +} + +export enum HeaderFooterType { + HEADER = "w:headerReference", + FOOTER = "w:footerReference", +} +export class HeaderFooterReference extends XmlComponent { + constructor(type: HeaderFooterType, options: IHeaderFooterOptions) { + super(type); + + this.root.push( + new FooterReferenceAttributes({ + type: options.type || HeaderFooterReferenceType.DEFAULT, + id: `rId${options.id}`, + }), + ); + } +} diff --git a/src/file/document/body/section-properties/properties/index.ts b/src/file/document/body/section-properties/properties/index.ts new file mode 100644 index 0000000000..089a005744 --- /dev/null +++ b/src/file/document/body/section-properties/properties/index.ts @@ -0,0 +1,11 @@ +export * from "./columns"; +export * from "./doc-grid"; +// export * from "./header-reference"; +export * from "./page-size"; +export * from "./page-number"; +export * from "./page-borders"; +export * from "./page-margin"; +export * from "./page-borders"; +export * from "./line-number"; +export * from "./section-type"; +export * from "./header-footer-reference"; diff --git a/src/file/document/body/section-properties/properties/line-number.ts b/src/file/document/body/section-properties/properties/line-number.ts new file mode 100644 index 0000000000..0ab1218568 --- /dev/null +++ b/src/file/document/body/section-properties/properties/line-number.ts @@ -0,0 +1,53 @@ +// http://officeopenxml.com/WPsectionLineNumbering.php +import { decimalNumber, twipsMeasureValue } from "file/values"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +export enum LineNumberRestartFormat { + NEW_PAGE = "newPage", + NEW_SECTION = "newSection", + CONTINUOUS = "continuous", +} + +// +// +// +// +// +// + +export interface ILineNumberAttributes { + readonly countBy?: number; + readonly start?: number; + readonly restart?: LineNumberRestartFormat; + readonly distance?: number | string; +} + +export class LineNumberAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + countBy: "w:countBy", + start: "w:start", + restart: "w:restart", + distance: "w:distance", + }; +} + +export class LineNumberType extends XmlComponent { + constructor({ countBy, start, restart, distance }: ILineNumberAttributes) { + super("w:lnNumType"); + this.root.push( + new LineNumberAttributes({ + countBy: countBy === undefined ? undefined : decimalNumber(countBy), + start: start === undefined ? undefined : decimalNumber(start), + restart, + distance: distance === undefined ? undefined : twipsMeasureValue(distance), + }), + ); + } +} diff --git a/src/file/document/body/section-properties/page-border/page-borders.spec.ts b/src/file/document/body/section-properties/properties/page-borders.spec.ts similarity index 89% rename from src/file/document/body/section-properties/page-border/page-borders.spec.ts rename to src/file/document/body/section-properties/properties/page-borders.spec.ts index 3b6152013a..977bcbd1b7 100644 --- a/src/file/document/body/section-properties/page-border/page-borders.spec.ts +++ b/src/file/document/body/section-properties/properties/page-borders.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { BorderStyle } from "file/styles"; +import { BorderStyle } from "file/border"; import { PageBorderDisplay, PageBorders, PageBorderZOrder } from "./page-borders"; @@ -70,22 +70,22 @@ describe("PageBorders", () => { expect(tree["w:pgBorders"][0]).to.deep.equal({ _attr: { "w:display": "firstPage", "w:zOrder": "back" } }); expect(tree["w:pgBorders"][1]).to.deep.equal({ "w:top": { - _attr: { "w:color": "001122", "w:size": 10, "w:val": "doubleWave" }, + _attr: { "w:color": "001122", "w:sz": 10, "w:val": "doubleWave" }, }, }); expect(tree["w:pgBorders"][2]).to.deep.equal({ - "w:right": { - _attr: { "w:color": "223344", "w:size": 20, "w:val": "double" }, + "w:left": { + _attr: { "w:color": "889900", "w:sz": 40, "w:val": "dotted" }, }, }); expect(tree["w:pgBorders"][3]).to.deep.equal({ "w:bottom": { - _attr: { "w:color": "556677", "w:size": 30, "w:val": "single" }, + _attr: { "w:color": "556677", "w:sz": 30, "w:val": "single" }, }, }); expect(tree["w:pgBorders"][4]).to.deep.equal({ - "w:left": { - _attr: { "w:color": "889900", "w:size": 40, "w:val": "dotted" }, + "w:right": { + _attr: { "w:color": "223344", "w:sz": 20, "w:val": "double" }, }, }); }); diff --git a/src/file/document/body/section-properties/properties/page-borders.ts b/src/file/document/body/section-properties/properties/page-borders.ts new file mode 100644 index 0000000000..d3a8ecbb61 --- /dev/null +++ b/src/file/document/body/section-properties/properties/page-borders.ts @@ -0,0 +1,106 @@ +// http://officeopenxml.com/WPsectionBorders.php +import { BorderElement, IBorderOptions } from "file/border"; +import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +export enum PageBorderDisplay { + ALL_PAGES = "allPages", + FIRST_PAGE = "firstPage", + NOT_FIRST_PAGE = "notFirstPage", +} + +// +// +// +// +// +// +export enum PageBorderOffsetFrom { + PAGE = "page", + TEXT = "text", +} + +// +// +// +// +// +// +export enum PageBorderZOrder { + BACK = "back", + FRONT = "front", +} + +export interface IPageBorderAttributes { + readonly display?: PageBorderDisplay; + readonly offsetFrom?: PageBorderOffsetFrom; + readonly zOrder?: PageBorderZOrder; +} + +export interface IPageBordersOptions { + readonly pageBorders?: IPageBorderAttributes; + readonly pageBorderTop?: IBorderOptions; + readonly pageBorderRight?: IBorderOptions; + readonly pageBorderBottom?: IBorderOptions; + readonly pageBorderLeft?: IBorderOptions; +} + +class PageBordersAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + display: "w:display", + offsetFrom: "w:offsetFrom", + zOrder: "w:zOrder", + }; +} + +// +// +// +// +// +// +// +// +// +// +// +export class PageBorders extends IgnoreIfEmptyXmlComponent { + constructor(options?: IPageBordersOptions) { + super("w:pgBorders"); + + if (!options) { + return; + } + + if (options.pageBorders) { + this.root.push( + new PageBordersAttributes({ + display: options.pageBorders.display, + offsetFrom: options.pageBorders.offsetFrom, + zOrder: options.pageBorders.zOrder, + }), + ); + } else { + this.root.push(new PageBordersAttributes({})); + } + + if (options.pageBorderTop) { + this.root.push(new BorderElement("w:top", options.pageBorderTop)); + } + if (options.pageBorderLeft) { + this.root.push(new BorderElement("w:left", options.pageBorderLeft)); + } + if (options.pageBorderBottom) { + this.root.push(new BorderElement("w:bottom", options.pageBorderBottom)); + } + if (options.pageBorderRight) { + this.root.push(new BorderElement("w:right", options.pageBorderRight)); + } + } +} diff --git a/src/file/document/body/section-properties/properties/page-margin.ts b/src/file/document/body/section-properties/properties/page-margin.ts new file mode 100644 index 0000000000..91df7db51a --- /dev/null +++ b/src/file/document/body/section-properties/properties/page-margin.ts @@ -0,0 +1,58 @@ +import { signedTwipsMeasureValue, twipsMeasureValue } from "file/values"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +// +// +export interface IPageMarginAttributes { + readonly top?: number | string; + readonly right?: number | string; + readonly bottom?: number | string; + readonly left?: number | string; + readonly header?: number | string; + readonly footer?: number | string; + readonly gutter?: number | string; +} + +export class PageMarginAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + top: "w:top", + right: "w:right", + bottom: "w:bottom", + left: "w:left", + header: "w:header", + footer: "w:footer", + gutter: "w:gutter", + }; +} + +export class PageMargin extends XmlComponent { + constructor( + top: number | string, + right: number | string, + bottom: number | string, + left: number | string, + header: number | string, + footer: number | string, + gutter: number | string, + ) { + super("w:pgMar"); + this.root.push( + new PageMarginAttributes({ + top: signedTwipsMeasureValue(top), + right: twipsMeasureValue(right), + bottom: signedTwipsMeasureValue(bottom), + left: twipsMeasureValue(left), + header: twipsMeasureValue(header), + footer: twipsMeasureValue(footer), + gutter: twipsMeasureValue(gutter), + }), + ); + } +} diff --git a/src/file/document/body/section-properties/properties/page-number.ts b/src/file/document/body/section-properties/properties/page-number.ts new file mode 100644 index 0000000000..0bca61ebba --- /dev/null +++ b/src/file/document/body/section-properties/properties/page-number.ts @@ -0,0 +1,54 @@ +// http://officeopenxml.com/WPSectionPgNumType.php +import { NumberFormat } from "file/shared/number-format"; +import { decimalNumber } from "file/values"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +// +// +export enum PageNumberSeparator { + HYPHEN = "hyphen", + PERIOD = "period", + COLON = "colon", + EM_DASH = "emDash", + EN_DASH = "endash", +} + +export interface IPageNumberTypeAttributes { + readonly start?: number; + readonly formatType?: NumberFormat; + readonly separator?: PageNumberSeparator; +} + +// +// +// +// +// +// + +export class PageNumberTypeAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + start: "w:start", + formatType: "w:fmt", + separator: "w:chapSep", + }; +} +export class PageNumberType extends XmlComponent { + constructor({ start, formatType, separator }: IPageNumberTypeAttributes) { + super("w:pgNumType"); + this.root.push( + new PageNumberTypeAttributes({ + start: start === undefined ? undefined : decimalNumber(start), + formatType, + separator, + }), + ); + } +} diff --git a/src/file/document/body/section-properties/page-size/page-size.spec.ts b/src/file/document/body/section-properties/properties/page-size.spec.ts similarity index 90% rename from src/file/document/body/section-properties/page-size/page-size.spec.ts rename to src/file/document/body/section-properties/properties/page-size.spec.ts index 0921006e1e..aa7ca8ae1d 100644 --- a/src/file/document/body/section-properties/page-size/page-size.spec.ts +++ b/src/file/document/body/section-properties/properties/page-size.spec.ts @@ -2,8 +2,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { PageSize } from "./page-size"; -import { PageOrientation } from "./page-size-attributes"; +import { PageOrientation, PageSize } from "./page-size"; describe("PageSize", () => { describe("#constructor()", () => { diff --git a/src/file/document/body/section-properties/properties/page-size.ts b/src/file/document/body/section-properties/properties/page-size.ts new file mode 100644 index 0000000000..ff99c52201 --- /dev/null +++ b/src/file/document/body/section-properties/properties/page-size.ts @@ -0,0 +1,52 @@ +import { twipsMeasureValue } from "file/values"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +export enum PageOrientation { + PORTRAIT = "portrait", + LANDSCAPE = "landscape", +} + +// +// +// +// +// +// +export interface IPageSizeAttributes { + readonly width?: number | string; + readonly height?: number | string; + readonly orientation?: PageOrientation; +} + +export class PageSizeAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + width: "w:w", + height: "w:h", + orientation: "w:orient", + }; +} + +export class PageSize extends XmlComponent { + constructor(width: number | string, height: number | string, orientation: PageOrientation) { + super("w:pgSz"); + + const flip = orientation === PageOrientation.LANDSCAPE; + + const widthTwips = twipsMeasureValue(width); + const heightTwips = twipsMeasureValue(height); + + this.root.push( + new PageSizeAttributes({ + width: flip ? heightTwips : widthTwips, + height: flip ? widthTwips : heightTwips, + orientation: orientation, + }), + ); + } +} diff --git a/src/file/document/body/section-properties/type/section-type.spec.ts b/src/file/document/body/section-properties/properties/section-type.spec.ts similarity index 89% rename from src/file/document/body/section-properties/type/section-type.spec.ts rename to src/file/document/body/section-properties/properties/section-type.spec.ts index 7276825fab..bbf08799f3 100644 --- a/src/file/document/body/section-properties/type/section-type.spec.ts +++ b/src/file/document/body/section-properties/properties/section-type.spec.ts @@ -1,8 +1,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { Type } from "./section-type"; -import { SectionType } from "./section-type-attributes"; +import { SectionType, Type } from "./section-type"; describe("Type", () => { it("should create with even page section type", () => { diff --git a/src/file/document/body/section-properties/properties/section-type.ts b/src/file/document/body/section-properties/properties/section-type.ts new file mode 100644 index 0000000000..e09d3c6508 --- /dev/null +++ b/src/file/document/body/section-properties/properties/section-type.ts @@ -0,0 +1,37 @@ +// http://officeopenxml.com/WPsection.php +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +// +// +export enum SectionType { + NEXT_PAGE = "nextPage", + NEXT_COLUMN = "nextColumn", + CONTINUOUS = "continuous", + EVEN_PAGE = "evenPage", + ODD_PAGE = "oddPage", +} + +// +// +// +export class SectionTypeAttributes extends XmlAttributeComponent<{ + readonly val: SectionType; +}> { + protected readonly xmlKeys = { + val: "w:val", + }; +} + +export class Type extends XmlComponent { + constructor(value: SectionType) { + super("w:type"); + this.root.push(new SectionTypeAttributes({ val: value })); + } +} diff --git a/src/file/document/body/section-properties/section-properties.spec.ts b/src/file/document/body/section-properties/section-properties.spec.ts index 9483a4d47e..d0c7e7fc5a 100644 --- a/src/file/document/body/section-properties/section-properties.spec.ts +++ b/src/file/document/body/section-properties/section-properties.spec.ts @@ -5,13 +5,30 @@ import { Formatter } from "export/formatter"; import { FooterWrapper } from "file/footer-wrapper"; import { HeaderWrapper } from "file/header-wrapper"; import { Media } from "file/media"; -import { LineNumberRestartFormat } from "./line-number"; +import { NumberFormat } from "file/shared/number-format"; +import { VerticalAlign } from "file/vertical-align"; -import { PageBorderOffsetFrom } from "./page-border"; -import { PageNumberFormat } from "./page-number"; -import { SectionProperties } from "./section-properties"; -import { SectionType } from "./type/section-type-attributes"; -import { SectionVerticalAlignValue } from "./vertical-align"; +import { PageOrientation } from "./properties"; +import { LineNumberRestartFormat } from "./properties/line-number"; +import { PageBorderOffsetFrom } from "./properties/page-borders"; +import { SectionType } from "./properties/section-type"; +import { sectionMarginDefaults, sectionPageSizeDefaults, SectionProperties } from "./section-properties"; + +const DEFAULT_MARGINS = { + "w:bottom": sectionMarginDefaults.BOTTOM, + "w:footer": sectionMarginDefaults.FOOTER, + "w:top": sectionMarginDefaults.TOP, + "w:right": sectionMarginDefaults.RIGHT, + "w:left": sectionMarginDefaults.LEFT, + "w:header": sectionMarginDefaults.HEADER, + "w:gutter": sectionMarginDefaults.GUTTER, +}; + +const PAGE_SIZE_DEFAULTS = { + "w:h": sectionPageSizeDefaults.HEIGHT, + "w:orient": sectionPageSizeDefaults.ORIENTATION, + "w:w": sectionPageSizeDefaults.WIDTH, +}; describe("SectionProperties", () => { describe("#constructor()", () => { @@ -21,26 +38,27 @@ describe("SectionProperties", () => { const properties = new SectionProperties({ page: { size: { - width: 11906, - height: 16838, + width: 1190, + height: 1680, + orientation: PageOrientation.PORTRAIT, }, margin: { - top: convertInchesToTwip(1), - right: convertInchesToTwip(1), - bottom: convertInchesToTwip(1), - left: convertInchesToTwip(1), - header: 708, - footer: 708, - gutter: 0, + top: "2in", + right: "2in", + bottom: "2in", + left: "2in", + header: 808, + footer: 808, + gutter: 10, }, pageNumbers: { start: 10, - formatType: PageNumberFormat.CARDINAL_TEXT, + formatType: NumberFormat.CARDINAL_TEXT, }, }, column: { - space: 708, - count: 1, + space: 208, + count: 2, separate: true, }, grid: { @@ -53,7 +71,7 @@ describe("SectionProperties", () => { even: new FooterWrapper(media, 200), }, titlePage: true, - verticalAlign: SectionVerticalAlignValue.TOP, + verticalAlign: VerticalAlign.TOP, }); const tree = new Formatter().format(properties); @@ -62,25 +80,25 @@ describe("SectionProperties", () => { expect(tree["w:sectPr"]).to.be.an.instanceof(Array); expect(tree["w:sectPr"][0]).to.deep.equal({ "w:headerReference": { _attr: { "r:id": "rId100", "w:type": "default" } } }); expect(tree["w:sectPr"][1]).to.deep.equal({ "w:footerReference": { _attr: { "r:id": "rId200", "w:type": "even" } } }); - expect(tree["w:sectPr"][2]).to.deep.equal({ "w:pgSz": { _attr: { "w:h": 16838, "w:w": 11906, "w:orient": "portrait" } } }); + expect(tree["w:sectPr"][2]).to.deep.equal({ "w:pgSz": { _attr: { "w:h": 1680, "w:w": 1190, "w:orient": "portrait" } } }); expect(tree["w:sectPr"][3]).to.deep.equal({ "w:pgMar": { _attr: { - "w:bottom": 1440, - "w:footer": 708, - "w:top": 1440, - "w:right": 1440, - "w:left": 1440, - "w:header": 708, - "w:gutter": 0, + "w:bottom": "2in", + "w:footer": 808, + "w:top": "2in", + "w:right": "2in", + "w:left": "2in", + "w:header": 808, + "w:gutter": 10, }, }, }); expect(tree["w:sectPr"][4]).to.deep.equal({ "w:pgNumType": { _attr: { "w:fmt": "cardinalText", "w:start": 10 } } }); - expect(tree["w:sectPr"][5]).to.deep.equal({ "w:cols": { _attr: { "w:space": 708, "w:sep": true, "w:num": 1 } } }); + expect(tree["w:sectPr"][5]).to.deep.equal({ "w:cols": { _attr: { "w:space": 208, "w:sep": true, "w:num": 2 } } }); expect(tree["w:sectPr"][6]).to.deep.equal({ "w:vAlign": { _attr: { "w:val": "top" } } }); - expect(tree["w:sectPr"][7]).to.deep.equal({ "w:titlePg": { _attr: { "w:val": "1" } } }); + expect(tree["w:sectPr"][7]).to.deep.equal({ "w:titlePg": {} }); expect(tree["w:sectPr"][8]).to.deep.equal({ "w:docGrid": { _attr: { "w:linePitch": 360 } } }); }); @@ -89,22 +107,12 @@ describe("SectionProperties", () => { const tree = new Formatter().format(properties); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); expect(tree["w:sectPr"]).to.be.an.instanceof(Array); - expect(tree["w:sectPr"][0]).to.deep.equal({ "w:pgSz": { _attr: { "w:h": 16838, "w:w": 11906, "w:orient": "portrait" } } }); + expect(tree["w:sectPr"][0]).to.deep.equal({ "w:pgSz": { _attr: PAGE_SIZE_DEFAULTS } }); expect(tree["w:sectPr"][1]).to.deep.equal({ - "w:pgMar": { - _attr: { - "w:bottom": 1440, - "w:footer": 708, - "w:top": 1440, - "w:right": 1440, - "w:left": 1440, - "w:header": 708, - "w:gutter": 0, - }, - }, + "w:pgMar": { _attr: DEFAULT_MARGINS }, }); - expect(tree["w:sectPr"][3]).to.deep.equal({ "w:cols": { _attr: { "w:space": 708, "w:sep": false, "w:num": 1 } } }); - expect(tree["w:sectPr"][4]).to.deep.equal({ "w:docGrid": { _attr: { "w:linePitch": 360 } } }); + // expect(tree["w:sectPr"][3]).to.deep.equal({ "w:cols": { _attr: { "w:space": 708, "w:sep": false, "w:num": 1 } } }); + expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": { _attr: { "w:linePitch": 360 } } }); }); it("should create section properties with changed options", () => { @@ -118,17 +126,12 @@ describe("SectionProperties", () => { const tree = new Formatter().format(properties); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); expect(tree["w:sectPr"]).to.be.an.instanceof(Array); - expect(tree["w:sectPr"][0]).to.deep.equal({ "w:pgSz": { _attr: { "w:h": 16838, "w:w": 11906, "w:orient": "portrait" } } }); + expect(tree["w:sectPr"][0]).to.deep.equal({ "w:pgSz": { _attr: PAGE_SIZE_DEFAULTS } }); expect(tree["w:sectPr"][1]).to.deep.equal({ "w:pgMar": { _attr: { - "w:bottom": 1440, - "w:footer": 708, + ...DEFAULT_MARGINS, "w:top": 0, - "w:right": 1440, - "w:left": 1440, - "w:header": 708, - "w:gutter": 0, }, }, }); @@ -145,17 +148,12 @@ describe("SectionProperties", () => { const tree = new Formatter().format(properties); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); expect(tree["w:sectPr"]).to.be.an.instanceof(Array); - expect(tree["w:sectPr"][0]).to.deep.equal({ "w:pgSz": { _attr: { "w:h": 16838, "w:w": 11906, "w:orient": "portrait" } } }); + expect(tree["w:sectPr"][0]).to.deep.equal({ "w:pgSz": { _attr: PAGE_SIZE_DEFAULTS } }); expect(tree["w:sectPr"][1]).to.deep.equal({ "w:pgMar": { _attr: { + ...DEFAULT_MARGINS, "w:bottom": 0, - "w:footer": 708, - "w:top": 1440, - "w:right": 1440, - "w:left": 1440, - "w:header": 708, - "w:gutter": 0, }, }, }); @@ -167,24 +165,25 @@ describe("SectionProperties", () => { size: { width: 0, height: 0, + orientation: PageOrientation.LANDSCAPE, }, }, }); const tree = new Formatter().format(properties); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); expect(tree["w:sectPr"]).to.be.an.instanceof(Array); - expect(tree["w:sectPr"][0]).to.deep.equal({ "w:pgSz": { _attr: { "w:h": 0, "w:w": 0, "w:orient": "portrait" } } }); + expect(tree["w:sectPr"][0]).to.deep.equal({ + "w:pgSz": { + _attr: { + "w:h": 0, + "w:orient": PageOrientation.LANDSCAPE, + "w:w": 0, + }, + }, + }); expect(tree["w:sectPr"][1]).to.deep.equal({ "w:pgMar": { - _attr: { - "w:bottom": 1440, - "w:footer": 708, - "w:top": 1440, - "w:right": 1440, - "w:left": 1440, - "w:header": 708, - "w:gutter": 0, - }, + _attr: DEFAULT_MARGINS, }, }); }); @@ -211,7 +210,7 @@ describe("SectionProperties", () => { const properties = new SectionProperties({ page: { pageNumbers: { - formatType: PageNumberFormat.UPPER_ROMAN, + formatType: NumberFormat.UPPER_ROMAN, }, }, }); diff --git a/src/file/document/body/section-properties/section-properties.ts b/src/file/document/body/section-properties/section-properties.ts index 095928d022..ba155a61c4 100644 --- a/src/file/document/body/section-properties/section-properties.ts +++ b/src/file/document/body/section-properties/section-properties.ts @@ -1,29 +1,21 @@ // http://officeopenxml.com/WPsection.php // tslint:disable: no-unnecessary-initializer -import { convertInchesToTwip } from "convenience-functions"; import { FooterWrapper } from "file/footer-wrapper"; import { HeaderWrapper } from "file/header-wrapper"; -import { XmlComponent } from "file/xml-components"; +import { VerticalAlign, VerticalAlignElement } from "file/vertical-align"; +import { OnOffElement, XmlComponent } from "file/xml-components"; -import { Columns } from "./columns/columns"; -import { DocumentGrid } from "./doc-grid/doc-grid"; -import { IDocGridAttributesProperties } from "./doc-grid/doc-grid-attributes"; -import { FooterReferenceType } from "./footer-reference"; -import { FooterReference } from "./footer-reference/footer-reference"; -import { HeaderReferenceType } from "./header-reference"; -import { HeaderReference } from "./header-reference/header-reference"; -import { ILineNumberAttributes, LineNumberType } from "./line-number"; -import { IPageBordersOptions, PageBorders } from "./page-border"; -import { PageMargin } from "./page-margin/page-margin"; -import { IPageMarginAttributes } from "./page-margin/page-margin-attributes"; -import { IPageNumberTypeAttributes, PageNumberType } from "./page-number"; -import { PageSize } from "./page-size/page-size"; -import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes"; -import { TitlePage } from "./title-page/title-page"; -import { Type } from "./type/section-type"; -import { SectionType } from "./type/section-type-attributes"; -import { SectionVerticalAlign, SectionVerticalAlignValue } from "./vertical-align"; +import { HeaderFooterReference, HeaderFooterReferenceType, HeaderFooterType } from "./properties/header-footer-reference"; + +import { Columns, IColumnsAttributes } from "./properties/columns"; +import { DocumentGrid, IDocGridAttributesProperties } from "./properties/doc-grid"; +import { ILineNumberAttributes, LineNumberType } from "./properties/line-number"; +import { IPageBordersOptions, PageBorders } from "./properties/page-borders"; +import { IPageMarginAttributes, PageMargin } from "./properties/page-margin"; +import { IPageNumberTypeAttributes, PageNumberType } from "./properties/page-number"; +import { IPageSizeAttributes, PageOrientation, PageSize } from "./properties/page-size"; +import { SectionType, Type } from "./properties/section-type"; export interface IHeaderFooterGroup { readonly default?: T; @@ -43,53 +35,93 @@ export interface ISectionPropertiesOptions { readonly footerWrapperGroup?: IHeaderFooterGroup; readonly lineNumbers?: ILineNumberAttributes; readonly titlePage?: boolean; - readonly verticalAlign?: SectionVerticalAlignValue; - readonly column?: { - readonly space?: number; - readonly count?: number; - readonly separate?: boolean; - }; + readonly verticalAlign?: VerticalAlign; + readonly column?: IColumnsAttributes; readonly type?: SectionType; } + +// +// +// +// +// +// +// +// + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + +export const sectionMarginDefaults = { + TOP: "1in", + RIGHT: "1in", + BOTTOM: "1in", + LEFT: "1in", + HEADER: 708, + FOOTER: 708, + GUTTER: 0, +}; + +export const sectionPageSizeDefaults = { + WIDTH: 11906, + HEIGHT: 16838, + ORIENTATION: PageOrientation.PORTRAIT, +}; + export class SectionProperties extends XmlComponent { constructor({ page: { - size: { width = 11906, height = 16838, orientation = PageOrientation.PORTRAIT } = {}, + size: { + width = sectionPageSizeDefaults.WIDTH, + height = sectionPageSizeDefaults.HEIGHT, + orientation = sectionPageSizeDefaults.ORIENTATION, + } = {}, margin: { - top = convertInchesToTwip(1), - right = convertInchesToTwip(1), - bottom = convertInchesToTwip(1), - left = convertInchesToTwip(1), - header = 708, - footer = 708, - gutter = 0, - } = {}, - pageNumbers: { - start: pageNumberStart = undefined, - formatType: pageNumberFormatType = undefined, - separator: pageNumberSeparator = undefined, - } = {}, - borders: { - pageBorders = undefined, - pageBorderTop = undefined, - pageBorderRight = undefined, - pageBorderBottom = undefined, - pageBorderLeft = undefined, + top = sectionMarginDefaults.TOP, + right = sectionMarginDefaults.RIGHT, + bottom = sectionMarginDefaults.BOTTOM, + left = sectionMarginDefaults.LEFT, + header = sectionMarginDefaults.HEADER, + footer = sectionMarginDefaults.FOOTER, + gutter = sectionMarginDefaults.GUTTER, } = {}, + pageNumbers = {}, + borders, } = {}, grid: { linePitch = 360 } = {}, headerWrapperGroup = {}, footerWrapperGroup = {}, - lineNumbers: { countBy: lineNumberCountBy, start: lineNumberStart, restart: lineNumberRestart, distance: lineNumberDistance } = {}, - titlePage = false, + lineNumbers, + titlePage, verticalAlign, - column: { space = 708, count = 1, separate = false } = {}, + column, type, }: ISectionPropertiesOptions = {}) { super("w:sectPr"); - this.addHeaders(headerWrapperGroup); - this.addFooters(footerWrapperGroup); + this.addHeaderFooterGroup(HeaderFooterType.HEADER, headerWrapperGroup); + this.addHeaderFooterGroup(HeaderFooterType.FOOTER, footerWrapperGroup); if (type) { this.root.push(new Type(type)); @@ -98,90 +130,58 @@ export class SectionProperties extends XmlComponent { this.root.push(new PageSize(width, height, orientation)); this.root.push(new PageMargin(top, right, bottom, left, header, footer, gutter)); - if (pageBorders || pageBorderTop || pageBorderRight || pageBorderBottom || pageBorderLeft) { - this.root.push( - new PageBorders({ - pageBorders: pageBorders, - pageBorderTop: pageBorderTop, - pageBorderRight: pageBorderRight, - pageBorderBottom: pageBorderBottom, - pageBorderLeft: pageBorderLeft, - }), - ); + if (borders) { + this.root.push(new PageBorders(borders)); } - if (lineNumberCountBy || lineNumberStart || lineNumberRestart || lineNumberDistance) { - this.root.push(new LineNumberType(lineNumberCountBy, lineNumberStart, lineNumberRestart, lineNumberDistance)); + if (lineNumbers) { + this.root.push(new LineNumberType(lineNumbers)); } - this.root.push(new PageNumberType(pageNumberStart, pageNumberFormatType, pageNumberSeparator)); + this.root.push(new PageNumberType(pageNumbers)); - this.root.push(new Columns(space, count, separate)); + if (column) { + this.root.push(new Columns(column)); + } if (verticalAlign) { - this.root.push(new SectionVerticalAlign(verticalAlign)); + this.root.push(new VerticalAlignElement(verticalAlign)); } - if (titlePage) { - this.root.push(new TitlePage()); + if (titlePage !== undefined) { + this.root.push(new OnOffElement("w:titlePg", titlePage)); } this.root.push(new DocumentGrid(linePitch)); } - private addHeaders(headers: IHeaderFooterGroup): void { - if (headers.default) { + private addHeaderFooterGroup( + type: HeaderFooterType, + group: IHeaderFooterGroup | IHeaderFooterGroup, + ): void { + if (group.default) { this.root.push( - new HeaderReference({ - headerType: HeaderReferenceType.DEFAULT, - headerId: headers.default.View.ReferenceId, + new HeaderFooterReference(type, { + type: HeaderFooterReferenceType.DEFAULT, + id: group.default.View.ReferenceId, }), ); } - if (headers.first) { + if (group.first) { this.root.push( - new HeaderReference({ - headerType: HeaderReferenceType.FIRST, - headerId: headers.first.View.ReferenceId, + new HeaderFooterReference(type, { + type: HeaderFooterReferenceType.FIRST, + id: group.first.View.ReferenceId, }), ); } - if (headers.even) { + if (group.even) { this.root.push( - new HeaderReference({ - headerType: HeaderReferenceType.EVEN, - headerId: headers.even.View.ReferenceId, - }), - ); - } - } - - private addFooters(footers: IHeaderFooterGroup): void { - if (footers.default) { - this.root.push( - new FooterReference({ - footerType: FooterReferenceType.DEFAULT, - footerId: footers.default.View.ReferenceId, - }), - ); - } - - if (footers.first) { - this.root.push( - new FooterReference({ - footerType: FooterReferenceType.FIRST, - footerId: footers.first.View.ReferenceId, - }), - ); - } - - if (footers.even) { - this.root.push( - new FooterReference({ - footerType: FooterReferenceType.EVEN, - footerId: footers.even.View.ReferenceId, + new HeaderFooterReference(type, { + type: HeaderFooterReferenceType.EVEN, + id: group.even.View.ReferenceId, }), ); } diff --git a/src/file/document/body/section-properties/title-page/title-page-attributes.ts b/src/file/document/body/section-properties/title-page/title-page-attributes.ts deleted file mode 100644 index 078b6ed47f..0000000000 --- a/src/file/document/body/section-properties/title-page/title-page-attributes.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export class TitlePageAttributes extends XmlAttributeComponent<{ - readonly value: string; -}> { - protected readonly xmlKeys = { - value: "w:val", - }; -} diff --git a/src/file/document/body/section-properties/title-page/title-page.spec.ts b/src/file/document/body/section-properties/title-page/title-page.spec.ts deleted file mode 100644 index e5eed2a429..0000000000 --- a/src/file/document/body/section-properties/title-page/title-page.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { TitlePage } from "./title-page"; - -describe("PageSize", () => { - describe("#constructor()", () => { - it("should create title page property for different first page header", () => { - const properties = new TitlePage(); - const tree = new Formatter().format(properties); - - expect(Object.keys(tree)).to.deep.equal(["w:titlePg"]); - expect(tree["w:titlePg"]).to.deep.equal({ _attr: { "w:val": "1" } }); - }); - }); -}); diff --git a/src/file/document/body/section-properties/title-page/title-page.ts b/src/file/document/body/section-properties/title-page/title-page.ts deleted file mode 100644 index 5b11d77581..0000000000 --- a/src/file/document/body/section-properties/title-page/title-page.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { XmlComponent } from "file/xml-components"; -import { TitlePageAttributes } from "./title-page-attributes"; - -export class TitlePage extends XmlComponent { - constructor() { - super("w:titlePg"); - this.root.push( - new TitlePageAttributes({ - value: "1", - }), - ); - } -} diff --git a/src/file/document/body/section-properties/type/index.ts b/src/file/document/body/section-properties/type/index.ts deleted file mode 100644 index fd7a8abd9c..0000000000 --- a/src/file/document/body/section-properties/type/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./section-type"; -export * from "./section-type-attributes"; diff --git a/src/file/document/body/section-properties/type/section-type-attributes.ts b/src/file/document/body/section-properties/type/section-type-attributes.ts deleted file mode 100644 index 4ac8dd60b4..0000000000 --- a/src/file/document/body/section-properties/type/section-type-attributes.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export enum SectionType { - CONTINUOUS = "continuous", - EVEN_PAGE = "evenPage", - NEXT_COLUMN = "nextColumn", - NEXT_PAGE = "nextPage", - ODD_PAGE = "oddPage", -} - -export class SectionTypeAttributes extends XmlAttributeComponent<{ - readonly val: SectionType; -}> { - protected readonly xmlKeys = { - val: "w:val", - }; -} diff --git a/src/file/document/body/section-properties/type/section-type.ts b/src/file/document/body/section-properties/type/section-type.ts deleted file mode 100644 index 3a11f2e041..0000000000 --- a/src/file/document/body/section-properties/type/section-type.ts +++ /dev/null @@ -1,10 +0,0 @@ -// http://officeopenxml.com/WPsection.php -import { XmlComponent } from "file/xml-components"; -import { SectionType, SectionTypeAttributes } from "./section-type-attributes"; - -export class Type extends XmlComponent { - constructor(value: SectionType) { - super("w:type"); - this.root.push(new SectionTypeAttributes({ val: value })); - } -} diff --git a/src/file/document/body/section-properties/vertical-align/index.ts b/src/file/document/body/section-properties/vertical-align/index.ts deleted file mode 100644 index 1f3fb76bb2..0000000000 --- a/src/file/document/body/section-properties/vertical-align/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./vertical-align"; -export * from "./vertical-align-attributes"; diff --git a/src/file/document/body/section-properties/vertical-align/vertical-align-attributes.ts b/src/file/document/body/section-properties/vertical-align/vertical-align-attributes.ts deleted file mode 100644 index 477bb448b4..0000000000 --- a/src/file/document/body/section-properties/vertical-align/vertical-align-attributes.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; -import { SectionVerticalAlignValue } from "./vertical-align"; - -export class SectionVerticalAlignAttributes extends XmlAttributeComponent<{ - readonly verticalAlign?: SectionVerticalAlignValue; -}> { - protected readonly xmlKeys = { - verticalAlign: "w:val", - }; -} diff --git a/src/file/document/body/section-properties/vertical-align/vertical-align.ts b/src/file/document/body/section-properties/vertical-align/vertical-align.ts deleted file mode 100644 index b025059d16..0000000000 --- a/src/file/document/body/section-properties/vertical-align/vertical-align.ts +++ /dev/null @@ -1,18 +0,0 @@ -// http://officeopenxml.com/WPsection.php - -import { XmlComponent } from "file/xml-components"; -import { SectionVerticalAlignAttributes } from "./vertical-align-attributes"; - -export enum SectionVerticalAlignValue { - BOTH = "both", - BOTTOM = "bottom", - CENTER = "center", - TOP = "top", -} - -export class SectionVerticalAlign extends XmlComponent { - constructor(value: SectionVerticalAlignValue) { - super("w:vAlign"); - this.root.push(new SectionVerticalAlignAttributes({ verticalAlign: value })); - } -} diff --git a/src/file/document/document-background/document-background.spec.ts b/src/file/document/document-background/document-background.spec.ts index 83d1fb36b1..c7d947473d 100644 --- a/src/file/document/document-background/document-background.spec.ts +++ b/src/file/document/document-background/document-background.spec.ts @@ -34,8 +34,8 @@ describe("DocumentBackground", () => { const documentBackground = new DocumentBackground({ color: "ffff00", themeColor: "test", - themeShade: "test", - themeTint: "test", + themeShade: "0A", + themeTint: "0B", }); const tree = new Formatter().format(documentBackground); expect(tree).to.deep.equal({ @@ -43,8 +43,8 @@ describe("DocumentBackground", () => { _attr: { "w:color": "ffff00", "w:themeColor": "test", - "w:themeShade": "test", - "w:themeTint": "test", + "w:themeShade": "0A", + "w:themeTint": "0B", }, }, }); diff --git a/src/file/document/document-background/document-background.ts b/src/file/document/document-background/document-background.ts index 44b04aabe6..bbda932882 100644 --- a/src/file/document/document-background/document-background.ts +++ b/src/file/document/document-background/document-background.ts @@ -1,7 +1,30 @@ // http://officeopenxml.com/WPdocument.php // http://www.datypic.com/sc/ooxml/e-w_background-1.html +import { hexColorValue, uCharHexNumber } from "file/values"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + export class DocumentBackgroundAttributes extends XmlAttributeComponent<{ readonly color: string; readonly themeColor?: string; @@ -23,16 +46,32 @@ export interface IDocumentBackgroundOptions { readonly themeTint?: string; } +// +// +// +// +// +// +// +// +// +// +// +// +// + export class DocumentBackground extends XmlComponent { constructor(options: IDocumentBackgroundOptions) { super("w:background"); this.root.push( new DocumentBackgroundAttributes({ - color: options.color ? options.color : "FFFFFF", + color: hexColorValue(options.color ? options.color : "FFFFFF"), themeColor: options.themeColor, - themeShade: options.themeShade, - themeTint: options.themeTint, + themeShade: options.themeShade === undefined ? undefined : uCharHexNumber(options.themeShade), + themeTint: options.themeTint === undefined ? undefined : uCharHexNumber(options.themeTint), }), ); } diff --git a/src/file/document/document.ts b/src/file/document/document.ts index cc33709076..4059eb5183 100644 --- a/src/file/document/document.ts +++ b/src/file/document/document.ts @@ -11,6 +11,25 @@ export interface IDocumentOptions { readonly background: IDocumentBackgroundOptions; } +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export class Document extends XmlComponent { private readonly body: Body; @@ -50,8 +69,4 @@ export class Document extends XmlComponent { public get Body(): Body { return this.body; } - - public getTablesOfContents(): TableOfContents[] { - return this.body.getTablesOfContents(); - } } diff --git a/src/file/drawing/anchor/anchor.ts b/src/file/drawing/anchor/anchor.ts index 3d2d906500..5fe56d3305 100644 --- a/src/file/drawing/anchor/anchor.ts +++ b/src/file/drawing/anchor/anchor.ts @@ -11,6 +11,31 @@ import { Extent } from "./../extent/extent"; import { GraphicFrameProperties } from "./../graphic-frame/graphic-frame-properties"; import { AnchorAttributes } from "./anchor-attributes"; +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export class Anchor extends XmlComponent { constructor(mediaData: IMediaData, transform: IMediaDataTransformation, drawingOptions: IDrawingOptions) { super("wp:anchor"); diff --git a/src/file/drawing/drawing.ts b/src/file/drawing/drawing.ts index d42f403cf2..df20c8f5f0 100644 --- a/src/file/drawing/drawing.ts +++ b/src/file/drawing/drawing.ts @@ -15,6 +15,13 @@ export interface IDrawingOptions { readonly floating?: IFloating; } +// +// +// +// +// +// + export class Drawing extends XmlComponent { private readonly inline: Inline; @@ -28,8 +35,4 @@ export class Drawing extends XmlComponent { this.root.push(new Anchor(imageData, imageData.transformation, drawingOptions)); } } - - public scale(factorX: number, factorY: number): void { - this.inline.scale(factorX, factorY); - } } diff --git a/src/file/drawing/extent/extent.ts b/src/file/drawing/extent/extent.ts index 500571425b..1945fd0eae 100644 --- a/src/file/drawing/extent/extent.ts +++ b/src/file/drawing/extent/extent.ts @@ -15,11 +15,4 @@ export class Extent extends XmlComponent { this.root.push(this.attributes); } - - public setXY(x: number, y: number): void { - this.attributes.set({ - cx: x, - cy: y, - }); - } } diff --git a/src/file/drawing/floating/horizontal-position.spec.ts b/src/file/drawing/floating/horizontal-position.spec.ts index 705e637dd5..7e9a6223d4 100644 --- a/src/file/drawing/floating/horizontal-position.spec.ts +++ b/src/file/drawing/floating/horizontal-position.spec.ts @@ -49,5 +49,9 @@ describe("HorizontalPosition", () => { ], }); }); + + it("should require one of align or offset", () => { + expect(() => new HorizontalPosition({})).to.throw(); + }); }); }); diff --git a/src/file/drawing/floating/vertical-position.spec.ts b/src/file/drawing/floating/vertical-position.spec.ts index 20f4cbe62a..29c69b5bdc 100644 --- a/src/file/drawing/floating/vertical-position.spec.ts +++ b/src/file/drawing/floating/vertical-position.spec.ts @@ -49,5 +49,9 @@ describe("VerticalPosition", () => { ], }); }); + + it("should require one of align or offset", () => { + expect(() => new VerticalPosition({})).to.throw(); + }); }); }); diff --git a/src/file/drawing/inline/graphic/graphic-data/graphic-data.ts b/src/file/drawing/inline/graphic/graphic-data/graphic-data.ts index 9a4c9a32de..b1a8337690 100644 --- a/src/file/drawing/inline/graphic/graphic-data/graphic-data.ts +++ b/src/file/drawing/inline/graphic/graphic-data/graphic-data.ts @@ -20,8 +20,4 @@ export class GraphicData extends XmlComponent { this.root.push(this.pic); } - - public setXY(x: number, y: number): void { - this.pic.setXY(x, y); - } } diff --git a/src/file/drawing/inline/graphic/graphic-data/pic/pic.ts b/src/file/drawing/inline/graphic/graphic-data/pic/pic.ts index 280dd8796e..df1254331e 100644 --- a/src/file/drawing/inline/graphic/graphic-data/pic/pic.ts +++ b/src/file/drawing/inline/graphic/graphic-data/pic/pic.ts @@ -8,8 +8,6 @@ import { PicAttributes } from "./pic-attributes"; import { ShapeProperties } from "./shape-properties/shape-properties"; export class Pic extends XmlComponent { - private readonly shapeProperties: ShapeProperties; - constructor(mediaData: IMediaData, transform: IMediaDataTransformation) { super("pic:pic"); @@ -19,14 +17,8 @@ export class Pic extends XmlComponent { }), ); - this.shapeProperties = new ShapeProperties(transform); - this.root.push(new NonVisualPicProperties()); this.root.push(new BlipFill(mediaData)); this.root.push(new ShapeProperties(transform)); } - - public setXY(x: number, y: number): void { - this.shapeProperties.setXY(x, y); - } } diff --git a/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/extents/extents.ts b/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/extents/extents.ts index 55bf8bae0b..f17c8de426 100644 --- a/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/extents/extents.ts +++ b/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/extents/extents.ts @@ -15,11 +15,4 @@ export class Extents extends XmlComponent { this.root.push(this.attributes); } - - public setXY(x: number, y: number): void { - this.attributes.set({ - cx: x, - cy: y, - }); - } } diff --git a/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/form.ts b/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/form.ts index 163af208d6..541f862cd5 100644 --- a/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/form.ts +++ b/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/form/form.ts @@ -37,8 +37,4 @@ export class Form extends XmlComponent { this.root.push(new Offset()); this.root.push(this.extents); } - - public setXY(x: number, y: number): void { - this.extents.setXY(x, y); - } } diff --git a/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/shape-properties.ts b/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/shape-properties.ts index 769e242205..2d22b00796 100644 --- a/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/shape-properties.ts +++ b/src/file/drawing/inline/graphic/graphic-data/pic/shape-properties/shape-properties.ts @@ -26,8 +26,4 @@ export class ShapeProperties extends XmlComponent { // this.root.push(new NoFill()); // this.root.push(new Outline()); } - - public setXY(x: number, y: number): void { - this.form.setXY(x, y); - } } diff --git a/src/file/drawing/inline/graphic/graphic.ts b/src/file/drawing/inline/graphic/graphic.ts index 3df31e3346..ca0d23a3a5 100644 --- a/src/file/drawing/inline/graphic/graphic.ts +++ b/src/file/drawing/inline/graphic/graphic.ts @@ -26,8 +26,4 @@ export class Graphic extends XmlComponent { this.root.push(this.data); } - - public setXY(x: number, y: number): void { - this.data.setXY(x, y); - } } diff --git a/src/file/drawing/inline/inline.ts b/src/file/drawing/inline/inline.ts index 2b124fc524..ee37094b84 100644 --- a/src/file/drawing/inline/inline.ts +++ b/src/file/drawing/inline/inline.ts @@ -8,11 +8,25 @@ import { GraphicFrameProperties } from "./../graphic-frame/graphic-frame-propert import { Graphic } from "./../inline/graphic"; import { InlineAttributes } from "./inline-attributes"; +// +// +// +// +// +// +// +// +// +// +// +// +// export class Inline extends XmlComponent { private readonly extent: Extent; private readonly graphic: Graphic; - constructor(mediaData: IMediaData, private readonly transform: IMediaDataTransformation) { + constructor(mediaData: IMediaData, transform: IMediaDataTransformation) { super("wp:inline"); this.root.push( @@ -33,12 +47,4 @@ export class Inline extends XmlComponent { this.root.push(new GraphicFrameProperties()); this.root.push(this.graphic); } - - public scale(factorX: number, factorY: number): void { - const newX = Math.round(this.transform.emus.x * factorX); - const newY = Math.round(this.transform.emus.y * factorY); - - this.extent.setXY(newX, newY); - this.graphic.setXY(newX, newY); - } } diff --git a/src/file/file.spec.ts b/src/file/file.spec.ts index a3cd9fbf65..321a03dc41 100644 --- a/src/file/file.spec.ts +++ b/src/file/file.spec.ts @@ -1,11 +1,18 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { sectionMarginDefaults, sectionPageSizeDefaults } from "./document"; import { File } from "./file"; import { Footer, Header } from "./header"; import { Paragraph } from "./paragraph"; +const PAGE_SIZE_DEFAULTS = { + "w:h": sectionPageSizeDefaults.HEIGHT, + "w:orient": sectionPageSizeDefaults.ORIENTATION, + "w:w": sectionPageSizeDefaults.WIDTH, +}; + describe("File", () => { describe("#constructor", () => { it("should create with correct headers and footers", () => { @@ -114,23 +121,19 @@ describe("File", () => { "w:sectPr": [ { "w:pgSz": { - _attr: { - "w:h": 16838, - "w:orient": "portrait", - "w:w": 11906, - }, + _attr: PAGE_SIZE_DEFAULTS, }, }, { "w:pgMar": { _attr: { - "w:bottom": 1440, - "w:footer": 708, - "w:gutter": 0, - "w:header": 708, - "w:left": 1440, - "w:right": 1440, - "w:top": 1440, + "w:bottom": sectionMarginDefaults.BOTTOM, + "w:footer": sectionMarginDefaults.FOOTER, + "w:gutter": sectionMarginDefaults.GUTTER, + "w:header": sectionMarginDefaults.HEADER, + "w:left": sectionMarginDefaults.LEFT, + "w:right": sectionMarginDefaults.RIGHT, + "w:top": sectionMarginDefaults.TOP, }, }, }, @@ -139,15 +142,15 @@ describe("File", () => { _attr: {}, }, }, - { - "w:cols": { - _attr: { - "w:num": 1, - "w:sep": false, - "w:space": 708, - }, - }, - }, + // { + // "w:cols": { + // _attr: { + // "w:num": 1, + // "w:sep": false, + // "w:space": 708, + // }, + // }, + // }, { "w:docGrid": { _attr: { @@ -162,20 +165,6 @@ describe("File", () => { }); }); - describe("#addTrackRevisionsFeature", () => { - it("should call the underlying document's add", () => { - const file = new File({ - features: { - trackRevisions: true, - }, - sections: [], - }); - - // tslint:disable-next-line: no-unused-expression no-string-literal - expect(file.Settings["trackRevisions"]).to.exist; - }); - }); - describe("#createFootnote", () => { it("should create footnote", () => { const wrapper = new File({ diff --git a/src/file/file.ts b/src/file/file.ts index 66d4d84b51..0e6c9fc352 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -3,7 +3,7 @@ import { ContentTypes } from "./content-types/content-types"; import { CoreProperties, IPropertiesOptions } from "./core-properties"; import { CustomProperties } from "./custom-properties"; import { DocumentWrapper } from "./document-wrapper"; -import { FooterReferenceType, HeaderReferenceType, ISectionPropertiesOptions } from "./document/body/section-properties"; +import { HeaderFooterReferenceType, ISectionPropertiesOptions } from "./document/body/section-properties"; import { IFileProperties } from "./file-properties"; import { FooterWrapper, IDocumentFooter } from "./footer-wrapper"; import { FootnotesWrapper } from "./footnotes-wrapper"; @@ -78,6 +78,8 @@ export class File { this.settings = new Settings({ compatabilityModeVersion: options.compatabilityModeVersion, evenAndOddHeaders: options.evenAndOddHeaderAndFooters ? true : false, + trackRevisions: options.features?.trackRevisions, + updateFields: options.features?.updateFields, }); this.media = fileProperties.template && fileProperties.template.media ? fileProperties.template.media : new Media(); @@ -132,18 +134,6 @@ export class File { this.footnotesWrapper.View.createFootNote(parseFloat(key), options.footnotes[key].children); } } - - if (options.features) { - if (options.features.trackRevisions) { - this.settings.addTrackRevisions(); - } - } - } - - public verifyUpdateFields(): void { - if (this.documentWrapper.View.getTablesOfContents().length) { - this.settings.addUpdateFields(); - } } private addSection({ headers = {}, footers = {}, children, properties }: ISectionOptions): void { @@ -188,7 +178,7 @@ export class File { return wrapper; } - private addHeaderToDocument(header: HeaderWrapper, type: HeaderReferenceType = HeaderReferenceType.DEFAULT): void { + private addHeaderToDocument(header: HeaderWrapper, type: HeaderFooterReferenceType = HeaderFooterReferenceType.DEFAULT): void { this.headers.push({ header, type }); this.documentWrapper.Relationships.createRelationship( header.View.ReferenceId, @@ -198,7 +188,7 @@ export class File { this.contentTypes.addHeader(this.headers.length); } - private addFooterToDocument(footer: FooterWrapper, type: FooterReferenceType = FooterReferenceType.DEFAULT): void { + private addFooterToDocument(footer: FooterWrapper, type: HeaderFooterReferenceType = HeaderFooterReferenceType.DEFAULT): void { this.footers.push({ footer, type }); this.documentWrapper.Relationships.createRelationship( footer.View.ReferenceId, diff --git a/src/file/footer-wrapper.ts b/src/file/footer-wrapper.ts index e16a72e974..34162320c0 100644 --- a/src/file/footer-wrapper.ts +++ b/src/file/footer-wrapper.ts @@ -1,6 +1,6 @@ import { XmlComponent } from "file/xml-components"; -import { FooterReferenceType } from "./document"; +import { HeaderFooterReferenceType } from "./document"; import { IViewWrapper } from "./document-wrapper"; import { Footer } from "./footer/footer"; import { Media } from "./media"; @@ -10,7 +10,7 @@ import { Table } from "./table"; export interface IDocumentFooter { readonly footer: FooterWrapper; - readonly type: FooterReferenceType; + readonly type: HeaderFooterReferenceType; } export class FooterWrapper implements IViewWrapper { diff --git a/src/file/footnotes/footnote/run/reference-run.ts b/src/file/footnotes/footnote/run/reference-run.ts index 3bfd772f50..c355bb17a3 100644 --- a/src/file/footnotes/footnote/run/reference-run.ts +++ b/src/file/footnotes/footnote/run/reference-run.ts @@ -1,5 +1,4 @@ import { Run } from "file/paragraph/run"; -import { Style } from "file/paragraph/run/style"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export class FootNoteReferenceRunAttributes extends XmlAttributeComponent<{ @@ -24,9 +23,7 @@ export class FootnoteReference extends XmlComponent { export class FootnoteReferenceRun extends Run { constructor(id: number) { - super({}); - - this.properties.push(new Style("FootnoteReference")); + super({ style: "FootnoteReference" }); this.root.push(new FootnoteReference(id)); } diff --git a/src/file/header-wrapper.ts b/src/file/header-wrapper.ts index 945a9d674a..40fbd7bb1a 100644 --- a/src/file/header-wrapper.ts +++ b/src/file/header-wrapper.ts @@ -1,6 +1,6 @@ import { XmlComponent } from "file/xml-components"; -import { HeaderReferenceType } from "./document"; +import { HeaderFooterReferenceType } from "./document"; import { IViewWrapper } from "./document-wrapper"; import { Header } from "./header/header"; import { Media } from "./media"; @@ -10,7 +10,7 @@ import { Table } from "./table"; export interface IDocumentHeader { readonly header: HeaderWrapper; - readonly type: HeaderReferenceType; + readonly type: HeaderFooterReferenceType; } export class HeaderWrapper implements IViewWrapper { diff --git a/src/file/index.ts b/src/file/index.ts index 63761d4555..1ab2a79cfd 100644 --- a/src/file/index.ts +++ b/src/file/index.ts @@ -6,6 +6,7 @@ export * from "./numbering"; export * from "./media"; export * from "./drawing"; export * from "./document"; +export * from "./shading"; export * from "./styles"; export * from "./table-of-contents"; export * from "./xml-components"; @@ -15,3 +16,6 @@ export * from "./header"; export * from "./footnotes"; export * from "./track-revision"; export * from "./shared"; +export * from "./border"; +export * from "./values"; +export * from "./vertical-align"; diff --git a/src/file/numbering/abstract-numbering.spec.ts b/src/file/numbering/abstract-numbering.spec.ts index b2d9f19bc1..8ab0845ed8 100644 --- a/src/file/numbering/abstract-numbering.spec.ts +++ b/src/file/numbering/abstract-numbering.spec.ts @@ -5,7 +5,7 @@ import { EMPTY_OBJECT } from "file/xml-components"; import { AlignmentType, EmphasisMarkType, TabStopPosition } from "../paragraph"; import { UnderlineType } from "../paragraph/run/underline"; -import { ShadingType } from "../table"; +import { ShadingType } from "../shading"; import { AbstractNumbering } from "./abstract-numbering"; import { LevelFormat, LevelSuffix } from "./level"; @@ -351,7 +351,7 @@ describe("AbstractNumbering", () => { ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ - "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:smallCaps": {} }], }); }); @@ -370,7 +370,7 @@ describe("AbstractNumbering", () => { ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ - "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:caps": {} }], }); }); @@ -390,7 +390,7 @@ describe("AbstractNumbering", () => { const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ - "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:strike": {} }], }); }); @@ -409,7 +409,7 @@ describe("AbstractNumbering", () => { ]); const tree = new Formatter().format(abstractNumbering); expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ - "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:dstrike": {} }], }); }); @@ -515,17 +515,17 @@ describe("AbstractNumbering", () => { const boldTests = [ { bold: true, - expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }, { "w:bCs": {} }], }, { bold: true, boldComplexScript: true, - expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }, { "w:bCs": {} }], }, { bold: true, boldComplexScript: false, - expected: [{ "w:b": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }], }, ]; boldTests.forEach(({ bold, boldComplexScript, expected }) => { @@ -548,17 +548,17 @@ describe("AbstractNumbering", () => { const italicsTests = [ { italics: true, - expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }, { "w:iCs": {} }], }, { italics: true, italicsComplexScript: true, - expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }, { "w:iCs": {} }], }, { italics: true, italicsComplexScript: false, - expected: [{ "w:i": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }], }, ]; italicsTests.forEach(({ italics, italicsComplexScript, expected }) => { @@ -617,37 +617,13 @@ describe("AbstractNumbering", () => { }); const shadingTests = [ - { - shadow: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, { shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", + type: ShadingType.DIAGONAL_STRIPE, + fill: "006622", + color: "0000FF", }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, - { - shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, - { - shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], + expected: [{ "w:shd": { _attr: { "w:val": "diagStripe", "w:fill": "006622", "w:color": "0000FF" } } }], }, { shading: { @@ -658,15 +634,15 @@ describe("AbstractNumbering", () => { expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], }, ]; - shadingTests.forEach(({ shadow, shading, expected }) => { - it("#shadow correctly", () => { + shadingTests.forEach(({ shading, expected }) => { + it("#shade correctly", () => { const abstractNumbering = new AbstractNumbering(1, [ { level: 0, format: LevelFormat.LOWER_ROMAN, text: "%0.", style: { - run: { shadow, shading }, + run: { shading }, }, }, ]); diff --git a/src/file/numbering/abstract-numbering.ts b/src/file/numbering/abstract-numbering.ts index 363d233ac9..6c4af129c3 100644 --- a/src/file/numbering/abstract-numbering.ts +++ b/src/file/numbering/abstract-numbering.ts @@ -1,8 +1,24 @@ +import { decimalNumber } from "file/values"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { ILevelsOptions, Level } from "./level"; import { MultiLevelType } from "./multi-level-type"; +// +// +// +// +// +// +// +// +// +// +// +// + +// +// https://docs.microsoft.com/en-us/openspecs/office_standards/ms-docx/cbddeff8-01aa-4486-a48e-6a83dede4f13 class AbstractNumberingAttributes extends XmlAttributeComponent<{ readonly abstractNumId: number; readonly restartNumberingAfterBreak: number; @@ -20,7 +36,7 @@ export class AbstractNumbering extends XmlComponent { super("w:abstractNum"); this.root.push( new AbstractNumberingAttributes({ - abstractNumId: id, + abstractNumId: decimalNumber(id), restartNumberingAfterBreak: 0, }), ); diff --git a/src/file/numbering/level.ts b/src/file/numbering/level.ts index d189e24e01..93fcbe6f67 100644 --- a/src/file/numbering/level.ts +++ b/src/file/numbering/level.ts @@ -1,5 +1,6 @@ // http://officeopenxml.com/WPnumbering-numFmt.php -import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { decimalNumber } from "file/values"; +import { Attributes, NumberValueElement, XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { AlignmentType } from "../paragraph/formatting"; import { IParagraphStylePropertiesOptions, ParagraphProperties } from "../paragraph/properties"; import { IRunStylePropertiesOptions, RunProperties } from "../paragraph/run/properties"; @@ -31,17 +32,10 @@ class LevelAttributes extends XmlAttributeComponent<{ }; } -class Start extends XmlComponent { - constructor(value: number) { - super("w:start"); - this.root.push( - new Attributes({ - val: value, - }), - ); - } -} - +// +// +// +// class NumberFormat extends XmlComponent { constructor(value: string) { super("w:numFmt"); @@ -53,6 +47,10 @@ class NumberFormat extends XmlComponent { } } +// +// +// +// class LevelText extends XmlComponent { constructor(value: string) { super("w:lvlText"); @@ -94,6 +92,16 @@ export interface ILevelsOptions { }; } +// +// +// +// +// +// +// +// +// +// class Suffix extends XmlComponent { constructor(value: LevelSuffix) { super("w:suff"); @@ -105,6 +113,25 @@ class Suffix extends XmlComponent { } } +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export class LevelBase extends XmlComponent { private readonly paragraphProperties: ParagraphProperties; private readonly runProperties: RunProperties; @@ -112,7 +139,7 @@ export class LevelBase extends XmlComponent { constructor({ level, format, text, alignment = AlignmentType.START, start = 1, style, suffix }: ILevelsOptions) { super("w:lvl"); - this.root.push(new Start(start)); + this.root.push(new NumberValueElement("w:start", decimalNumber(start))); if (format) { this.root.push(new NumberFormat(format)); @@ -136,7 +163,7 @@ export class LevelBase extends XmlComponent { this.root.push( new LevelAttributes({ - ilvl: level, + ilvl: decimalNumber(level), tentative: 1, }), ); diff --git a/src/file/numbering/multi-level-type.ts b/src/file/numbering/multi-level-type.ts index c73ebec3cf..c2d53bb8cf 100644 --- a/src/file/numbering/multi-level-type.ts +++ b/src/file/numbering/multi-level-type.ts @@ -1,5 +1,19 @@ import { Attributes, XmlComponent } from "file/xml-components"; +// +// ... +// + +// +// +// +// +// +// +// +// +// +// export class MultiLevelType extends XmlComponent { constructor(value: string) { super("w:multiLevelType"); diff --git a/src/file/numbering/num.ts b/src/file/numbering/num.ts index 526d0f1b9f..87cd399a96 100644 --- a/src/file/numbering/num.ts +++ b/src/file/numbering/num.ts @@ -1,3 +1,4 @@ +import { decimalNumber } from "file/values"; import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components"; class AbstractNumId extends XmlComponent { @@ -28,6 +29,17 @@ export interface IConcreteNumberingOptions { }; } +// +// ... +// + +// +// +// +// +// +// +// export class ConcreteNumbering extends XmlComponent { public readonly numId: number; public readonly reference: string; @@ -42,11 +54,11 @@ export class ConcreteNumbering extends XmlComponent { this.root.push( new NumAttributes({ - numId: options.numId, + numId: decimalNumber(options.numId), }), ); - this.root.push(new AbstractNumId(options.abstractNumId)); + this.root.push(new AbstractNumId(decimalNumber(options.abstractNumId))); if (options.overrideLevel) { this.root.push(new LevelOverride(options.overrideLevel.num, options.overrideLevel.start)); diff --git a/src/file/numbering/numbering.ts b/src/file/numbering/numbering.ts index 7798d373d6..8f37b386c5 100644 --- a/src/file/numbering/numbering.ts +++ b/src/file/numbering/numbering.ts @@ -16,6 +16,16 @@ export interface INumberingOptions { }[]; } +// +// +// +// +// +// +// +// +// +// export class Numbering extends XmlComponent { private readonly abstractNumberingMap = new Map(); private readonly concreteNumberingMap = new Map(); @@ -149,7 +159,7 @@ export class Numbering extends XmlComponent { this.concreteNumberingMap.set( "default-bullet-numbering", new ConcreteNumbering({ - numId: 0, + numId: 1, abstractNumId: abstractNumbering.id, reference: "default-bullet-numbering", instance: 0, diff --git a/src/file/paragraph/formatting/bidirectional.spec.ts b/src/file/paragraph/formatting/bidirectional.spec.ts deleted file mode 100644 index a197ada54e..0000000000 --- a/src/file/paragraph/formatting/bidirectional.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { Bidirectional } from "./bidirectional"; - -describe("Bidirectional", () => { - it("should create", () => { - const bidirectional = new Bidirectional(); - const tree = new Formatter().format(bidirectional); - expect(tree).to.deep.equal({ - "w:bidi": {}, - }); - }); -}); diff --git a/src/file/paragraph/formatting/bidirectional.ts b/src/file/paragraph/formatting/bidirectional.ts deleted file mode 100644 index 4083247e78..0000000000 --- a/src/file/paragraph/formatting/bidirectional.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { XmlComponent } from "file/xml-components"; - -export class Bidirectional extends XmlComponent { - constructor() { - super("w:bidi"); - } -} diff --git a/src/file/paragraph/formatting/border-attributes.ts b/src/file/paragraph/formatting/border-attributes.ts deleted file mode 100644 index 304128426b..0000000000 --- a/src/file/paragraph/formatting/border-attributes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { XmlAttributeComponent } from "file/xml-components"; - -export class BorderAttributes extends XmlAttributeComponent<{ - readonly color: string; - readonly space: number; - readonly val: string; - readonly sz: number; -}> { - protected readonly xmlKeys = { - val: "w:val", - color: "w:color", - space: "w:space", - sz: "w:sz", - }; -} diff --git a/src/file/paragraph/formatting/border.spec.ts b/src/file/paragraph/formatting/border.spec.ts index 6e89ffcfc8..33e026f7c4 100644 --- a/src/file/paragraph/formatting/border.spec.ts +++ b/src/file/paragraph/formatting/border.spec.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { BorderStyle } from "file/border"; import { Border, ThematicBreak } from "./border"; describe("Border", () => { @@ -9,27 +10,27 @@ describe("Border", () => { it("should create", () => { const border = new Border({ top: { - color: "red", + color: "FF0000", space: 1, - value: "test", + style: BorderStyle.WAVE, size: 2, }, bottom: { - color: "red", + color: "FF0000", space: 3, - value: "test", + style: BorderStyle.WAVE, size: 4, }, left: { - color: "red", + color: "FF0000", space: 5, - value: "test", + style: BorderStyle.WAVE, size: 6, }, right: { - color: "red", + color: "FF0000", space: 7, - value: "test", + style: BorderStyle.WAVE, size: 8, }, }); @@ -41,46 +42,51 @@ describe("Border", () => { { "w:top": { _attr: { - "w:color": "red", + "w:color": "FF0000", "w:space": 1, "w:sz": 2, - "w:val": "test", + "w:val": "wave", }, }, }, { "w:bottom": { _attr: { - "w:color": "red", + "w:color": "FF0000", "w:space": 3, "w:sz": 4, - "w:val": "test", + "w:val": "wave", }, }, }, { "w:left": { _attr: { - "w:color": "red", + "w:color": "FF0000", "w:space": 5, "w:sz": 6, - "w:val": "test", + "w:val": "wave", }, }, }, { "w:right": { _attr: { - "w:color": "red", + "w:color": "FF0000", "w:space": 7, "w:sz": 8, - "w:val": "test", + "w:val": "wave", }, }, }, ], }); }); + + it("should not add empty borders element if there are no borders defined", () => { + const tb = new Border({}); + expect(() => new Formatter().format(tb)).to.throw(); + }); }); }); diff --git a/src/file/paragraph/formatting/border.ts b/src/file/paragraph/formatting/border.ts index 9fdb9b65c3..b84b24df99 100644 --- a/src/file/paragraph/formatting/border.ts +++ b/src/file/paragraph/formatting/border.ts @@ -1,57 +1,32 @@ // http://officeopenxml.com/WPborders.php -import { XmlComponent } from "file/xml-components"; -import { BorderAttributes } from "./border-attributes"; +import { BorderElement, BorderStyle, IBorderOptions } from "file/border"; +import { IgnoreIfEmptyXmlComponent, XmlComponent } from "file/xml-components"; -interface IBorderPropertyOptions { - readonly color: string; - readonly space: number; - readonly value: string; - readonly size: number; +export interface IBordersOptions { + readonly top?: IBorderOptions; + readonly bottom?: IBorderOptions; + readonly left?: IBorderOptions; + readonly right?: IBorderOptions; } -export interface IBorderOptions { - readonly top?: IBorderPropertyOptions; - readonly bottom?: IBorderPropertyOptions; - readonly left?: IBorderPropertyOptions; - readonly right?: IBorderPropertyOptions; -} - -class BorderProperty extends XmlComponent { - constructor(rootKey: string, options: IBorderPropertyOptions = { color: "auto", space: 1, value: "single", size: 6 }) { - super(rootKey); - - const attrs = new BorderAttributes({ - color: options.color, - space: options.space, - val: options.value, - sz: options.size, - }); - this.root.push(attrs); - } -} - -export class Border extends XmlComponent { - constructor(options: IBorderOptions) { +export class Border extends IgnoreIfEmptyXmlComponent { + constructor(options: IBordersOptions) { super("w:pBdr"); - if (options.top !== undefined) { - const borderProperty = new BorderProperty("w:top", options.top); - this.root.push(borderProperty); + if (options.top) { + this.root.push(new BorderElement("w:top", options.top)); } - if (options.bottom !== undefined) { - const borderProperty = new BorderProperty("w:bottom", options.bottom); - this.root.push(borderProperty); + if (options.bottom) { + this.root.push(new BorderElement("w:bottom", options.bottom)); } - if (options.left !== undefined) { - const borderProperty = new BorderProperty("w:left", options.left); - this.root.push(borderProperty); + if (options.left) { + this.root.push(new BorderElement("w:left", options.left)); } - if (options.right !== undefined) { - const borderProperty = new BorderProperty("w:right", options.right); - this.root.push(borderProperty); + if (options.right) { + this.root.push(new BorderElement("w:right", options.right)); } } } @@ -59,10 +34,10 @@ export class Border extends XmlComponent { export class ThematicBreak extends XmlComponent { constructor() { super("w:pBdr"); - const bottom = new BorderProperty("w:bottom", { + const bottom = new BorderElement("w:bottom", { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }); this.root.push(bottom); diff --git a/src/file/paragraph/formatting/indent.ts b/src/file/paragraph/formatting/indent.ts index 3aad7d4e8a..aad272fa7c 100644 --- a/src/file/paragraph/formatting/indent.ts +++ b/src/file/paragraph/formatting/indent.ts @@ -1,29 +1,57 @@ // http://officeopenxml.com/WPindentation.php +import { signedTwipsMeasureValue, twipsMeasureValue } from "file/values"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export interface IIndentAttributesProperties { - readonly left?: number; - readonly hanging?: number; - readonly firstLine?: number; - readonly start?: number; - readonly end?: number; - readonly right?: number; + readonly start?: number | string; + readonly end?: number | string; + readonly left?: number | string; + readonly right?: number | string; + readonly hanging?: number | string; + readonly firstLine?: number | string; } +// +// +// +// +// +// +// +// +// +// +// +// +// +// class IndentAttributes extends XmlAttributeComponent { protected readonly xmlKeys = { - left: "w:left", - hanging: "w:hanging", - firstLine: "w:firstLine", start: "w:start", end: "w:end", - right: "w:end", // alias + left: "w:left", + right: "w:right", + hanging: "w:hanging", + firstLine: "w:firstLine", }; } +// +// +// ... +// export class Indent extends XmlComponent { - constructor(attrs: IIndentAttributesProperties) { + constructor({ start, end, left, right, hanging, firstLine }: IIndentAttributesProperties) { super("w:ind"); - this.root.push(new IndentAttributes(attrs)); + this.root.push( + new IndentAttributes({ + start: start === undefined ? undefined : signedTwipsMeasureValue(start), + end: end === undefined ? undefined : signedTwipsMeasureValue(end), + left: left === undefined ? undefined : signedTwipsMeasureValue(left), + right: right === undefined ? undefined : signedTwipsMeasureValue(right), + hanging: hanging === undefined ? undefined : twipsMeasureValue(hanging), + firstLine: firstLine === undefined ? undefined : twipsMeasureValue(firstLine), + }), + ); } } diff --git a/src/file/paragraph/formatting/index.ts b/src/file/paragraph/formatting/index.ts index 2c8829ee9d..0d83804dc6 100644 --- a/src/file/paragraph/formatting/index.ts +++ b/src/file/paragraph/formatting/index.ts @@ -1,7 +1,6 @@ export * from "./alignment"; export * from "./border"; export * from "./indent"; -export * from "./keep"; export * from "./page-break"; export * from "./spacing"; export * from "./style"; diff --git a/src/file/paragraph/formatting/keep.ts b/src/file/paragraph/formatting/keep.ts deleted file mode 100644 index 5f1abb53f8..0000000000 --- a/src/file/paragraph/formatting/keep.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { XmlComponent } from "file/xml-components"; - -export class KeepLines extends XmlComponent { - constructor() { - super("w:keepLines"); - } -} - -export class KeepNext extends XmlComponent { - constructor() { - super("w:keepNext"); - } -} diff --git a/src/file/paragraph/formatting/spacing.spec.ts b/src/file/paragraph/formatting/spacing.spec.ts index 8241fb282c..09d88dc31c 100644 --- a/src/file/paragraph/formatting/spacing.spec.ts +++ b/src/file/paragraph/formatting/spacing.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { ContextualSpacing, Spacing } from "./spacing"; +import { Spacing } from "./spacing"; describe("Spacing", () => { describe("#constructor", () => { @@ -23,23 +23,3 @@ describe("Spacing", () => { }); }); }); - -describe("ContextualSpacing", () => { - describe("#constructor", () => { - it("should create", () => { - const spacing = new ContextualSpacing(true); - const tree = new Formatter().format(spacing); - expect(tree).to.deep.equal({ - "w:contextualSpacing": { _attr: { "w:val": 1 } }, - }); - }); - - it("should create with value of 0 if param is false", () => { - const spacing = new ContextualSpacing(false); - const tree = new Formatter().format(spacing); - expect(tree).to.deep.equal({ - "w:contextualSpacing": { _attr: { "w:val": 0 } }, - }); - }); - }); -}); diff --git a/src/file/paragraph/formatting/spacing.ts b/src/file/paragraph/formatting/spacing.ts index 7eac5cea72..bac1a17049 100644 --- a/src/file/paragraph/formatting/spacing.ts +++ b/src/file/paragraph/formatting/spacing.ts @@ -1,5 +1,5 @@ // http://officeopenxml.com/WPspacing.php -import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export enum LineRuleType { AT_LEAST = "atLeast", @@ -30,14 +30,3 @@ export class Spacing extends XmlComponent { this.root.push(new SpacingAttributes(options)); } } - -export class ContextualSpacing extends XmlComponent { - constructor(value: boolean) { - super("w:contextualSpacing"); - this.root.push( - new Attributes({ - val: value === false ? 0 : 1, - }), - ); - } -} diff --git a/src/file/paragraph/formatting/widow-control.spec.ts b/src/file/paragraph/formatting/widow-control.spec.ts deleted file mode 100644 index e6e50fb13c..0000000000 --- a/src/file/paragraph/formatting/widow-control.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { WidowControl } from "./widow-control"; - -describe("WidowControl", () => { - it("should create", () => { - const widowControl = new WidowControl(true); - const tree = new Formatter().format(widowControl); - - expect(tree).to.deep.equal({ - "w:widowControl": { - _attr: { - "w:val": true, - }, - }, - }); - }); -}); diff --git a/src/file/paragraph/formatting/widow-control.ts b/src/file/paragraph/formatting/widow-control.ts deleted file mode 100644 index cbfc125564..0000000000 --- a/src/file/paragraph/formatting/widow-control.ts +++ /dev/null @@ -1,13 +0,0 @@ -// http://www.datypic.com/sc/ooxml/e-w_widowControl-1.html -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -export class WidowControlAttributes extends XmlAttributeComponent<{ readonly val: boolean }> { - protected readonly xmlKeys = { val: "w:val" }; -} - -export class WidowControl extends XmlComponent { - constructor(value: boolean) { - super("w:widowControl"); - this.root.push(new WidowControlAttributes({ val: value })); - } -} diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 5b2f95ed58..d898777b12 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -2,13 +2,15 @@ import { assert, expect } from "chai"; import { SinonStub, stub } from "sinon"; import * as convenienceFunctions from "convenience-functions"; + import { Formatter } from "export/formatter"; +import { BorderStyle } from "file/border"; import { EMPTY_OBJECT } from "file/xml-components"; import { IViewWrapper } from "../document-wrapper"; import { File } from "../file"; +import { ShadingType } from "../shading"; import { HorizontalPositionAlign, VerticalPositionAlign } from "../shared"; -import { ShadingType } from "../table/shading"; import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; import { FrameAnchorType } from "./frame"; import { Bookmark, ExternalHyperlink } from "./links"; @@ -414,7 +416,7 @@ describe("Paragraph", () => { }); describe("#contextualSpacing()", () => { - it("should add contextualSpacing to JSON, and set 1 if true", () => { + it("should add contextualSpacing", () => { const paragraph = new Paragraph({ contextualSpacing: true, }); @@ -422,7 +424,20 @@ describe("Paragraph", () => { expect(tree).to.deep.equal({ "w:p": [ { - "w:pPr": [{ "w:contextualSpacing": { _attr: { "w:val": 1 } } }], + "w:pPr": [{ "w:contextualSpacing": {} }], + }, + ], + }); + }); + it("should remove contextualSpacing", () => { + const paragraph = new Paragraph({ + contextualSpacing: false, + }); + const tree = new Formatter().format(paragraph); + expect(tree).to.deep.equal({ + "w:p": [ + { + "w:pPr": [{ "w:contextualSpacing": { _attr: { "w:val": false } } }], }, ], }); @@ -467,13 +482,13 @@ describe("Paragraph", () => { left: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, right: { color: "auto", space: 1, - value: "single", + style: BorderStyle.SINGLE, size: 6, }, }, diff --git a/src/file/paragraph/properties.spec.ts b/src/file/paragraph/properties.spec.ts index deb7f416c2..e8961ceea6 100644 --- a/src/file/paragraph/properties.spec.ts +++ b/src/file/paragraph/properties.spec.ts @@ -86,11 +86,7 @@ describe("ParagraphProperties", () => { expect(tree).to.deep.equal({ "w:pPr": [ { - "w:widowControl": { - _attr: { - "w:val": true, - }, - }, + "w:widowControl": {}, }, ], }); diff --git a/src/file/paragraph/properties.ts b/src/file/paragraph/properties.ts index 6a7fe283fd..d3cb2081e9 100644 --- a/src/file/paragraph/properties.ts +++ b/src/file/paragraph/properties.ts @@ -1,21 +1,17 @@ // http://officeopenxml.com/WPparagraphProperties.php -import { IContext, IgnoreIfEmptyXmlComponent, IXmlableObject, XmlComponent } from "file/xml-components"; +import { IContext, IgnoreIfEmptyXmlComponent, IXmlableObject, OnOffElement, XmlComponent } from "file/xml-components"; import { DocumentWrapper } from "../document-wrapper"; -import { ShadingType } from "../table/shading"; +import { IShadingAttributesProperties, Shading } from "../shading"; import { Alignment, AlignmentType } from "./formatting/alignment"; -import { Bidirectional } from "./formatting/bidirectional"; -import { Border, IBorderOptions, ThematicBreak } from "./formatting/border"; +import { Border, IBordersOptions, ThematicBreak } from "./formatting/border"; import { IIndentAttributesProperties, Indent } from "./formatting/indent"; -import { KeepLines, KeepNext } from "./formatting/keep"; import { PageBreakBefore } from "./formatting/page-break"; -import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spacing"; +import { ISpacingProperties, Spacing } from "./formatting/spacing"; import { HeadingLevel, Style } from "./formatting/style"; import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; -import { WidowControl } from "./formatting/widow-control"; import { FrameProperties, IFrameOptions } from "./frame/frame-properties"; import { OutlineLevel } from "./links"; -import { Shading } from "./run/formatting"; export interface IParagraphStylePropertiesOptions { readonly alignment?: AlignmentType; @@ -31,7 +27,7 @@ export interface IParagraphStylePropertiesOptions { } export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOptions { - readonly border?: IBorderOptions; + readonly border?: IBordersOptions; readonly heading?: HeadingLevel; readonly bidirectional?: boolean; readonly pageBreakBefore?: boolean; @@ -50,11 +46,7 @@ export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOp readonly instance?: number; readonly custom?: boolean; }; - readonly shading?: { - readonly type: ShadingType; - readonly fill: string; - readonly color: string; - }; + readonly shading?: IShadingAttributesProperties; readonly widowControl?: boolean; readonly frame?: IFrameOptions; } @@ -89,12 +81,12 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { this.push(new Style(options.style)); } - if (options.keepNext) { - this.push(new KeepNext()); + if (options.keepNext !== undefined) { + this.push(new OnOffElement("w:keepNext", options.keepNext)); } - if (options.keepLines) { - this.push(new KeepLines()); + if (options.keepLines !== undefined) { + this.push(new OnOffElement("w:keepLines", options.keepLines)); } if (options.pageBreakBefore) { @@ -105,8 +97,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { this.push(new FrameProperties(options.frame)); } - if (options.widowControl) { - this.push(new WidowControl(options.widowControl)); + if (options.widowControl !== undefined) { + this.push(new OnOffElement("w:widowControl", options.widowControl)); } if (options.bullet) { @@ -131,7 +123,7 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { } if (options.shading) { - this.push(new Shading(options.shading.type, options.shading.fill, options.shading.color)); + this.push(new Shading(options.shading)); } if (options.rightTabStop) { @@ -148,8 +140,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { this.push(new TabStop(TabStopType.LEFT, options.leftTabStop)); } - if (options.bidirectional) { - this.push(new Bidirectional()); + if (options.bidirectional !== undefined) { + this.push(new OnOffElement("w:bidi", options.contextualSpacing)); } if (options.spacing) { @@ -160,8 +152,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { this.push(new Indent(options.indent)); } - if (options.contextualSpacing) { - this.push(new ContextualSpacing(options.contextualSpacing)); + if (options.contextualSpacing !== undefined) { + this.push(new OnOffElement("w:contextualSpacing", options.contextualSpacing)); } if (options.alignment) { diff --git a/src/file/paragraph/run/break.ts b/src/file/paragraph/run/break.ts index 293071773b..0d0eac8c8c 100644 --- a/src/file/paragraph/run/break.ts +++ b/src/file/paragraph/run/break.ts @@ -1,6 +1,30 @@ // http://officeopenxml.com/WPtextSpecialContent-break.php import { XmlComponent } from "file/xml-components"; +// +// ... +// + +// +// +// +// + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export class Break extends XmlComponent { constructor() { super("w:br"); diff --git a/src/file/paragraph/run/formatting.spec.ts b/src/file/paragraph/run/formatting.spec.ts index 43a1de1fd0..cf590d8cf6 100644 --- a/src/file/paragraph/run/formatting.spec.ts +++ b/src/file/paragraph/run/formatting.spec.ts @@ -2,18 +2,35 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { Bold } from "./formatting"; +import { CharacterSpacing, Color } from "./formatting"; -describe("Bold", () => { +describe("CharacterSpacing", () => { describe("#constructor()", () => { it("should create", () => { - const currentBold = new Bold(); + const element = new CharacterSpacing(32); - const tree = new Formatter().format(currentBold); + const tree = new Formatter().format(element); expect(tree).to.deep.equal({ - "w:b": { + "w:spacing": { _attr: { - "w:val": true, + "w:val": 32, + }, + }, + }); + }); + }); +}); + +describe("Color", () => { + describe("#constructor()", () => { + it("should create", () => { + const element = new Color("#FFFFFF"); + + const tree = new Formatter().format(element); + expect(tree).to.deep.equal({ + "w:color": { + _attr: { + "w:val": "FFFFFF", }, }, }); diff --git a/src/file/paragraph/run/formatting.ts b/src/file/paragraph/run/formatting.ts index 07e401a91e..5f735fdfbb 100644 --- a/src/file/paragraph/run/formatting.ts +++ b/src/file/paragraph/run/formatting.ts @@ -1,170 +1,55 @@ +import { hexColorValue, signedTwipsMeasureValue } from "file/values"; import { Attributes, XmlComponent } from "file/xml-components"; -export class Bold extends XmlComponent { - constructor() { - super("w:b"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class BoldComplexScript extends XmlComponent { - constructor() { - super("w:bCs"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - export class CharacterSpacing extends XmlComponent { - constructor(value: number) { + constructor(value: number | string) { super("w:spacing"); this.root.push( new Attributes({ - val: value, - }), - ); - } -} - -export class Italics extends XmlComponent { - constructor() { - super("w:i"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class ItalicsComplexScript extends XmlComponent { - constructor() { - super("w:iCs"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class Caps extends XmlComponent { - constructor() { - super("w:caps"); - this.root.push( - new Attributes({ - val: true, + val: signedTwipsMeasureValue(value), }), ); } } +// +// +// +// +// +// export class Color extends XmlComponent { constructor(color: string) { super("w:color"); this.root.push( new Attributes({ - val: color, - }), - ); - } -} - -export class DoubleStrike extends XmlComponent { - constructor() { - super("w:dstrike"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class Emboss extends XmlComponent { - constructor() { - super("w:emboss"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class Imprint extends XmlComponent { - constructor() { - super("w:imprint"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class SmallCaps extends XmlComponent { - constructor() { - super("w:smallCaps"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class Strike extends XmlComponent { - constructor() { - super("w:strike"); - this.root.push( - new Attributes({ - val: true, - }), - ); - } -} - -export class Size extends XmlComponent { - constructor(size: number) { - super("w:sz"); - this.root.push( - new Attributes({ - val: size, - }), - ); - } -} - -export class SizeComplexScript extends XmlComponent { - constructor(size: number) { - super("w:szCs"); - this.root.push( - new Attributes({ - val: size, - }), - ); - } -} - -export class RightToLeft extends XmlComponent { - constructor() { - super("w:rtl"); - this.root.push( - new Attributes({ - val: true, + val: hexColorValue(color), }), ); } } +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export class Highlight extends XmlComponent { constructor(color: string) { super("w:highlight"); @@ -186,16 +71,3 @@ export class HighlightComplexScript extends XmlComponent { ); } } - -export class Shading extends XmlComponent { - constructor(value: string, fill: string, color: string) { - super("w:shd"); - this.root.push( - new Attributes({ - val: value, - fill: fill, - color: color, - }), - ); - } -} diff --git a/src/file/paragraph/run/properties.ts b/src/file/paragraph/run/properties.ts index fa61e4d9d7..3c47099b85 100644 --- a/src/file/paragraph/run/properties.ts +++ b/src/file/paragraph/run/properties.ts @@ -1,29 +1,9 @@ -import { ShadingType } from "file/table"; -import { IgnoreIfEmptyXmlComponent, XmlComponent } from "file/xml-components"; +import { IShadingAttributesProperties, Shading } from "file/shading"; +import { HpsMeasureElement, IgnoreIfEmptyXmlComponent, OnOffElement, StringValueElement, XmlComponent } from "file/xml-components"; import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark"; -import { - Bold, - BoldComplexScript, - Caps, - CharacterSpacing, - Color, - DoubleStrike, - Emboss, - Highlight, - HighlightComplexScript, - Imprint, - Italics, - ItalicsComplexScript, - RightToLeft, - Shading, - Size, - SizeComplexScript, - SmallCaps, - Strike, -} from "./formatting"; +import { CharacterSpacing, Color, Highlight, HighlightComplexScript } from "./formatting"; import { IFontAttributesProperties, RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; -import { Style } from "./style"; import { Underline, UnderlineType } from "./underline"; interface IFontOptions { @@ -44,8 +24,8 @@ export interface IRunStylePropertiesOptions { readonly type?: EmphasisMarkType; }; readonly color?: string; - readonly size?: number; - readonly sizeComplexScript?: boolean | number; + readonly size?: number | string; + readonly sizeComplexScript?: boolean | number | string; readonly rightToLeft?: boolean; readonly smallCaps?: boolean; readonly allCaps?: boolean; @@ -57,12 +37,7 @@ export interface IRunStylePropertiesOptions { readonly highlight?: string; readonly highlightComplexScript?: boolean | string; readonly characterSpacing?: number; - readonly shading?: { - readonly type: ShadingType; - readonly fill: string; - readonly color: string; - }; - readonly shadow?: IRunStylePropertiesOptions["shading"]; + readonly shading?: IShadingAttributesProperties; readonly emboss?: boolean; readonly imprint?: boolean; } @@ -71,6 +46,49 @@ export interface IRunPropertiesOptions extends IRunStylePropertiesOptions { readonly style?: string; } +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export class RunProperties extends IgnoreIfEmptyXmlComponent { constructor(options?: IRunPropertiesOptions) { super("w:rPr"); @@ -79,18 +97,19 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { return; } - if (options.bold) { - this.push(new Bold()); + if (options.bold !== undefined) { + this.push(new OnOffElement("w:b", options.bold)); } - if ((options.boldComplexScript === undefined && options.bold) || options.boldComplexScript) { - this.push(new BoldComplexScript()); + if ((options.boldComplexScript === undefined && options.bold !== undefined) || options.boldComplexScript) { + this.push(new OnOffElement("w:bCs", options.boldComplexScript ?? options.bold)); } - if (options.italics) { - this.push(new Italics()); + if (options.italics !== undefined) { + this.push(new OnOffElement("w:i", options.italics)); } - if ((options.italicsComplexScript === undefined && options.italics) || options.italicsComplexScript) { - this.push(new ItalicsComplexScript()); + + if ((options.italicsComplexScript === undefined && options.italics !== undefined) || options.italicsComplexScript) { + this.push(new OnOffElement("w:iCs", options.italicsComplexScript ?? options.italics)); } if (options.underline) { @@ -105,33 +124,32 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { this.push(new Color(options.color)); } - if (options.size) { - this.push(new Size(options.size)); + if (options.size !== undefined) { + this.push(new HpsMeasureElement("w:sz", options.size)); } const szCs = options.sizeComplexScript === undefined || options.sizeComplexScript === true ? options.size : options.sizeComplexScript; if (szCs) { - this.push(new SizeComplexScript(szCs)); + this.push(new HpsMeasureElement("w:szCs", szCs)); } - if (options.rightToLeft) { - this.push(new RightToLeft()); + if (options.rightToLeft !== undefined) { + this.push(new OnOffElement("w:rtl", options.rightToLeft)); } - if (options.smallCaps) { - this.push(new SmallCaps()); + // These two are mutually exclusive + if (options.smallCaps !== undefined) { + this.push(new OnOffElement("w:smallCaps", options.smallCaps)); + } else if (options.allCaps !== undefined) { + this.push(new OnOffElement("w:caps", options.allCaps)); } - if (options.allCaps) { - this.push(new Caps()); + if (options.strike !== undefined) { + this.push(new OnOffElement("w:strike", options.strike)); } - if (options.strike) { - this.push(new Strike()); - } - - if (options.doubleStrike) { - this.push(new DoubleStrike()); + if (options.doubleStrike !== undefined) { + this.push(new OnOffElement("w:dstrike", options.doubleStrike)); } if (options.subScript) { @@ -143,7 +161,7 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { } if (options.style) { - this.push(new Style(options.style)); + this.push(new StringValueElement("w:rStyle", options.style)); } if (options.font) { @@ -171,17 +189,16 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { this.push(new CharacterSpacing(options.characterSpacing)); } - if (options.emboss) { - this.push(new Emboss()); + if (options.emboss !== undefined) { + this.push(new OnOffElement("w:emboss", options.emboss)); } - if (options.imprint) { - this.push(new Imprint()); + if (options.imprint !== undefined) { + this.push(new OnOffElement("w:imprint", options.imprint)); } - const shading = options.shading || options.shadow; - if (shading) { - this.push(new Shading(shading.type, shading.fill, shading.color)); + if (options.shading) { + this.push(new Shading(options.shading)); } } diff --git a/src/file/paragraph/run/run.spec.ts b/src/file/paragraph/run/run.spec.ts index a001699a7b..aaa2a640de 100644 --- a/src/file/paragraph/run/run.spec.ts +++ b/src/file/paragraph/run/run.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; // import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; -import { ShadingType } from "file/table"; +import { ShadingType } from "file/shading"; import { Run } from "./"; import { EmphasisMarkType } from "./emphasis-mark"; @@ -20,13 +20,9 @@ describe("Run", () => { "w:r": [ { "w:rPr": [ - { "w:b": { _attr: { "w:val": true } } }, + { "w:b": {} }, { - "w:bCs": { - _attr: { - "w:val": true, - }, - }, + "w:bCs": {}, }, ], }, @@ -45,13 +41,9 @@ describe("Run", () => { "w:r": [ { "w:rPr": [ - { "w:i": { _attr: { "w:val": true } } }, + { "w:i": {} }, { - "w:iCs": { - _attr: { - "w:val": true, - }, - }, + "w:iCs": {}, }, ], }, @@ -116,7 +108,7 @@ describe("Run", () => { }); const tree = new Formatter().format(run); expect(tree).to.deep.equal({ - "w:r": [{ "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }] }], + "w:r": [{ "w:rPr": [{ "w:smallCaps": {} }] }], }); }); }); @@ -128,7 +120,7 @@ describe("Run", () => { }); const tree = new Formatter().format(run); expect(tree).to.deep.equal({ - "w:r": [{ "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }] }], + "w:r": [{ "w:rPr": [{ "w:caps": {} }] }], }); }); }); @@ -140,7 +132,7 @@ describe("Run", () => { }); const tree = new Formatter().format(run); expect(tree).to.deep.equal({ - "w:r": [{ "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }] }], + "w:r": [{ "w:rPr": [{ "w:strike": {} }] }], }); }); }); @@ -152,7 +144,7 @@ describe("Run", () => { }); const tree = new Formatter().format(run); expect(tree).to.deep.equal({ - "w:r": [{ "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }] }], + "w:r": [{ "w:rPr": [{ "w:dstrike": {} }] }], }); }); }); @@ -164,7 +156,7 @@ describe("Run", () => { }); const tree = new Formatter().format(run); expect(tree).to.deep.equal({ - "w:r": [{ "w:rPr": [{ "w:emboss": { _attr: { "w:val": true } } }] }], + "w:r": [{ "w:rPr": [{ "w:emboss": {} }] }], }); }); }); @@ -176,7 +168,7 @@ describe("Run", () => { }); const tree = new Formatter().format(run); expect(tree).to.deep.equal({ - "w:r": [{ "w:rPr": [{ "w:imprint": { _attr: { "w:val": true } } }] }], + "w:r": [{ "w:rPr": [{ "w:imprint": {} }] }], }); }); }); @@ -367,7 +359,7 @@ describe("Run", () => { }); const tree = new Formatter().format(run); expect(tree).to.deep.equal({ - "w:r": [{ "w:rPr": [{ "w:rtl": { _attr: { "w:val": true } } }] }], + "w:r": [{ "w:rPr": [{ "w:rtl": {} }] }], }); }); }); diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts index 461fd9da29..02b97ec47f 100644 --- a/src/file/paragraph/run/run.ts +++ b/src/file/paragraph/run/run.ts @@ -29,6 +29,12 @@ export class Run extends XmlComponent { this.properties = new RunProperties(options); this.root.push(this.properties); + if (options.break) { + for (let i = 0; i < options.break; i++) { + this.root.push(new Break()); + } + } + if (options.children) { for (const child of options.children) { if (typeof child === "string") { @@ -63,11 +69,5 @@ export class Run extends XmlComponent { } else if (options.text) { this.root.push(new Text(options.text)); } - - if (options.break) { - for (let i = 0; i < options.break; i++) { - this.root.splice(1, 0, new Break()); - } - } } } diff --git a/src/file/paragraph/run/strike.spec.ts b/src/file/paragraph/run/strike.spec.ts deleted file mode 100644 index 73c7e853c8..0000000000 --- a/src/file/paragraph/run/strike.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { DoubleStrike, Strike } from "./formatting"; - -describe("Strike", () => { - let strike: Strike; - - beforeEach(() => { - strike = new Strike(); - }); - - describe("#constructor()", () => { - it("should create a Strike with correct root key", () => { - const tree = new Formatter().format(strike); - expect(tree).to.deep.equal({ - "w:strike": { - _attr: { - "w:val": true, - }, - }, - }); - }); - }); -}); - -describe("DoubleStrike", () => { - let strike: DoubleStrike; - - beforeEach(() => { - strike = new DoubleStrike(); - }); - - describe("#constructor()", () => { - it("should create a Double Strike with correct root key", () => { - const tree = new Formatter().format(strike); - expect(tree).to.deep.equal({ - "w:dstrike": { - _attr: { - "w:val": true, - }, - }, - }); - }); - }); -}); diff --git a/src/file/paragraph/run/style.ts b/src/file/paragraph/run/style.ts deleted file mode 100644 index 2472c1d406..0000000000 --- a/src/file/paragraph/run/style.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -class StyleAttributes extends XmlAttributeComponent<{ readonly val: string }> { - protected readonly xmlKeys = { val: "w:val" }; -} - -export class Style extends XmlComponent { - constructor(styleId: string) { - super("w:rStyle"); - this.root.push(new StyleAttributes({ val: styleId })); - } -} diff --git a/src/file/paragraph/run/symbol-run.spec.ts b/src/file/paragraph/run/symbol-run.spec.ts index c41e6e1d51..06e98a4373 100644 --- a/src/file/paragraph/run/symbol-run.spec.ts +++ b/src/file/paragraph/run/symbol-run.spec.ts @@ -43,13 +43,13 @@ describe("SymbolRun", () => { italics: true, bold: true, underline: { - color: "red", + color: "ff0000", type: UnderlineType.DOUBLE, }, emphasisMark: { type: EmphasisMarkType.DOT, }, - color: "green", + color: "00FF00", size: 40, highlight: "yellow", }); @@ -59,13 +59,13 @@ describe("SymbolRun", () => { "w:r": [ { "w:rPr": [ - { "w:b": { _attr: { "w:val": true } } }, - { "w:bCs": { _attr: { "w:val": true } } }, - { "w:i": { _attr: { "w:val": true } } }, - { "w:iCs": { _attr: { "w:val": true } } }, - { "w:u": { _attr: { "w:val": "double", "w:color": "red" } } }, + { "w:b": {} }, + { "w:bCs": {} }, + { "w:i": {} }, + { "w:iCs": {} }, + { "w:u": { _attr: { "w:val": "double", "w:color": "ff0000" } } }, { "w:em": { _attr: { "w:val": "dot" } } }, - { "w:color": { _attr: { "w:val": "green" } } }, + { "w:color": { _attr: { "w:val": "00FF00" } } }, { "w:sz": { _attr: { "w:val": 40 } } }, { "w:szCs": { _attr: { "w:val": 40 } } }, { "w:highlight": { _attr: { "w:val": "yellow" } } }, diff --git a/src/file/paragraph/run/tab.ts b/src/file/paragraph/run/tab.ts index 55893862af..33f2b09427 100644 --- a/src/file/paragraph/run/tab.ts +++ b/src/file/paragraph/run/tab.ts @@ -1,5 +1,11 @@ import { XmlComponent } from "file/xml-components"; +// +// ... +// +// +// TODO: this is unused and undocumented currently. +// I think the intended use was for users to import, and insert as a child of `Run`. export class Tab extends XmlComponent { constructor() { super("w:tab"); diff --git a/src/file/paragraph/run/underline.spec.ts b/src/file/paragraph/run/underline.spec.ts index 77f7819e7b..bc406a1da4 100644 --- a/src/file/paragraph/run/underline.spec.ts +++ b/src/file/paragraph/run/underline.spec.ts @@ -2,12 +2,12 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import * as u from "./underline"; +import { Underline, UnderlineType } from "./underline"; describe("Underline", () => { describe("#constructor()", () => { it("should create a new Underline object with u:u as the rootKey", () => { - const underline = new u.Underline(); + const underline = new Underline(); const tree = new Formatter().format(underline); expect(tree).to.deep.equal({ "w:u": { @@ -19,7 +19,7 @@ describe("Underline", () => { }); it("should default to 'single' and no color", () => { - const underline = new u.Underline(); + const underline = new Underline(); const tree = new Formatter().format(underline); expect(tree).to.deep.equal({ "w:u": { _attr: { "w:val": "single" } }, @@ -27,7 +27,7 @@ describe("Underline", () => { }); it("should use the given style type and color", () => { - const underline = new u.Underline(u.UnderlineType.DOUBLE, "FF00CC"); + const underline = new Underline(UnderlineType.DOUBLE, "FF00CC"); const tree = new Formatter().format(underline); expect(tree).to.deep.equal({ "w:u": { _attr: { "w:val": "double", "w:color": "FF00CC" } }, @@ -35,259 +35,3 @@ describe("Underline", () => { }); }); }); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DashDotDotHeavyUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dashDotDotHeavy", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DashDotHeavyUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dashDotHeavy", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DashLongHeavyUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dashLongHeavy", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DashLongUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dashLong", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DashUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dash", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DotDashUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dotDash", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DotDotDashUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dotDotDash", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DottedHeavyUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dottedHeavy", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DottedUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "dotted", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.DoubleUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "double", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.SingleUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "single", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.ThickUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "thick", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.WaveUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "wave", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.WavyDoubleUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "wavyDouble", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.WavyHeavyUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "wavyHeavy", - }, - }, - }); - }); - }); -}); - -describe("DashDotDotHeavyUnderline", () => { - describe("#constructor()", () => { - it("should put value in attribute", () => { - const underline = new u.WordsUnderline(); - const tree = new Formatter().format(underline); - expect(tree).to.deep.equal({ - "w:u": { - _attr: { - "w:val": "words", - }, - }, - }); - }); - }); -}); diff --git a/src/file/paragraph/run/underline.ts b/src/file/paragraph/run/underline.ts index 9cdb7b25a1..a94c37e1f5 100644 --- a/src/file/paragraph/run/underline.ts +++ b/src/file/paragraph/run/underline.ts @@ -1,3 +1,4 @@ +import { hexColorValue } from "file/values"; import { Attributes, XmlComponent } from "file/xml-components"; export enum UnderlineType { @@ -20,116 +21,14 @@ export enum UnderlineType { WAVYDOUBLE = "wavyDouble", } -export abstract class BaseUnderline extends XmlComponent { - constructor(underlineType: string, color?: string) { +export class Underline extends XmlComponent { + constructor(underlineType: UnderlineType = UnderlineType.SINGLE, color?: string) { super("w:u"); this.root.push( new Attributes({ val: underlineType, - color: color, + color: color === undefined ? undefined : hexColorValue(color), }), ); } } - -export class Underline extends BaseUnderline { - constructor(underlineType: UnderlineType = UnderlineType.SINGLE, color?: string) { - super(underlineType, color); - } -} - -export class DashUnderline extends BaseUnderline { - constructor() { - super("dash"); - } -} - -export class DashDotDotHeavyUnderline extends BaseUnderline { - constructor() { - super("dashDotDotHeavy"); - } -} - -export class DashDotHeavyUnderline extends BaseUnderline { - constructor() { - super("dashDotHeavy"); - } -} - -export class DashLongUnderline extends BaseUnderline { - constructor() { - super("dashLong"); - } -} - -export class DashLongHeavyUnderline extends BaseUnderline { - constructor() { - super("dashLongHeavy"); - } -} - -export class DotDashUnderline extends BaseUnderline { - constructor() { - super("dotDash"); - } -} - -export class DotDotDashUnderline extends BaseUnderline { - constructor() { - super("dotDotDash"); - } -} - -export class DottedUnderline extends BaseUnderline { - constructor() { - super("dotted"); - } -} - -export class DottedHeavyUnderline extends BaseUnderline { - constructor() { - super("dottedHeavy"); - } -} - -export class DoubleUnderline extends BaseUnderline { - constructor() { - super("double"); - } -} - -export class SingleUnderline extends BaseUnderline { - constructor() { - super("single"); - } -} - -export class ThickUnderline extends BaseUnderline { - constructor() { - super("thick"); - } -} - -export class WaveUnderline extends BaseUnderline { - constructor() { - super("wave"); - } -} - -export class WavyDoubleUnderline extends BaseUnderline { - constructor() { - super("wavyDouble"); - } -} - -export class WavyHeavyUnderline extends BaseUnderline { - constructor() { - super("wavyHeavy"); - } -} - -export class WordsUnderline extends BaseUnderline { - constructor() { - super("words"); - } -} diff --git a/src/file/settings/compatibility-setting/compatibility-setting.ts b/src/file/settings/compatibility-setting/compatibility-setting.ts index 9f2a0b4982..43a7c5d71a 100644 --- a/src/file/settings/compatibility-setting/compatibility-setting.ts +++ b/src/file/settings/compatibility-setting/compatibility-setting.ts @@ -1,5 +1,15 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +// Currently, this is hard-coded for Microsoft word compatSettings. +// Theoretically, we could add compatSettings for other programs, but +// currently there isn't a need. + +// +// +// +// +// + export class CompatibilitySettingAttributes extends XmlAttributeComponent<{ readonly version: number; readonly name: string; @@ -12,6 +22,8 @@ export class CompatibilitySettingAttributes extends XmlAttributeComponent<{ }; } +// https://docs.microsoft.com/en-us/openspecs/office_standards/ms-docx/90138c4d-eb18-4edc-aa6c-dfb799cb1d0d + export class CompatibilitySetting extends XmlComponent { constructor(version: number) { super("w:compatSetting"); diff --git a/src/file/settings/compatibility.ts b/src/file/settings/compatibility.ts index fe63da92c5..1405697bca 100644 --- a/src/file/settings/compatibility.ts +++ b/src/file/settings/compatibility.ts @@ -1,11 +1,77 @@ -import { XmlComponent } from "file/xml-components"; +import { OnOffElement, XmlComponent } from "file/xml-components"; import { CompatibilitySetting } from "./compatibility-setting/compatibility-setting"; -class DoNotExpandShiftReturn extends XmlComponent { - constructor() { - super("w:doNotExpandShiftReturn"); - } -} +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export interface ICompatibilityOptions { readonly doNotExpandShiftReturn?: boolean; @@ -16,8 +82,9 @@ export class Compatibility extends XmlComponent { constructor(options: ICompatibilityOptions) { super("w:compat"); - if (options.doNotExpandShiftReturn) { - this.root.push(new DoNotExpandShiftReturn()); + // Don't justify lines ending in soft line break setting + if (options.doNotExpandShiftReturn !== undefined) { + this.root.push(new OnOffElement("w:doNotExpandShiftReturn", options.doNotExpandShiftReturn)); } if (options.version) { diff --git a/src/file/settings/display-background-shape.spec.ts b/src/file/settings/display-background-shape.spec.ts deleted file mode 100644 index 34c23b65db..0000000000 --- a/src/file/settings/display-background-shape.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { DisplayBackgroundShape } from "./display-background-shape"; - -describe("DisplayBackgroundShape", () => { - describe("#constructor()", () => { - it("should create", () => { - const displayBackgroundShape = new DisplayBackgroundShape(); - const tree = new Formatter().format(displayBackgroundShape); - expect(tree).to.deep.equal({ - "w:displayBackgroundShape": {}, - }); - }); - }); -}); diff --git a/src/file/settings/display-background-shape.ts b/src/file/settings/display-background-shape.ts deleted file mode 100644 index bd0cd15675..0000000000 --- a/src/file/settings/display-background-shape.ts +++ /dev/null @@ -1,9 +0,0 @@ -// http://officeopenxml.com/WPdocument.php -// http://www.datypic.com/sc/ooxml/e-w_background-1.html -import { XmlComponent } from "file/xml-components"; - -export class DisplayBackgroundShape extends XmlComponent { - constructor() { - super("w:displayBackgroundShape"); - } -} diff --git a/src/file/settings/even-odd-headers.ts b/src/file/settings/even-odd-headers.ts deleted file mode 100644 index c6f85a7aca..0000000000 --- a/src/file/settings/even-odd-headers.ts +++ /dev/null @@ -1,9 +0,0 @@ -// http://officeopenxml.com/WPSectionFooterReference.php -// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_evenAndOddHeaders_topic_ID0ET1WU.html -import { XmlComponent } from "file/xml-components"; - -export class EvenAndOddHeadersAndFooters extends XmlComponent { - constructor() { - super("w:evenAndOddHeaders"); - } -} diff --git a/src/file/settings/index.ts b/src/file/settings/index.ts index d750485a16..dcf101b0c6 100644 --- a/src/file/settings/index.ts +++ b/src/file/settings/index.ts @@ -1,2 +1 @@ export * from "./settings"; -export * from "./update-fields"; diff --git a/src/file/settings/settings.spec.ts b/src/file/settings/settings.spec.ts index 03a3612a0b..b12f340f43 100644 --- a/src/file/settings/settings.spec.ts +++ b/src/file/settings/settings.spec.ts @@ -7,83 +7,37 @@ import { Settings } from "./settings"; describe("Settings", () => { describe("#constructor", () => { it("should create a empty Settings with correct rootKey", () => { - const settings = new Settings({ - evenAndOddHeaders: false, - }); + const settings = new Settings({}); const tree = new Formatter().format(settings); - let keys = Object.keys(tree); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:settings"); - keys = Object.keys(tree["w:settings"]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(3); + + expect(Object.keys(tree)).has.length(1); + expect(tree["w:settings"]).to.be.an("array"); }); - }); - describe("#addUpdateFields", () => { - const assertSettingsWithUpdateFields = (settings: Settings) => { - const tree = new Formatter().format(settings); - let keys = Object.keys(tree); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:settings"); - const rootArray = tree["w:settings"]; - expect(rootArray).is.an.instanceof(Array); - expect(rootArray).has.length(4); - keys = Object.keys(rootArray[0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - keys = Object.keys(rootArray[3]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:updateFields"); - const updateFields = rootArray[3]["w:updateFields"]; - keys = Object.keys(updateFields); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - const updateFieldsAttr = updateFields._attr; - expect(updateFieldsAttr["w:val"]).to.be.equal(true); - }; - it("should add a UpdateFields with value true", () => { + + it("should add updateFields setting", () => { const settings = new Settings({ - evenAndOddHeaders: false, - }); - settings.addUpdateFields(); - assertSettingsWithUpdateFields(settings); - }); - it("should add a UpdateFields with value true only once", () => { - const settings = new Settings({ - evenAndOddHeaders: false, - }); - settings.addUpdateFields(); - assertSettingsWithUpdateFields(settings); - settings.addUpdateFields(); - assertSettingsWithUpdateFields(settings); - }); - }); - describe("#addCompatibility", () => { - it("should add an empty Compatibility by default", () => { - const settings = new Settings({ - evenAndOddHeaders: false, + updateFields: true, }); const tree = new Formatter().format(settings); - let keys: string[] = Object.keys(tree); - expect(keys[0]).to.be.equal("w:settings"); - const rootArray = tree["w:settings"]; - expect(rootArray).is.an.instanceof(Array); - expect(rootArray).has.length(3); - keys = Object.keys(rootArray[0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - keys = Object.keys(rootArray[1]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:compat"); - expect(rootArray[1]["w:compat"][0]).to.deep.equal({ + expect(Object.keys(tree)).has.length(1); + expect(tree["w:settings"]).to.be.an("array"); + + expect(tree["w:settings"]).to.deep.include({ + "w:updateFields": {}, + }); + }); + + it("should indicate modern word compatibility by default", () => { + const settings = new Settings({}); + + const tree = new Formatter().format(settings); + expect(Object.keys(tree)).has.length(1); + expect(tree["w:settings"]).to.be.an("array"); + + const compat = tree["w:settings"][2]; + expect(compat).to.be.an("object").with.keys("w:compat"); + expect(compat["w:compat"]).to.deep.include({ "w:compatSetting": { _attr: { "w:val": 15, @@ -93,81 +47,19 @@ describe("Settings", () => { }, }); }); - }); - describe("#addTrackRevisions", () => { - it("should add an empty Track Revisions", () => { + + it("should add trackRevisions setting", () => { const settings = new Settings({ - evenAndOddHeaders: false, + trackRevisions: true, }); - settings.addTrackRevisions(); const tree = new Formatter().format(settings); - let keys: string[] = Object.keys(tree); - expect(keys[0]).to.be.equal("w:settings"); - const rootArray = tree["w:settings"]; - expect(rootArray).is.an.instanceof(Array); - expect(rootArray).has.length(4); - keys = Object.keys(rootArray[0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - keys = Object.keys(rootArray[3]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:trackRevisions"); - }); - }); - describe("#addTrackRevisionsTwice", () => { - it("should add an empty Track Revisions if called twice", () => { - const settings = new Settings({ - evenAndOddHeaders: false, + expect(Object.keys(tree)).has.length(1); + expect(tree["w:settings"]).to.be.an("array"); + + expect(tree["w:settings"]).to.deep.include({ + "w:trackRevisions": {}, }); - settings.addTrackRevisions(); - settings.addTrackRevisions(); - - const tree = new Formatter().format(settings); - let keys: string[] = Object.keys(tree); - expect(keys[0]).to.be.equal("w:settings"); - const rootArray = tree["w:settings"]; - expect(rootArray).is.an.instanceof(Array); - expect(rootArray).has.length(4); - keys = Object.keys(rootArray[0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - keys = Object.keys(rootArray[3]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:trackRevisions"); - }); - }); - - describe("#addTrackRevisionsTwice", () => { - it("should add an empty Track Revisions if called twice", () => { - const settings = new Settings({ - evenAndOddHeaders: true, - }); - settings.addTrackRevisions(); - settings.addTrackRevisions(); - - const tree = new Formatter().format(settings); - let keys: string[] = Object.keys(tree); - expect(keys[0]).to.be.equal("w:settings"); - const rootArray = tree["w:settings"]; - expect(rootArray).is.an.instanceof(Array); - expect(rootArray).has.length(5); - keys = Object.keys(rootArray[0]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("_attr"); - keys = Object.keys(rootArray[4]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:trackRevisions"); - keys = Object.keys(rootArray[2]); - expect(keys).is.an.instanceof(Array); - expect(keys).has.length(1); - expect(keys[0]).to.be.equal("w:evenAndOddHeaders"); }); }); }); diff --git a/src/file/settings/settings.ts b/src/file/settings/settings.ts index 30b26d8f04..4c57532a41 100644 --- a/src/file/settings/settings.ts +++ b/src/file/settings/settings.ts @@ -1,10 +1,6 @@ -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { OnOffElement, XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { Compatibility } from "./compatibility"; -import { DisplayBackgroundShape } from "./display-background-shape"; -import { EvenAndOddHeadersAndFooters } from "./even-odd-headers"; -import { TrackRevisions } from "./track-revisions"; -import { UpdateFields } from "./update-fields"; export class SettingsAttributes extends XmlAttributeComponent<{ readonly wpc?: string; @@ -46,14 +42,118 @@ export class SettingsAttributes extends XmlAttributeComponent<{ }; } +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + export interface ISettingsOptions { readonly compatabilityModeVersion?: number; - readonly evenAndOddHeaders: boolean; + readonly evenAndOddHeaders?: boolean; + readonly trackRevisions?: boolean; + readonly updateFields?: boolean; } export class Settings extends XmlComponent { - private readonly trackRevisions: TrackRevisions; - constructor(options: ISettingsOptions) { super("w:settings"); this.root.push( @@ -78,32 +178,29 @@ export class Settings extends XmlComponent { }), ); + // http://officeopenxml.com/WPdocument.php + // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_displayBackgroundSha_topic_ID0ET4SX.html + this.root.push(new OnOffElement("w:displayBackgroundShape", true)); + + // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_trackRevisions_topic_ID0EKXKY.html + if (options.trackRevisions !== undefined) { + this.root.push(new OnOffElement("w:trackRevisions", options.trackRevisions)); + } + + // http://officeopenxml.com/WPSectionFooterReference.php + // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_evenAndOddHeaders_topic_ID0ET1WU.html + if (options.evenAndOddHeaders !== undefined) { + this.root.push(new OnOffElement("w:evenAndOddHeaders", options.evenAndOddHeaders)); + } + + if (options.updateFields !== undefined) { + this.root.push(new OnOffElement("w:updateFields", options.updateFields)); + } + this.root.push( new Compatibility({ version: options.compatabilityModeVersion || 15, }), ); - - if (options.evenAndOddHeaders) { - this.root.push(new EvenAndOddHeadersAndFooters()); - } - - this.trackRevisions = new TrackRevisions(); - - this.root.push(new DisplayBackgroundShape()); - } - - public addUpdateFields(): void { - if (!this.root.find((child) => child instanceof UpdateFields)) { - this.addChildElement(new UpdateFields()); - } - } - - public addTrackRevisions(): TrackRevisions { - if (!this.root.find((child) => child instanceof TrackRevisions)) { - this.addChildElement(this.trackRevisions); - } - - return this.trackRevisions; } } diff --git a/src/file/settings/track-revisions.spec.ts b/src/file/settings/track-revisions.spec.ts deleted file mode 100644 index 3875d51f0a..0000000000 --- a/src/file/settings/track-revisions.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expect } from "chai"; -import { Formatter } from "export/formatter"; -import { TrackRevisions } from "file/settings/track-revisions"; - -import { EMPTY_OBJECT } from "file/xml-components"; - -describe("TrackRevisions", () => { - describe("#constructor", () => { - it("creates an initially empty property object", () => { - const trackRevisions = new TrackRevisions(); - - const tree = new Formatter().format(trackRevisions); - expect(tree).to.deep.equal({ "w:trackRevisions": EMPTY_OBJECT }); - }); - }); -}); diff --git a/src/file/settings/track-revisions.ts b/src/file/settings/track-revisions.ts deleted file mode 100644 index 2da692827e..0000000000 --- a/src/file/settings/track-revisions.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { XmlComponent } from "file/xml-components"; - -export class TrackRevisions extends XmlComponent { - constructor() { - super("w:trackRevisions"); - } -} diff --git a/src/file/settings/update-fields.spec.ts b/src/file/settings/update-fields.spec.ts deleted file mode 100644 index 2ccc9df8c4..0000000000 --- a/src/file/settings/update-fields.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { UpdateFields } from "./update-fields"; - -const UF_TRUE = { - "w:updateFields": { - _attr: { - "w:val": true, - }, - }, -}; -const UF_FALSE = { - "w:updateFields": { - _attr: { - "w:val": false, - }, - }, -}; -describe("Update Fields", () => { - describe("#constructor", () => { - it("should construct a Update Fields with TRUE value by default", () => { - const uf = new UpdateFields(); - const tree = new Formatter().format(uf); - expect(tree).to.be.deep.equal(UF_TRUE); - }); - it("should construct a Update Fields with TRUE value", () => { - const uf = new UpdateFields(true); - const tree = new Formatter().format(uf); - expect(tree).to.be.deep.equal(UF_TRUE); - }); - it("should construct a Update Fields with FALSE value", () => { - const uf = new UpdateFields(false); - const tree = new Formatter().format(uf); - expect(tree).to.be.deep.equal(UF_FALSE); - }); - }); -}); diff --git a/src/file/settings/update-fields.ts b/src/file/settings/update-fields.ts deleted file mode 100644 index 47a2ec6e18..0000000000 --- a/src/file/settings/update-fields.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -export class UpdateFieldsAttributes extends XmlAttributeComponent<{ - readonly enabled: boolean; -}> { - protected readonly xmlKeys = { - enabled: "w:val", - }; -} -export class UpdateFields extends XmlComponent { - constructor(enabled: boolean = true) { - super("w:updateFields"); - this.root.push( - new UpdateFieldsAttributes({ - enabled, - }), - ); - } -} diff --git a/src/file/table/shading/index.ts b/src/file/shading/index.ts similarity index 100% rename from src/file/table/shading/index.ts rename to src/file/shading/index.ts diff --git a/src/file/table/shading/shading.spec.ts b/src/file/shading/shading.spec.ts similarity index 75% rename from src/file/table/shading/shading.spec.ts rename to src/file/shading/shading.spec.ts index ce28fcca92..7b808aa829 100644 --- a/src/file/table/shading/shading.spec.ts +++ b/src/file/shading/shading.spec.ts @@ -2,12 +2,12 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { ShadingType, TableShading } from "./shading"; +import { Shading, ShadingType } from "./shading"; -describe("TableShading", () => { +describe("Shading", () => { describe("#constructor", () => { it("should create", () => { - const shading = new TableShading({}); + const shading = new Shading({}); const tree = new Formatter().format(shading); expect(tree).to.deep.equal({ "w:shd": { @@ -17,7 +17,7 @@ describe("TableShading", () => { }); it("should create with params", () => { - const shading = new TableShading({ val: ShadingType.PERCENT_40, color: "FF0000", fill: "555555" }); + const shading = new Shading({ type: ShadingType.PERCENT_40, color: "FF0000", fill: "555555" }); const tree = new Formatter().format(shading); expect(tree).to.deep.equal({ "w:shd": { diff --git a/src/file/shading/shading.ts b/src/file/shading/shading.ts new file mode 100644 index 0000000000..9199122c78 --- /dev/null +++ b/src/file/shading/shading.ts @@ -0,0 +1,88 @@ +// Note that the shading type is identical in all places, +// regardless of where it's used like paragraph/table/etc. +// +// http://officeopenxml.com/WPshading.php +// http://officeopenxml.com/WPtableShading.php +// http://officeopenxml.com/WPtableCellProperties-Shading.php +// +// This describes the CT_Shd type. +// +// +// +// +// +// +// +// +// +// +// +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { hexColorValue } from "../values"; + +export interface IShadingAttributesProperties { + readonly fill?: string; + readonly color?: string; + readonly type?: ShadingType; +} + +class ShadingAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + fill: "w:fill", + color: "w:color", + type: "w:val", + }; +} + +export class Shading extends XmlComponent { + constructor({ fill, color, type }: IShadingAttributesProperties) { + super("w:shd"); + this.root.push( + new ShadingAttributes({ + fill: fill === undefined ? undefined : hexColorValue(fill), + color: color === undefined ? undefined : hexColorValue(color), + type, + }), + ); + } +} + +export enum ShadingType { + CLEAR = "clear", + DIAGONAL_CROSS = "diagCross", + DIAGONAL_STRIPE = "diagStripe", + HORIZONTAL_CROSS = "horzCross", + HORIZONTAL_STRIPE = "horzStripe", + NIL = "nil", + PERCENT_5 = "pct5", + PERCENT_10 = "pct10", + PERCENT_12 = "pct12", + PERCENT_15 = "pct15", + PERCENT_20 = "pct20", + PERCENT_25 = "pct25", + PERCENT_30 = "pct30", + PERCENT_35 = "pct35", + PERCENT_37 = "pct37", + PERCENT_40 = "pct40", + PERCENT_45 = "pct45", + PERCENT_50 = "pct50", + PERCENT_55 = "pct55", + PERCENT_60 = "pct60", + PERCENT_62 = "pct62", + PERCENT_65 = "pct65", + PERCENT_70 = "pct70", + PERCENT_75 = "pct75", + PERCENT_80 = "pct80", + PERCENT_85 = "pct85", + PERCENT_87 = "pct87", + PERCENT_90 = "pct90", + PERCENT_95 = "pct95", + REVERSE_DIAGONAL_STRIPE = "reverseDiagStripe", + SOLID = "solid", + THIN_DIAGONAL_CROSS = "thinDiagCross", + THIN_DIAGONAL_STRIPE = "thinDiagStripe", + THIN_HORIZONTAL_CROSS = "thinHorzCross", + THIN_REVERSE_DIAGONAL_STRIPE = "thinReverseDiagStripe", + THIN_VERTICAL_STRIPE = "thinVertStripe", + VERTICAL_STRIPE = "vertStripe", +} diff --git a/src/file/shared/index.ts b/src/file/shared/index.ts index bc61bbe9ca..6abfbc0a8e 100644 --- a/src/file/shared/index.ts +++ b/src/file/shared/index.ts @@ -1 +1,2 @@ export * from "./alignment"; +export * from "./number-format"; diff --git a/src/file/shared/number-format.ts b/src/file/shared/number-format.ts new file mode 100644 index 0000000000..8ee5ad10fc --- /dev/null +++ b/src/file/shared/number-format.ts @@ -0,0 +1,133 @@ +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// + +export enum NumberFormat { + DECIMAL = "decimal", + UPPER_ROMAN = "upperRoman", + LOWER_ROMAN = "lowerRoman", + UPPER_LETTER = "upperLetter", + LOWER_LETTER = "lowerLetter", + ORDINAL = "ordinal", + CARDINAL_TEXT = "cardinalText", + ORDINAL_TEXT = "ordinalText", + HEX = "hex", + CHICAGO = "chicago", + IDEOGRAPH_DIGITAL = "ideographDigital", + JAPANESE_COUNTING = "japaneseCounting", + AIUEO = "aiueo", + IROHA = "iroha", + DECIMAL_FULL_WIDTH = "decimalFullWidth", + DECIMAL_HALF_WIDTH = "decimalHalfWidth", + JAPANESE_LEGAL = "japaneseLegal", + JAPANESE_DIGITAL_TEN_THOUSAND = "japaneseDigitalTenThousand", + DECIMAL_ENCLOSED_CIRCLE = "decimalEnclosedCircle", + DECIMAL_FULL_WIDTH_2 = "decimalFullWidth2", + AIUEO_FULL_WIDTH = "aiueoFullWidth", + IROHA_FULL_WIDTH = "irohaFullWidth", + DECIMAL_ZERO = "decimalZero", + BULLET = "bullet", + GANADA = "ganada", + CHOSUNG = "chosung", + DECIMAL_ENCLOSED_FULL_STOP = "decimalEnclosedFullstop", + DECIMAL_ENCLOSED_PAREN = "decimalEnclosedParen", + DECIMAL_ENCLOSED_CIRCLE_CHINESE = "decimalEnclosedCircleChinese", + IDEOGRAPH_ENCLOSED_CIRCLE = "ideographEnclosedCircle", + IDEOGRAPH_TRADITIONAL = "ideographTraditional", + IDEOGRAPH_ZODIAC = "ideographZodiac", + IDEOGRAPH_ZODIAC_TRADITIONAL = "ideographZodiacTraditional", + TAIWANESE_COUNTING = "taiwaneseCounting", + IDEOGRAPH_LEGAL_TRADITIONAL = "ideographLegalTraditional", + TAIWANESE_COUNTING_THOUSAND = "taiwaneseCountingThousand", + TAIWANESE_DIGITAL = "taiwaneseDigital", + CHINESE_COUNTING = "chineseCounting", + CHINESE_LEGAL_SIMPLIFIED = "chineseLegalSimplified", + CHINESE_COUNTING_TEN_THOUSAND = "chineseCountingThousand", + KOREAN_DIGITAL = "koreanDigital", + KOREAN_COUNTING = "koreanCounting", + KOREAN_LEGAL = "koreanLegal", + KOREAN_DIGITAL_2 = "koreanDigital2", + VIETNAMESE_COUNTING = "vietnameseCounting", + RUSSIAN_LOWER = "russianLower", + RUSSIAN_UPPER = "russianUpper", + NONE = "none", + NUMBER_IN_DASH = "numberInDash", + HEBREW_1 = "hebrew1", + HEBREW_2 = "hebrew2", + ARABIC_ALPHA = "arabicAlpha", + ARABIC_ABJAD = "arabicAbjad", + HINDI_VOWELS = "hindiVowels", + HINDI_CONSONANTS = "hindiConsonants", + HINDI_NUMBERS = "hindiNumbers", + HINDI_COUNTING = "hindiCounting", + THAI_LETTERS = "thaiLetters", + THAI_NUMBERS = "thaiNumbers", + THAI_COUNTING = "thaiCounting", + BAHT_TEXT = "bahtText", + DOLLAR_TEXT = "dollarText", + // +} diff --git a/src/file/styles/border/border-style.ts b/src/file/styles/border/border-style.ts deleted file mode 100644 index 00f4117d31..0000000000 --- a/src/file/styles/border/border-style.ts +++ /dev/null @@ -1,29 +0,0 @@ -export enum BorderStyle { - SINGLE = "single", - DASH_DOT_STROKED = "dashDotStroked", - DASHED = "dashed", - DASH_SMALL_GAP = "dashSmallGap", - DOT_DASH = "dotDash", - DOT_DOT_DASH = "dotDotDash", - DOTTED = "dotted", - DOUBLE = "double", - DOUBLE_WAVE = "doubleWave", - INSET = "inset", - NIL = "nil", - NONE = "none", - OUTSET = "outset", - THICK = "thick", - THICK_THIN_LARGE_GAP = "thickThinLargeGap", - THICK_THIN_MEDIUM_GAP = "thickThinMediumGap", - THICK_THIN_SMALL_GAP = "thickThinSmallGap", - THIN_THICK_LARGE_GAP = "thinThickLargeGap", - THIN_THICK_MEDIUM_GAP = "thinThickMediumGap", - THIN_THICK_SMALL_GAP = "thinThickSmallGap", - THIN_THICK_THIN_LARGE_GAP = "thinThickThinLargeGap", - THIN_THICK_THIN_MEDIUM_GAP = "thinThickThinMediumGap", - THIN_THICK_THIN_SMALL_GAP = "thinThickThinSmallGap", - THREE_D_EMBOSS = "threeDEmboss", - THREE_D_ENGRAVE = "threeDEngrave", - TRIPLE = "triple", - WAVE = "wave", -} diff --git a/src/file/styles/border/index.ts b/src/file/styles/border/index.ts deleted file mode 100644 index e62e5f7dcc..0000000000 --- a/src/file/styles/border/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./border-style"; diff --git a/src/file/styles/external-styles-factory.spec.ts b/src/file/styles/external-styles-factory.spec.ts index 2ddc849ae8..9db766008b 100644 --- a/src/file/styles/external-styles-factory.spec.ts +++ b/src/file/styles/external-styles-factory.spec.ts @@ -62,19 +62,14 @@ describe("External styles factory", () => { // tslint:disable-next-line:no-any const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any; expect(importedStyle.root[1]).to.deep.equal({ - deleted: false, root: [ { - deleted: false, root: [ { - deleted: false, root: [ { - deleted: false, root: [ { - deleted: false, root: { "w:ascii": "Arial", "w:cstheme": "minorHAnsi", @@ -87,10 +82,8 @@ describe("External styles factory", () => { rootKey: "w:rFonts", }, { - deleted: false, root: [ { - deleted: false, root: { "w:bidi": "ar-SA", "w:eastAsia": "en-US", @@ -108,16 +101,12 @@ describe("External styles factory", () => { rootKey: "w:rPrDefault", }, { - deleted: false, root: [ { - deleted: false, root: [ { - deleted: false, root: [ { - deleted: false, root: { "w:after": "160", "w:line": "259", @@ -138,10 +127,8 @@ describe("External styles factory", () => { rootKey: "w:docDefaults", }); expect(importedStyle.root[2]).to.deep.equal({ - deleted: false, root: [ { - deleted: false, root: { "w:defLockedState": "1", "w:defUIPriority": "99", @@ -165,10 +152,8 @@ describe("External styles factory", () => { expect(importedStyle.root.length).to.equal(5); expect(importedStyle.root[3]).to.deep.equal({ - deleted: false, root: [ { - deleted: false, root: { "w:default": "1", "w:styleId": "Normal", @@ -177,10 +162,8 @@ describe("External styles factory", () => { rootKey: "_attr", }, { - deleted: false, root: [ { - deleted: false, root: { "w:val": "Normal", }, @@ -190,7 +173,6 @@ describe("External styles factory", () => { rootKey: "w:name", }, { - deleted: false, root: [], rootKey: "w:qFormat", }, @@ -199,10 +181,8 @@ describe("External styles factory", () => { }); expect(importedStyle.root[4]).to.deep.equal({ - deleted: false, root: [ { - deleted: false, root: { "w:styleId": "Heading1", "w:type": "paragraph", @@ -210,10 +190,8 @@ describe("External styles factory", () => { rootKey: "_attr", }, { - deleted: false, root: [ { - deleted: false, root: { "w:val": "heading 1", }, @@ -223,10 +201,8 @@ describe("External styles factory", () => { rootKey: "w:name", }, { - deleted: false, root: [ { - deleted: false, root: { "w:val": "Normal", }, @@ -236,26 +212,20 @@ describe("External styles factory", () => { rootKey: "w:basedOn", }, { - deleted: false, root: [ { - deleted: false, root: [], rootKey: "w:keepNext", }, { - deleted: false, root: [], rootKey: "w:keepLines", }, { - deleted: false, root: [ { - deleted: false, root: [ { - deleted: false, root: { "w:color": "auto", "w:space": "1", diff --git a/src/file/styles/style/character-style.spec.ts b/src/file/styles/style/character-style.spec.ts index 65dcdc26ee..4ddf087f2d 100644 --- a/src/file/styles/style/character-style.spec.ts +++ b/src/file/styles/style/character-style.spec.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import { EmphasisMarkType } from "file/paragraph/run/emphasis-mark"; import { UnderlineType } from "file/paragraph/run/underline"; -import { ShadingType } from "file/table"; +import { ShadingType } from "file/shading"; import { EMPTY_OBJECT } from "file/xml-components"; import { StyleForCharacter } from "./character-style"; @@ -65,9 +65,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], - }, { "w:uiPriority": { _attr: { @@ -78,6 +75,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:smallCaps": {} }], + }, ], }); }); @@ -93,9 +93,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], - }, { "w:uiPriority": { _attr: { @@ -106,6 +103,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:caps": {} }], + }, ], }); }); @@ -121,9 +121,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], - }, { "w:uiPriority": { _attr: { @@ -134,6 +131,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:strike": {} }], + }, ], }); }); @@ -149,9 +149,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], - }, { "w:uiPriority": { _attr: { @@ -162,6 +159,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:dstrike": {} }], + }, ], }); }); @@ -177,6 +177,16 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:rPr": [ { @@ -188,16 +198,6 @@ describe("CharacterStyle", () => { }, ], }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, ], }); }); @@ -213,6 +213,16 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:rPr": [ { @@ -227,16 +237,6 @@ describe("CharacterStyle", () => { }, ], }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, ], }); }); @@ -255,6 +255,16 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:rPr": [ { @@ -267,16 +277,6 @@ describe("CharacterStyle", () => { }, ], }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, ], }); }); @@ -292,9 +292,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:spacing": { _attr: { "w:val": 100 } } }], - }, { "w:uiPriority": { _attr: { @@ -305,6 +302,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:spacing": { _attr: { "w:val": 100 } } }], + }, ], }); }); @@ -317,6 +317,7 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { "w:basedOn": { _attr: { "w:val": "otherId" } } }, { "w:uiPriority": { _attr: { @@ -327,7 +328,6 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, - { "w:basedOn": { _attr: { "w:val": "otherId" } } }, ], }); }); @@ -365,9 +365,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": expected, - }, { "w:uiPriority": { _attr: { @@ -378,6 +375,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": expected, + }, ], }); }); @@ -395,9 +395,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }], - }, { "w:uiPriority": { _attr: { @@ -408,6 +405,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }], + }, ], }); }); @@ -425,9 +425,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }], - }, { "w:uiPriority": { _attr: { @@ -438,6 +435,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }], + }, ], }); }); @@ -456,9 +456,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }], - }, { "w:uiPriority": { _attr: { @@ -469,6 +466,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }], + }, ], }); }); @@ -486,9 +486,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], - }, { "w:uiPriority": { _attr: { @@ -499,6 +496,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }, ], }); }); @@ -516,9 +516,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], - }, { "w:uiPriority": { _attr: { @@ -529,6 +526,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }, ], }); }); @@ -545,6 +545,16 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:rPr": [ { @@ -556,16 +566,6 @@ describe("CharacterStyle", () => { }, ], }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, ], }); }); @@ -581,9 +581,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }], - }, { "w:uiPriority": { _attr: { @@ -594,6 +591,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }], + }, ], }); }); @@ -601,17 +601,17 @@ describe("CharacterStyle", () => { const boldTests = [ { bold: true, - expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }, { "w:bCs": {} }], }, { bold: true, boldComplexScript: true, - expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }, { "w:bCs": {} }], }, { bold: true, boldComplexScript: false, - expected: [{ "w:b": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }], }, ]; boldTests.forEach(({ bold, boldComplexScript, expected }) => { @@ -624,9 +624,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": expected, - }, { "w:uiPriority": { _attr: { @@ -637,6 +634,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": expected, + }, ], }); }); @@ -645,17 +645,17 @@ describe("CharacterStyle", () => { const italicsTests = [ { italics: true, - expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }, { "w:iCs": {} }], }, { italics: true, italicsComplexScript: true, - expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }, { "w:iCs": {} }], }, { italics: true, italicsComplexScript: false, - expected: [{ "w:i": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }], }, ]; italicsTests.forEach(({ italics, italicsComplexScript, expected }) => { @@ -668,9 +668,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": expected, - }, { "w:uiPriority": { _attr: { @@ -681,6 +678,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": expected, + }, ], }); }); @@ -692,6 +692,7 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { "w:link": { _attr: { "w:val": "MyLink" } } }, { "w:uiPriority": { _attr: { @@ -702,7 +703,6 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, - { "w:link": { _attr: { "w:val": "MyLink" } } }, ], }); }); @@ -720,8 +720,8 @@ describe("CharacterStyle", () => { }, }, }, - { "w:unhideWhenUsed": EMPTY_OBJECT }, { "w:semiHidden": EMPTY_OBJECT }, + { "w:unhideWhenUsed": EMPTY_OBJECT }, ], }); }); @@ -757,9 +757,6 @@ describe("CharacterStyle", () => { expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": expected, - }, { "w:uiPriority": { _attr: { @@ -770,20 +767,15 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": expected, + }, ], }); }); }); const shadingTests = [ - { - shadow: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, { shading: { type: ShadingType.PERCENT_10, @@ -794,42 +786,23 @@ describe("CharacterStyle", () => { }, { shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", + type: ShadingType.SOLID, + fill: "AA0000", + color: "DD0000", }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, - { - shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, - { - shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], + expected: [{ "w:shd": { _attr: { "w:val": "solid", "w:fill": "AA0000", "w:color": "DD0000" } } }], }, ]; - shadingTests.forEach(({ shadow, shading, expected }) => { + shadingTests.forEach(({ shading, expected }) => { it("#shadow correctly", () => { const style = new StyleForCharacter({ id: "myStyleId", - run: { shadow, shading }, + run: { shading }, }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, - { - "w:rPr": expected, - }, { "w:uiPriority": { _attr: { @@ -840,6 +813,9 @@ describe("CharacterStyle", () => { { "w:unhideWhenUsed": EMPTY_OBJECT, }, + { + "w:rPr": expected, + }, ], }); }); diff --git a/src/file/styles/style/character-style.ts b/src/file/styles/style/character-style.ts index 63252bddd5..229c8e54c6 100644 --- a/src/file/styles/style/character-style.ts +++ b/src/file/styles/style/character-style.ts @@ -1,12 +1,8 @@ import { IRunStylePropertiesOptions, RunProperties } from "file/paragraph/run/properties"; -import { BasedOn, Link, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; -import { Style } from "./style"; +import { IStyleOptions, Style } from "./style"; -export interface IBaseCharacterStyleOptions { - readonly basedOn?: string; - readonly link?: string; - readonly semiHidden?: boolean; +export interface IBaseCharacterStyleOptions extends IStyleOptions { readonly run?: IRunStylePropertiesOptions; } @@ -19,24 +15,16 @@ export class StyleForCharacter extends Style { private readonly runProperties: RunProperties; constructor(options: ICharacterStyleOptions) { - super({ type: "character", styleId: options.id }, options.name); + super( + { type: "character", styleId: options.id }, + { + uiPriority: 99, + unhideWhenUsed: true, + ...options, + }, + ); this.runProperties = new RunProperties(options.run); - this.root.push(this.runProperties); - this.root.push(new UiPriority(99)); - this.root.push(new UnhideWhenUsed()); - - if (options.basedOn) { - this.root.push(new BasedOn(options.basedOn)); - } - - if (options.link) { - this.root.push(new Link(options.link)); - } - - if (options.semiHidden) { - this.root.push(new SemiHidden()); - } } } diff --git a/src/file/styles/style/components.spec.ts b/src/file/styles/style/components.spec.ts index b1720ef4b4..7ce7df62ab 100644 --- a/src/file/styles/style/components.spec.ts +++ b/src/file/styles/style/components.spec.ts @@ -2,8 +2,6 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import * as components from "./components"; -import { EMPTY_OBJECT } from "file/xml-components"; - describe("Style components", () => { it("Name#constructor", () => { const style = new components.Name("Style Name"); @@ -11,45 +9,9 @@ describe("Style components", () => { expect(tree).to.deep.equal({ "w:name": { _attr: { "w:val": "Style Name" } } }); }); - it("BasedOn#constructor", () => { - const style = new components.BasedOn("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:basedOn": { _attr: { "w:val": "otherId" } } }); - }); - - it("Next#constructor", () => { - const style = new components.Next("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:next": { _attr: { "w:val": "otherId" } } }); - }); - - it("Link#constructor", () => { - const style = new components.Link("otherId"); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:link": { _attr: { "w:val": "otherId" } } }); - }); - it("UiPriority#constructor", () => { const style = new components.UiPriority(123); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:uiPriority": { _attr: { "w:val": 123 } } }); }); - - it("UnhideWhenUsed#constructor", () => { - const style = new components.UnhideWhenUsed(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:unhideWhenUsed": EMPTY_OBJECT }); - }); - - it("QuickFormat#constructor", () => { - const style = new components.QuickFormat(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:qFormat": EMPTY_OBJECT }); - }); - - it("SemiHidden#constructor", () => { - const style = new components.SemiHidden(); - const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:semiHidden": EMPTY_OBJECT }); - }); }); diff --git a/src/file/styles/style/components.ts b/src/file/styles/style/components.ts index e8a217c638..4b35ba38b3 100644 --- a/src/file/styles/style/components.ts +++ b/src/file/styles/style/components.ts @@ -1,4 +1,5 @@ // http://officeopenxml.com/WPstyleGenProps.php +import { decimalNumber } from "file/values"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; class ComponentAttributes extends XmlAttributeComponent<{ @@ -14,53 +15,13 @@ export class Name extends XmlComponent { } } -export class BasedOn extends XmlComponent { - constructor(value: string) { - super("w:basedOn"); - this.root.push(new ComponentAttributes({ val: value })); - } -} - -export class Next extends XmlComponent { - constructor(value: string) { - super("w:next"); - this.root.push(new ComponentAttributes({ val: value })); - } -} - -export class Link extends XmlComponent { - constructor(value: string) { - super("w:link"); - this.root.push(new ComponentAttributes({ val: value })); - } -} - export class UiPriority extends XmlComponent { constructor(value: number) { super("w:uiPriority"); - // TODO: this value should be a ST_DecimalNumber - this.root.push(new ComponentAttributes({ val: value })); - } -} - -export class UnhideWhenUsed extends XmlComponent { - constructor() { - super("w:unhideWhenUsed"); - } -} - -export class QuickFormat extends XmlComponent { - constructor() { - super("w:qFormat"); + this.root.push(new ComponentAttributes({ val: decimalNumber(value) })); } } export class TableProperties extends XmlComponent {} export class RsId extends XmlComponent {} - -export class SemiHidden extends XmlComponent { - constructor() { - super("w:semiHidden"); - } -} diff --git a/src/file/styles/style/default-styles.spec.ts b/src/file/styles/style/default-styles.spec.ts index 962f47d9c0..dda921a110 100644 --- a/src/file/styles/style/default-styles.spec.ts +++ b/src/file/styles/style/default-styles.spec.ts @@ -154,6 +154,21 @@ describe("Default Styles", () => { "w:style": [ { _attr: { "w:type": "paragraph", "w:styleId": "FootnoteText" } }, { "w:name": { _attr: { "w:val": "footnote text" } } }, + { "w:basedOn": { _attr: { "w:val": "Normal" } } }, + { "w:link": { _attr: { "w:val": "FootnoteTextChar" } } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:semiHidden": EMPTY_OBJECT, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:pPr": [ { @@ -185,21 +200,6 @@ describe("Default Styles", () => { }, ], }, - { "w:basedOn": { _attr: { "w:val": "Normal" } } }, - { "w:link": { _attr: { "w:val": "FootnoteTextChar" } } }, - { - "w:semiHidden": EMPTY_OBJECT, - }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, ], }); }); @@ -211,6 +211,20 @@ describe("Default Styles", () => { "w:style": [ { _attr: { "w:type": "character", "w:styleId": "FootnoteReference" } }, { "w:name": { _attr: { "w:val": "footnote reference" } } }, + { "w:basedOn": { _attr: { "w:val": "DefaultParagraphFont" } } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:semiHidden": EMPTY_OBJECT, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:rPr": [ { @@ -222,21 +236,6 @@ describe("Default Styles", () => { }, ], }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, - { "w:basedOn": { _attr: { "w:val": "DefaultParagraphFont" } } }, - - { - "w:semiHidden": EMPTY_OBJECT, - }, ], }); }); @@ -248,6 +247,21 @@ describe("Default Styles", () => { "w:style": [ { _attr: { "w:type": "character", "w:styleId": "FootnoteTextChar" } }, { "w:name": { _attr: { "w:val": "Footnote Text Char" } } }, + { "w:basedOn": { _attr: { "w:val": "DefaultParagraphFont" } } }, + { "w:link": { _attr: { "w:val": "FootnoteText" } } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:semiHidden": EMPTY_OBJECT, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:rPr": [ { @@ -266,21 +280,6 @@ describe("Default Styles", () => { }, ], }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, - { "w:basedOn": { _attr: { "w:val": "DefaultParagraphFont" } } }, - { "w:link": { _attr: { "w:val": "FootnoteText" } } }, - { - "w:semiHidden": EMPTY_OBJECT, - }, ], }); }); @@ -292,6 +291,17 @@ describe("Default Styles", () => { "w:style": [ { _attr: { "w:type": "character", "w:styleId": "Hyperlink" } }, { "w:name": { _attr: { "w:val": "Hyperlink" } } }, + { "w:basedOn": { _attr: { "w:val": "DefaultParagraphFont" } } }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, { "w:rPr": [ { "w:u": { _attr: { "w:val": "single" } } }, @@ -304,17 +314,6 @@ describe("Default Styles", () => { }, ], }, - { - "w:uiPriority": { - _attr: { - "w:val": 99, - }, - }, - }, - { - "w:unhideWhenUsed": EMPTY_OBJECT, - }, - { "w:basedOn": { _attr: { "w:val": "DefaultParagraphFont" } } }, ], }); }); diff --git a/src/file/styles/style/paragraph-style.spec.ts b/src/file/styles/style/paragraph-style.spec.ts index 0885181c0c..28f229677b 100644 --- a/src/file/styles/style/paragraph-style.spec.ts +++ b/src/file/styles/style/paragraph-style.spec.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import { AlignmentType, EmphasisMarkType, TabStopPosition } from "file/paragraph"; import { UnderlineType } from "file/paragraph/run/underline"; -import { ShadingType } from "file/table"; +import { ShadingType } from "file/shading"; import { EMPTY_OBJECT } from "file/xml-components"; import { StyleForParagraph } from "./paragraph-style"; @@ -78,7 +78,7 @@ describe("ParagraphStyle", () => { const style = new StyleForParagraph({ id: "myStyleId", paragraph: { - indent: { left: 720 }, + indent: { left: 720, right: 500 }, }, }); const tree = new Formatter().format(style); @@ -86,7 +86,7 @@ describe("ParagraphStyle", () => { "w:style": [ { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { - "w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }], + "w:pPr": [{ "w:ind": { _attr: { "w:left": 720, "w:right": 500 } } }], }, ], }); @@ -242,11 +242,7 @@ describe("ParagraphStyle", () => { { "w:pPr": [ { - "w:contextualSpacing": { - _attr: { - "w:val": 1, - }, - }, + "w:contextualSpacing": {}, }, ], }, @@ -404,7 +400,7 @@ describe("ParagraphStyle", () => { "w:style": [ { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { - "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:smallCaps": {} }], }, ], }); @@ -422,7 +418,7 @@ describe("ParagraphStyle", () => { "w:style": [ { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { - "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:caps": {} }], }, ], }); @@ -440,7 +436,7 @@ describe("ParagraphStyle", () => { "w:style": [ { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { - "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:strike": {} }], }, ], }); @@ -458,7 +454,7 @@ describe("ParagraphStyle", () => { "w:style": [ { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { - "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], + "w:rPr": [{ "w:dstrike": {} }], }, ], }); @@ -562,17 +558,17 @@ describe("ParagraphStyle", () => { const boldTests = [ { bold: true, - expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }, { "w:bCs": {} }], }, { bold: true, boldComplexScript: true, - expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }, { "w:bCs": {} }], }, { bold: true, boldComplexScript: false, - expected: [{ "w:b": { _attr: { "w:val": true } } }], + expected: [{ "w:b": {} }], }, ]; boldTests.forEach(({ bold, boldComplexScript, expected }) => { @@ -591,17 +587,17 @@ describe("ParagraphStyle", () => { const italicsTests = [ { italics: true, - expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }, { "w:iCs": {} }], }, { italics: true, italicsComplexScript: true, - expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }, { "w:iCs": {} }], }, { italics: true, italicsComplexScript: false, - expected: [{ "w:i": { _attr: { "w:val": true } } }], + expected: [{ "w:i": {} }], }, ]; italicsTests.forEach(({ italics, italicsComplexScript, expected }) => { @@ -652,14 +648,6 @@ describe("ParagraphStyle", () => { }); const shadingTests = [ - { - shadow: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, { shading: { type: ShadingType.PERCENT_10, @@ -670,34 +658,18 @@ describe("ParagraphStyle", () => { }, { shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", + type: ShadingType.DIAGONAL_CROSS, + fill: "0066FF", + color: "0000FF", }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, - { - shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], - }, - { - shading: { - type: ShadingType.PERCENT_10, - fill: "00FFFF", - color: "FF0000", - }, - expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], + expected: [{ "w:shd": { _attr: { "w:val": "diagCross", "w:fill": "0066FF", "w:color": "0000FF" } } }], }, ]; - shadingTests.forEach(({ shadow, shading, expected }) => { - it("#shadow correctly", () => { + shadingTests.forEach(({ shading, expected }) => { + it("#shade correctly", () => { const style = new StyleForParagraph({ id: "myStyleId", - run: { shadow, shading }, + run: { shading }, }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index 25c035e514..5f90a87fe2 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -1,17 +1,8 @@ import { IParagraphStylePropertiesOptions, IRunStylePropertiesOptions, ParagraphProperties } from "file/paragraph"; import { RunProperties } from "file/paragraph/run/properties"; +import { IStyleOptions, Style } from "./style"; -import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; -import { Style } from "./style"; - -export interface IBaseParagraphStyleOptions { - readonly basedOn?: string; - readonly next?: string; - readonly quickFormat?: boolean; - readonly link?: string; - readonly semiHidden?: boolean; - readonly uiPriority?: number; - readonly unhideWhenUsed?: boolean; +export interface IBaseParagraphStyleOptions extends IStyleOptions { readonly paragraph?: IParagraphStylePropertiesOptions; readonly run?: IRunStylePropertiesOptions; } @@ -26,40 +17,12 @@ export class StyleForParagraph extends Style { private readonly runProperties: RunProperties; constructor(options: IParagraphStyleOptions) { - super({ type: "paragraph", styleId: options.id }, options.name); + super({ type: "paragraph", styleId: options.id }, options); this.paragraphProperties = new ParagraphProperties(options.paragraph); this.runProperties = new RunProperties(options.run); this.root.push(this.paragraphProperties); this.root.push(this.runProperties); - - if (options.basedOn) { - this.root.push(new BasedOn(options.basedOn)); - } - - if (options.next) { - this.root.push(new Next(options.next)); - } - - if (options.quickFormat) { - this.root.push(new QuickFormat()); - } - - if (options.link) { - this.root.push(new Link(options.link)); - } - - if (options.semiHidden) { - this.root.push(new SemiHidden()); - } - - if (options.uiPriority) { - this.root.push(new UiPriority(options.uiPriority)); - } - - if (options.unhideWhenUsed) { - this.root.push(new UnhideWhenUsed()); - } } } diff --git a/src/file/styles/style/style.spec.ts b/src/file/styles/style/style.spec.ts index 55f55bd398..4135272e5d 100644 --- a/src/file/styles/style/style.spec.ts +++ b/src/file/styles/style/style.spec.ts @@ -5,11 +5,14 @@ import { Style } from "./style"; describe("Style", () => { describe("#constructor()", () => { it("should set the given properties", () => { - const style = new Style({ - type: "paragraph", - styleId: "myStyleId", - default: true, - }); + const style = new Style( + { + type: "paragraph", + styleId: "myStyleId", + default: true, + }, + {}, + ); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId", "w:default": true } }, @@ -22,7 +25,7 @@ describe("Style", () => { type: "paragraph", styleId: "myStyleId", }, - "Style Name", + { name: "Style Name" }, ); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ diff --git a/src/file/styles/style/style.ts b/src/file/styles/style/style.ts index d0d69f17ed..767539503b 100644 --- a/src/file/styles/style/style.ts +++ b/src/file/styles/style/style.ts @@ -1,5 +1,36 @@ -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -import { Name } from "./components"; +import { OnOffElement, StringValueElement, XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { Name, UiPriority } from "./components"; + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// export interface IStyleAttributes { readonly type?: string; @@ -8,6 +39,17 @@ export interface IStyleAttributes { readonly customStyle?: string; } +export interface IStyleOptions { + readonly name?: string; + readonly basedOn?: string; + readonly next?: string; + readonly link?: string; + readonly uiPriority?: number; + readonly semiHidden?: boolean; + readonly unhideWhenUsed?: boolean; + readonly quickFormat?: boolean; +} + class StyleAttributes extends XmlAttributeComponent { protected readonly xmlKeys = { type: "w:type", @@ -18,11 +60,39 @@ class StyleAttributes extends XmlAttributeComponent { } export class Style extends XmlComponent { - constructor(attributes: IStyleAttributes, name?: string) { + constructor(attributes: IStyleAttributes, options: IStyleOptions) { super("w:style"); this.root.push(new StyleAttributes(attributes)); - if (name) { - this.root.push(new Name(name)); + if (options.name) { + this.root.push(new Name(options.name)); + } + + if (options.basedOn) { + this.root.push(new StringValueElement("w:basedOn", options.basedOn)); + } + + if (options.next) { + this.root.push(new StringValueElement("w:next", options.next)); + } + + if (options.link) { + this.root.push(new StringValueElement("w:link", options.link)); + } + + if (options.uiPriority !== undefined) { + this.root.push(new UiPriority(options.uiPriority)); + } + + if (options.semiHidden !== undefined) { + this.root.push(new OnOffElement("w:semiHidden", options.semiHidden)); + } + + if (options.unhideWhenUsed !== undefined) { + this.root.push(new OnOffElement("w:unhideWhenUsed", options.unhideWhenUsed)); + } + + if (options.quickFormat !== undefined) { + this.root.push(new OnOffElement("w:qFormat", options.quickFormat)); } } } diff --git a/src/file/styles/styles.ts b/src/file/styles/styles.ts index 1906b45ba0..e37fe8c263 100644 --- a/src/file/styles/styles.ts +++ b/src/file/styles/styles.ts @@ -3,7 +3,6 @@ import { BaseXmlComponent, ImportedXmlComponent, XmlComponent } from "file/xml-c import { StyleForCharacter, StyleForParagraph } from "./style"; import { ICharacterStyleOptions } from "./style/character-style"; import { IParagraphStyleOptions } from "./style/paragraph-style"; -export * from "./border"; export interface IStylesOptions { readonly default?: IDefaultStylesOptions; @@ -13,6 +12,13 @@ export interface IStylesOptions { readonly importedStyles?: (XmlComponent | StyleForParagraph | StyleForCharacter | ImportedXmlComponent)[]; } +// +// +// +// +// +// +// export class Styles extends XmlComponent { constructor(options: IStylesOptions) { super("w:styles"); diff --git a/src/file/table/grid.ts b/src/file/table/grid.ts index 5ce6486fcc..b8a683f255 100644 --- a/src/file/table/grid.ts +++ b/src/file/table/grid.ts @@ -1,8 +1,19 @@ // http://officeopenxml.com/WPtableGrid.php + +// +// +// +// +// +// +// +// + +import { twipsMeasureValue } from "file/values"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export class TableGrid extends XmlComponent { - constructor(widths: number[]) { + constructor(widths: number[] | string[]) { super("w:tblGrid"); for (const width of widths) { this.root.push(new GridCol(width)); @@ -10,15 +21,15 @@ export class TableGrid extends XmlComponent { } } -class GridColAttributes extends XmlAttributeComponent<{ readonly w: number }> { +class GridColAttributes extends XmlAttributeComponent<{ readonly w: number | string }> { protected readonly xmlKeys = { w: "w:w" }; } export class GridCol extends XmlComponent { - constructor(width?: number) { + constructor(width?: number | string) { super("w:gridCol"); if (width !== undefined) { - this.root.push(new GridColAttributes({ w: width })); + this.root.push(new GridColAttributes({ w: twipsMeasureValue(width) })); } } } diff --git a/src/file/table/index.ts b/src/file/table/index.ts index cec3788f8e..81ceac8b1a 100644 --- a/src/file/table/index.ts +++ b/src/file/table/index.ts @@ -1,5 +1,5 @@ export * from "./table"; export * from "./table-cell"; export * from "./table-properties"; -export * from "./shading"; export * from "./table-row"; +export * from "./table-width"; diff --git a/src/file/table/shading/shading.ts b/src/file/table/shading/shading.ts deleted file mode 100644 index baeb426e22..0000000000 --- a/src/file/table/shading/shading.ts +++ /dev/null @@ -1,64 +0,0 @@ -// http://officeopenxml.com/WPtableShading.php -// http://officeopenxml.com/WPtableCellProperties-Shading.php -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -export interface ITableShadingAttributesProperties { - readonly fill?: string; - readonly color?: string; - readonly val?: ShadingType; -} - -class TableShadingAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - fill: "w:fill", - color: "w:color", - val: "w:val", - }; -} - -export class TableShading extends XmlComponent { - constructor(attrs: ITableShadingAttributesProperties) { - super("w:shd"); - this.root.push(new TableShadingAttributes(attrs)); - } -} - -export enum ShadingType { - CLEAR = "clear", - DIAGONAL_CROSS = "diagCross", - DIAGONAL_STRIPE = "diagStripe", - HORIZONTAL_CROSS = "horzCross", - HORIZONTAL_STRIPE = "horzStripe", - NIL = "nil", - PERCENT_5 = "pct5", - PERCENT_10 = "pct10", - PERCENT_12 = "pct12", - PERCENT_15 = "pct15", - PERCENT_20 = "pct20", - PERCENT_25 = "pct25", - PERCENT_30 = "pct30", - PERCENT_35 = "pct35", - PERCENT_37 = "pct37", - PERCENT_40 = "pct40", - PERCENT_45 = "pct45", - PERCENT_50 = "pct50", - PERCENT_55 = "pct55", - PERCENT_60 = "pct60", - PERCENT_62 = "pct62", - PERCENT_65 = "pct65", - PERCENT_70 = "pct70", - PERCENT_75 = "pct75", - PERCENT_80 = "pct80", - PERCENT_85 = "pct85", - PERCENT_87 = "pct87", - PERCENT_90 = "pct90", - PERCENT_95 = "pct95", - REVERSE_DIAGONAL_STRIPE = "reverseDiagStripe", - SOLID = "solid", - THIN_DIAGONAL_CROSS = "thinDiagCross", - THIN_DIAGONAL_STRIPE = "thinDiagStripe", - THIN_HORIZONTAL_CROSS = "thinHorzCross", - THIN_REVERSE_DIAGONAL_STRIPE = "thinReverseDiagStripe", - THIN_VERTICAL_STRIPE = "thinVertStripe", - VERTICAL_STRIPE = "vertStripe", -} diff --git a/src/file/table/table-cell/cell-margin/cell-margin.spec.ts b/src/file/table/table-cell/cell-margin/cell-margin.spec.ts deleted file mode 100644 index 399020a2d2..0000000000 --- a/src/file/table/table-cell/cell-margin/cell-margin.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { BottomCellMargin, LeftCellMargin, RightCellMargin, TopCellMargin } from "./cell-margin"; - -describe("TopCellMargin", () => { - describe("#constructor", () => { - it("should create", () => { - const cellMargin = new TopCellMargin(1); - const tree = new Formatter().format(cellMargin); - expect(tree).to.deep.equal({ - "w:top": { - _attr: { - "w:type": "dxa", - "w:w": 1, - }, - }, - }); - }); - }); -}); - -describe("BottomCellMargin", () => { - describe("#constructor", () => { - it("should create", () => { - const cellMargin = new BottomCellMargin(1); - const tree = new Formatter().format(cellMargin); - expect(tree).to.deep.equal({ - "w:bottom": { - _attr: { - "w:type": "dxa", - "w:w": 1, - }, - }, - }); - }); - }); -}); - -describe("LeftCellMargin", () => { - describe("#constructor", () => { - it("should create", () => { - const cellMargin = new LeftCellMargin(1); - const tree = new Formatter().format(cellMargin); - expect(tree).to.deep.equal({ - "w:start": { - _attr: { - "w:type": "dxa", - "w:w": 1, - }, - }, - }); - }); - }); -}); - -describe("RightCellMargin", () => { - describe("#constructor", () => { - it("should create", () => { - const cellMargin = new RightCellMargin(1); - const tree = new Formatter().format(cellMargin); - expect(tree).to.deep.equal({ - "w:end": { - _attr: { - "w:type": "dxa", - "w:w": 1, - }, - }, - }); - }); - }); -}); diff --git a/src/file/table/table-cell/cell-margin/cell-margin.ts b/src/file/table/table-cell/cell-margin/cell-margin.ts deleted file mode 100644 index 090f83367a..0000000000 --- a/src/file/table/table-cell/cell-margin/cell-margin.ts +++ /dev/null @@ -1,61 +0,0 @@ -// http://officeopenxml.com/WPtableCellProperties-Margins.php -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -class CellMarginAttributes extends XmlAttributeComponent<{ - readonly type: string; - readonly width: number; -}> { - protected readonly xmlKeys = { width: "w:w", type: "w:type" }; -} - -export class TopCellMargin extends XmlComponent { - constructor(value: number) { - super("w:top"); - - this.root.push( - new CellMarginAttributes({ - width: value, - type: "dxa", - }), - ); - } -} - -export class BottomCellMargin extends XmlComponent { - constructor(value: number) { - super("w:bottom"); - - this.root.push( - new CellMarginAttributes({ - width: value, - type: "dxa", - }), - ); - } -} - -export class LeftCellMargin extends XmlComponent { - constructor(value: number) { - super("w:start"); - - this.root.push( - new CellMarginAttributes({ - width: value, - type: "dxa", - }), - ); - } -} - -export class RightCellMargin extends XmlComponent { - constructor(value: number) { - super("w:end"); - - this.root.push( - new CellMarginAttributes({ - width: value, - type: "dxa", - }), - ); - } -} diff --git a/src/file/table/table-cell/cell-margin/table-cell-margins.spec.ts b/src/file/table/table-cell/cell-margin/table-cell-margins.spec.ts deleted file mode 100644 index 8777ec5a9b..0000000000 --- a/src/file/table/table-cell/cell-margin/table-cell-margins.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { TableCellMargin } from "./table-cell-margins"; - -describe("TableCellMargin", () => { - describe("#constructor", () => { - it("should create with default values", () => { - const cellMargin = new TableCellMargin({}); - const tree = new Formatter().format(cellMargin); - expect(tree).to.deep.equal({ - "w:tcMar": [ - { - "w:top": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - { - "w:start": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - { - "w:bottom": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - { - "w:end": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - ], - }); - }); - - it("should create with values", () => { - const cellMargin = new TableCellMargin({ - top: 5, - bottom: 5, - left: 5, - right: 5, - }); - const tree = new Formatter().format(cellMargin); - expect(tree).to.deep.equal({ - "w:tcMar": [ - { - "w:top": { - _attr: { - "w:type": "dxa", - "w:w": 5, - }, - }, - }, - { - "w:start": { - _attr: { - "w:type": "dxa", - "w:w": 5, - }, - }, - }, - { - "w:bottom": { - _attr: { - "w:type": "dxa", - "w:w": 5, - }, - }, - }, - { - "w:end": { - _attr: { - "w:type": "dxa", - "w:w": 5, - }, - }, - }, - ], - }); - }); - }); -}); diff --git a/src/file/table/table-cell/cell-margin/table-cell-margins.ts b/src/file/table/table-cell/cell-margin/table-cell-margins.ts deleted file mode 100644 index efb2beb5bd..0000000000 --- a/src/file/table/table-cell/cell-margin/table-cell-margins.ts +++ /dev/null @@ -1,21 +0,0 @@ -// http://officeopenxml.com/WPtableCellProperties-Margins.php -import { XmlComponent } from "file/xml-components"; - -import { BottomCellMargin, LeftCellMargin, RightCellMargin, TopCellMargin } from "./cell-margin"; - -export interface ITableCellMarginOptions { - readonly top?: number; - readonly left?: number; - readonly bottom?: number; - readonly right?: number; -} - -export class TableCellMargin extends XmlComponent { - constructor({ top = 0, left = 0, right = 0, bottom = 0 }: ITableCellMarginOptions) { - super("w:tcMar"); - this.root.push(new TopCellMargin(top)); - this.root.push(new LeftCellMargin(left)); - this.root.push(new BottomCellMargin(bottom)); - this.root.push(new RightCellMargin(right)); - } -} diff --git a/src/file/table/table-cell/table-cell-components.ts b/src/file/table/table-cell/table-cell-components.ts index 41858d4e17..e57785a19f 100644 --- a/src/file/table/table-cell/table-cell-components.ts +++ b/src/file/table/table-cell/table-cell-components.ts @@ -1,78 +1,53 @@ -import { BorderStyle } from "file/styles"; +import { BorderElement, IBorderOptions } from "file/border"; +import { decimalNumber } from "file/values"; import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "file/xml-components"; -class CellBorderAttributes extends XmlAttributeComponent<{ - readonly style: BorderStyle; - readonly size: number; - readonly color: string; -}> { - protected readonly xmlKeys = { style: "w:val", size: "w:sz", color: "w:color" }; -} +// +// +// +// +// +// +// +// +// +// +// +// +// +// -class BaseTableCellBorder extends XmlComponent { - public setProperties(style: BorderStyle, size: number, color: string): BaseTableCellBorder { - const attrs = new CellBorderAttributes({ - style: style, - size: size, - color: color, - }); - this.root.push(attrs); - - return this; - } +export interface ITableCellBorders { + readonly top?: IBorderOptions; + readonly start?: IBorderOptions; + readonly left?: IBorderOptions; + readonly bottom?: IBorderOptions; + readonly end?: IBorderOptions; + readonly right?: IBorderOptions; } export class TableCellBorders extends IgnoreIfEmptyXmlComponent { - constructor() { + constructor(options: ITableCellBorders) { super("w:tcBorders"); - } - public addTopBorder(style: BorderStyle, size: number, color: string): TableCellBorders { - const top = new BaseTableCellBorder("w:top"); - top.setProperties(style, size, color); - this.root.push(top); - - return this; - } - - public addStartBorder(style: BorderStyle, size: number, color: string): TableCellBorders { - const start = new BaseTableCellBorder("w:start"); - start.setProperties(style, size, color); - this.root.push(start); - - return this; - } - - public addBottomBorder(style: BorderStyle, size: number, color: string): TableCellBorders { - const bottom = new BaseTableCellBorder("w:bottom"); - bottom.setProperties(style, size, color); - this.root.push(bottom); - - return this; - } - - public addEndBorder(style: BorderStyle, size: number, color: string): TableCellBorders { - const end = new BaseTableCellBorder("w:end"); - end.setProperties(style, size, color); - this.root.push(end); - - return this; - } - - public addLeftBorder(style: BorderStyle, size: number, color: string): TableCellBorders { - const left = new BaseTableCellBorder("w:left"); - left.setProperties(style, size, color); - this.root.push(left); - - return this; - } - - public addRightBorder(style: BorderStyle, size: number, color: string): TableCellBorders { - const right = new BaseTableCellBorder("w:right"); - right.setProperties(style, size, color); - this.root.push(right); - - return this; + if (options.top) { + this.root.push(new BorderElement("w:top", options.top)); + } + if (options.start) { + this.root.push(new BorderElement("w:start", options.start)); + } + if (options.left) { + this.root.push(new BorderElement("w:left", options.left)); + } + if (options.bottom) { + this.root.push(new BorderElement("w:bottom", options.bottom)); + } + if (options.end) { + this.root.push(new BorderElement("w:end", options.end)); + } + if (options.right) { + this.root.push(new BorderElement("w:right", options.right)); + } } } @@ -83,6 +58,10 @@ class GridSpanAttributes extends XmlAttributeComponent<{ readonly val: number }> protected readonly xmlKeys = { val: "w:val" }; } +// +// ... +// +// /** * GridSpan element. Should be used in a table cell. Pass the number of columns that this cell need to span. */ @@ -92,7 +71,7 @@ export class GridSpan extends XmlComponent { this.root.push( new GridSpanAttributes({ - val: value, + val: decimalNumber(value), }), ); } @@ -131,31 +110,6 @@ export class VerticalMerge extends XmlComponent { } } -export enum VerticalAlign { - BOTTOM = "bottom", - CENTER = "center", - TOP = "top", -} - -class VAlignAttributes extends XmlAttributeComponent<{ readonly val: VerticalAlign }> { - protected readonly xmlKeys = { val: "w:val" }; -} - -/** - * Vertical align element. - */ -export class VAlign extends XmlComponent { - constructor(value: VerticalAlign) { - super("w:vAlign"); - - this.root.push( - new VAlignAttributes({ - val: value, - }), - ); - } -} - export enum TextDirection { BOTTOM_TO_TOP_LEFT_TO_RIGHT = "btLr", LEFT_TO_RIGHT_TOP_TO_BOTTOM = "lrTb", @@ -180,34 +134,3 @@ export class TDirection extends XmlComponent { ); } } - -export enum WidthType { - /** Auto. */ - AUTO = "auto", - /** Value is in twentieths of a point */ - DXA = "dxa", - /** No (empty) value. */ - NIL = "nil", - /** Value is in percentage. */ - PERCENTAGE = "pct", -} - -class TableCellWidthAttributes extends XmlAttributeComponent<{ readonly type: WidthType; readonly width: string | number }> { - protected readonly xmlKeys = { width: "w:w", type: "w:type" }; -} - -/** - * Table cell width element. - */ -export class TableCellWidth extends XmlComponent { - constructor(value: string | number, type: WidthType) { - super("w:tcW"); - - this.root.push( - new TableCellWidthAttributes({ - width: value, - type: type, - }), - ); - } -} diff --git a/src/file/table/table-cell/table-cell-properties.spec.ts b/src/file/table/table-cell/table-cell-properties.spec.ts index 24c17b674c..03bd2adc3b 100644 --- a/src/file/table/table-cell/table-cell-properties.spec.ts +++ b/src/file/table/table-cell/table-cell-properties.spec.ts @@ -1,137 +1,114 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { BorderStyle } from "file/styles"; +import { BorderStyle } from "file/border"; +import { VerticalAlign } from "file/vertical-align"; -import { VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components"; +import { WidthType } from "../table-width"; + +import { VerticalMergeType } from "./table-cell-components"; import { TableCellProperties } from "./table-cell-properties"; describe("TableCellProperties", () => { describe("#constructor", () => { it("creates an initially empty property object", () => { - const properties = new TableCellProperties(); + const properties = new TableCellProperties({}); // The TableCellProperties is ignorable if there are no attributes, // which results in prepForXml returning undefined, which causes // the formatter to throw an error if that is the only object it // has been asked to format. expect(() => new Formatter().format(properties)).to.throw("XMLComponent did not format correctly"); }); - }); - describe("#addGridSpan", () => { it("adds grid span", () => { - const properties = new TableCellProperties(); - properties.addGridSpan(1); + const properties = new TableCellProperties({ columnSpan: 1 }); const tree = new Formatter().format(properties); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:gridSpan": { _attr: { "w:val": 1 } } }] }); }); - }); - describe("#addVerticalMerge", () => { it("adds vertical merge", () => { - const properties = new TableCellProperties(); - properties.addVerticalMerge(VerticalMergeType.CONTINUE); + const properties = new TableCellProperties({ verticalMerge: VerticalMergeType.CONTINUE }); const tree = new Formatter().format(properties); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }); }); - }); - describe("#setVerticalAlign", () => { it("sets vertical align", () => { - const properties = new TableCellProperties(); - properties.setVerticalAlign(VerticalAlign.BOTTOM); + const properties = new TableCellProperties({ verticalAlign: VerticalAlign.BOTTOM }); const tree = new Formatter().format(properties); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:vAlign": { _attr: { "w:val": "bottom" } } }] }); }); - }); - describe("#setWidth", () => { it("should set width", () => { - const properties = new TableCellProperties(); - properties.setWidth(1, WidthType.DXA); + const properties = new TableCellProperties({ + width: { + size: 1, + type: WidthType.DXA, + }, + }); const tree = new Formatter().format(properties); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:tcW": { _attr: { "w:type": "dxa", "w:w": 1 } } }] }); }); it("should set width using default of AUTO", () => { - const properties = new TableCellProperties(); - properties.setWidth(1); + const properties = new TableCellProperties({ + width: { + size: 1, + }, + }); const tree = new Formatter().format(properties); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:tcW": { _attr: { "w:type": "auto", "w:w": 1 } } }] }); }); - }); - describe("#setShading", () => { it("sets shading", () => { - const properties = new TableCellProperties(); - properties.setShading({ - fill: "test", - color: "000", + const properties = new TableCellProperties({ + shading: { + fill: "ffffff", + color: "000000", + }, }); const tree = new Formatter().format(properties); - expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": { _attr: { "w:fill": "test", "w:color": "000" } } }] }); + expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": { _attr: { "w:fill": "ffffff", "w:color": "000000" } } }] }); }); - }); - describe("#addMargins", () => { - it("sets shading", () => { - const properties = new TableCellProperties(); - properties.addMargins({}); - const tree = new Formatter().format(properties); - expect(tree).to.deep.equal({ - "w:tcPr": [ - { - "w:tcMar": [ - { - "w:top": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - { - "w:start": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - { - "w:bottom": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - { - "w:end": { - _attr: { - "w:type": "dxa", - "w:w": 0, - }, - }, - }, - ], + it("should set the TableCellBorders", () => { + const properties = new TableCellProperties({ + borders: { + top: { + style: BorderStyle.DASH_DOT_STROKED, + color: "ff0000", + size: 3, }, + }, + }); + + const tree = new Formatter().format(properties); + + expect(tree["w:tcPr"][0]).to.deep.equal({ + "w:tcBorders": [{ "w:top": { _attr: { "w:val": "dashDotStroked", "w:sz": 3, "w:color": "ff0000" } } }], + }); + }); + + it("should set the margins", () => { + const properties = new TableCellProperties({ + margins: { + marginUnitType: WidthType.DXA, + top: 5, + left: 10, + bottom: 15, + right: 20, + }, + }); + + const tree = new Formatter().format(properties); + + expect(tree["w:tcPr"][0]).to.deep.equal({ + "w:tcMar": [ + { "w:top": { _attr: { "w:type": "dxa", "w:w": 5 } } }, + { "w:left": { _attr: { "w:type": "dxa", "w:w": 10 } } }, + { "w:bottom": { _attr: { "w:type": "dxa", "w:w": 15 } } }, + { "w:right": { _attr: { "w:type": "dxa", "w:w": 20 } } }, ], }); }); }); - - describe("#Borders", () => { - it("should return the TableCellBorders if Border has borders", () => { - const properties = new TableCellProperties(); - properties.Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red"); - const borders = properties.Borders; - - const tree = new Formatter().format(borders); - - expect(tree).to.deep.equal({ - "w:tcBorders": [{ "w:top": { _attr: { "w:val": "dashDotStroked", "w:sz": 3, "w:color": "red" } } }], - }); - }); - }); }); diff --git a/src/file/table/table-cell/table-cell-properties.ts b/src/file/table/table-cell/table-cell-properties.ts index 68f21e22dc..a10808ef7c 100644 --- a/src/file/table/table-cell/table-cell-properties.ts +++ b/src/file/table/table-cell/table-cell-properties.ts @@ -1,77 +1,68 @@ +import { VerticalAlign, VerticalAlignElement } from "file/vertical-align"; import { IgnoreIfEmptyXmlComponent } from "file/xml-components"; -import { ITableShadingAttributesProperties, TableShading } from "../shading"; -import { ITableCellMarginOptions, TableCellMargin } from "./cell-margin/table-cell-margins"; +import { IShadingAttributesProperties, Shading } from "../../shading"; +import { ITableCellMarginOptions, TableCellMargin, TableCellMarginElementType } from "../table-properties/table-cell-margin"; +import { ITableWidthProperties, TableWidthElement } from "../table-width"; import { GridSpan, + ITableCellBorders, TableCellBorders, - TableCellWidth, TDirection, TextDirection, - VAlign, - VerticalAlign, VerticalMerge, VerticalMergeType, - WidthType, } from "./table-cell-components"; +export interface ITableCellPropertiesOptions { + readonly shading?: IShadingAttributesProperties; + readonly margins?: ITableCellMarginOptions; + readonly verticalAlign?: VerticalAlign; + readonly textDirection?: TextDirection; + readonly verticalMerge?: VerticalMergeType; + readonly width?: ITableWidthProperties; + readonly columnSpan?: number; + readonly rowSpan?: number; + readonly borders?: ITableCellBorders; +} + export class TableCellProperties extends IgnoreIfEmptyXmlComponent { - private readonly cellBorder: TableCellBorders; - - constructor() { + constructor(options: ITableCellPropertiesOptions) { super("w:tcPr"); - this.cellBorder = new TableCellBorders(); - } - public get Borders(): TableCellBorders { - return this.cellBorder; - } + if (options.width) { + this.root.push(new TableWidthElement("w:tcW", options.width)); + } - public addGridSpan(cellSpan: number): TableCellProperties { - this.root.push(new GridSpan(cellSpan)); + if (options.columnSpan) { + this.root.push(new GridSpan(options.columnSpan)); + } - return this; - } + if (options.verticalMerge) { + this.root.push(new VerticalMerge(options.verticalMerge)); + } else if (options.rowSpan && options.rowSpan > 1) { + // if cell already have a `verticalMerge`, don't handle `rowSpan` + this.root.push(new VerticalMerge(VerticalMergeType.RESTART)); + } - public addVerticalMerge(type: VerticalMergeType): TableCellProperties { - this.root.push(new VerticalMerge(type)); + if (options.borders) { + this.root.push(new TableCellBorders(options.borders)); + } - return this; - } + if (options.shading) { + this.root.push(new Shading(options.shading)); + } - public setVerticalAlign(type: VerticalAlign): TableCellProperties { - this.root.push(new VAlign(type)); + if (options.margins) { + this.root.push(new TableCellMargin(TableCellMarginElementType.TABLE_CELL, options.margins)); + } - return this; - } + if (options.textDirection) { + this.root.push(new TDirection(options.textDirection)); + } - public setWidth(width: string | number, type: WidthType = WidthType.AUTO): TableCellProperties { - this.root.push(new TableCellWidth(width, type)); - - return this; - } - - public setShading(attrs: ITableShadingAttributesProperties): TableCellProperties { - this.root.push(new TableShading(attrs)); - - return this; - } - - public addMargins(options: ITableCellMarginOptions): TableCellProperties { - this.root.push(new TableCellMargin(options)); - - return this; - } - - public setTextDirection(type: TextDirection): TableCellProperties { - this.root.push(new TDirection(type)); - - return this; - } - - public addBorders(): TableCellProperties { - this.root.push(this.cellBorder); - - return this; + if (options.verticalAlign) { + this.root.push(new VerticalAlignElement(options.verticalAlign)); + } } } diff --git a/src/file/table/table-cell/table-cell.spec.ts b/src/file/table/table-cell/table-cell.spec.ts index 3ea123fb5b..b4fa5e7429 100644 --- a/src/file/table/table-cell/table-cell.spec.ts +++ b/src/file/table/table-cell/table-cell.spec.ts @@ -1,24 +1,31 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { BorderStyle } from "file/styles"; +import { BorderStyle } from "file/border"; +import { ShadingType } from "file/shading"; +import { VerticalAlign } from "file/vertical-align"; -import { ShadingType } from "../shading"; +import { WidthType } from "../table-width"; import { TableCell } from "./table-cell"; -import { TableCellBorders, TableCellWidth, TextDirection, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components"; +import { TableCellBorders, TextDirection, VerticalMergeType } from "./table-cell-components"; describe("TableCellBorders", () => { describe("#prepForXml", () => { it("should not add empty borders element if there are no borders defined", () => { - const tb = new TableCellBorders(); + const tb = new TableCellBorders({}); expect(() => new Formatter().format(tb)).to.throw(); }); }); describe("#addingBorders", () => { it("should add top border", () => { - const tb = new TableCellBorders(); - tb.addTopBorder(BorderStyle.DOTTED, 1, "FF00FF"); + const tb = new TableCellBorders({ + top: { + style: BorderStyle.DOTTED, + size: 1, + color: "FF00FF", + }, + }); const tree = new Formatter().format(tb); expect(tree).to.deep.equal({ @@ -37,8 +44,13 @@ describe("TableCellBorders", () => { }); it("should add start(left) border", () => { - const tb = new TableCellBorders(); - tb.addStartBorder(BorderStyle.SINGLE, 2, "FF00FF"); + const tb = new TableCellBorders({ + start: { + style: BorderStyle.SINGLE, + size: 2, + color: "FF00FF", + }, + }); const tree = new Formatter().format(tb); expect(tree).to.deep.equal({ @@ -57,8 +69,13 @@ describe("TableCellBorders", () => { }); it("should add bottom border", () => { - const tb = new TableCellBorders(); - tb.addBottomBorder(BorderStyle.DOUBLE, 1, "FF00FF"); + const tb = new TableCellBorders({ + bottom: { + style: BorderStyle.DOUBLE, + size: 1, + color: "FF00FF", + }, + }); const tree = new Formatter().format(tb); expect(tree).to.deep.equal({ @@ -77,8 +94,13 @@ describe("TableCellBorders", () => { }); it("should add end(right) border", () => { - const tb = new TableCellBorders(); - tb.addEndBorder(BorderStyle.THICK, 3, "FF00FF"); + const tb = new TableCellBorders({ + end: { + style: BorderStyle.THICK, + size: 3, + color: "FF0000", + }, + }); const tree = new Formatter().format(tb); expect(tree).to.deep.equal({ @@ -86,7 +108,7 @@ describe("TableCellBorders", () => { { "w:end": { _attr: { - "w:color": "FF00FF", + "w:color": "FF0000", "w:sz": 3, "w:val": "thick", }, @@ -97,8 +119,13 @@ describe("TableCellBorders", () => { }); it("should add left border", () => { - const tb = new TableCellBorders(); - tb.addLeftBorder(BorderStyle.THICK, 3, "FF00FF"); + const tb = new TableCellBorders({ + left: { + style: BorderStyle.THICK, + size: 3, + color: "FF00FF", + }, + }); const tree = new Formatter().format(tb); expect(tree).to.deep.equal({ @@ -117,8 +144,13 @@ describe("TableCellBorders", () => { }); it("should add right border", () => { - const tb = new TableCellBorders(); - tb.addRightBorder(BorderStyle.THICK, 3, "FF00FF"); + const tb = new TableCellBorders({ + right: { + style: BorderStyle.THICK, + size: 3, + color: "FF00FF", + }, + }); const tree = new Formatter().format(tb); expect(tree).to.deep.equal({ @@ -137,13 +169,38 @@ describe("TableCellBorders", () => { }); it("should add multiple borders", () => { - const tb = new TableCellBorders(); - tb.addTopBorder(BorderStyle.DOTTED, 1, "FF00FF"); - tb.addEndBorder(BorderStyle.THICK, 3, "FF00FF"); - tb.addBottomBorder(BorderStyle.DOUBLE, 1, "FF00FF"); - tb.addStartBorder(BorderStyle.SINGLE, 2, "FF00FF"); - tb.addLeftBorder(BorderStyle.SINGLE, 2, "FF00FF"); - tb.addRightBorder(BorderStyle.SINGLE, 2, "FF00FF"); + const tb = new TableCellBorders({ + top: { + style: BorderStyle.DOTTED, + size: 1, + color: "FF00FF", + }, + end: { + style: BorderStyle.THICK, + size: 3, + color: "FF00FF", + }, + bottom: { + style: BorderStyle.DOUBLE, + size: 1, + color: "FF00FF", + }, + start: { + style: BorderStyle.SINGLE, + size: 2, + color: "FF00FF", + }, + left: { + style: BorderStyle.SINGLE, + size: 2, + color: "FF00FF", + }, + right: { + style: BorderStyle.SINGLE, + size: 2, + color: "FF00FF", + }, + }); const tree = new Formatter().format(tb); expect(tree).to.deep.equal({ @@ -157,24 +214,6 @@ describe("TableCellBorders", () => { }, }, }, - { - "w:end": { - _attr: { - "w:color": "FF00FF", - "w:sz": 3, - "w:val": "thick", - }, - }, - }, - { - "w:bottom": { - _attr: { - "w:color": "FF00FF", - "w:sz": 1, - "w:val": "double", - }, - }, - }, { "w:start": { _attr: { @@ -193,6 +232,24 @@ describe("TableCellBorders", () => { }, }, }, + { + "w:bottom": { + _attr: { + "w:color": "FF00FF", + "w:sz": 1, + "w:val": "double", + }, + }, + }, + { + "w:end": { + _attr: { + "w:color": "FF00FF", + "w:sz": 3, + "w:val": "thick", + }, + }, + }, { "w:right": { _attr: { @@ -208,23 +265,6 @@ describe("TableCellBorders", () => { }); }); -describe("TableCellWidth", () => { - describe("#constructor", () => { - it("should create object", () => { - const tcWidth = new TableCellWidth(100, WidthType.DXA); - const tree = new Formatter().format(tcWidth); - expect(tree).to.deep.equal({ - "w:tcW": { - _attr: { - "w:type": "dxa", - "w:w": 100, - }, - }, - }); - }); - }); -}); - describe("TableCell", () => { describe("#constructor", () => { it("should create", () => { @@ -355,7 +395,7 @@ describe("TableCell", () => { }, }, { - "w:start": { + "w:left": { _attr: { "w:type": "dxa", "w:w": 1, @@ -371,7 +411,7 @@ describe("TableCell", () => { }, }, { - "w:end": { + "w:right": { _attr: { "w:type": "dxa", "w:w": 1, @@ -393,9 +433,9 @@ describe("TableCell", () => { const cell = new TableCell({ children: [], shading: { - fill: "red", - color: "blue", - val: ShadingType.PERCENT_10, + fill: "FF0000", + color: "0000ff", + type: ShadingType.PERCENT_10, }, }); @@ -408,8 +448,8 @@ describe("TableCell", () => { { "w:shd": { _attr: { - "w:color": "blue", - "w:fill": "red", + "w:color": "0000ff", + "w:fill": "FF0000", "w:val": "pct10", }, }, @@ -531,17 +571,17 @@ describe("TableCell", () => { top: { style: BorderStyle.DASH_DOT_STROKED, size: 3, - color: "red", + color: "FF0000", }, bottom: { style: BorderStyle.DOUBLE, size: 3, - color: "blue", + color: "0000ff", }, left: { style: BorderStyle.DASH_DOT_STROKED, size: 3, - color: "green", + color: "00ff00", }, right: { style: BorderStyle.DASH_DOT_STROKED, @@ -562,7 +602,7 @@ describe("TableCell", () => { { "w:top": { _attr: { - "w:color": "red", + "w:color": "FF0000", "w:sz": 3, "w:val": "dashDotStroked", }, @@ -571,7 +611,7 @@ describe("TableCell", () => { { "w:left": { _attr: { - "w:color": "green", + "w:color": "00ff00", "w:sz": 3, "w:val": "dashDotStroked", }, @@ -580,7 +620,7 @@ describe("TableCell", () => { { "w:bottom": { _attr: { - "w:color": "blue", + "w:color": "0000ff", "w:sz": 3, "w:val": "double", }, @@ -589,7 +629,7 @@ describe("TableCell", () => { { "w:right": { _attr: { - "w:color": "#ff8000", + "w:color": "ff8000", "w:sz": 3, "w:val": "dashDotStroked", }, diff --git a/src/file/table/table-cell/table-cell.ts b/src/file/table/table-cell/table-cell.ts index 672b5d2f94..565d389c6d 100644 --- a/src/file/table/table-cell/table-cell.ts +++ b/src/file/table/table-cell/table-cell.ts @@ -1,48 +1,11 @@ // http://officeopenxml.com/WPtableGrid.php import { Paragraph } from "file/paragraph"; -import { BorderStyle } from "file/styles"; import { IContext, IXmlableObject, XmlComponent } from "file/xml-components"; -import { ITableShadingAttributesProperties } from "../shading"; import { Table } from "../table"; -import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins"; -import { TextDirection, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components"; -import { TableCellProperties } from "./table-cell-properties"; +import { ITableCellPropertiesOptions, TableCellProperties } from "./table-cell-properties"; -export interface ITableCellOptions { - readonly shading?: ITableShadingAttributesProperties; - readonly margins?: ITableCellMarginOptions; - readonly verticalAlign?: VerticalAlign; - readonly textDirection?: TextDirection; - readonly verticalMerge?: VerticalMergeType; - readonly width?: { - readonly size: number | string; - readonly type?: WidthType; - }; - readonly columnSpan?: number; - readonly rowSpan?: number; - readonly borders?: { - readonly top?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly bottom?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly left?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly right?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - }; +export interface ITableCellOptions extends ITableCellPropertiesOptions { readonly children: (Paragraph | Table)[]; } @@ -50,59 +13,11 @@ export class TableCell extends XmlComponent { constructor(readonly options: ITableCellOptions) { super("w:tc"); - const properties = new TableCellProperties(); - this.root.push(properties); + this.root.push(new TableCellProperties(options)); for (const child of options.children) { this.root.push(child); } - - if (options.width) { - properties.setWidth(options.width.size, options.width.type); - } - - if (options.columnSpan) { - properties.addGridSpan(options.columnSpan); - } - - if (options.verticalMerge) { - properties.addVerticalMerge(options.verticalMerge); - } else if (options.rowSpan && options.rowSpan > 1) { - // if cell already have a `verticalMerge`, don't handle `rowSpan` - properties.addVerticalMerge(VerticalMergeType.RESTART); - } - - if (options.borders) { - properties.addBorders(); - if (options.borders.top) { - properties.Borders.addTopBorder(options.borders.top.style, options.borders.top.size, options.borders.top.color); - } - if (options.borders.left) { - properties.Borders.addLeftBorder(options.borders.left.style, options.borders.left.size, options.borders.left.color); - } - if (options.borders.bottom) { - properties.Borders.addBottomBorder(options.borders.bottom.style, options.borders.bottom.size, options.borders.bottom.color); - } - if (options.borders.right) { - properties.Borders.addRightBorder(options.borders.right.style, options.borders.right.size, options.borders.right.color); - } - } - - if (options.shading) { - properties.setShading(options.shading); - } - - if (options.margins) { - properties.addMargins(options.margins); - } - - if (options.textDirection) { - properties.setTextDirection(options.textDirection); - } - - if (options.verticalAlign) { - properties.setVerticalAlign(options.verticalAlign); - } } public prepForXml(context: IContext): IXmlableObject | undefined { diff --git a/src/file/table/table-properties/table-borders.spec.ts b/src/file/table/table-properties/table-borders.spec.ts index 4a3212feb1..a13482f349 100644 --- a/src/file/table/table-properties/table-borders.spec.ts +++ b/src/file/table/table-properties/table-borders.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { BorderStyle } from "file/styles"; +import { BorderStyle } from "file/border"; import { TableBorders } from "./table-borders"; @@ -18,7 +18,6 @@ describe("TableBorders", () => { "w:top": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -28,7 +27,6 @@ describe("TableBorders", () => { "w:left": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -38,7 +36,6 @@ describe("TableBorders", () => { "w:bottom": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -48,7 +45,6 @@ describe("TableBorders", () => { "w:right": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -58,7 +54,6 @@ describe("TableBorders", () => { "w:insideH": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -68,7 +63,6 @@ describe("TableBorders", () => { "w:insideV": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -85,7 +79,7 @@ describe("TableBorders", () => { top: { style: BorderStyle.DOUBLE, size: 1, - color: "red", + color: "FF0000", }, }); @@ -95,8 +89,7 @@ describe("TableBorders", () => { { "w:top": { _attr: { - "w:color": "red", - "w:space": 0, + "w:color": "FF0000", "w:sz": 1, "w:val": "double", }, @@ -106,7 +99,6 @@ describe("TableBorders", () => { "w:left": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -116,7 +108,6 @@ describe("TableBorders", () => { "w:bottom": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -126,7 +117,6 @@ describe("TableBorders", () => { "w:right": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -136,7 +126,6 @@ describe("TableBorders", () => { "w:insideH": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -146,7 +135,6 @@ describe("TableBorders", () => { "w:insideV": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -163,7 +151,7 @@ describe("TableBorders", () => { left: { style: BorderStyle.DOUBLE, size: 1, - color: "red", + color: "ff0000", }, }); const tree = new Formatter().format(tableBorders); @@ -174,7 +162,6 @@ describe("TableBorders", () => { "w:top": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -183,8 +170,7 @@ describe("TableBorders", () => { { "w:left": { _attr: { - "w:color": "red", - "w:space": 0, + "w:color": "ff0000", "w:sz": 1, "w:val": "double", }, @@ -194,7 +180,6 @@ describe("TableBorders", () => { "w:bottom": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -204,7 +189,6 @@ describe("TableBorders", () => { "w:right": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -214,7 +198,6 @@ describe("TableBorders", () => { "w:insideH": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -224,7 +207,6 @@ describe("TableBorders", () => { "w:insideV": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -241,7 +223,7 @@ describe("TableBorders", () => { bottom: { style: BorderStyle.DOUBLE, size: 1, - color: "red", + color: "ff0000", }, }); const tree = new Formatter().format(tableBorders); @@ -252,7 +234,6 @@ describe("TableBorders", () => { "w:top": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -262,7 +243,6 @@ describe("TableBorders", () => { "w:left": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -271,8 +251,7 @@ describe("TableBorders", () => { { "w:bottom": { _attr: { - "w:color": "red", - "w:space": 0, + "w:color": "ff0000", "w:sz": 1, "w:val": "double", }, @@ -282,7 +261,6 @@ describe("TableBorders", () => { "w:right": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -292,7 +270,6 @@ describe("TableBorders", () => { "w:insideH": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -302,7 +279,6 @@ describe("TableBorders", () => { "w:insideV": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -319,7 +295,7 @@ describe("TableBorders", () => { right: { style: BorderStyle.DOUBLE, size: 1, - color: "red", + color: "ff0000", }, }); const tree = new Formatter().format(tableBorders); @@ -330,7 +306,6 @@ describe("TableBorders", () => { "w:top": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -340,7 +315,6 @@ describe("TableBorders", () => { "w:left": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -350,7 +324,6 @@ describe("TableBorders", () => { "w:bottom": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -359,8 +332,7 @@ describe("TableBorders", () => { { "w:right": { _attr: { - "w:color": "red", - "w:space": 0, + "w:color": "ff0000", "w:sz": 1, "w:val": "double", }, @@ -370,7 +342,6 @@ describe("TableBorders", () => { "w:insideH": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -380,7 +351,6 @@ describe("TableBorders", () => { "w:insideV": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -397,7 +367,7 @@ describe("TableBorders", () => { insideHorizontal: { style: BorderStyle.DOUBLE, size: 1, - color: "red", + color: "ff0000", }, }); const tree = new Formatter().format(tableBorders); @@ -408,7 +378,6 @@ describe("TableBorders", () => { "w:top": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -418,7 +387,6 @@ describe("TableBorders", () => { "w:left": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -428,7 +396,6 @@ describe("TableBorders", () => { "w:bottom": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -438,7 +405,6 @@ describe("TableBorders", () => { "w:right": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -447,8 +413,7 @@ describe("TableBorders", () => { { "w:insideH": { _attr: { - "w:color": "red", - "w:space": 0, + "w:color": "ff0000", "w:sz": 1, "w:val": "double", }, @@ -458,7 +423,6 @@ describe("TableBorders", () => { "w:insideV": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -475,7 +439,7 @@ describe("TableBorders", () => { insideVertical: { style: BorderStyle.DOUBLE, size: 1, - color: "red", + color: "ff0000", }, }); const tree = new Formatter().format(tableBorders); @@ -486,7 +450,6 @@ describe("TableBorders", () => { "w:top": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -496,7 +459,6 @@ describe("TableBorders", () => { "w:left": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -506,7 +468,6 @@ describe("TableBorders", () => { "w:bottom": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -516,7 +477,6 @@ describe("TableBorders", () => { "w:right": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -526,7 +486,6 @@ describe("TableBorders", () => { "w:insideH": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 4, "w:val": "single", }, @@ -535,8 +494,7 @@ describe("TableBorders", () => { { "w:insideV": { _attr: { - "w:color": "red", - "w:space": 0, + "w:color": "ff0000", "w:sz": 1, "w:val": "double", }, @@ -558,7 +516,6 @@ describe("TableBorders", () => { "w:top": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 0, "w:val": "none", }, @@ -568,7 +525,6 @@ describe("TableBorders", () => { "w:left": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 0, "w:val": "none", }, @@ -578,7 +534,6 @@ describe("TableBorders", () => { "w:bottom": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 0, "w:val": "none", }, @@ -588,7 +543,6 @@ describe("TableBorders", () => { "w:right": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 0, "w:val": "none", }, @@ -598,7 +552,6 @@ describe("TableBorders", () => { "w:insideH": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 0, "w:val": "none", }, @@ -608,7 +561,6 @@ describe("TableBorders", () => { "w:insideV": { _attr: { "w:color": "auto", - "w:space": 0, "w:sz": 0, "w:val": "none", }, diff --git a/src/file/table/table-properties/table-borders.ts b/src/file/table/table-properties/table-borders.ts index 8de354fc36..923a7bb019 100644 --- a/src/file/table/table-properties/table-borders.ts +++ b/src/file/table/table-properties/table-borders.ts @@ -1,155 +1,75 @@ // http://officeopenxml.com/WPtableBorders.php -import { BorderStyle } from "file/styles"; -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { BorderElement, BorderStyle, IBorderOptions } from "file/border"; +import { XmlComponent } from "file/xml-components"; export interface ITableBordersOptions { - readonly top?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly bottom?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly left?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly right?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly insideHorizontal?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; - readonly insideVertical?: { - readonly style: BorderStyle; - readonly size: number; - readonly color: string; - }; + readonly top?: IBorderOptions; + readonly bottom?: IBorderOptions; + readonly left?: IBorderOptions; + readonly right?: IBorderOptions; + readonly insideHorizontal?: IBorderOptions; + readonly insideVertical?: IBorderOptions; } +const NONE_BORDER = { + style: BorderStyle.NONE, + size: 0, + color: "auto", +}; + +const DEFAULT_BORDER = { + style: BorderStyle.SINGLE, + size: 4, + color: "auto", +}; + export class TableBorders extends XmlComponent { public static readonly NONE = { - top: { - style: BorderStyle.NONE, - size: 0, - color: "auto", - }, - bottom: { - style: BorderStyle.NONE, - size: 0, - color: "auto", - }, - left: { - style: BorderStyle.NONE, - size: 0, - color: "auto", - }, - right: { - style: BorderStyle.NONE, - size: 0, - color: "auto", - }, - insideHorizontal: { - style: BorderStyle.NONE, - size: 0, - color: "auto", - }, - insideVertical: { - style: BorderStyle.NONE, - size: 0, - color: "auto", - }, + top: NONE_BORDER, + bottom: NONE_BORDER, + left: NONE_BORDER, + right: NONE_BORDER, + insideHorizontal: NONE_BORDER, + insideVertical: NONE_BORDER, }; constructor(options: ITableBordersOptions) { super("w:tblBorders"); if (options.top) { - this.root.push(new TableBordersElement("w:top", options.top.style, options.top.size, 0, options.top.color)); + this.root.push(new BorderElement("w:top", options.top)); } else { - this.root.push(new TableBordersElement("w:top", BorderStyle.SINGLE, 4, 0, "auto")); + this.root.push(new BorderElement("w:top", DEFAULT_BORDER)); } if (options.left) { - this.root.push(new TableBordersElement("w:left", options.left.style, options.left.size, 0, options.left.color)); + this.root.push(new BorderElement("w:left", options.left)); } else { - this.root.push(new TableBordersElement("w:left", BorderStyle.SINGLE, 4, 0, "auto")); + this.root.push(new BorderElement("w:left", DEFAULT_BORDER)); } if (options.bottom) { - this.root.push(new TableBordersElement("w:bottom", options.bottom.style, options.bottom.size, 0, options.bottom.color)); + this.root.push(new BorderElement("w:bottom", options.bottom)); } else { - this.root.push(new TableBordersElement("w:bottom", BorderStyle.SINGLE, 4, 0, "auto")); + this.root.push(new BorderElement("w:bottom", DEFAULT_BORDER)); } if (options.right) { - this.root.push(new TableBordersElement("w:right", options.right.style, options.right.size, 0, options.right.color)); + this.root.push(new BorderElement("w:right", options.right)); } else { - this.root.push(new TableBordersElement("w:right", BorderStyle.SINGLE, 4, 0, "auto")); + this.root.push(new BorderElement("w:right", DEFAULT_BORDER)); } if (options.insideHorizontal) { - this.root.push( - new TableBordersElement( - "w:insideH", - options.insideHorizontal.style, - options.insideHorizontal.size, - 0, - options.insideHorizontal.color, - ), - ); + this.root.push(new BorderElement("w:insideH", options.insideHorizontal)); } else { - this.root.push(new TableBordersElement("w:insideH", BorderStyle.SINGLE, 4, 0, "auto")); + this.root.push(new BorderElement("w:insideH", DEFAULT_BORDER)); } if (options.insideVertical) { - this.root.push( - new TableBordersElement( - "w:insideV", - options.insideVertical.style, - options.insideVertical.size, - 0, - options.insideVertical.color, - ), - ); + this.root.push(new BorderElement("w:insideV", options.insideVertical)); } else { - this.root.push(new TableBordersElement("w:insideV", BorderStyle.SINGLE, 4, 0, "auto")); + this.root.push(new BorderElement("w:insideV", DEFAULT_BORDER)); } } } - -class TableBordersElement extends XmlComponent { - constructor(elementName: string, value: string, size: number, space: number, color: string) { - super(elementName); - this.root.push( - new TableBordersAttributes({ - value, - size, - space, - color, - }), - ); - } -} - -class TableBordersAttributes extends XmlAttributeComponent<{ - readonly value: string; - readonly size: number; - readonly space: number; - readonly color: string; -}> { - protected readonly xmlKeys = { - value: "w:val", - size: "w:sz", - space: "w:space", - color: "w:color", - }; -} diff --git a/src/file/table/table-properties/table-cell-margin.spec.ts b/src/file/table/table-properties/table-cell-margin.spec.ts index 814888adc0..0361b37f27 100644 --- a/src/file/table/table-properties/table-cell-margin.spec.ts +++ b/src/file/table/table-properties/table-cell-margin.spec.ts @@ -1,25 +1,23 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { WidthType } from "../table-width"; -import { WidthType } from "../table-cell"; -import { TableCellMargin } from "./table-cell-margin"; +import { TableCellMargin, TableCellMarginElementType } from "./table-cell-margin"; describe("TableCellMargin", () => { describe("#constructor", () => { it("should throw an error if theres no child elements", () => { - const cellMargin = new TableCellMargin({}); + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, {}); expect(() => new Formatter().format(cellMargin)).to.throw(); }); }); describe("#addTopMargin", () => { it("should add a table cell top margin", () => { - const cellMargin = new TableCellMargin({ - top: { - value: 1234, - type: WidthType.DXA, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + marginUnitType: WidthType.DXA, + top: 1234, }); const tree = new Formatter().format(cellMargin); @@ -27,10 +25,8 @@ describe("TableCellMargin", () => { }); it("should add a table cell top margin using default width type", () => { - const cellMargin = new TableCellMargin({ - top: { - value: 1234, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + top: 1234, }); const tree = new Formatter().format(cellMargin); @@ -40,21 +36,17 @@ describe("TableCellMargin", () => { describe("#addLeftMargin", () => { it("should add a table cell left margin", () => { - const cellMargin = new TableCellMargin({ - left: { - value: 1234, - type: WidthType.DXA, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + marginUnitType: WidthType.DXA, + left: 1234, }); const tree = new Formatter().format(cellMargin); expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:left": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); }); it("should add a table cell left margin using default width type", () => { - const cellMargin = new TableCellMargin({ - left: { - value: 1234, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + left: 1234, }); const tree = new Formatter().format(cellMargin); expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:left": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }); @@ -63,11 +55,9 @@ describe("TableCellMargin", () => { describe("#addBottomMargin", () => { it("should add a table cell bottom margin", () => { - const cellMargin = new TableCellMargin({ - bottom: { - value: 1234, - type: WidthType.DXA, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + marginUnitType: WidthType.DXA, + bottom: 1234, }); const tree = new Formatter().format(cellMargin); @@ -75,10 +65,8 @@ describe("TableCellMargin", () => { }); it("should add a table cell bottom margin using default width type", () => { - const cellMargin = new TableCellMargin({ - bottom: { - value: 1234, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + bottom: 1234, }); const tree = new Formatter().format(cellMargin); @@ -88,11 +76,9 @@ describe("TableCellMargin", () => { describe("#addRightMargin", () => { it("should add a table cell right margin", () => { - const cellMargin = new TableCellMargin({ - right: { - value: 1234, - type: WidthType.DXA, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + marginUnitType: WidthType.DXA, + right: 1234, }); const tree = new Formatter().format(cellMargin); @@ -100,10 +86,8 @@ describe("TableCellMargin", () => { }); it("should add a table cell right margin using default width type", () => { - const cellMargin = new TableCellMargin({ - right: { - value: 1234, - }, + const cellMargin = new TableCellMargin(TableCellMarginElementType.TABLE, { + right: 1234, }); const tree = new Formatter().format(cellMargin); diff --git a/src/file/table/table-properties/table-cell-margin.ts b/src/file/table/table-properties/table-cell-margin.ts index ffa554a8de..c01cf84f6e 100644 --- a/src/file/table/table-properties/table-cell-margin.ts +++ b/src/file/table/table-properties/table-cell-margin.ts @@ -1,54 +1,61 @@ -import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -import { WidthType } from "../table-cell"; - -class TableCellMarginAttributes extends XmlAttributeComponent<{ readonly type: WidthType; readonly value: number }> { - protected readonly xmlKeys = { value: "w:w", type: "w:type" }; -} - -interface IBaseTableCellMarginOptions { - readonly value: number; - readonly type?: WidthType; -} - -class BaseTableCellMargin extends XmlComponent { - constructor(rootKey: string, options: IBaseTableCellMarginOptions) { - super(rootKey); - - this.root.push( - new TableCellMarginAttributes({ - type: options.type ?? WidthType.DXA, - value: options.value, - }), - ); - } -} +import { IgnoreIfEmptyXmlComponent } from "file/xml-components"; +import { TableWidthElement, WidthType } from "../table-width"; export interface ITableCellMarginOptions { - readonly top?: IBaseTableCellMarginOptions; - readonly bottom?: IBaseTableCellMarginOptions; - readonly left?: IBaseTableCellMarginOptions; - readonly right?: IBaseTableCellMarginOptions; + readonly marginUnitType?: WidthType; + readonly top?: number; + readonly bottom?: number; + readonly left?: number; + readonly right?: number; +} + +// Technically two different types, but they're identical +// +// +// +// +// +// +// +// +// +// +// + +// +// +// +// +// +// +// +// +// +// + +export enum TableCellMarginElementType { + TABLE = "w:tblCellMar", + TABLE_CELL = "w:tcMar", } export class TableCellMargin extends IgnoreIfEmptyXmlComponent { - constructor(options: ITableCellMarginOptions) { - super("w:tblCellMar"); + constructor(type: TableCellMarginElementType, { marginUnitType = WidthType.DXA, top, left, bottom, right }: ITableCellMarginOptions) { + super(type); - if (options.top) { - this.root.push(new BaseTableCellMargin("w:top", options.top)); + if (top !== undefined) { + this.root.push(new TableWidthElement("w:top", { type: marginUnitType, size: top })); } - if (options.left) { - this.root.push(new BaseTableCellMargin("w:left", options.left)); + if (left !== undefined) { + this.root.push(new TableWidthElement("w:left", { type: marginUnitType, size: left })); } - if (options.bottom) { - this.root.push(new BaseTableCellMargin("w:bottom", options.bottom)); + if (bottom !== undefined) { + this.root.push(new TableWidthElement("w:bottom", { type: marginUnitType, size: bottom })); } - if (options.right) { - this.root.push(new BaseTableCellMargin("w:right", options.right)); + if (right !== undefined) { + this.root.push(new TableWidthElement("w:right", { type: marginUnitType, size: right })); } } } diff --git a/src/file/table/table-properties/table-float-properties.ts b/src/file/table/table-properties/table-float-properties.ts index 2b97d86c1c..8e365fc93d 100644 --- a/src/file/table/table-properties/table-float-properties.ts +++ b/src/file/table/table-properties/table-float-properties.ts @@ -1,3 +1,4 @@ +import { signedTwipsMeasureValue, twipsMeasureValue } from "file/values"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { OverlapType, TableOverlap } from "./table-overlap"; @@ -43,7 +44,7 @@ export interface ITableFloatOptions { * If relativeHorizontalPosition is also specified, then the absoluteHorizontalPosition attribute is ignored. * If the attribute is omitted, the value is assumed to be zero. */ - readonly absoluteHorizontalPosition?: number; + readonly absoluteHorizontalPosition?: number | string; /** * Specifies a relative horizontal position for the table, relative to the horizontalAnchor attribute. @@ -74,7 +75,7 @@ export interface ITableFloatOptions { * If relativeVerticalPosition is also specified, then the absoluteVerticalPosition attribute is ignored. * If the attribute is omitted, the value is assumed to be zero. */ - readonly absoluteVerticalPosition?: number; + readonly absoluteVerticalPosition?: number | string; /** * Specifies a relative vertical position for the table, relative to the verticalAnchor attribute. @@ -92,28 +93,41 @@ export interface ITableFloatOptions { * Specifies the minimun distance to be maintained between the table and the top of text in the paragraph * below the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly bottomFromText?: number; + readonly bottomFromText?: number | string; /** * Specifies the minimun distance to be maintained between the table and the bottom edge of text in the paragraph * above the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly topFromText?: number; + readonly topFromText?: number | string; /** * Specifies the minimun distance to be maintained between the table and the edge of text in the paragraph * to the left of the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly leftFromText?: number; + readonly leftFromText?: number | string; /** * Specifies the minimun distance to be maintained between the table and the edge of text in the paragraph * to the right of the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly rightFromText?: number; + readonly rightFromText?: number | string; readonly overlap?: OverlapType; } +// +// +// +// +// +// +// +// +// +// +// +// + export class TableFloatOptionsAttributes extends XmlAttributeComponent { protected readonly xmlKeys = { horizontalAnchor: "w:horzAnchor", @@ -130,9 +144,29 @@ export class TableFloatOptionsAttributes extends XmlAttributeComponent +// +// +// +// +// export enum TableLayoutType { AUTOFIT = "autofit", FIXED = "fixed", @@ -9,6 +15,9 @@ class TableLayoutAttributes extends XmlAttributeComponent<{ readonly type: Table protected readonly xmlKeys = { type: "w:type" }; } +// +// +// export class TableLayout extends XmlComponent { constructor(type: TableLayoutType) { super("w:tblLayout"); diff --git a/src/file/table/table-properties/table-overlap.ts b/src/file/table/table-properties/table-overlap.ts index 387cf27943..102d2afa5d 100644 --- a/src/file/table/table-properties/table-overlap.ts +++ b/src/file/table/table-properties/table-overlap.ts @@ -1,10 +1,19 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +// +// +// +// +// +// export enum OverlapType { NEVER = "never", OVERLAP = "overlap", } +// +// +// class TableOverlapAttributes extends XmlAttributeComponent<{ readonly val: OverlapType }> { protected readonly xmlKeys = { val: "w:val" }; } diff --git a/src/file/table/table-properties/table-properties.spec.ts b/src/file/table/table-properties/table-properties.spec.ts index a6a9b5879f..ceadb5c3a8 100644 --- a/src/file/table/table-properties/table-properties.spec.ts +++ b/src/file/table/table-properties/table-properties.spec.ts @@ -2,9 +2,10 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { AlignmentType } from "../../paragraph"; -import { ShadingType } from "../shading"; -import { WidthType } from "../table-cell"; +import { AlignmentType } from "file/paragraph"; +import { ShadingType } from "file/shading"; + +import { WidthType } from "../table-width"; import { TableLayoutType } from "./table-layout"; import { TableProperties } from "./table-properties"; @@ -18,9 +19,7 @@ describe("TableProperties", () => { // has been asked to format. expect(() => new Formatter().format(tp)).to.throw("XMLComponent did not format correctly"); }); - }); - describe("#setStyle", () => { it("should add a table style property", () => { const tp = new TableProperties({ style: "TableNormal", @@ -30,9 +29,7 @@ describe("TableProperties", () => { "w:tblPr": [{ "w:tblStyle": { _attr: { "w:val": "TableNormal" } } }], }); }); - }); - describe("#setWidth", () => { it("should add a table width property", () => { const tp = new TableProperties({ width: { @@ -58,9 +55,33 @@ describe("TableProperties", () => { "w:tblPr": [{ "w:tblW": { _attr: { "w:type": "auto", "w:w": 1234 } } }], }); }); - }); - describe("#setLayout", () => { + it("should add a table indent property", () => { + const tp = new TableProperties({ + indent: { + size: 1234, + type: WidthType.DXA, + }, + }); + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({ + "w:tblPr": [{ "w:tblInd": { _attr: { "w:type": "dxa", "w:w": 1234 } } }], + }); + }); + + it("should add a table indent property with default of AUTO", () => { + const tp = new TableProperties({ + indent: { + size: 1234, + }, + }); + + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({ + "w:tblPr": [{ "w:tblInd": { _attr: { "w:type": "auto", "w:w": 1234 } } }], + }); + }); + it("sets the table to fixed width layout", () => { const tp = new TableProperties({ layout: TableLayoutType.FIXED, @@ -77,10 +98,8 @@ describe("TableProperties", () => { it("adds a table cell top margin", () => { const tp = new TableProperties({ cellMargin: { - top: { - value: 1234, - type: WidthType.DXA, - }, + marginUnitType: WidthType.DXA, + top: 1234, }, }); @@ -93,10 +112,8 @@ describe("TableProperties", () => { it("adds a table cell left margin", () => { const tp = new TableProperties({ cellMargin: { - left: { - value: 1234, - type: WidthType.DXA, - }, + marginUnitType: WidthType.DXA, + left: 1234, }, }); @@ -112,7 +129,7 @@ describe("TableProperties", () => { const tp = new TableProperties({ shading: { fill: "b79c2f", - val: ShadingType.REVERSE_DIAGONAL_STRIPE, + type: ShadingType.REVERSE_DIAGONAL_STRIPE, color: "auto", }, }); diff --git a/src/file/table/table-properties/table-properties.ts b/src/file/table/table-properties/table-properties.ts index 5d1aa048e1..5e46cfa92a 100644 --- a/src/file/table/table-properties/table-properties.ts +++ b/src/file/table/table-properties/table-properties.ts @@ -1,26 +1,43 @@ // http://officeopenxml.com/WPtableProperties.php -import { IgnoreIfEmptyXmlComponent } from "file/xml-components"; +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +import { IgnoreIfEmptyXmlComponent, OnOffElement, StringValueElement } from "file/xml-components"; import { Alignment, AlignmentType } from "../../paragraph"; -import { ITableShadingAttributesProperties, TableShading } from "../shading"; -import { WidthType } from "../table-cell"; +import { IShadingAttributesProperties, Shading } from "../../shading"; +import { ITableWidthProperties, TableWidthElement } from "../table-width"; import { ITableBordersOptions, TableBorders } from "./table-borders"; -import { ITableCellMarginOptions, TableCellMargin } from "./table-cell-margin"; +import { ITableCellMarginOptions, TableCellMargin, TableCellMarginElementType } from "./table-cell-margin"; import { ITableFloatOptions, TableFloatProperties } from "./table-float-properties"; import { TableLayout, TableLayoutType } from "./table-layout"; -import { TableStyle } from "./table-style"; -import { PreferredTableWidth } from "./table-width"; -import { VisuallyRightToLeft } from "./visually-right-to-left"; export interface ITablePropertiesOptions { - readonly width?: { - readonly size: number; - readonly type?: WidthType; - }; + readonly width?: ITableWidthProperties; + readonly indent?: ITableWidthProperties; readonly layout?: TableLayoutType; readonly borders?: ITableBordersOptions; readonly float?: ITableFloatOptions; - readonly shading?: ITableShadingAttributesProperties; + readonly shading?: IShadingAttributesProperties; readonly style?: string; readonly alignment?: AlignmentType; readonly cellMargin?: ITableCellMarginOptions; @@ -32,37 +49,43 @@ export class TableProperties extends IgnoreIfEmptyXmlComponent { super("w:tblPr"); if (options.style) { - this.root.push(new TableStyle(options.style)); + this.root.push(new StringValueElement("w:tblStyle", options.style)); } if (options.float) { this.root.push(new TableFloatProperties(options.float)); } - if (options.visuallyRightToLeft) { - this.root.push(new VisuallyRightToLeft()); + if (options.visuallyRightToLeft !== undefined) { + this.root.push(new OnOffElement("w:bidiVisual", options.visuallyRightToLeft)); } if (options.width) { - this.root.push(new PreferredTableWidth(options.width.type, options.width.size)); + this.root.push(new TableWidthElement("w:tblW", options.width)); } if (options.alignment) { this.root.push(new Alignment(options.alignment)); } + if (options.indent) { + this.root.push(new TableWidthElement("w:tblInd", options.indent)); + } + if (options.borders) { this.root.push(new TableBorders(options.borders)); } if (options.shading) { - this.root.push(new TableShading(options.shading)); + this.root.push(new Shading(options.shading)); } if (options.layout) { this.root.push(new TableLayout(options.layout)); } - this.root.push(new TableCellMargin(options.cellMargin || {})); + if (options.cellMargin) { + this.root.push(new TableCellMargin(TableCellMarginElementType.TABLE, options.cellMargin)); + } } } diff --git a/src/file/table/table-properties/table-style.spec.ts b/src/file/table/table-properties/table-style.spec.ts deleted file mode 100644 index 1b9b0e9748..0000000000 --- a/src/file/table/table-properties/table-style.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; - -import { TableStyle } from "./table-style"; - -describe("TableStyle", () => { - describe("#constructor", () => { - it("should create", () => { - const tableStyle = new TableStyle("test-id"); - const tree = new Formatter().format(tableStyle); - - expect(tree).to.deep.equal({ - "w:tblStyle": { - _attr: { - "w:val": "test-id", - }, - }, - }); - }); - }); -}); diff --git a/src/file/table/table-properties/table-style.ts b/src/file/table/table-properties/table-style.ts deleted file mode 100644 index 22b4dc5e89..0000000000 --- a/src/file/table/table-properties/table-style.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Attributes, XmlComponent } from "file/xml-components"; - -export class TableStyle extends XmlComponent { - constructor(styleId: string) { - super("w:tblStyle"); - - this.root.push( - new Attributes({ - val: styleId, - }), - ); - } -} diff --git a/src/file/table/table-properties/table-width.ts b/src/file/table/table-properties/table-width.ts deleted file mode 100644 index 1b86125e90..0000000000 --- a/src/file/table/table-properties/table-width.ts +++ /dev/null @@ -1,19 +0,0 @@ -// http://officeopenxml.com/WPtableWidth.php -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; - -import { WidthType } from "../table-cell"; - -class TableWidthAttributes extends XmlAttributeComponent<{ - readonly type: WidthType; - readonly w: number | string; -}> { - protected readonly xmlKeys = { type: "w:type", w: "w:w" }; -} - -export class PreferredTableWidth extends XmlComponent { - constructor(type: WidthType = WidthType.AUTO, w: number) { - super("w:tblW"); - const width: number | string = type === WidthType.PERCENTAGE ? `${w}%` : w; - this.root.push(new TableWidthAttributes({ type: type, w: width })); - } -} diff --git a/src/file/table/table-properties/visually-right-to-left.spec.ts b/src/file/table/table-properties/visually-right-to-left.spec.ts deleted file mode 100644 index 792c90194b..0000000000 --- a/src/file/table/table-properties/visually-right-to-left.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { expect } from "chai"; - -import { Formatter } from "export/formatter"; -import { VisuallyRightToLeft } from "./visually-right-to-left"; - -describe("VisuallyRightToLeft", () => { - it("should create", () => { - const visuallyRightToLeft = new VisuallyRightToLeft(); - const tree = new Formatter().format(visuallyRightToLeft); - expect(tree).to.deep.equal({ - "w:bidiVisual": {}, - }); - }); -}); diff --git a/src/file/table/table-properties/visually-right-to-left.ts b/src/file/table/table-properties/visually-right-to-left.ts deleted file mode 100644 index c0598a5a26..0000000000 --- a/src/file/table/table-properties/visually-right-to-left.ts +++ /dev/null @@ -1,8 +0,0 @@ -// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_bidiVisual_topic_ID0EOXIQ.html -import { XmlComponent } from "file/xml-components"; - -export class VisuallyRightToLeft extends XmlComponent { - constructor() { - super("w:bidiVisual"); - } -} diff --git a/src/file/table/table-row/table-row-height.ts b/src/file/table/table-row/table-row-height.ts index 6803d9b17e..84d696b63b 100644 --- a/src/file/table/table-row/table-row-height.ts +++ b/src/file/table/table-row/table-row-height.ts @@ -1,5 +1,18 @@ +import { twipsMeasureValue } from "file/values"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +// +// +// +// + +// +// +// +// +// +// +// export enum HeightRule { /** Height is determined based on the content, so value is ignored. */ AUTO = "auto", @@ -10,19 +23,19 @@ export enum HeightRule { } export class TableRowHeightAttributes extends XmlAttributeComponent<{ - readonly value: number; + readonly value: number | string; readonly rule: HeightRule; }> { protected readonly xmlKeys = { value: "w:val", rule: "w:hRule" }; } export class TableRowHeight extends XmlComponent { - constructor(value: number, rule: HeightRule) { + constructor(value: number | string, rule: HeightRule) { super("w:trHeight"); this.root.push( new TableRowHeightAttributes({ - value: value, + value: twipsMeasureValue(value), rule: rule, }), ); diff --git a/src/file/table/table-row/table-row-properties.spec.ts b/src/file/table/table-row/table-row-properties.spec.ts index 05092d4178..3f008ce77e 100644 --- a/src/file/table/table-row/table-row-properties.spec.ts +++ b/src/file/table/table-row/table-row-properties.spec.ts @@ -6,49 +6,55 @@ import { TableRowProperties } from "./table-row-properties"; describe("TableRowProperties", () => { describe("#constructor", () => { it("creates an initially empty property object", () => { - const rowProperties = new TableRowProperties(); + const rowProperties = new TableRowProperties({}); // The TableRowProperties is ignorable if there are no attributes, // which results in prepForXml returning undefined, which causes // the formatter to throw an error if that is the only object it // has been asked to format. expect(() => new Formatter().format(rowProperties)).to.throw("XMLComponent did not format correctly"); }); - }); - describe("#setCantSplit", () => { it("sets cantSplit to avoid row been paginated", () => { - const rowProperties = new TableRowProperties(); - rowProperties.setCantSplit(); + const rowProperties = new TableRowProperties({ cantSplit: true }); const tree = new Formatter().format(rowProperties); - expect(tree).to.deep.equal({ "w:trPr": [{ "w:cantSplit": { _attr: { "w:val": true } } }] }); + expect(tree).to.deep.equal({ "w:trPr": [{ "w:cantSplit": {} }] }); }); - }); - describe("#setTableHeader", () => { it("sets row as table header (repeat row on each page of table)", () => { - const rowProperties = new TableRowProperties(); - rowProperties.setTableHeader(); + const rowProperties = new TableRowProperties({ tableHeader: true }); const tree = new Formatter().format(rowProperties); - expect(tree).to.deep.equal({ "w:trPr": [{ "w:tblHeader": { _attr: { "w:val": true } } }] }); + expect(tree).to.deep.equal({ "w:trPr": [{ "w:tblHeader": {} }] }); }); - }); - describe("#setHeight", () => { it("sets row height exact", () => { - const rowProperties = new TableRowProperties(); - rowProperties.setHeight(100, HeightRule.EXACT); + const rowProperties = new TableRowProperties({ + height: { + value: 100, + rule: HeightRule.EXACT, + }, + }); const tree = new Formatter().format(rowProperties); expect(tree).to.deep.equal({ "w:trPr": [{ "w:trHeight": { _attr: { "w:val": 100, "w:hRule": "exact" } } }] }); }); + it("sets row height auto", () => { - const rowProperties = new TableRowProperties(); - rowProperties.setHeight(100, HeightRule.AUTO); + const rowProperties = new TableRowProperties({ + height: { + value: 100, + rule: HeightRule.AUTO, + }, + }); const tree = new Formatter().format(rowProperties); expect(tree).to.deep.equal({ "w:trPr": [{ "w:trHeight": { _attr: { "w:val": 100, "w:hRule": "auto" } } }] }); }); + it("sets row height at least", () => { - const rowProperties = new TableRowProperties(); - rowProperties.setHeight(100, HeightRule.ATLEAST); + const rowProperties = new TableRowProperties({ + height: { + value: 100, + rule: HeightRule.ATLEAST, + }, + }); const tree = new Formatter().format(rowProperties); expect(tree).to.deep.equal({ "w:trPr": [{ "w:trHeight": { _attr: { "w:val": 100, "w:hRule": "atLeast" } } }] }); }); diff --git a/src/file/table/table-row/table-row-properties.ts b/src/file/table/table-row/table-row-properties.ts index 1b4c9784cd..2168d0774a 100644 --- a/src/file/table/table-row/table-row-properties.ts +++ b/src/file/table/table-row/table-row-properties.ts @@ -1,50 +1,59 @@ // http://officeopenxml.com/WPtableRowProperties.php -import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +import { IgnoreIfEmptyXmlComponent, OnOffElement } from "file/xml-components"; import { HeightRule, TableRowHeight } from "./table-row-height"; +export interface ITableRowPropertiesOptions { + readonly cantSplit?: boolean; + readonly tableHeader?: boolean; + readonly height?: { + readonly value: number | string; + readonly rule: HeightRule; + }; +} + export class TableRowProperties extends IgnoreIfEmptyXmlComponent { - constructor() { + constructor(options: ITableRowPropertiesOptions) { super("w:trPr"); - } - public setCantSplit(): TableRowProperties { - this.root.push(new CantSplit()); + if (options.cantSplit !== undefined) { + this.root.push(new OnOffElement("w:cantSplit", options.cantSplit)); + } - return this; - } + if (options.tableHeader !== undefined) { + this.root.push(new OnOffElement("w:tblHeader", options.tableHeader)); + } - public setTableHeader(): TableRowProperties { - this.root.push(new TableHeader()); - - return this; - } - - public setHeight(value: number, rule: HeightRule): TableRowProperties { - this.root.push(new TableRowHeight(value, rule)); - - return this; - } -} - -class CantSplitAttributes extends XmlAttributeComponent<{ readonly val: boolean }> { - protected readonly xmlKeys = { val: "w:val" }; -} - -export class CantSplit extends XmlComponent { - constructor() { - super("w:cantSplit"); - this.root.push(new CantSplitAttributes({ val: true })); - } -} - -class TableHeaderAttributes extends XmlAttributeComponent<{ readonly val: boolean }> { - protected readonly xmlKeys = { val: "w:val" }; -} - -export class TableHeader extends XmlComponent { - constructor() { - super("w:tblHeader"); - this.root.push(new TableHeaderAttributes({ val: true })); + if (options.height) { + this.root.push(new TableRowHeight(options.height.value, options.height.rule)); + } } } diff --git a/src/file/table/table-row/table-row.spec.ts b/src/file/table/table-row/table-row.spec.ts index 16a08e10ae..f1bcbb1aaf 100644 --- a/src/file/table/table-row/table-row.spec.ts +++ b/src/file/table/table-row/table-row.spec.ts @@ -53,11 +53,7 @@ describe("TableRow", () => { { "w:trPr": [ { - "w:cantSplit": { - _attr: { - "w:val": true, - }, - }, + "w:cantSplit": {}, }, ], }, @@ -76,11 +72,7 @@ describe("TableRow", () => { { "w:trPr": [ { - "w:tblHeader": { - _attr: { - "w:val": true, - }, - }, + "w:tblHeader": {}, }, ], }, @@ -141,11 +133,7 @@ describe("TableRow", () => { { "w:trPr": [ { - "w:tblHeader": { - _attr: { - "w:val": true, - }, - }, + "w:tblHeader": {}, }, ], }, diff --git a/src/file/table/table-row/table-row.ts b/src/file/table/table-row/table-row.ts index 92a8614511..11dc9c776a 100644 --- a/src/file/table/table-row/table-row.ts +++ b/src/file/table/table-row/table-row.ts @@ -1,41 +1,19 @@ -import { HeightRule } from "file/table/table-row/table-row-height"; import { XmlComponent } from "file/xml-components"; import { TableCell } from "../table-cell"; -import { TableRowProperties } from "./table-row-properties"; +import { ITableRowPropertiesOptions, TableRowProperties } from "./table-row-properties"; -export interface ITableRowOptions { - readonly cantSplit?: boolean; - readonly tableHeader?: boolean; - readonly height?: { - readonly value: number; - readonly rule: HeightRule; - }; +export interface ITableRowOptions extends ITableRowPropertiesOptions { readonly children: TableCell[]; } export class TableRow extends XmlComponent { - private readonly properties: TableRowProperties; - constructor(private readonly options: ITableRowOptions) { super("w:tr"); - this.properties = new TableRowProperties(); - this.root.push(this.properties); + this.root.push(new TableRowProperties(options)); for (const child of options.children) { this.root.push(child); } - - if (options.cantSplit) { - this.properties.setCantSplit(); - } - - if (options.tableHeader) { - this.properties.setTableHeader(); - } - - if (options.height) { - this.properties.setHeight(options.height.value, options.height.rule); - } } public get CellCount(): number { diff --git a/src/file/table/table-width.ts b/src/file/table/table-width.ts new file mode 100644 index 0000000000..ff203506eb --- /dev/null +++ b/src/file/table/table-width.ts @@ -0,0 +1,43 @@ +// http://officeopenxml.com/WPtableWidth.php +import { measurementOrPercentValue } from "file/values"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// +// +// +// +// +// +export enum WidthType { + /** Auto. */ + AUTO = "auto", + /** Value is in twentieths of a point */ + DXA = "dxa", + /** No (empty) value. */ + NIL = "nil", + /** Value is in percentage. */ + PERCENTAGE = "pct", +} + +// +// +// +// +export interface ITableWidthProperties { + readonly size: string | number; + readonly type?: WidthType; +} + +class TableWidthAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { type: "w:type", size: "w:w" }; +} + +export class TableWidthElement extends XmlComponent { + constructor(name: string, { type = WidthType.AUTO, size }: ITableWidthProperties) { + super(name); + // super("w:tblW"); + this.root.push(new TableWidthAttributes({ type: type, size: measurementOrPercentValue(size) })); + } +} diff --git a/src/file/table/table.spec.ts b/src/file/table/table.spec.ts index 623540985f..d3e703decd 100644 --- a/src/file/table/table.spec.ts +++ b/src/file/table/table.spec.ts @@ -5,58 +5,21 @@ import { Formatter } from "export/formatter"; import { AlignmentType, Paragraph } from "../paragraph"; import { Table } from "./table"; -// import { WidthType } from "./table-cell"; import { RelativeHorizontalPosition, RelativeVerticalPosition, TableAnchorType } from "./table-properties"; -import { TableCell, WidthType } from "./table-cell"; +import { TableCell } from "./table-cell"; import { TableLayoutType } from "./table-properties/table-layout"; import { TableRow } from "./table-row"; - -const DEFAULT_TABLE_PROPERTIES = { - "w:tblCellMar": [ - { - "w:top": { - _attr: { - "w:type": "auto", - "w:w": 0, - }, - }, - }, - { - "w:left": { - _attr: { - "w:type": "auto", - "w:w": 0, - }, - }, - }, - { - "w:bottom": { - _attr: { - "w:type": "auto", - "w:w": 0, - }, - }, - }, - { - "w:right": { - _attr: { - "w:type": "auto", - "w:w": 0, - }, - }, - }, - ], -}; +import { WidthType } from "./table-width"; const BORDERS = { "w:tblBorders": [ - { "w:top": { _attr: { "w:val": "single", "w:sz": 4, "w:space": 0, "w:color": "auto" } } }, - { "w:left": { _attr: { "w:val": "single", "w:sz": 4, "w:space": 0, "w:color": "auto" } } }, - { "w:bottom": { _attr: { "w:val": "single", "w:sz": 4, "w:space": 0, "w:color": "auto" } } }, - { "w:right": { _attr: { "w:val": "single", "w:sz": 4, "w:space": 0, "w:color": "auto" } } }, - { "w:insideH": { _attr: { "w:val": "single", "w:sz": 4, "w:space": 0, "w:color": "auto" } } }, - { "w:insideV": { _attr: { "w:val": "single", "w:sz": 4, "w:space": 0, "w:color": "auto" } } }, + { "w:top": { _attr: { "w:val": "single", "w:sz": 4, "w:color": "auto" } } }, + { "w:left": { _attr: { "w:val": "single", "w:sz": 4, "w:color": "auto" } } }, + { "w:bottom": { _attr: { "w:val": "single", "w:sz": 4, "w:color": "auto" } } }, + { "w:right": { _attr: { "w:val": "single", "w:sz": 4, "w:color": "auto" } } }, + { "w:insideH": { _attr: { "w:val": "single", "w:sz": 4, "w:color": "auto" } } }, + { "w:insideV": { _attr: { "w:val": "single", "w:sz": 4, "w:color": "auto" } } }, ], }; @@ -177,7 +140,7 @@ describe("Table", () => { }; expect(tree).to.deep.equal({ "w:tbl": [ - { "w:tblPr": [WIDTHS, BORDERS, DEFAULT_TABLE_PROPERTIES] }, + { "w:tblPr": [WIDTHS, BORDERS] }, { "w:tblGrid": [{ "w:gridCol": { _attr: { "w:w": 100 } } }, { "w:gridCol": { _attr: { "w:w": 100 } } }], }, @@ -223,7 +186,7 @@ describe("Table", () => { const cellP = { "w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "hello"] }] }] }; expect(tree).to.deep.equal({ "w:tbl": [ - { "w:tblPr": [WIDTHS, BORDERS, DEFAULT_TABLE_PROPERTIES] }, + { "w:tblPr": [WIDTHS, BORDERS] }, { "w:tblGrid": [{ "w:gridCol": { _attr: { "w:w": 100 } } }, { "w:gridCol": { _attr: { "w:w": 100 } } }], }, @@ -270,7 +233,7 @@ describe("Table", () => { const tree = new Formatter().format(table); expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1); expect(tree["w:tbl"][0]).to.deep.equal({ - "w:tblPr": [WIDTHS, BORDERS, { "w:tblLayout": { _attr: { "w:type": "fixed" } } }, DEFAULT_TABLE_PROPERTIES], + "w:tblPr": [WIDTHS, BORDERS, { "w:tblLayout": { _attr: { "w:type": "fixed" } } }], }); }); @@ -290,7 +253,7 @@ describe("Table", () => { const tree = new Formatter().format(table); expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1); expect(tree["w:tbl"][0]).to.deep.equal({ - "w:tblPr": [WIDTHS, { "w:jc": { _attr: { "w:val": "center" } } }, BORDERS, DEFAULT_TABLE_PROPERTIES], + "w:tblPr": [WIDTHS, { "w:jc": { _attr: { "w:val": "center" } } }, BORDERS], }); }); @@ -319,13 +282,12 @@ describe("Table", () => { "w:tblW": { _attr: { "w:type": "pct", - "w:w": "100%", + "w:w": 100, }, }, }, BORDERS, { "w:tblLayout": { _attr: { "w:type": "fixed" } } }, - DEFAULT_TABLE_PROPERTIES, ], }); }); @@ -495,7 +457,6 @@ describe("Table", () => { }, WIDTHS, BORDERS, - DEFAULT_TABLE_PROPERTIES, ], }); }); diff --git a/src/file/table/table.ts b/src/file/table/table.ts index ea4acd93d5..5751207ba9 100644 --- a/src/file/table/table.ts +++ b/src/file/table/table.ts @@ -3,10 +3,12 @@ import { XmlComponent } from "file/xml-components"; import { AlignmentType } from "../paragraph"; import { TableGrid } from "./grid"; -import { TableCell, VerticalMergeType, WidthType } from "./table-cell"; +import { TableCell, VerticalMergeType } from "./table-cell"; import { ITableBordersOptions, ITableFloatOptions, TableProperties } from "./table-properties"; +import { ITableCellMarginOptions } from "./table-properties/table-cell-margin"; import { TableLayoutType } from "./table-properties/table-layout"; import { TableRow } from "./table-row"; +import { ITableWidthProperties } from "./table-width"; /* 0-width columns don't get rendered correctly, so we need @@ -20,18 +22,10 @@ import { TableRow } from "./table-row"; */ export interface ITableOptions { readonly rows: TableRow[]; - readonly width?: { - readonly size: number; - readonly type?: WidthType; - }; + readonly width?: ITableWidthProperties; readonly columnWidths?: number[]; - readonly margins?: { - readonly marginUnitType?: WidthType; - readonly top?: number; - readonly bottom?: number; - readonly right?: number; - readonly left?: number; - }; + readonly margins?: ITableCellMarginOptions; + readonly indent?: ITableWidthProperties; readonly float?: ITableFloatOptions; readonly layout?: TableLayoutType; readonly style?: string; @@ -45,7 +39,8 @@ export class Table extends XmlComponent { rows, width, columnWidths = Array(Math.max(...rows.map((row) => row.CellCount))).fill(100), - margins: { marginUnitType, top, bottom, right, left } = { marginUnitType: WidthType.AUTO, top: 0, bottom: 0, right: 0, left: 0 }, + margins, + indent, float, layout, style, @@ -59,28 +54,12 @@ export class Table extends XmlComponent { new TableProperties({ borders: borders ?? {}, width: width ?? { size: 100 }, + indent, float, layout, style, alignment, - cellMargin: { - bottom: { - value: bottom || 0, - type: marginUnitType, - }, - top: { - value: top || 0, - type: marginUnitType, - }, - left: { - value: left || 0, - type: marginUnitType, - }, - right: { - value: right || 0, - type: marginUnitType, - }, - }, + cellMargin: margins, visuallyRightToLeft, }), ); diff --git a/src/file/track-revision/track-revision-components/deleted-text-run.spec.ts b/src/file/track-revision/track-revision-components/deleted-text-run.spec.ts index 6bd11c24d7..9eacb7c23c 100644 --- a/src/file/track-revision/track-revision-components/deleted-text-run.spec.ts +++ b/src/file/track-revision/track-revision-components/deleted-text-run.spec.ts @@ -54,18 +54,10 @@ describe("DeletedTextRun", () => { { "w:rPr": [ { - "w:b": { - _attr: { - "w:val": true, - }, - }, + "w:b": {}, }, { - "w:bCs": { - _attr: { - "w:val": true, - }, - }, + "w:bCs": {}, }, ], }, diff --git a/src/file/values.spec.ts b/src/file/values.spec.ts new file mode 100644 index 0000000000..3ef06a6abc --- /dev/null +++ b/src/file/values.spec.ts @@ -0,0 +1,193 @@ +import { expect } from "chai"; +import { + dateTimeValue, + hexColorValue, + hpsMeasureValue, + longHexNumber, + measurementOrPercentValue, + percentageValue, + positiveUniversalMeasureValue, + shortHexNumber, + signedHpsMeasureValue, + signedTwipsMeasureValue, + twipsMeasureValue, + universalMeasureValue, + unsignedDecimalNumber, +} from "./values"; + +describe("values", () => { + describe("universalMeasureValue", () => { + it("should allow valid values", () => { + // "-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)" + expect(universalMeasureValue("-9mm")).to.eq("-9mm"); + expect(universalMeasureValue("-0.5in")).to.eq("-0.5in"); + expect(universalMeasureValue("20.pt")).to.eq("20pt"); + expect(universalMeasureValue("5.22pc")).to.eq("5.22pc"); + expect(universalMeasureValue("100 pi")).to.eq("100pi"); + }); + it("should throw on invalid values", () => { + expect(() => universalMeasureValue("100pp")).to.throw(); + expect(() => universalMeasureValue("foo")).to.throw(); + expect(() => universalMeasureValue("--in")).to.throw(); + expect(() => universalMeasureValue("NaNpc")).to.throw(); + expect(() => universalMeasureValue("50")).to.throw(); + }); + }); + + describe("positiveUniversalMeasureValue", () => { + it("should allow valid values", () => { + // "[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)" + expect(positiveUniversalMeasureValue("9mm")).to.eq("9mm"); + expect(positiveUniversalMeasureValue("0.5in")).to.eq("0.5in"); + expect(positiveUniversalMeasureValue("20.pt")).to.eq("20pt"); + expect(positiveUniversalMeasureValue("5.22pc")).to.eq("5.22pc"); + expect(positiveUniversalMeasureValue("100 pi")).to.eq("100pi"); + }); + it("should throw on invalid values", () => { + expect(() => positiveUniversalMeasureValue("-9mm")).to.throw(); + expect(() => positiveUniversalMeasureValue("-0.5in")).to.throw(); + expect(() => positiveUniversalMeasureValue("100pp")).to.throw(); + expect(() => positiveUniversalMeasureValue("foo")).to.throw(); + expect(() => positiveUniversalMeasureValue("--in")).to.throw(); + expect(() => positiveUniversalMeasureValue("NaNpc")).to.throw(); + expect(() => positiveUniversalMeasureValue("50")).to.throw(); + }); + }); + + describe("longHexNumber", () => { + it("should allow valid values", () => { + expect(longHexNumber("112233FF")).to.eq("112233FF"); + }); + it("should throw on invalid values", () => { + expect(() => longHexNumber("112233GG")).to.throw(); + expect(() => longHexNumber("112233F")).to.throw(); + expect(() => longHexNumber("112233FFF")).to.throw(); + }); + }); + + describe("shortHexNumber", () => { + it("should allow valid values", () => { + expect(shortHexNumber("1122")).to.eq("1122"); + expect(shortHexNumber("FFFF")).to.eq("FFFF"); + }); + it("should throw on invalid values", () => { + expect(() => shortHexNumber("11")).to.throw(); + expect(() => shortHexNumber("112233")).to.throw(); + expect(() => shortHexNumber("FFFG")).to.throw(); + }); + }); + + describe("hexColorValue", () => { + it("should allow valid values", () => { + expect(hexColorValue("auto")).to.eq("auto"); + expect(hexColorValue("FF0000")).to.eq("FF0000"); + expect(hexColorValue("aabbcc")).to.eq("aabbcc"); + expect(hexColorValue("#BEEFEE")).to.eq("BEEFEE"); + expect(hexColorValue("abcdef")).to.eq("abcdef"); + }); + it("should throw on invalid values", () => { + expect(() => hexColorValue("foo")).to.throw(); + expect(() => hexColorValue("fff")).to.throw(); + expect(() => hexColorValue("a")).to.throw(); + expect(() => hexColorValue("abcde")).to.throw(); + expect(() => hexColorValue("---")).to.throw(); + expect(() => hexColorValue("brown")).to.throw(); + }); + }); + + describe("unsignedDecimalNumber", () => { + it("should allow valid values", () => { + expect(unsignedDecimalNumber(1243)).to.eq(1243); + expect(unsignedDecimalNumber(12.43)).to.eq(12); + expect(unsignedDecimalNumber(1e10)).to.eq(1e10); + }); + it("should throw on invalid values", () => { + expect(() => unsignedDecimalNumber(NaN)).to.throw(); + expect(() => unsignedDecimalNumber(-10)).to.throw(); + }); + }); + + describe("signedTwipsMeasureValue", () => { + it("should allow valid values", () => { + expect(signedTwipsMeasureValue(1243)).to.eq(1243); + expect(signedTwipsMeasureValue("-5mm")).to.eq("-5mm"); + expect(signedTwipsMeasureValue("10.in")).to.eq("10in"); + }); + it("should throw on invalid values", () => { + expect(() => signedTwipsMeasureValue(NaN)).to.throw(); + expect(() => signedTwipsMeasureValue("foo")).to.throw(); + }); + }); + + describe("twipsMeasureValue", () => { + it("should allow valid values", () => { + expect(twipsMeasureValue(1243)).to.eq(1243); + expect(twipsMeasureValue("5mm")).to.eq("5mm"); + expect(twipsMeasureValue("10.in")).to.eq("10in"); + }); + it("should throw on invalid values", () => { + expect(() => twipsMeasureValue(-12)).to.throw(); + expect(() => twipsMeasureValue(NaN)).to.throw(); + expect(() => twipsMeasureValue("foo")).to.throw(); + expect(() => twipsMeasureValue("-5mm")).to.throw(); + }); + }); + + describe("hpsMeasureValue", () => { + it("should allow valid values", () => { + expect(hpsMeasureValue(1243)).to.eq(1243); + expect(hpsMeasureValue("5mm")).to.eq("5mm"); + }); + it("should throw on invalid values", () => { + expect(() => hpsMeasureValue(NaN)).to.throw(); + expect(() => hpsMeasureValue("-5mm")).to.throw(); + }); + }); + + describe("signedHpsMeasureValue", () => { + it("should allow valid values", () => { + expect(signedHpsMeasureValue(1243)).to.eq(1243); + expect(signedHpsMeasureValue(-1243)).to.eq(-1243); + expect(signedHpsMeasureValue("5mm")).to.eq("5mm"); + expect(signedHpsMeasureValue("-5mm")).to.eq("-5mm"); + }); + it("should throw on invalid values", () => { + expect(() => hpsMeasureValue(NaN)).to.throw(); + expect(() => hpsMeasureValue("5FF")).to.throw(); + }); + }); + + describe("percentageValue", () => { + it("should allow valid values", () => { + expect(percentageValue("0%")).to.eq("0%"); + expect(percentageValue("-20%")).to.eq("-20%"); + expect(percentageValue("100%")).to.eq("100%"); + expect(percentageValue("1000%")).to.eq("1000%"); + }); + it("should throw on invalid values", () => { + expect(() => percentageValue("0%%")).to.throw(); + expect(() => percentageValue("20")).to.throw(); + expect(() => percentageValue("FF%")).to.throw(); + }); + }); + + describe("measurementOrPercentValue", () => { + it("should allow valid values", () => { + expect(measurementOrPercentValue(1243)).to.eq(1243); + expect(measurementOrPercentValue(-1243)).to.eq(-1243); + expect(measurementOrPercentValue("10%")).to.eq("10%"); + expect(measurementOrPercentValue("5mm")).to.eq("5mm"); + }); + it("should throw on invalid values", () => { + expect(() => measurementOrPercentValue(NaN)).to.throw(); + expect(() => measurementOrPercentValue("10%%")).to.throw(); + expect(() => measurementOrPercentValue("10F")).to.throw(); + }); + }); + + describe("dateTimeValue", () => { + it("should allow valid values", () => { + expect(dateTimeValue(new Date())).to.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d+)?Z/); + }); + }); +}); diff --git a/src/file/values.ts b/src/file/values.ts new file mode 100644 index 0000000000..8634d47de4 --- /dev/null +++ b/src/file/values.ts @@ -0,0 +1,226 @@ +// Runtime checks and cleanup for value types in the spec that aren't easily expressed through our type system. +// These will help us to prevent silent failures and corrupted documents. +// +// Most of the rest of the types not defined here are either aliases of existing types or enumerations. +// Enumerations should probably just be implemented as enums, with instructions to end-users, without a runtime check. + +// +// +// +export function decimalNumber(val: number): number { + if (isNaN(val)) { + throw new Error(`Invalid value '${val}' specified. Must be an integer.`); + } + return Math.floor(val); +} + +// +// +// +export function unsignedDecimalNumber(val: number): number { + const value = decimalNumber(val); + if (value < 0) { + throw new Error(`Invalid value '${val}' specified. Must be a positive integer.`); + } + return value; +} + +// The xsd:hexBinary type represents binary data as a sequence of binary octets. +// It uses hexadecimal encoding, where each binary octet is a two-character hexadecimal number. +// Lowercase and uppercase letters A through F are permitted. For example, 0FB8 and 0fb8 are two +// equal xsd:hexBinary representations consisting of two octets. +// http://www.datypic.com/sc/xsd/t-xsd_hexBinary.html +function hexBinary(val: string, length: number): string { + const expectedLength = length * 2; + if (val.length !== expectedLength || isNaN(Number("0x" + val))) { + throw new Error(`Invalid hex value '${val}'. Expected ${expectedLength} digit hex value`); + } + return val; +} + +// +// +// +// +// +export function longHexNumber(val: string): string { + return hexBinary(val, 4); +} + +// +// +// +// +// +export function shortHexNumber(val: string): string { + return hexBinary(val, 2); +} + +// +// +// +// +// +export function uCharHexNumber(val: string): string { + return hexBinary(val, 1); +} + +// +// +// +// +// + +// +// +// +// +// +export function universalMeasureValue(val: string): string { + const unit = val.slice(-2); + if (!universalMeasureUnits.includes(unit)) { + throw new Error(`Invalid unit '${unit}' specified. Valid units are ${universalMeasureUnits.join(", ")}`); + } + const amount = val.substring(0, val.length - 2); + if (isNaN(Number(amount))) { + throw new Error(`Invalid value '${amount}' specified. Expected a valid number.`); + } + return `${Number(amount)}${unit}`; +} +const universalMeasureUnits = ["mm", "cm", "in", "pt", "pc", "pi"]; + +// +// +// +// +// +export function positiveUniversalMeasureValue(val: string): string { + const value = universalMeasureValue(val); + if (parseFloat(value) < 0) { + throw new Error(`Invalid value '${value}' specified. Expected a positive number.`); + } + return value; +} + +// +// +// +// +// +// +// +// + +// +// +// +// +// +export function hexColorValue(val: string): string { + if (val === "auto") { + return val; + } + // It's super common to see colors prefixed with a pound, but technically invalid here. + // Most clients work with it, but strip it off anyway for strict compliance. + const color = val.charAt(0) === "#" ? val.substring(1) : val; + return hexBinary(color, 3); +} + +// +// +// +export function signedTwipsMeasureValue(val: string | number): string | number { + return typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val); +} + +// +// +// +export function hpsMeasureValue(val: string | number): string | number { + return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); +} + +// +// +// +export function signedHpsMeasureValue(val: string | number): string | number { + return typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val); +} + +// +// +// +export function twipsMeasureValue(val: string | number): string | number { + return typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); +} + +// +// +// +// +// +export function percentageValue(val: string): string { + if (val.slice(-1) !== "%") { + throw new Error(`Invalid value '${val}'. Expected percentage value (eg '55%')`); + } + const percent = val.substring(0, val.length - 1); + if (isNaN(Number(percent))) { + throw new Error(`Invalid value '${percent}' specified. Expected a valid number.`); + } + return `${Number(percent)}%`; +} + +// +// +// + +// +// +// + +// +// +// + +export function measurementOrPercentValue(val: number | string): number | string { + if (typeof val === "number") { + return decimalNumber(val); + } + if (val.slice(-1) === "%") { + return percentageValue(val); + } + return universalMeasureValue(val); +} + +// +// +// +export const eighthPointMeasureValue = unsignedDecimalNumber; + +// +// +// +export const pointMeasureValue = unsignedDecimalNumber; + +// +// +// +// +// http://www.datypic.com/sc/xsd/t-xsd_dateTime.html +// The type xsd:dateTime represents a specific date and time in the format +// CCYY-MM-DDThh:mm:ss.sss, which is a concatenation of the date and time forms, +// separated by a literal letter "T". All of the same rules that apply to the date +// and time types are applicable to xsd:dateTime as well. +// +// An optional time zone expression may be added at the end of the value. +// The letter Z is used to indicate Coordinated Universal Time (UTC). All other time +// zones are represented by their difference from Coordinated Universal Time in the +// format +hh:mm, or -hh:mm. These values may range from -14:00 to 14:00. For example, +// US Eastern Standard Time, which is five hours behind UTC, is represented as -05:00. +// If no time zone value is present, it is considered unknown; it is not assumed to be UTC. +// +// Luckily, js has this format built in already. See: +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString +export function dateTimeValue(val: Date): string { + return val.toISOString(); +} diff --git a/src/file/vertical-align/index.ts b/src/file/vertical-align/index.ts new file mode 100644 index 0000000000..29714c88b6 --- /dev/null +++ b/src/file/vertical-align/index.ts @@ -0,0 +1 @@ +export * from "./vertical-align"; diff --git a/src/file/vertical-align/vertical-align.ts b/src/file/vertical-align/vertical-align.ts new file mode 100644 index 0000000000..a031eb674d --- /dev/null +++ b/src/file/vertical-align/vertical-align.ts @@ -0,0 +1,34 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +// +// +// + +// +// +// +// +// +// +// +// +export enum VerticalAlign { + BOTH = "both", + BOTTOM = "bottom", + CENTER = "center", + TOP = "top", +} +export class VerticalAlignAttributes extends XmlAttributeComponent<{ + readonly verticalAlign?: VerticalAlign; +}> { + protected readonly xmlKeys = { + verticalAlign: "w:val", + }; +} + +export class VerticalAlignElement extends XmlComponent { + constructor(value: VerticalAlign) { + super("w:vAlign"); + this.root.push(new VerticalAlignAttributes({ verticalAlign: value })); + } +} diff --git a/src/file/xml-components/base.ts b/src/file/xml-components/base.ts index 375e30d910..7a0d5a758b 100644 --- a/src/file/xml-components/base.ts +++ b/src/file/xml-components/base.ts @@ -9,16 +9,10 @@ export interface IContext { export abstract class BaseXmlComponent { protected readonly rootKey: string; - // tslint:disable-next-line:readonly-keyword - protected deleted: boolean = false; constructor(rootKey: string) { this.rootKey = rootKey; } public abstract prepForXml(context: IContext): IXmlableObject | undefined; - - public get IsDeleted(): boolean { - return this.deleted; - } } diff --git a/src/file/xml-components/imported-xml-component.spec.ts b/src/file/xml-components/imported-xml-component.spec.ts index 0f590b1286..58c07d09ca 100644 --- a/src/file/xml-components/imported-xml-component.spec.ts +++ b/src/file/xml-components/imported-xml-component.spec.ts @@ -20,28 +20,24 @@ const xmlString = ` `; const convertedXmlElement = { - deleted: false, root: [ { - deleted: false, rootKey: "w:p", root: [ - { deleted: false, rootKey: "_attr", root: { "w:one": "value 1", "w:two": "value 2" } }, - { deleted: false, rootKey: "w:rPr", root: [{ deleted: false, rootKey: "w:noProof", root: ["some value"] }] }, + { rootKey: "_attr", root: { "w:one": "value 1", "w:two": "value 2" } }, + { rootKey: "w:rPr", root: [{ rootKey: "w:noProof", root: ["some value"] }] }, { - deleted: false, rootKey: "w:r", root: [ - { deleted: false, rootKey: "_attr", root: { active: "true" } }, - { deleted: false, rootKey: "w:t", root: ["Text 1"] }, + { rootKey: "_attr", root: { active: "true" } }, + { rootKey: "w:t", root: ["Text 1"] }, ], }, { - deleted: false, rootKey: "w:r", root: [ - { deleted: false, rootKey: "_attr", root: { active: "true" } }, - { deleted: false, rootKey: "w:t", root: ["Text 2"] }, + { rootKey: "_attr", root: { active: "true" } }, + { rootKey: "w:t", root: ["Text 2"] }, ], }, ], diff --git a/src/file/xml-components/index.ts b/src/file/xml-components/index.ts index 295161b395..9ad7572069 100644 --- a/src/file/xml-components/index.ts +++ b/src/file/xml-components/index.ts @@ -4,4 +4,5 @@ export * from "./default-attributes"; export * from "./imported-xml-component"; export * from "./xmlable-object"; export * from "./initializable-xml-component"; +export * from "./simple-elements"; export * from "./base"; diff --git a/src/file/xml-components/simple-elements.ts b/src/file/xml-components/simple-elements.ts new file mode 100644 index 0000000000..02562e4c58 --- /dev/null +++ b/src/file/xml-components/simple-elements.ts @@ -0,0 +1,65 @@ +import { Attributes, XmlComponent } from "file/xml-components"; + +import { hpsMeasureValue } from "../values"; + +// This represents element type CT_OnOff, which indicate a boolean value. +// +// A value of 1 or true specifies that the property shall be explicitly applied. +// This is the default value for this attribute, and is implied when the parent +// element is present, but this attribute is omitted. +// A value of 0 or false specifies that the property shall be explicitly turned off. +// +// +// +// +export class OnOffElement extends XmlComponent { + constructor(name: string, val: boolean | undefined = true) { + super(name); + if (val !== true) { + this.root.push(new Attributes({ val })); + } + } +} + +// This represents element type CT_HpsMeasure, which indicate an unsigned int or a measurement with unit. +// +// +// +// +export class HpsMeasureElement extends XmlComponent { + constructor(name: string, val: number | string) { + super(name); + this.root.push(new Attributes({ val: hpsMeasureValue(val) })); + } +} + +// This represents element type CT_String, which indicate a string value. +// +// +// +// +export class StringValueElement extends XmlComponent { + constructor(name: string, val: string) { + super(name); + this.root.push(new Attributes({ val })); + } +} + +// This represents various number element types. +export class NumberValueElement extends XmlComponent { + constructor(name: string, val: number) { + super(name); + this.root.push(new Attributes({ val })); + } +} + +// Simple nodes containing text. +// +// new StringContainer("hello", "world") +// world +export class StringContainer extends XmlComponent { + constructor(name: string, val: string) { + super(name); + this.root.push(val); + } +} diff --git a/src/file/xml-components/xml-component.spec.ts b/src/file/xml-components/xml-component.spec.ts index 4fa5bfc515..497f14eab2 100644 --- a/src/file/xml-components/xml-component.spec.ts +++ b/src/file/xml-components/xml-component.spec.ts @@ -1,41 +1,62 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { EMPTY_OBJECT, XmlComponent } from "./"; -import { IContext } from "./base"; +import { Attributes, BaseXmlComponent, XmlComponent } from "./"; -class TestComponent extends XmlComponent {} +class TestComponent extends XmlComponent { + public push(el: BaseXmlComponent): void { + this.root.push(el); + } +} describe("XmlComponent", () => { - let xmlComponent: TestComponent; - - beforeEach(() => { - xmlComponent = new TestComponent("w:test"); - }); - describe("#constructor()", () => { it("should create an Xml Component which has the correct rootKey", () => { + const xmlComponent = new TestComponent("w:test"); const tree = new Formatter().format(xmlComponent); expect(tree).to.deep.equal({ "w:test": {}, }); }); - }); + it("should handle children elements", () => { + const xmlComponent = new TestComponent("w:test"); + xmlComponent.push( + new Attributes({ + val: "test", + }), + ); + xmlComponent.push(new TestComponent("innerTest")); - describe("#prepForXml()", () => { - it("should skip deleted elements", () => { - const child = new TestComponent("w:test1"); - child.delete(); - xmlComponent.addChildElement(child); + const tree = new Formatter().format(xmlComponent); + expect(tree).to.deep.equal({ + "w:test": [ + { + _attr: { + "w:val": "test", + }, + }, + { + innerTest: {}, + }, + ], + }); + }); + it("should hoist attrs if only attrs are present", () => { + const xmlComponent = new TestComponent("w:test"); + xmlComponent.push( + new Attributes({ + val: "test", + }), + ); - // tslint:disable-next-line: no-object-literal-type-assertion - const xml = xmlComponent.prepForXml({} as IContext); - - if (!xml) { - return; - } - - expect(xml["w:test"]).to.deep.equal(EMPTY_OBJECT); + const tree = new Formatter().format(xmlComponent); + expect(tree).to.deep.equal({ + "w:test": { + _attr: { + "w:val": "test", + }, + }, + }); }); }); }); diff --git a/src/file/xml-components/xml-component.ts b/src/file/xml-components/xml-component.ts index a4e77b30a6..f1e76aa797 100644 --- a/src/file/xml-components/xml-component.ts +++ b/src/file/xml-components/xml-component.ts @@ -14,12 +14,6 @@ export abstract class XmlComponent extends BaseXmlComponent { public prepForXml(context: IContext): IXmlableObject | undefined { const children = this.root - .filter((c) => { - if (c instanceof BaseXmlComponent) { - return !c.IsDeleted; - } - return c !== undefined; - }) .map((comp) => { if (comp instanceof BaseXmlComponent) { return comp.prepForXml(context); @@ -29,13 +23,11 @@ export abstract class XmlComponent extends BaseXmlComponent { .filter((comp) => comp !== undefined); // Exclude undefined // If we only have a single IXmlableObject in our children array and it // represents our attributes, use the object itself as our children to - // avoid an unneeded XML close element. (Note: We have to use this - // function to get typescript to allow our check.) + // avoid an unneeded XML close element. // Additionally, if the array is empty, use an empty object as our // children in order to get an empty XML element generated. - const onlyAttrs = (c) => typeof c === "object" && c._attr; return { - [this.rootKey]: children.length ? (children.length === 1 && onlyAttrs(children[0]) ? children[0] : children) : EMPTY_OBJECT, + [this.rootKey]: children.length ? (children.length === 1 && children[0]?._attr ? children[0] : children) : EMPTY_OBJECT, }; } @@ -44,10 +36,6 @@ export abstract class XmlComponent extends BaseXmlComponent { return this; } - - public delete(): void { - this.deleted = true; - } } export abstract class IgnoreIfEmptyXmlComponent extends XmlComponent { diff --git a/src/import-dotx/import-dotx.ts b/src/import-dotx/import-dotx.ts index 69a1ff0bcc..61340bf79e 100644 --- a/src/import-dotx/import-dotx.ts +++ b/src/import-dotx/import-dotx.ts @@ -1,8 +1,7 @@ import * as JSZip from "jszip"; import { Element as XMLElement, ElementCompact as XMLElementCompact, xml2js } from "xml-js"; -import { FooterReferenceType } from "file/document/body/section-properties/footer-reference"; -import { HeaderReferenceType } from "file/document/body/section-properties/header-reference"; +import { HeaderFooterReferenceType } from "file/document/body/section-properties"; import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper"; import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper"; import { Media } from "file/media"; @@ -17,8 +16,8 @@ const schemeToType = { }; interface IDocumentRefs { - readonly headers: { readonly id: number; readonly type: HeaderReferenceType }[]; - readonly footers: { readonly id: number; readonly type: FooterReferenceType }[]; + readonly headers: { readonly id: number; readonly type: HeaderFooterReferenceType }[]; + readonly footers: { readonly id: number; readonly type: HeaderFooterReferenceType }[]; } enum RelationshipType { @@ -219,7 +218,7 @@ export class ImportDotx { throw Error("header referecne element has no attributes"); } return { - type: item._attributes["w:type"] as HeaderReferenceType, + type: item._attributes["w:type"] as HeaderFooterReferenceType, id: this.parseRefId(item._attributes["r:id"] as string), }; }); @@ -239,7 +238,7 @@ export class ImportDotx { throw Error("footer referecne element has no attributes"); } return { - type: item._attributes["w:type"] as FooterReferenceType, + type: item._attributes["w:type"] as HeaderFooterReferenceType, id: this.parseRefId(item._attributes["r:id"] as string), }; });