diff --git a/src/file/table/index.ts b/src/file/table/index.ts index 0e948df9e8..ef3d91a47b 100644 --- a/src/file/table/index.ts +++ b/src/file/table/index.ts @@ -1 +1,2 @@ export * from "./table"; +export * from './table-cell'; \ No newline at end of file diff --git a/src/file/table/table-cell.spec.ts b/src/file/table/table-cell.spec.ts new file mode 100644 index 0000000000..596150d6d5 --- /dev/null +++ b/src/file/table/table-cell.spec.ts @@ -0,0 +1,181 @@ +import { expect } from "chai"; + +import { TableCellBorders, BorderStyle, TableCellWidth, WidthType } from "./table-cell"; +import { Formatter } from "../../export/formatter"; + +describe("TableCellBorders", () => { + describe("#prepForXml", () => { + it("should not add empty borders element if there are no borders defined", () => { + const tb = new TableCellBorders(); + const tree = new Formatter().format(tb); + expect(tree).to.deep.equal(""); + }); + }); + + describe("#addingBorders", () => { + it("should add top border", () => { + const tb = new TableCellBorders(); + tb.addTopBorder(BorderStyle.DOTTED, 1, "FF00FF"); + + const tree = new Formatter().format(tb); + expect(tree).to.deep.equal({ + "w:tcBorders": [ + { + "w:top": [ + { + _attr: { + "w:color": "FF00FF", + "w:sz": 1, + "w:val": "dotted", + }, + }, + ], + }, + ], + }); + }); + + it("should add start(left) border", () => { + const tb = new TableCellBorders(); + tb.addStartBorder(BorderStyle.SINGLE, 2, "FF00FF"); + + const tree = new Formatter().format(tb); + expect(tree).to.deep.equal({ + "w:tcBorders": [ + { + "w:start": [ + { + _attr: { + "w:color": "FF00FF", + "w:sz": 2, + "w:val": "single", + }, + }, + ], + }, + ], + }); + }); + + it("should add bottom border", () => { + const tb = new TableCellBorders(); + tb.addBottomBorder(BorderStyle.DOUBLE, 1, "FF00FF"); + + const tree = new Formatter().format(tb); + expect(tree).to.deep.equal({ + "w:tcBorders": [ + { + "w:bottom": [ + { + _attr: { + "w:color": "FF00FF", + "w:sz": 1, + "w:val": "double", + }, + }, + ], + }, + ], + }); + }); + + it("should add end(right) border", () => { + const tb = new TableCellBorders(); + tb.addEndBorder(BorderStyle.THICK, 3, "FF00FF"); + + const tree = new Formatter().format(tb); + expect(tree).to.deep.equal({ + "w:tcBorders": [ + { + "w:end": [ + { + _attr: { + "w:color": "FF00FF", + "w:sz": 3, + "w:val": "thick", + }, + }, + ], + }, + ], + }); + }); + + 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"); + + const tree = new Formatter().format(tb); + expect(tree).to.deep.equal({ + "w:tcBorders": [ + { + "w:top": [ + { + _attr: { + "w:color": "FF00FF", + "w:sz": 1, + "w:val": "dotted", + }, + }, + ], + }, + { + "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: { + "w:color": "FF00FF", + "w:sz": 2, + "w:val": "single", + }, + }, + ], + }, + ], + }); + }); + }); +}); + +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 + } + } + ] + }); + }); + }); +}); \ No newline at end of file diff --git a/src/file/table/table-cell.ts b/src/file/table/table-cell.ts new file mode 100644 index 0000000000..99282f1125 --- /dev/null +++ b/src/file/table/table-cell.ts @@ -0,0 +1,197 @@ +import { XmlComponent, XmlAttributeComponent, IXmlableObject } from "file/xml-components"; + +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", +} + +interface ICellBorder { + style: BorderStyle; + size: number; + color: string; +} + +class CellBorderAttributes extends XmlAttributeComponent { + protected xmlKeys = { style: "w:val", size: "w:sz", color: "w:color" }; +} + +class BaseTableCellBorder extends XmlComponent { + setProperties(style: BorderStyle, size: number, color: string) { + let attrs = new CellBorderAttributes({ + style: style, + size: size, + color: color, + }); + this.root.push(attrs); + } +} + +export class TableCellBorders extends XmlComponent { + constructor() { + super("w:tcBorders"); + } + + public prepForXml(): IXmlableObject { + return this.root.length > 0 ? super.prepForXml() : ""; + } + + addTopBorder(style: BorderStyle, size: number, color: string) { + const top = new BaseTableCellBorder("w:top"); + top.setProperties(style, size, color); + this.root.push(top); + } + + addStartBorder(style: BorderStyle, size: number, color: string) { + const start = new BaseTableCellBorder("w:start"); + start.setProperties(style, size, color); + this.root.push(start); + } + + addBottomBorder(style: BorderStyle, size: number, color: string) { + const bottom = new BaseTableCellBorder("w:bottom"); + bottom.setProperties(style, size, color); + this.root.push(bottom); + } + + addEndBorder(style: BorderStyle, size: number, color: string) { + const end = new BaseTableCellBorder("w:end"); + end.setProperties(style, size, color); + this.root.push(end); + } +} + +/** + * Attributes fot the GridSpan element. + */ +class GridSpanAttributes extends XmlAttributeComponent<{ val: number }> { + protected xmlKeys = { val: "w:val" }; +} + +/** + * GridSpan element. Should be used in a table cell. Pass the number of columns that this cell need to span. + */ +export class GridSpan extends XmlComponent { + constructor(value: number) { + super("w:gridSpan"); + + this.root.push( + new GridSpanAttributes({ + val: value, + }), + ); + } +} + +/** + * Vertical merge types. + */ +export enum VMergeType { + /** + * Cell that is merged with upper one. + */ + CONTINUE = "continue", + /** + * Cell that is starting the vertical merge. + */ + RESTART = "restart", +} + +class VMergeAttributes extends XmlAttributeComponent<{ val: VMergeType }> { + protected xmlKeys = { val: "w:val" }; +} + +/** + * Vertical merge element. Should be used in a table cell. + */ +export class VMerge extends XmlComponent { + constructor(value: VMergeType) { + super("w:vMerge"); + + this.root.push( + new VMergeAttributes({ + val: value, + }), + ); + } +} + +export enum VerticalAlign { + BOTTOM = "bottom", + CENTER = "center", + TOP = "top", +} + +class VAlignAttributes extends XmlAttributeComponent<{ val: VerticalAlign }> { + protected 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 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<{ type: WidthType; width: string | number }> { + protected 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.ts b/src/file/table/table.ts index aa6e760d27..452ef92549 100644 --- a/src/file/table/table.ts +++ b/src/file/table/table.ts @@ -2,6 +2,7 @@ import { IXmlableObject, XmlComponent } from "file/xml-components"; import { Paragraph } from "../paragraph"; import { TableGrid } from "./grid"; import { TableProperties, WidthTypes } from "./properties"; +import { TableCellBorders, GridSpan, VMerge, VMergeType, VerticalAlign, VAlign, TableCellWidth, WidthType } from "file/table/table-cell"; export class Table extends XmlComponent { private readonly properties: TableProperties; @@ -123,7 +124,29 @@ export class TableCell extends XmlComponent { } export class TableCellProperties extends XmlComponent { + private cellBorder: TableCellBorders; constructor() { super("w:tcPr"); + this.cellBorder = new TableCellBorders(); + this.root.push(this.cellBorder); + } + + get borders() { + return this.cellBorder; + } + addGridSpan(cellSpan: number) { + this.root.push(new GridSpan(cellSpan)); + } + + addVerticalMerge(type: VMergeType) { + this.root.push(new VMerge(type)); + } + + setVerticalAlign(vAlignType: VerticalAlign) { + this.root.push(new VAlign(vAlignType)); + } + + setWidth(width: string | number, type: WidthType) { + this.root.push(new TableCellWidth(width, type)); } }