From 59af4b4f81d5ab9753082c1bcdb1ab6219ced1e7 Mon Sep 17 00:00:00 2001 From: Nico Kunz Date: Mon, 8 Dec 2025 16:25:27 +0000 Subject: [PATCH] Added the table look parameter to new Table class to enable controlling various conditional formatting table properties (first row, first column etc) --- demo/97-table-look.ts | 112 ++++++++++++ docs/usage/tables.md | 31 ++++ src/file/table/table-properties/index.ts | 1 + .../table/table-properties/table-look.spec.ts | 159 ++++++++++++++++++ src/file/table/table-properties/table-look.ts | 37 ++++ .../table-properties/table-properties.spec.ts | 56 ++++++ .../table-properties/table-properties.ts | 6 + src/file/table/table.ts | 4 + 8 files changed, 406 insertions(+) create mode 100644 demo/97-table-look.ts create mode 100644 src/file/table/table-properties/table-look.spec.ts create mode 100644 src/file/table/table-properties/table-look.ts diff --git a/demo/97-table-look.ts b/demo/97-table-look.ts new file mode 100644 index 00000000000..93e1da07361 --- /dev/null +++ b/demo/97-table-look.ts @@ -0,0 +1,112 @@ +// Example of using tableLook to control conditional table formatting +import { Document, Packer, Paragraph, Table, TableCell, TableRow, TextRun, WidthType } from "docx"; +import * as fs from "fs"; + +const styles = fs.readFileSync("./demo/assets/custom-styles.xml", "utf-8"); + +const doc = new Document({ + externalStyles: styles, + sections: [ + { + children: [ + new Paragraph({ + children: [new TextRun({ text: "Table 1: Table Look Default Values", bold: true })], + }), + new Paragraph({ text: "" }), + new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Header 1")] }), + new TableCell({ children: [new Paragraph("Header 2")] }), + new TableCell({ children: [new Paragraph("Header 3")] }), + ], + }), + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Row 1, Col 1")] }), + new TableCell({ children: [new Paragraph("Row 1, Col 2")] }), + new TableCell({ children: [new Paragraph("Row 1, Col 3")] }), + ], + }), + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Row 2, Col 1")] }), + new TableCell({ children: [new Paragraph("Row 2, Col 2")] }), + new TableCell({ children: [new Paragraph("Row 2, Col 3")] }), + ], + }), + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Row 3, Col 1")] }), + new TableCell({ children: [new Paragraph("Row 3, Col 2")] }), + new TableCell({ children: [new Paragraph("Row 3, Col 3")] }), + ], + }), + ], + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + style: "MyCustomTableStyle", + }), + new Paragraph({ text: "" }), + new Paragraph({ text: "" }), + new Paragraph({ + children: [new TextRun({ text: "Table 2: Table Look All Look Values Enabled", bold: true })], + }), + new Paragraph({ text: "" }), + new Table({ + tableLook: { + firstRow: true, + lastRow: true, + firstColumn: true, + lastColumn: true, + noHBand: false, + noVBand: false, + }, + rows: [ + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Header 1")] }), + new TableCell({ children: [new Paragraph("Header 2")] }), + new TableCell({ children: [new Paragraph("Header 3")] }), + ], + }), + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Row 1, Col 1")] }), + new TableCell({ children: [new Paragraph("Row 1, Col 2")] }), + new TableCell({ children: [new Paragraph("Row 1, Col 3")] }), + ], + }), + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Row 2, Col 1")] }), + new TableCell({ children: [new Paragraph("Row 2, Col 2")] }), + new TableCell({ children: [new Paragraph("Row 2, Col 3")] }), + ], + }), + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph("Row 3, Col 1")] }), + new TableCell({ children: [new Paragraph("Row 3, Col 2")] }), + new TableCell({ children: [new Paragraph("Row 3, Col 3")] }), + ], + }), + ], + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + style: "MyCustomTableStyle", + }), + ], + }, + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("97-table-look.docx", buffer); + console.log("Document created successfully at 97-table-look.docx"); +}); diff --git a/docs/usage/tables.md b/docs/usage/tables.md index 6455dd25497..8cfb69ac40f 100644 --- a/docs/usage/tables.md +++ b/docs/usage/tables.md @@ -344,6 +344,37 @@ const table = new Table({ }); ``` +### Table Look (Conditional Formatting) + +Control which conditional formatting from a table style is applied. Table styles can define special formatting for the first row, first column, etc. Use `tableLook` to toggle these formatting options. + +```ts +const table = new Table({ + style: "GridTable5Dark-Accent3", + tableLook: { + firstRow: true, // Apply first row formatting + lastRow: false, // Don't apply last row formatting + firstColumn: true, // Apply first column formatting + lastColumn: false, // Don't apply last column formatting + noHBand: false, // Apply horizontal banding (row stripes) + noVBand: true, // Don't apply vertical banding (column stripes) + }, +}); +``` + +**Note**: When `tableLook` is not specified at all, Word applies row and column banding by default, but does not apply first/last row/column formatting. + +#### Options + +| Property | Type | Description | +| ------------ | --------- | -------------------------------------------- | +| firstRow | `boolean` | Apply special formatting to first row | +| lastRow | `boolean` | Apply special formatting to last row | +| firstColumn | `boolean` | Apply special formatting to first column | +| lastColumn | `boolean` | Apply special formatting to last column | +| noHBand | `boolean` | Disable horizontal banding (row stripes) | +| noVBand | `boolean` | Disable vertical banding (column stripes) | + ## Examples [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/4-basic-table.ts ':include') diff --git a/src/file/table/table-properties/index.ts b/src/file/table/table-properties/index.ts index aefcc9e0aa3..d9e48269d5c 100644 --- a/src/file/table/table-properties/index.ts +++ b/src/file/table/table-properties/index.ts @@ -2,3 +2,4 @@ export * from "./table-properties"; export * from "./table-float-properties"; export * from "./table-layout"; export * from "./table-borders"; +export * from "./table-look"; diff --git a/src/file/table/table-properties/table-look.spec.ts b/src/file/table/table-properties/table-look.spec.ts new file mode 100644 index 00000000000..15f312a5ea8 --- /dev/null +++ b/src/file/table/table-properties/table-look.spec.ts @@ -0,0 +1,159 @@ +import { describe, expect, it } from "vitest"; + +import { Formatter } from "@export/formatter"; + +import { TableLook } from "./table-look"; + +describe("TableLook", () => { + describe("#constructor", () => { + it("should create table look with firstRow enabled", () => { + const tableLook = new TableLook({ + firstRow: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:firstRow": true, + }, + }, + }); + }); + + it("should create table look with lastRow enabled", () => { + const tableLook = new TableLook({ + lastRow: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:lastRow": true, + }, + }, + }); + }); + + it("should create table look with firstColumn enabled", () => { + const tableLook = new TableLook({ + firstColumn: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:firstColumn": true, + }, + }, + }); + }); + + it("should create table look with lastColumn enabled", () => { + const tableLook = new TableLook({ + lastColumn: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:lastColumn": true, + }, + }, + }); + }); + + it("should create table look with noHBand enabled", () => { + const tableLook = new TableLook({ + noHBand: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:noHBand": true, + }, + }, + }); + }); + + it("should create table look with noVBand enabled", () => { + const tableLook = new TableLook({ + noVBand: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:noVBand": true, + }, + }, + }); + }); + + it("should create table look with firstRow set to false", () => { + const tableLook = new TableLook({ + firstRow: false, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:firstRow": false, + }, + }, + }); + }); + + it("should create table look with multiple attributes", () => { + const tableLook = new TableLook({ + firstRow: true, + firstColumn: true, + noVBand: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:firstRow": true, + "w:firstColumn": true, + "w:noVBand": true, + }, + }, + }); + }); + + it("should create table look with all attributes", () => { + const tableLook = new TableLook({ + firstRow: true, + lastRow: false, + firstColumn: true, + lastColumn: false, + noHBand: false, + noVBand: true, + }); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: { + "w:firstRow": true, + "w:lastRow": false, + "w:firstColumn": true, + "w:lastColumn": false, + "w:noHBand": false, + "w:noVBand": true, + }, + }, + }); + }); + + it("should create table look with empty options", () => { + const tableLook = new TableLook({}); + const tree = new Formatter().format(tableLook); + expect(tree).to.deep.equal({ + "w:tblLook": { + _attr: {}, + }, + }); + }); + }); +}); diff --git a/src/file/table/table-properties/table-look.ts b/src/file/table/table-properties/table-look.ts new file mode 100644 index 00000000000..2dd0bb94159 --- /dev/null +++ b/src/file/table/table-properties/table-look.ts @@ -0,0 +1,37 @@ +// http://officeopenxml.com/WPtblLook.php +// +// +// +// +// +// +// +// +import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; + +export type ITableLookOptions = { + readonly firstRow?: boolean; + readonly lastRow?: boolean; + readonly firstColumn?: boolean; + readonly lastColumn?: boolean; + readonly noHBand?: boolean; + readonly noVBand?: boolean; +}; + +class TableLookAttributes extends XmlAttributeComponent { + protected readonly xmlKeys = { + firstRow: "w:firstRow", + lastRow: "w:lastRow", + firstColumn: "w:firstColumn", + lastColumn: "w:lastColumn", + noHBand: "w:noHBand", + noVBand: "w:noVBand", + }; +} + +export class TableLook extends XmlComponent { + public constructor(options: ITableLookOptions) { + super("w:tblLook"); + this.root.push(new TableLookAttributes(options)); + } +} diff --git a/src/file/table/table-properties/table-properties.spec.ts b/src/file/table/table-properties/table-properties.spec.ts index 02da55821e1..c841e34c211 100644 --- a/src/file/table/table-properties/table-properties.spec.ts +++ b/src/file/table/table-properties/table-properties.spec.ts @@ -199,4 +199,60 @@ describe("TableProperties", () => { }); }); }); + + describe("#tableLook", () => { + it("adds table look with first row and first column enabled", () => { + const tp = new TableProperties({ + tableLook: { + firstRow: true, + firstColumn: true, + noVBand: true, + }, + }); + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({ + "w:tblPr": [ + { + "w:tblLook": { + _attr: { + "w:firstRow": true, + "w:firstColumn": true, + "w:noVBand": true, + }, + }, + }, + ], + }); + }); + + it("adds table look with all attributes", () => { + const tp = new TableProperties({ + tableLook: { + firstRow: true, + lastRow: false, + firstColumn: true, + lastColumn: false, + noHBand: false, + noVBand: true, + }, + }); + const tree = new Formatter().format(tp); + expect(tree).to.deep.equal({ + "w:tblPr": [ + { + "w:tblLook": { + _attr: { + "w:firstRow": true, + "w:lastRow": false, + "w:firstColumn": true, + "w:lastColumn": false, + "w:noHBand": false, + "w:noVBand": true, + }, + }, + }, + ], + }); + }); + }); }); diff --git a/src/file/table/table-properties/table-properties.ts b/src/file/table/table-properties/table-properties.ts index b0946a1d60b..2329a5580be 100644 --- a/src/file/table/table-properties/table-properties.ts +++ b/src/file/table/table-properties/table-properties.ts @@ -31,6 +31,7 @@ import { ITableCellMarginOptions, TableCellMargin, TableCellMarginElementType } import { ITableFloatOptions, TableFloatProperties } from "./table-float-properties"; import { TableLayout, TableLayoutType } from "./table-layout"; import { ITableCellSpacingProperties, TableCellSpacingElement } from "../table-cell-spacing"; +import { ITableLookOptions, TableLook } from "./table-look"; export type ITablePropertiesOptions = { readonly width?: ITableWidthProperties; @@ -43,6 +44,7 @@ export type ITablePropertiesOptions = { readonly alignment?: (typeof AlignmentType)[keyof typeof AlignmentType]; readonly cellMargin?: ITableCellMarginOptions; readonly visuallyRightToLeft?: boolean; + readonly tableLook?: ITableLookOptions; readonly cellSpacing?: ITableCellSpacingProperties; }; @@ -90,6 +92,10 @@ export class TableProperties extends IgnoreIfEmptyXmlComponent { this.root.push(new TableCellMargin(TableCellMarginElementType.TABLE, options.cellMargin)); } + if (options.tableLook) { + this.root.push(new TableLook(options.tableLook)); + } + if (options.cellSpacing) { this.root.push(new TableCellSpacingElement(options.cellSpacing)); } diff --git a/src/file/table/table.ts b/src/file/table/table.ts index 37d4bcfbcac..3860a74d02c 100644 --- a/src/file/table/table.ts +++ b/src/file/table/table.ts @@ -8,6 +8,7 @@ import { ITableCellSpacingProperties } from "./table-cell-spacing"; import { ITableBordersOptions, ITableFloatOptions, TableProperties } from "./table-properties"; import { ITableCellMarginOptions } from "./table-properties/table-cell-margin"; import { TableLayoutType } from "./table-properties/table-layout"; +import { ITableLookOptions } from "./table-properties/table-look"; import { TableRow } from "./table-row"; import { ITableWidthProperties } from "./table-width"; @@ -33,6 +34,7 @@ export type ITableOptions = { readonly borders?: ITableBordersOptions; readonly alignment?: (typeof AlignmentType)[keyof typeof AlignmentType]; readonly visuallyRightToLeft?: boolean; + readonly tableLook?: ITableLookOptions; readonly cellSpacing?: ITableCellSpacingProperties; }; @@ -50,6 +52,7 @@ export class Table extends FileChild { borders, alignment, visuallyRightToLeft, + tableLook, cellSpacing, }: ITableOptions) { super("w:tbl"); @@ -65,6 +68,7 @@ export class Table extends FileChild { alignment, cellMargin: margins, visuallyRightToLeft, + tableLook, cellSpacing, }), );