Merge pull request #795 from dolanmiu/feat/table-work

Visual Right to Left Table
This commit is contained in:
Dolan
2021-03-04 02:17:50 +00:00
committed by GitHub
12 changed files with 297 additions and 129 deletions

8
.nycrc
View File

@ -1,9 +1,9 @@
{
"check-coverage": true,
"lines": 96.81,
"functions": 93.80,
"branches": 92.63,
"statements": 96.80,
"lines": 97.77,
"functions": 93.89,
"branches": 94.55,
"statements": 97.75,
"include": [
"src/**/*.ts"
],

View File

@ -1,7 +1,7 @@
// This demo shows right to left for special languages
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, Paragraph, TextRun } from "../build";
import { Document, Packer, Paragraph, Table, TableCell, TableRow, TextRun } from "../build";
const doc = new Document();
@ -36,6 +36,31 @@ doc.addSection({
}),
],
}),
new Table({
visuallyRightToLeft: true,
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("שלום עולם")],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("שלום עולם")],
}),
],
}),
],
}),
],
});

View File

@ -326,6 +326,17 @@ const cell = new TableCell({
});
```
### Visual Right to Left Table
It is possible to reverse how the cells of the table are displayed. The table direction. More info here: https://superuser.com/questions/996912/how-to-change-a-table-direction-in-microsoft-word
```ts
const table = new Table({
visuallyRightToLeft: true,
});
```
## Examples
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/4-basic-table.ts ':include')

View File

@ -0,0 +1,15 @@
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": {},
});
});
});

View File

@ -8,22 +8,31 @@ import { TableCellMargin } 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({});
expect(() => new Formatter().format(cellMargin)).to.throw();
});
});
describe("#addTopMargin", () => {
it("should add a table cell top margin", () => {
const cellMargin = new TableCellMargin();
cellMargin.addTopMargin(1234, WidthType.DXA);
const cellMargin = new TableCellMargin({
top: {
value: 1234,
type: WidthType.DXA,
},
});
const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:top": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] });
});
it("should add a table cell top margin using default width type", () => {
const cellMargin = new TableCellMargin();
cellMargin.addTopMargin(1234);
const cellMargin = new TableCellMargin({
top: {
value: 1234,
},
});
const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:top": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] });
});
@ -31,15 +40,22 @@ describe("TableCellMargin", () => {
describe("#addLeftMargin", () => {
it("should add a table cell left margin", () => {
const cellMargin = new TableCellMargin();
cellMargin.addLeftMargin(1234, WidthType.DXA);
const cellMargin = new TableCellMargin({
left: {
value: 1234,
type: WidthType.DXA,
},
});
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();
cellMargin.addLeftMargin(1234);
const cellMargin = new TableCellMargin({
left: {
value: 1234,
},
});
const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:left": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] });
});
@ -47,15 +63,24 @@ describe("TableCellMargin", () => {
describe("#addBottomMargin", () => {
it("should add a table cell bottom margin", () => {
const cellMargin = new TableCellMargin();
cellMargin.addBottomMargin(1234, WidthType.DXA);
const cellMargin = new TableCellMargin({
bottom: {
value: 1234,
type: WidthType.DXA,
},
});
const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:bottom": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] });
});
it("should add a table cell bottom margin using default width type", () => {
const cellMargin = new TableCellMargin();
cellMargin.addBottomMargin(1234);
const cellMargin = new TableCellMargin({
bottom: {
value: 1234,
},
});
const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:bottom": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] });
});
@ -63,15 +88,24 @@ describe("TableCellMargin", () => {
describe("#addRightMargin", () => {
it("should add a table cell right margin", () => {
const cellMargin = new TableCellMargin();
cellMargin.addRightMargin(1234, WidthType.DXA);
const cellMargin = new TableCellMargin({
right: {
value: 1234,
type: WidthType.DXA,
},
});
const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:right": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] });
});
it("should add a table cell right margin using default width type", () => {
const cellMargin = new TableCellMargin();
cellMargin.addRightMargin(1234);
const cellMargin = new TableCellMargin({
right: {
value: 1234,
},
});
const tree = new Formatter().format(cellMargin);
expect(tree).to.deep.equal({ "w:tblCellMar": [{ "w:right": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] });
});

View File

@ -6,7 +6,23 @@ class TableCellMarginAttributes extends XmlAttributeComponent<{ readonly type: W
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,
}),
);
}
public setProperties(value: number, type: WidthType = WidthType.DXA): void {
this.root.push(
new TableCellMarginAttributes({
@ -17,36 +33,31 @@ class BaseTableCellMargin extends XmlComponent {
}
}
export interface ITableCellMarginOptions {
readonly top?: IBaseTableCellMarginOptions;
readonly bottom?: IBaseTableCellMarginOptions;
readonly left?: IBaseTableCellMarginOptions;
readonly right?: IBaseTableCellMarginOptions;
}
export class TableCellMargin extends IgnoreIfEmptyXmlComponent {
constructor() {
constructor(options: ITableCellMarginOptions) {
super("w:tblCellMar");
}
public addTopMargin(value: number, type: WidthType = WidthType.DXA): void {
const top = new BaseTableCellMargin("w:top");
if (options.bottom) {
this.root.push(new BaseTableCellMargin("w:bottom", options.bottom));
}
top.setProperties(value, type);
this.root.push(top);
}
if (options.top) {
this.root.push(new BaseTableCellMargin("w:top", options.top));
}
public addLeftMargin(value: number, type: WidthType = WidthType.DXA): void {
const left = new BaseTableCellMargin("w:left");
if (options.left) {
this.root.push(new BaseTableCellMargin("w:left", options.left));
}
left.setProperties(value, type);
this.root.push(left);
}
public addBottomMargin(value: number, type: WidthType = WidthType.DXA): void {
const bottom = new BaseTableCellMargin("w:bottom");
bottom.setProperties(value, type);
this.root.push(bottom);
}
public addRightMargin(value: number, type: WidthType = WidthType.DXA): void {
const right = new BaseTableCellMargin("w:right");
right.setProperties(value, type);
this.root.push(right);
if (options.right) {
this.root.push(new BaseTableCellMargin("w:right", options.right));
}
}
}

View File

@ -11,7 +11,7 @@ import { TableProperties } from "./table-properties";
describe("TableProperties", () => {
describe("#constructor", () => {
it("creates an initially empty property object", () => {
const tp = new TableProperties();
const tp = new TableProperties({});
// The TableProperties 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
@ -22,7 +22,12 @@ describe("TableProperties", () => {
describe("#setWidth", () => {
it("should add a table width property", () => {
const tp = new TableProperties().setWidth(1234, WidthType.DXA);
const tp = new TableProperties({
width: {
size: 1234,
type: WidthType.DXA,
},
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [{ "w:tblW": { _attr: { "w:type": "dxa", "w:w": 1234 } } }],
@ -30,7 +35,12 @@ describe("TableProperties", () => {
});
it("should add a table width property with default of AUTO", () => {
const tp = new TableProperties().setWidth(1234);
const tp = new TableProperties({
width: {
size: 1234,
},
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [{ "w:tblW": { _attr: { "w:type": "auto", "w:w": 1234 } } }],
@ -40,8 +50,10 @@ describe("TableProperties", () => {
describe("#setLayout", () => {
it("sets the table to fixed width layout", () => {
const tp = new TableProperties();
tp.setLayout(TableLayoutType.FIXED);
const tp = new TableProperties({
layout: TableLayoutType.FIXED,
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [{ "w:tblLayout": { _attr: { "w:type": "fixed" } } }],
@ -51,8 +63,15 @@ describe("TableProperties", () => {
describe("#cellMargin", () => {
it("adds a table cell top margin", () => {
const tp = new TableProperties();
tp.CellMargin.addTopMargin(1234, WidthType.DXA);
const tp = new TableProperties({
cellMargin: {
top: {
value: 1234,
type: WidthType.DXA,
},
},
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [{ "w:tblCellMar": [{ "w:top": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }],
@ -60,8 +79,15 @@ describe("TableProperties", () => {
});
it("adds a table cell left margin", () => {
const tp = new TableProperties();
tp.CellMargin.addLeftMargin(1234, WidthType.DXA);
const tp = new TableProperties({
cellMargin: {
left: {
value: 1234,
type: WidthType.DXA,
},
},
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [{ "w:tblCellMar": [{ "w:left": { _attr: { "w:type": "dxa", "w:w": 1234 } } }] }],
@ -71,12 +97,14 @@ describe("TableProperties", () => {
describe("#setShading", () => {
it("sets the shading of the table", () => {
const tp = new TableProperties();
tp.setShading({
fill: "b79c2f",
val: ShadingType.REVERSE_DIAGONAL_STRIPE,
color: "auto",
const tp = new TableProperties({
shading: {
fill: "b79c2f",
val: ShadingType.REVERSE_DIAGONAL_STRIPE,
color: "auto",
},
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [
@ -95,9 +123,10 @@ describe("TableProperties", () => {
});
describe("#setAlignment", () => {
it("sets the shading of the table", () => {
const tp = new TableProperties();
tp.setAlignment(AlignmentType.CENTER);
it("sets the alignment of the table", () => {
const tp = new TableProperties({
alignment: AlignmentType.CENTER,
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [
@ -112,4 +141,20 @@ describe("TableProperties", () => {
});
});
});
describe("#Set Virtual Right to Left", () => {
it("sets the alignment of the table", () => {
const tp = new TableProperties({
visuallyRightToLeft: true,
});
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [
{
"w:bidiVisual": {},
},
],
});
});
});
});

View File

@ -5,51 +5,58 @@ import { Alignment, AlignmentType } from "../../paragraph";
import { ITableShadingAttributesProperties, TableShading } from "../shading";
import { WidthType } from "../table-cell";
import { ITableBordersOptions, TableBorders } from "./table-borders";
import { TableCellMargin } from "./table-cell-margin";
import { ITableCellMarginOptions, TableCellMargin } from "./table-cell-margin";
import { ITableFloatOptions, TableFloatProperties } from "./table-float-properties";
import { TableLayout, TableLayoutType } from "./table-layout";
import { PreferredTableWidth } from "./table-width";
import { VisuallyRightToLeft } from "./visually-right-to-left";
export interface ITablePropertiesOptions {
readonly width?: {
readonly size: number;
readonly type?: WidthType;
};
readonly layout?: TableLayoutType;
readonly borders?: ITableBordersOptions;
readonly float?: ITableFloatOptions;
readonly shading?: ITableShadingAttributesProperties;
readonly alignment?: AlignmentType;
readonly cellMargin?: ITableCellMarginOptions;
readonly visuallyRightToLeft?: boolean;
}
export class TableProperties extends IgnoreIfEmptyXmlComponent {
private readonly cellMargin: TableCellMargin;
constructor() {
constructor(options: ITablePropertiesOptions) {
super("w:tblPr");
this.cellMargin = new TableCellMargin();
this.root.push(this.cellMargin);
}
this.root.push(new TableCellMargin(options.cellMargin || {}));
public setWidth(width: number, type: WidthType = WidthType.AUTO): TableProperties {
this.root.push(new PreferredTableWidth(type, width));
return this;
}
if (options.borders) {
this.root.push(new TableBorders(options.borders));
}
public setLayout(type: TableLayoutType): void {
this.root.push(new TableLayout(type));
}
if (options.width) {
this.root.push(new PreferredTableWidth(options.width.type, options.width.size));
}
public setBorder(borderOptions: ITableBordersOptions): TableProperties {
this.root.push(new TableBorders(borderOptions));
return this;
}
if (options.float) {
this.root.push(new TableFloatProperties(options.float));
}
public get CellMargin(): TableCellMargin {
return this.cellMargin;
}
if (options.layout) {
this.root.push(new TableLayout(options.layout));
}
public setTableFloatProperties(tableFloatOptions: ITableFloatOptions): TableProperties {
this.root.push(new TableFloatProperties(tableFloatOptions));
return this;
}
if (options.alignment) {
this.root.push(new Alignment(options.alignment));
}
public setShading(attrs: ITableShadingAttributesProperties): TableProperties {
this.root.push(new TableShading(attrs));
if (options.shading) {
this.root.push(new TableShading(options.shading));
}
return this;
}
public setAlignment(type: AlignmentType): void {
this.root.push(new Alignment(type));
if (options.visuallyRightToLeft) {
this.root.push(new VisuallyRightToLeft());
}
}
}

View File

@ -13,7 +13,7 @@ class TableWidthAttributes extends XmlAttributeComponent<ITableWidth> {
}
export class PreferredTableWidth extends XmlComponent {
constructor(type: WidthType, w: number) {
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 }));

View File

@ -0,0 +1,14 @@
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": {},
});
});
});

View File

@ -0,0 +1,8 @@
// 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");
}
}

View File

@ -36,11 +36,10 @@ export interface ITableOptions {
readonly layout?: TableLayoutType;
readonly borders?: ITableBordersOptions;
readonly alignment?: AlignmentType;
readonly visuallyRightToLeft?: boolean;
}
export class Table extends XmlComponent {
private readonly properties: TableProperties;
constructor({
rows,
width,
@ -50,27 +49,38 @@ export class Table extends XmlComponent {
layout,
borders,
alignment,
visuallyRightToLeft,
}: ITableOptions) {
super("w:tbl");
this.properties = new TableProperties();
this.root.push(this.properties);
if (borders) {
this.properties.setBorder(borders);
} else {
this.properties.setBorder({});
}
if (width) {
this.properties.setWidth(width.size, width.type);
} else {
this.properties.setWidth(100);
}
this.properties.CellMargin.addBottomMargin(bottom || 0, marginUnitType);
this.properties.CellMargin.addTopMargin(top || 0, marginUnitType);
this.properties.CellMargin.addLeftMargin(left || 0, marginUnitType);
this.properties.CellMargin.addRightMargin(right || 0, marginUnitType);
this.root.push(
new TableProperties({
borders: borders ?? {},
width: width ?? { size: 100 },
float,
layout,
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,
},
},
visuallyRightToLeft,
}),
);
this.root.push(new TableGrid(columnWidths));
@ -101,17 +111,5 @@ export class Table extends XmlComponent {
columnIndex += cell.options.columnSpan || 1;
});
});
if (float) {
this.properties.setTableFloatProperties(float);
}
if (layout) {
this.properties.setLayout(layout);
}
if (alignment) {
this.properties.setAlignment(alignment);
}
}
}