update table-row-properties, table-cell-properties, table-cell-borders to declarative style

This commit is contained in:
Tom Hunkapiller
2021-05-22 04:03:40 +03:00
parent acc23be297
commit 2611e0c74f
8 changed files with 259 additions and 328 deletions

View File

@ -1,6 +1,12 @@
import { BorderStyle } from "file/styles";
import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "file/xml-components";
interface ITableCellBorderPropertyOptions {
readonly style: BorderStyle;
readonly size: number;
readonly color: string;
}
class CellBorderAttributes extends XmlAttributeComponent<{
readonly style: BorderStyle;
readonly size: number;
@ -10,69 +16,43 @@ class CellBorderAttributes extends XmlAttributeComponent<{
}
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;
constructor(key: string, options: ITableCellBorderPropertyOptions) {
super(key);
this.root.push(new CellBorderAttributes(options));
}
}
export interface ITableCellBorders {
readonly top?: ITableCellBorderPropertyOptions;
readonly start?: ITableCellBorderPropertyOptions;
readonly left?: ITableCellBorderPropertyOptions;
readonly bottom?: ITableCellBorderPropertyOptions;
readonly end?: ITableCellBorderPropertyOptions;
readonly right?: ITableCellBorderPropertyOptions;
}
export class TableCellBorders extends IgnoreIfEmptyXmlComponent {
constructor() {
constructor(options: ITableCellBorders) {
super("w:tcBorders");
if (options.top) {
this.root.push(new BaseTableCellBorder("w:top", options.top));
}
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;
if (options.start) {
this.root.push(new BaseTableCellBorder("w:start", options.start));
}
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;
if (options.left) {
this.root.push(new BaseTableCellBorder("w:left", options.left));
}
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;
if (options.bottom) {
this.root.push(new BaseTableCellBorder("w:bottom", options.bottom));
}
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;
if (options.end) {
this.root.push(new BaseTableCellBorder("w:end", options.end));
}
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;
if (options.right) {
this.root.push(new BaseTableCellBorder("w:right", options.right));
}
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;
}
}
@ -200,7 +180,7 @@ class TableCellWidthAttributes extends XmlAttributeComponent<{ readonly type: Wi
* Table cell width element.
*/
export class TableCellWidth extends XmlComponent {
constructor(value: string | number, type: WidthType) {
constructor(value: string | number, type: WidthType = WidthType.AUTO) {
super("w:tcW");
this.root.push(

View File

@ -9,74 +9,68 @@ 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({
const properties = new TableCellProperties({
shading: {
fill: "test",
color: "000",
},
});
const tree = new Formatter().format(properties);
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": { _attr: { "w:fill": "test", "w:color": "000" } } }] });
});
});
describe("#addMargins", () => {
it("sets shading", () => {
const properties = new TableCellProperties();
properties.addMargins({});
const properties = new TableCellProperties({
margins: {},
});
const tree = new Formatter().format(properties);
expect(tree).to.deep.equal({
"w:tcPr": [
@ -119,17 +113,21 @@ describe("TableCellProperties", () => {
],
});
});
it("should set the TableCellBorders", () => {
const properties = new TableCellProperties({
borders: {
top: {
style: BorderStyle.DASH_DOT_STROKED,
color: "red",
size: 3,
},
},
});
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(properties);
const tree = new Formatter().format(borders);
expect(tree).to.deep.equal({
expect(tree["w:tcPr"][0]).to.deep.equal({
"w:tcBorders": [{ "w:top": { _attr: { "w:val": "dashDotStroked", "w:sz": 3, "w:color": "red" } } }],
});
});

View File

@ -4,6 +4,7 @@ import { ITableShadingAttributesProperties, TableShading } from "../shading";
import { ITableCellMarginOptions, TableCellMargin } from "./cell-margin/table-cell-margins";
import {
GridSpan,
ITableCellBorders,
TableCellBorders,
TableCellWidth,
TDirection,
@ -15,63 +16,58 @@ import {
WidthType,
} from "./table-cell-components";
export interface ITableCellPropertiesOptions {
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?: ITableCellBorders;
}
export class TableCellProperties extends IgnoreIfEmptyXmlComponent {
private readonly cellBorder: TableCellBorders;
constructor() {
constructor(options: ITableCellPropertiesOptions) {
super("w:tcPr");
this.cellBorder = new TableCellBorders();
if (options.width) {
this.root.push(new TableCellWidth(options.width.size, options.width.type));
}
public get Borders(): TableCellBorders {
return this.cellBorder;
if (options.columnSpan) {
this.root.push(new GridSpan(options.columnSpan));
}
public addGridSpan(cellSpan: number): TableCellProperties {
this.root.push(new GridSpan(cellSpan));
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));
return this;
if (options.borders) {
this.root.push(new TableCellBorders(options.borders));
}
public setVerticalAlign(type: VerticalAlign): TableCellProperties {
this.root.push(new VAlign(type));
return this;
if (options.shading) {
this.root.push(new TableShading(options.shading));
}
public setWidth(width: string | number, type: WidthType = WidthType.AUTO): TableCellProperties {
this.root.push(new TableCellWidth(width, type));
return this;
if (options.margins) {
this.root.push(new TableCellMargin(options.margins));
}
public setShading(attrs: ITableShadingAttributesProperties): TableCellProperties {
this.root.push(new TableShading(attrs));
return this;
if (options.textDirection) {
this.root.push(new TDirection(options.textDirection));
}
public addMargins(options: ITableCellMarginOptions): TableCellProperties {
this.root.push(new TableCellMargin(options));
return this;
if (options.verticalAlign) {
this.root.push(new VAlign(options.verticalAlign));
}
public setTextDirection(type: TextDirection): TableCellProperties {
this.root.push(new TDirection(type));
return this;
}
public addBorders(): TableCellProperties {
this.root.push(this.cellBorder);
return this;
}
}

View File

@ -10,15 +10,20 @@ import { TableCellBorders, TableCellWidth, TextDirection, VerticalAlign, Vertica
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 +42,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 +67,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 +92,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 +106,7 @@ describe("TableCellBorders", () => {
{
"w:end": {
_attr: {
"w:color": "FF00FF",
"w:color": "FF0000",
"w:sz": 3,
"w:val": "thick",
},
@ -97,8 +117,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 +142,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 +167,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 +212,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 +230,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: {

View File

@ -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 {

View File

@ -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 } } }] });
});
});
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 } } }] });
});
});
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" } } }] });
});

View File

@ -3,27 +3,30 @@ import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "
import { HeightRule, TableRowHeight } from "./table-row-height";
export interface ITableRowPropertiesOptions {
readonly cantSplit?: boolean;
readonly tableHeader?: boolean;
readonly height?: {
readonly value: number;
readonly rule: HeightRule;
};
}
export class TableRowProperties extends IgnoreIfEmptyXmlComponent {
constructor() {
constructor(options: ITableRowPropertiesOptions) {
super("w:trPr");
}
public setCantSplit(): TableRowProperties {
if (options.cantSplit) {
this.root.push(new CantSplit());
return this;
}
public setTableHeader(): TableRowProperties {
if (options.tableHeader) {
this.root.push(new TableHeader());
return this;
}
public setHeight(value: number, rule: HeightRule): TableRowProperties {
this.root.push(new TableRowHeight(value, rule));
return this;
if (options.height) {
this.root.push(new TableRowHeight(options.height.value, options.height.rule));
}
}
}

View File

@ -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 {