Merge branch 'master' into add-table-option-styleId
# Conflicts: # src/file/table/table.ts
This commit is contained in:
@ -158,6 +158,31 @@ export class VAlign extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export enum TextDirection {
|
||||
BOTTOM_TO_TOP_LEFT_TO_RIGHT = "btLr",
|
||||
LEFT_TO_RIGHT_TOP_TO_BOTTOM = "lrTb",
|
||||
TOP_TO_BOTTOM_RIGHT_TO_LEFT = "tbRl",
|
||||
}
|
||||
|
||||
class TDirectionAttributes extends XmlAttributeComponent<{ readonly val: TextDirection }> {
|
||||
protected readonly xmlKeys = { val: "w:val" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Text Direction within a table cell
|
||||
*/
|
||||
export class TDirection extends XmlComponent {
|
||||
constructor(value: TextDirection) {
|
||||
super("w:textDirection");
|
||||
|
||||
this.root.push(
|
||||
new TDirectionAttributes({
|
||||
val: value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export enum WidthType {
|
||||
/** Auto. */
|
||||
AUTO = "auto",
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
GridSpan,
|
||||
TableCellBorders,
|
||||
TableCellWidth,
|
||||
TDirection,
|
||||
TextDirection,
|
||||
VAlign,
|
||||
VerticalAlign,
|
||||
VerticalMerge,
|
||||
@ -61,4 +63,10 @@ export class TableCellProperties extends IgnoreIfEmptyXmlComponent {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setTextDirection(type: TextDirection): TableCellProperties {
|
||||
this.root.push(new TDirection(type));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { BorderStyle } from "file/styles";
|
||||
|
||||
import { ShadingType } from "../shading";
|
||||
import { TableCell } from "./table-cell";
|
||||
import { TableCellBorders, TableCellWidth, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components";
|
||||
import { TableCellBorders, TableCellWidth, TextDirection, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components";
|
||||
|
||||
describe("TableCellBorders", () => {
|
||||
describe("#prepForXml", () => {
|
||||
@ -271,6 +271,34 @@ describe("TableCell", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with text direction", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
textDirection: TextDirection.BOTTOM_TO_TOP_LEFT_TO_RIGHT,
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(cell);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tc": [
|
||||
{
|
||||
"w:tcPr": [
|
||||
{
|
||||
"w:textDirection": {
|
||||
_attr: {
|
||||
"w:val": "btLr",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:p": {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with vertical merge", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
@ -395,6 +423,33 @@ describe("TableCell", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with width", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
width: { size: 100, type: WidthType.DXA },
|
||||
});
|
||||
const tree = new Formatter().format(cell);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tc": [
|
||||
{
|
||||
"w:tcPr": [
|
||||
{
|
||||
"w:tcW": {
|
||||
_attr: {
|
||||
"w:type": "dxa",
|
||||
"w:w": 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:p": {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with column span", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
|
@ -1,4 +1,5 @@
|
||||
// http://officeopenxml.com/WPtableGrid.php
|
||||
import { IViewWrapper } from "file/document-wrapper";
|
||||
import { Paragraph } from "file/paragraph";
|
||||
import { BorderStyle } from "file/styles";
|
||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
@ -6,14 +7,19 @@ import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
import { ITableShadingAttributesProperties } from "../shading";
|
||||
import { Table } from "../table";
|
||||
import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins";
|
||||
import { VerticalAlign, VerticalMergeType } from "./table-cell-components";
|
||||
import { TextDirection, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components";
|
||||
import { 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?: {
|
||||
@ -38,75 +44,72 @@ export interface ITableCellOptions {
|
||||
readonly color: string;
|
||||
};
|
||||
};
|
||||
readonly children: Array<Paragraph | Table>;
|
||||
readonly children: (Paragraph | Table)[];
|
||||
}
|
||||
|
||||
export class TableCell extends XmlComponent {
|
||||
private readonly properties: TableCellProperties;
|
||||
|
||||
constructor(readonly options: ITableCellOptions) {
|
||||
super("w:tc");
|
||||
|
||||
this.properties = new TableCellProperties();
|
||||
this.root.push(this.properties);
|
||||
const properties = new TableCellProperties();
|
||||
this.root.push(properties);
|
||||
|
||||
for (const child of options.children) {
|
||||
this.root.push(child);
|
||||
}
|
||||
|
||||
if (options.verticalAlign) {
|
||||
this.properties.setVerticalAlign(options.verticalAlign);
|
||||
properties.setVerticalAlign(options.verticalAlign);
|
||||
}
|
||||
|
||||
if (options.textDirection) {
|
||||
properties.setTextDirection(options.textDirection);
|
||||
}
|
||||
|
||||
if (options.verticalMerge) {
|
||||
this.properties.addVerticalMerge(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.margins) {
|
||||
this.properties.addMargins(options.margins);
|
||||
properties.addMargins(options.margins);
|
||||
}
|
||||
|
||||
if (options.shading) {
|
||||
this.properties.setShading(options.shading);
|
||||
properties.setShading(options.shading);
|
||||
}
|
||||
|
||||
if (options.columnSpan) {
|
||||
this.properties.addGridSpan(options.columnSpan);
|
||||
properties.addGridSpan(options.columnSpan);
|
||||
}
|
||||
|
||||
if (options.rowSpan && options.rowSpan > 1) {
|
||||
this.properties.addVerticalMerge(VerticalMergeType.RESTART);
|
||||
if (options.width) {
|
||||
properties.setWidth(options.width.size, options.width.type);
|
||||
}
|
||||
|
||||
if (options.borders) {
|
||||
if (options.borders.top) {
|
||||
this.properties.Borders.addTopBorder(options.borders.top.style, options.borders.top.size, options.borders.top.color);
|
||||
properties.Borders.addTopBorder(options.borders.top.style, options.borders.top.size, options.borders.top.color);
|
||||
}
|
||||
if (options.borders.bottom) {
|
||||
this.properties.Borders.addBottomBorder(
|
||||
options.borders.bottom.style,
|
||||
options.borders.bottom.size,
|
||||
options.borders.bottom.color,
|
||||
);
|
||||
properties.Borders.addBottomBorder(options.borders.bottom.style, options.borders.bottom.size, options.borders.bottom.color);
|
||||
}
|
||||
if (options.borders.left) {
|
||||
this.properties.Borders.addLeftBorder(options.borders.left.style, options.borders.left.size, options.borders.left.color);
|
||||
properties.Borders.addLeftBorder(options.borders.left.style, options.borders.left.size, options.borders.left.color);
|
||||
}
|
||||
if (options.borders.right) {
|
||||
this.properties.Borders.addRightBorder(
|
||||
options.borders.right.style,
|
||||
options.borders.right.size,
|
||||
options.borders.right.color,
|
||||
);
|
||||
properties.Borders.addRightBorder(options.borders.right.style, options.borders.right.size, options.borders.right.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public prepForXml(): IXmlableObject | undefined {
|
||||
public prepForXml(file?: IViewWrapper): IXmlableObject | undefined {
|
||||
// Cells must end with a paragraph
|
||||
if (!(this.root[this.root.length - 1] instanceof Paragraph)) {
|
||||
this.root.push(new Paragraph({}));
|
||||
}
|
||||
return super.prepForXml();
|
||||
return super.prepForXml(file);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export * from "./table-properties";
|
||||
export * from "./table-float-properties";
|
||||
export * from "./table-layout";
|
||||
export * from "./table-borders";
|
||||
export * from "./table-overlap";
|
||||
|
622
src/file/table/table-properties/table-borders.spec.ts
Normal file
622
src/file/table/table-properties/table-borders.spec.ts
Normal file
@ -0,0 +1,622 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
import { BorderStyle } from "file/styles";
|
||||
|
||||
import { TableBorders } from "./table-borders";
|
||||
|
||||
describe("TableBorders", () => {
|
||||
describe("#constructor", () => {
|
||||
describe("default borders", () => {
|
||||
it("should add a table cell top border using default width type", () => {
|
||||
const tableBorders = new TableBorders({});
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("top border", () => {
|
||||
it("should add a table cell top border", () => {
|
||||
const tableBorders = new TableBorders({
|
||||
top: {
|
||||
style: BorderStyle.DOUBLE,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "red",
|
||||
"w:space": 0,
|
||||
"w:sz": 1,
|
||||
"w:val": "double",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("left border", () => {
|
||||
it("should add a table cell left border", () => {
|
||||
const tableBorders = new TableBorders({
|
||||
left: {
|
||||
style: BorderStyle.DOUBLE,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "red",
|
||||
"w:space": 0,
|
||||
"w:sz": 1,
|
||||
"w:val": "double",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("bottom border", () => {
|
||||
it("should add a table cell bottom border", () => {
|
||||
const tableBorders = new TableBorders({
|
||||
bottom: {
|
||||
style: BorderStyle.DOUBLE,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "red",
|
||||
"w:space": 0,
|
||||
"w:sz": 1,
|
||||
"w:val": "double",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("right border", () => {
|
||||
it("should add a table cell right border", () => {
|
||||
const tableBorders = new TableBorders({
|
||||
right: {
|
||||
style: BorderStyle.DOUBLE,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "red",
|
||||
"w:space": 0,
|
||||
"w:sz": 1,
|
||||
"w:val": "double",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("inside horizontal border", () => {
|
||||
it("should add a table cell inside horizontal border", () => {
|
||||
const tableBorders = new TableBorders({
|
||||
insideHorizontal: {
|
||||
style: BorderStyle.DOUBLE,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "red",
|
||||
"w:space": 0,
|
||||
"w:sz": 1,
|
||||
"w:val": "double",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("inside vertical border", () => {
|
||||
it("should add a table cell inside horizontal border", () => {
|
||||
const tableBorders = new TableBorders({
|
||||
insideVertical: {
|
||||
style: BorderStyle.DOUBLE,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 4,
|
||||
"w:val": "single",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "red",
|
||||
"w:space": 0,
|
||||
"w:sz": 1,
|
||||
"w:val": "double",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("TableBorders.NONE convenience object", () => {
|
||||
it("should add no borders", () => {
|
||||
const tableBorders = new TableBorders(TableBorders.NONE);
|
||||
const tree = new Formatter().format(tableBorders);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblBorders": [
|
||||
{
|
||||
"w:top": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 0,
|
||||
"w:val": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:left": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 0,
|
||||
"w:val": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 0,
|
||||
"w:val": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:right": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 0,
|
||||
"w:val": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideH": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 0,
|
||||
"w:val": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:insideV": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 0,
|
||||
"w:sz": 0,
|
||||
"w:val": "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,14 +1,128 @@
|
||||
// http://officeopenxml.com/WPtableBorders.php
|
||||
import { BorderStyle } from "file/styles";
|
||||
import { XmlAttributeComponent, 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;
|
||||
};
|
||||
}
|
||||
|
||||
export class TableBorders extends XmlComponent {
|
||||
constructor() {
|
||||
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",
|
||||
},
|
||||
};
|
||||
|
||||
constructor(options: ITableBordersOptions) {
|
||||
super("w:tblBorders");
|
||||
this.root.push(new TableBordersElement("w:top", "single", 4, 0, "auto"));
|
||||
this.root.push(new TableBordersElement("w:left", "single", 4, 0, "auto"));
|
||||
this.root.push(new TableBordersElement("w:bottom", "single", 4, 0, "auto"));
|
||||
this.root.push(new TableBordersElement("w:right", "single", 4, 0, "auto"));
|
||||
this.root.push(new TableBordersElement("w:insideH", "single", 4, 0, "auto"));
|
||||
this.root.push(new TableBordersElement("w:insideV", "single", 4, 0, "auto"));
|
||||
|
||||
if (options.top) {
|
||||
this.root.push(new TableBordersElement("w:top", options.top.style, options.top.size, 0, options.top.color));
|
||||
} else {
|
||||
this.root.push(new TableBordersElement("w:top", BorderStyle.SINGLE, 4, 0, "auto"));
|
||||
}
|
||||
|
||||
if (options.left) {
|
||||
this.root.push(new TableBordersElement("w:left", options.left.style, options.left.size, 0, options.left.color));
|
||||
} else {
|
||||
this.root.push(new TableBordersElement("w:left", BorderStyle.SINGLE, 4, 0, "auto"));
|
||||
}
|
||||
|
||||
if (options.bottom) {
|
||||
this.root.push(new TableBordersElement("w:bottom", options.bottom.style, options.bottom.size, 0, options.bottom.color));
|
||||
} else {
|
||||
this.root.push(new TableBordersElement("w:bottom", BorderStyle.SINGLE, 4, 0, "auto"));
|
||||
}
|
||||
|
||||
if (options.right) {
|
||||
this.root.push(new TableBordersElement("w:right", options.right.style, options.right.size, 0, options.right.color));
|
||||
} else {
|
||||
this.root.push(new TableBordersElement("w:right", BorderStyle.SINGLE, 4, 0, "auto"));
|
||||
}
|
||||
|
||||
if (options.insideHorizontal) {
|
||||
this.root.push(
|
||||
new TableBordersElement(
|
||||
"w:insideH",
|
||||
options.insideHorizontal.style,
|
||||
options.insideHorizontal.size,
|
||||
0,
|
||||
options.insideHorizontal.color,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
this.root.push(new TableBordersElement("w:insideH", BorderStyle.SINGLE, 4, 0, "auto"));
|
||||
}
|
||||
|
||||
if (options.insideVertical) {
|
||||
this.root.push(
|
||||
new TableBordersElement(
|
||||
"w:insideV",
|
||||
options.insideVertical.style,
|
||||
options.insideVertical.size,
|
||||
0,
|
||||
options.insideVertical.color,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
this.root.push(new TableBordersElement("w:insideV", BorderStyle.SINGLE, 4, 0, "auto"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 } } }] });
|
||||
});
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,12 @@ import { expect } from "chai";
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { RelativeHorizontalPosition, RelativeVerticalPosition, TableAnchorType, TableFloatProperties } from "./table-float-properties";
|
||||
import { OverlapType } from "./table-overlap";
|
||||
|
||||
describe("Table Float Properties", () => {
|
||||
describe("#constructor", () => {
|
||||
it("should construct a TableFloatProperties with all options", () => {
|
||||
const tfp = new TableFloatProperties({
|
||||
const properties = new TableFloatProperties({
|
||||
horizontalAnchor: TableAnchorType.MARGIN,
|
||||
verticalAnchor: TableAnchorType.PAGE,
|
||||
absoluteHorizontalPosition: 10,
|
||||
@ -19,8 +20,32 @@ describe("Table Float Properties", () => {
|
||||
leftFromText: 50,
|
||||
rightFromText: 60,
|
||||
});
|
||||
const tree = new Formatter().format(tfp);
|
||||
expect(tree).to.be.deep.equal(DEFAULT_TFP);
|
||||
const tree = new Formatter().format(properties);
|
||||
expect(tree).to.deep.equal(DEFAULT_TFP);
|
||||
});
|
||||
|
||||
it("should add overlap", () => {
|
||||
const properties = new TableFloatProperties({
|
||||
overlap: OverlapType.NEVER,
|
||||
});
|
||||
const tree = new Formatter().format(properties);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblpPr": [
|
||||
{
|
||||
_attr: {
|
||||
overlap: "never",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:tblOverlap": {
|
||||
_attr: {
|
||||
"w:val": "never",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { OverlapType, TableOverlap } from "./table-overlap";
|
||||
|
||||
export enum TableAnchorType {
|
||||
MARGIN = "margin",
|
||||
PAGE = "page",
|
||||
@ -109,6 +111,7 @@ export interface ITableFloatOptions {
|
||||
* 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 overlap?: OverlapType;
|
||||
}
|
||||
|
||||
export class TableFloatOptionsAttributes extends XmlAttributeComponent<ITableFloatOptions> {
|
||||
@ -130,5 +133,9 @@ export class TableFloatProperties extends XmlComponent {
|
||||
constructor(options: ITableFloatOptions) {
|
||||
super("w:tblpPr");
|
||||
this.root.push(new TableFloatOptionsAttributes(options));
|
||||
|
||||
if (options.overlap) {
|
||||
this.root.push(new TableOverlap(options.overlap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
src/file/table/table-properties/table-overlap.spec.ts
Normal file
22
src/file/table/table-properties/table-overlap.spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { OverlapType, TableOverlap } from "./table-overlap";
|
||||
|
||||
describe("TableOverlap", () => {
|
||||
describe("#constructor", () => {
|
||||
it("sets the width attribute to the value given", () => {
|
||||
const tableOverlap = new TableOverlap(OverlapType.OVERLAP);
|
||||
const tree = new Formatter().format(tableOverlap);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblOverlap": {
|
||||
_attr: {
|
||||
"w:val": "overlap",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
17
src/file/table/table-properties/table-overlap.ts
Normal file
17
src/file/table/table-properties/table-overlap.ts
Normal file
@ -0,0 +1,17 @@
|
||||
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" };
|
||||
}
|
||||
|
||||
export class TableOverlap extends XmlComponent {
|
||||
constructor(type: OverlapType) {
|
||||
super("w:tblOverlap");
|
||||
this.root.push(new TableOverlapAttributes({ val: type }));
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { AlignmentType } from "../../paragraph";
|
||||
import { ShadingType } from "../shading";
|
||||
import { WidthType } from "../table-cell";
|
||||
import { TableLayoutType } from "./table-layout";
|
||||
@ -10,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
|
||||
@ -31,7 +32,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 } } }],
|
||||
@ -39,7 +45,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 } } }],
|
||||
@ -49,8 +60,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" } } }],
|
||||
@ -60,8 +73,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 } } }] }],
|
||||
@ -69,8 +89,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 } } }] }],
|
||||
@ -80,12 +107,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": [
|
||||
@ -102,4 +131,40 @@ describe("TableProperties", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setAlignment", () => {
|
||||
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": [
|
||||
{
|
||||
"w:jc": {
|
||||
_attr: {
|
||||
"w:val": "center",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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": {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,55 +1,68 @@
|
||||
// http://officeopenxml.com/WPtableProperties.php
|
||||
import { IgnoreIfEmptyXmlComponent } from "file/xml-components";
|
||||
|
||||
import { Alignment, AlignmentType } from "../../paragraph";
|
||||
import { ITableShadingAttributesProperties, TableShading } from "../shading";
|
||||
import { WidthType } from "../table-cell";
|
||||
import { TableBorders } from "./table-borders";
|
||||
import { TableCellMargin } from "./table-cell-margin";
|
||||
import { ITableBordersOptions, TableBorders } from "./table-borders";
|
||||
import { ITableCellMarginOptions, TableCellMargin } 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 layout?: TableLayoutType;
|
||||
readonly borders?: ITableBordersOptions;
|
||||
readonly float?: ITableFloatOptions;
|
||||
readonly shading?: ITableShadingAttributesProperties;
|
||||
readonly style?: string;
|
||||
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(): TableProperties {
|
||||
this.root.push(new TableBorders());
|
||||
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;
|
||||
}
|
||||
if (options.visuallyRightToLeft) {
|
||||
this.root.push(new VisuallyRightToLeft());
|
||||
}
|
||||
|
||||
public setStyle(styleId: string): TableProperties {
|
||||
this.root.push(new TableStyle(styleId));
|
||||
return this;
|
||||
if (options.style) {
|
||||
this.root.push(new TableStyle(options.style));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 }));
|
||||
|
@ -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": {},
|
||||
});
|
||||
});
|
||||
});
|
@ -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");
|
||||
}
|
||||
}
|
@ -10,12 +10,12 @@ export enum HeightRule {
|
||||
}
|
||||
|
||||
interface ITableRowHeight {
|
||||
readonly height: number;
|
||||
readonly value: number;
|
||||
readonly rule: HeightRule;
|
||||
}
|
||||
|
||||
export class TableRowHeightAttributes extends XmlAttributeComponent<ITableRowHeight> {
|
||||
protected readonly xmlKeys = { height: "w:val", rule: "w:hRule" };
|
||||
protected readonly xmlKeys = { value: "w:val", rule: "w:hRule" };
|
||||
}
|
||||
|
||||
export class TableRowHeight extends XmlComponent {
|
||||
@ -24,7 +24,7 @@ export class TableRowHeight extends XmlComponent {
|
||||
|
||||
this.root.push(
|
||||
new TableRowHeightAttributes({
|
||||
height: value,
|
||||
value: value,
|
||||
rule: rule,
|
||||
}),
|
||||
);
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { HeightRule, TableRowHeight } from "file/table/table-row/table-row-height";
|
||||
// http://officeopenxml.com/WPtableRowProperties.php
|
||||
import { IgnoreIfEmptyXmlComponent, XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { HeightRule, TableRowHeight } from "./table-row-height";
|
||||
|
||||
export class TableRowProperties extends IgnoreIfEmptyXmlComponent {
|
||||
constructor() {
|
||||
super("w:trPr");
|
||||
@ -18,8 +20,8 @@ export class TableRowProperties extends IgnoreIfEmptyXmlComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public setHeight(height: number, rule: HeightRule): TableRowProperties {
|
||||
this.root.push(new TableRowHeight(height, rule));
|
||||
public setHeight(value: number, rule: HeightRule): TableRowProperties {
|
||||
this.root.push(new TableRowHeight(value, rule));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ describe("TableRow", () => {
|
||||
const tableRow = new TableRow({
|
||||
children: [],
|
||||
height: {
|
||||
height: 100,
|
||||
value: 100,
|
||||
rule: HeightRule.EXACT,
|
||||
},
|
||||
});
|
||||
@ -182,4 +182,97 @@ describe("TableRow", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#rootIndexToColumnIndex", () => {
|
||||
it("should get the correct virtual column index by root index", () => {
|
||||
const tableRow = new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
columnSpan: 3,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
columnSpan: 3,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(tableRow.rootIndexToColumnIndex(1)).to.equal(0);
|
||||
expect(tableRow.rootIndexToColumnIndex(2)).to.equal(3);
|
||||
expect(tableRow.rootIndexToColumnIndex(3)).to.equal(4);
|
||||
expect(tableRow.rootIndexToColumnIndex(4)).to.equal(5);
|
||||
|
||||
expect(() => tableRow.rootIndexToColumnIndex(0)).to.throw(`cell 'rootIndex' should between 1 to 4`);
|
||||
expect(() => tableRow.rootIndexToColumnIndex(5)).to.throw(`cell 'rootIndex' should between 1 to 4`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#columnIndexToRootIndex", () => {
|
||||
it("should get the correct root index by virtual column index", () => {
|
||||
const tableRow = new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
columnSpan: 3,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
columnSpan: 3,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(tableRow.columnIndexToRootIndex(0)).to.equal(1);
|
||||
expect(tableRow.columnIndexToRootIndex(1)).to.equal(1);
|
||||
expect(tableRow.columnIndexToRootIndex(2)).to.equal(1);
|
||||
|
||||
expect(tableRow.columnIndexToRootIndex(3)).to.equal(2);
|
||||
expect(tableRow.columnIndexToRootIndex(4)).to.equal(3);
|
||||
|
||||
expect(tableRow.columnIndexToRootIndex(5)).to.equal(4);
|
||||
expect(tableRow.columnIndexToRootIndex(6)).to.equal(4);
|
||||
expect(tableRow.columnIndexToRootIndex(7)).to.equal(4);
|
||||
|
||||
expect(() => tableRow.columnIndexToRootIndex(-1)).to.throw(`cell 'columnIndex' should not less than zero`);
|
||||
expect(() => tableRow.columnIndexToRootIndex(8)).to.throw(`cell 'columnIndex' should not great than 7`);
|
||||
});
|
||||
|
||||
it("should allow end new cell index", () => {
|
||||
const tableRow = new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
columnSpan: 3,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("test")],
|
||||
columnSpan: 3,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(tableRow.columnIndexToRootIndex(8, true)).to.equal(5);
|
||||
// for column 10, just place the new cell at the end of row
|
||||
expect(tableRow.columnIndexToRootIndex(10, true)).to.equal(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ export interface ITableRowOptions {
|
||||
readonly cantSplit?: boolean;
|
||||
readonly tableHeader?: boolean;
|
||||
readonly height?: {
|
||||
readonly height: number;
|
||||
readonly value: number;
|
||||
readonly rule: HeightRule;
|
||||
};
|
||||
readonly children: TableCell[];
|
||||
@ -34,7 +34,7 @@ export class TableRow extends XmlComponent {
|
||||
}
|
||||
|
||||
if (options.height) {
|
||||
this.properties.setHeight(options.height.height, options.height.rule);
|
||||
this.properties.setHeight(options.height.value, options.height.rule);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,12 +42,56 @@ export class TableRow extends XmlComponent {
|
||||
return this.options.children.length;
|
||||
}
|
||||
|
||||
public get Children(): TableCell[] {
|
||||
return this.options.children;
|
||||
public get cells(): TableCell[] {
|
||||
return this.root.filter((xmlComponent) => xmlComponent instanceof TableCell);
|
||||
}
|
||||
|
||||
public addCellToIndex(cell: TableCell, index: number): void {
|
||||
// Offset because properties is also in root.
|
||||
this.root.splice(index + 1, 0, cell);
|
||||
}
|
||||
|
||||
public addCellToColumnIndex(cell: TableCell, columnIndex: number): void {
|
||||
const rootIndex = this.columnIndexToRootIndex(columnIndex, true);
|
||||
this.addCellToIndex(cell, rootIndex - 1);
|
||||
}
|
||||
|
||||
public rootIndexToColumnIndex(rootIndex: number): number {
|
||||
// convert the root index to the virtual column index
|
||||
if (rootIndex < 1 || rootIndex >= this.root.length) {
|
||||
throw new Error(`cell 'rootIndex' should between 1 to ${this.root.length - 1}`);
|
||||
}
|
||||
let colIdx = 0;
|
||||
// Offset because properties is also in root.
|
||||
for (let rootIdx = 1; rootIdx < rootIndex; rootIdx++) {
|
||||
const cell = this.root[rootIdx] as TableCell;
|
||||
colIdx += cell.options.columnSpan || 1;
|
||||
}
|
||||
return colIdx;
|
||||
}
|
||||
|
||||
public columnIndexToRootIndex(columnIndex: number, allowEndNewCell: boolean = false): number {
|
||||
// convert the virtual column index to the root index
|
||||
// `allowEndNewCell` for get index to inert new cell
|
||||
if (columnIndex < 0) {
|
||||
throw new Error(`cell 'columnIndex' should not less than zero`);
|
||||
}
|
||||
let colIdx = 0;
|
||||
// Offset because properties is also in root.
|
||||
let rootIdx = 1;
|
||||
while (colIdx <= columnIndex) {
|
||||
if (rootIdx >= this.root.length) {
|
||||
if (allowEndNewCell) {
|
||||
// for inserting verticalMerge CONTINUE cell at end of row
|
||||
return this.root.length;
|
||||
} else {
|
||||
throw new Error(`cell 'columnIndex' should not great than ${colIdx - 1}`);
|
||||
}
|
||||
}
|
||||
const cell = this.root[rootIdx] as TableCell;
|
||||
rootIdx += 1;
|
||||
colIdx += (cell && cell.options.columnSpan) || 1;
|
||||
}
|
||||
return rootIdx - 1;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { Paragraph } from "../paragraph";
|
||||
import { AlignmentType, Paragraph } from "../paragraph";
|
||||
import { Table } from "./table";
|
||||
// import { WidthType } from "./table-cell";
|
||||
import { RelativeHorizontalPosition, RelativeVerticalPosition, TableAnchorType } from "./table-properties";
|
||||
@ -188,6 +188,72 @@ describe("Table", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a table with the correct columnSpan and rowSpan", () => {
|
||||
const table = new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("hello")],
|
||||
columnSpan: 2,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("hello")],
|
||||
rowSpan: 2,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("hello")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("hello")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const tree = new Formatter().format(table);
|
||||
const cellP = { "w:p": [{ "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "hello"] }] }] };
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tbl": [
|
||||
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS] },
|
||||
{
|
||||
"w:tblGrid": [{ "w:gridCol": { _attr: { "w:w": 100 } } }, { "w:gridCol": { _attr: { "w:w": 100 } } }],
|
||||
},
|
||||
{
|
||||
"w:tr": [
|
||||
{
|
||||
"w:tc": [{ "w:tcPr": [{ "w:gridSpan": { _attr: { "w:val": 2 } } }] }, cellP],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:tr": [
|
||||
{
|
||||
"w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "restart" } } }] }, cellP],
|
||||
},
|
||||
{ "w:tc": [cellP] },
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:tr": [
|
||||
{
|
||||
"w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }, { "w:p": {} }],
|
||||
},
|
||||
{ "w:tc": [cellP] },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the table to fixed width layout", () => {
|
||||
const table = new Table({
|
||||
rows: [
|
||||
@ -202,15 +268,32 @@ describe("Table", () => {
|
||||
layout: TableLayoutType.FIXED,
|
||||
});
|
||||
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).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": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS, { "w:tblLayout": { _attr: { "w:type": "fixed" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should center the table", () => {
|
||||
const table = new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("hello")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
alignment: AlignmentType.CENTER,
|
||||
});
|
||||
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": [DEFAULT_TABLE_PROPERTIES, BORDERS, WIDTHS, { "w:jc": { _attr: { "w:val": "center" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the table to provided width", () => {
|
||||
const table = new Table({
|
||||
rows: [
|
||||
@ -229,10 +312,7 @@ describe("Table", () => {
|
||||
layout: TableLayoutType.FIXED,
|
||||
});
|
||||
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).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": [
|
||||
DEFAULT_TABLE_PROPERTIES,
|
||||
@ -266,14 +346,10 @@ describe("Table", () => {
|
||||
],
|
||||
});
|
||||
const tree = new Formatter().format(table);
|
||||
expect(tree)
|
||||
.to.have.property("w:tbl")
|
||||
.which.is.an("array");
|
||||
expect(tree).to.have.property("w:tbl").which.is.an("array");
|
||||
const row = tree["w:tbl"].find((x) => x["w:tr"]);
|
||||
expect(row).not.to.be.undefined;
|
||||
expect(row["w:tr"])
|
||||
.to.be.an("array")
|
||||
.which.has.length.at.least(1);
|
||||
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
|
||||
"w:tc": [
|
||||
{
|
||||
@ -398,10 +474,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).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": [
|
||||
DEFAULT_TABLE_PROPERTIES,
|
||||
|
@ -1,8 +1,10 @@
|
||||
// http://officeopenxml.com/WPtableGrid.php
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { AlignmentType } from "../paragraph";
|
||||
import { TableGrid } from "./grid";
|
||||
import { TableCell, VerticalMergeType, WidthType } from "./table-cell";
|
||||
import { ITableFloatOptions, TableProperties } from "./table-properties";
|
||||
import { ITableBordersOptions, ITableFloatOptions, TableProperties } from "./table-properties";
|
||||
import { TableLayoutType } from "./table-properties/table-layout";
|
||||
import { TableRow } from "./table-row";
|
||||
|
||||
@ -33,11 +35,12 @@ export interface ITableOptions {
|
||||
readonly float?: ITableFloatOptions;
|
||||
readonly layout?: TableLayoutType;
|
||||
readonly style?: string;
|
||||
readonly borders?: ITableBordersOptions;
|
||||
readonly alignment?: AlignmentType;
|
||||
readonly visuallyRightToLeft?: boolean;
|
||||
}
|
||||
|
||||
export class Table extends XmlComponent {
|
||||
private readonly properties: TableProperties;
|
||||
|
||||
constructor({
|
||||
rows,
|
||||
width,
|
||||
@ -46,26 +49,41 @@ export class Table extends XmlComponent {
|
||||
float,
|
||||
layout,
|
||||
style,
|
||||
borders,
|
||||
alignment,
|
||||
visuallyRightToLeft,
|
||||
}: ITableOptions) {
|
||||
super("w:tbl");
|
||||
this.properties = new TableProperties();
|
||||
this.root.push(this.properties);
|
||||
this.properties.setBorder();
|
||||
|
||||
if (style) {
|
||||
this.properties.setStyle(style);
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
},
|
||||
},
|
||||
visuallyRightToLeft,
|
||||
}),
|
||||
);
|
||||
|
||||
this.root.push(new TableGrid(columnWidths));
|
||||
|
||||
@ -73,34 +91,28 @@ export class Table extends XmlComponent {
|
||||
this.root.push(row);
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
row.Children.forEach((cell, cellIndex) => {
|
||||
const column = rows.map((r) => r.Children[cellIndex]);
|
||||
rows.forEach((row, rowIndex) => {
|
||||
if (rowIndex === rows.length - 1) {
|
||||
// don't process the end row
|
||||
return;
|
||||
}
|
||||
let columnIndex = 0;
|
||||
row.cells.forEach((cell) => {
|
||||
// Row Span has to be added in this method and not the constructor because it needs to know information about the column which happens after Table Cell construction
|
||||
// Row Span of 1 will crash word as it will add RESTART and not a corresponding CONTINUE
|
||||
if (cell.options.rowSpan && cell.options.rowSpan > 1) {
|
||||
const thisCellsColumnIndex = column.indexOf(cell);
|
||||
const endColumnIndex = thisCellsColumnIndex + (cell.options.rowSpan - 1);
|
||||
|
||||
for (let i = thisCellsColumnIndex + 1; i <= endColumnIndex; i++) {
|
||||
rows[i].addCellToIndex(
|
||||
new TableCell({
|
||||
children: [],
|
||||
verticalMerge: VerticalMergeType.CONTINUE,
|
||||
}),
|
||||
i,
|
||||
);
|
||||
}
|
||||
const continueCell = new TableCell({
|
||||
// the inserted CONTINUE cell has rowSpan, and will be handled when process the next row
|
||||
rowSpan: cell.options.rowSpan - 1,
|
||||
columnSpan: cell.options.columnSpan,
|
||||
borders: cell.options.borders,
|
||||
children: [],
|
||||
verticalMerge: VerticalMergeType.CONTINUE,
|
||||
});
|
||||
rows[rowIndex + 1].addCellToColumnIndex(continueCell, columnIndex);
|
||||
}
|
||||
columnIndex += cell.options.columnSpan || 1;
|
||||
});
|
||||
}
|
||||
|
||||
if (float) {
|
||||
this.properties.setTableFloatProperties(float);
|
||||
}
|
||||
|
||||
if (layout) {
|
||||
this.properties.setLayout(layout);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user