diff --git a/README.md b/README.md index 020cb78f96..f387c41a14 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,10 @@ Here are examples of `docx` being used with basic `HTML/JS` in a browser environ * https://codepen.io/anon/pen/dqoVgQ * https://jsfiddle.net/3xhezb5w/2 +Here is an example of `docx` working in `Angular`: + +* https://stackblitz.com/edit/angular-afvxtz + ## Node Press `endpoint` on the `RunKit` website: diff --git a/demo/demo29.ts b/demo/demo29.ts new file mode 100644 index 0000000000..0650aac09c --- /dev/null +++ b/demo/demo29.ts @@ -0,0 +1,32 @@ +import * as fs from "fs"; +import { Document, Indent, Numbering, Packer, Paragraph } from "../build"; + +const doc = new Document(); + +const numbering = new Numbering(); + +const abstractNum = numbering.createAbstractNumbering(); +abstractNum.createLevel(0, "upperRoman", "%1", "start").addParagraphProperty(new Indent({ left: 720, hanging: 260 })); + +const concrete = numbering.createConcreteNumbering(abstractNum); + +const item1 = new Paragraph("line with contextual spacing"); +const item2 = new Paragraph("line with contextual spacing"); +const item3 = new Paragraph("line without contextual spacing"); +const item4 = new Paragraph("line without contextual spacing"); + +item1.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(true); +item2.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(true); +item3.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(false); +item4.setNumbering(concrete, 0).spacing({before: 200}).contextualSpacing(false); + +doc.addParagraph(item1); +doc.addParagraph(item2); +doc.addParagraph(item3); +doc.addParagraph(item4); + +const packer = new Packer(); + +packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); \ No newline at end of file diff --git a/demo/demo31.ts b/demo/demo31.ts new file mode 100644 index 0000000000..366227102a --- /dev/null +++ b/demo/demo31.ts @@ -0,0 +1,26 @@ +// Example of how you would create a table and add data to it +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, Packer, Paragraph, VerticalAlign } from "../build"; + +const doc = new Document(); + +const table = doc.createTable(2, 2); +table + .getCell(1, 1) + .addContent(new Paragraph("This text should be in the middle of the cell")) + .CellProperties.setVerticalAlign(VerticalAlign.CENTER); + +table + .getCell(1, 0) + .addContent( + new Paragraph( + "Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah", + ).heading1(), + ); + +const packer = new Packer(); + +packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/demo/demo32.ts b/demo/demo32.ts new file mode 100644 index 0000000000..373ca64266 --- /dev/null +++ b/demo/demo32.ts @@ -0,0 +1,35 @@ +// Example of how you would create a table and add data to it +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, Packer, Paragraph } from "../build"; + +const doc = new Document(); + +let table = doc.createTable(2, 2); + +table.getCell(0, 0).addContent(new Paragraph("Hello")); +table.getRow(0).mergeCells(0, 1); + +doc.createParagraph("Another table").heading2(); + +table = doc.createTable(2, 3); +table.getCell(0, 0).addContent(new Paragraph("World")); +table.getRow(0).mergeCells(0, 2); + +doc.createParagraph("Another table").heading2(); + +table = doc.createTable(2, 4); +table.getCell(0, 0).addContent(new Paragraph("Foo")); + +table.getCell(1, 0).addContent(new Paragraph("Bar1")); +table.getCell(1, 1).addContent(new Paragraph("Bar2")); +table.getCell(1, 2).addContent(new Paragraph("Bar3")); +table.getCell(1, 3).addContent(new Paragraph("Bar4")); + +table.getRow(0).mergeCells(0, 3); + +const packer = new Packer(); + +packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/demo/demo33.ts b/demo/demo33.ts new file mode 100644 index 0000000000..c6efe2ed0f --- /dev/null +++ b/demo/demo33.ts @@ -0,0 +1,22 @@ +// Sequential Captions +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, Packer, Paragraph, TextRun } from "../build"; + +const doc = new Document(); + +const paragraph = new Paragraph("Hello World 1->").addSequentialIdentifier("Caption").addRun(new TextRun(" text after sequencial caption 2->")).addSequentialIdentifier("Caption"); +const paragraph2 = new Paragraph("Hello World 1->").addSequentialIdentifier("Label").addRun(new TextRun(" text after sequencial caption 2->")).addSequentialIdentifier("Label"); +const paragraph3 = new Paragraph("Hello World 1->").addSequentialIdentifier("Another").addRun(new TextRun(" text after sequencial caption 3->")).addSequentialIdentifier("Label"); +const paragraph4 = new Paragraph("Hello World 2->").addSequentialIdentifier("Another").addRun(new TextRun(" text after sequencial caption 4->")).addSequentialIdentifier("Label"); + +doc.addParagraph(paragraph); +doc.addParagraph(paragraph2); +doc.addParagraph(paragraph3); +doc.addParagraph(paragraph4); + +const packer = new Packer(); + +packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 0b159a95a8..4b9a626a03 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -16,6 +16,7 @@ * [Bullet Points](usage/bullet-points.md) * [Numbering](usage/numbering.md) * [Tab Stops](usage/tab-stops.md) + * [Table of Contents](usage/table-of-contents.md) * Styling * [Styling with JS](usage/styling-with-js.md) * [Styling with XML](usage/styling-with-xml.md) diff --git a/docs/contribution-guidelines.md b/docs/contribution-guidelines.md index 5d1442fe0c..a212a081c2 100644 --- a/docs/contribution-guidelines.md +++ b/docs/contribution-guidelines.md @@ -1,5 +1,30 @@ # Contribution Guidelines +## Always think about the user + +The number one pillar for contribution is to **ALWAYS** think about how the user will use the library. + +Put yourself in their position, and imagine how they would feel about your feature you wrote. + +1. Is it easy to use? +2. Has it been documented well? +3. Is it intuative? +4. Is it consistent with the rest of the API? +5. Is it fun to use? + +## Good Commit Names + +Please write good commit messages when making a commit: https://chris.beams.io/posts/git-commit/ + +**Do not:** +``` +c // What? +rtl // Adding acryonyms without explaining anything else is not helpful +works! // Glad its working, but the message is not helpful +demo updated // Getting better, but capitalize the first letter +Unesesary coment removed // Make sure to use correct spelling +``` + ## Writing Code * Include documentation reference(s) at the top of each file: @@ -45,18 +70,98 @@ public get Level() { There is no performance advantage by doing this. It means we don't need to prefix all private variables with the ugly `_`: -**Do not:** +**Do not:** ```js private get _level: string; ``` -**Do** +**Do** ```js private get level: string; ``` +## Interfaces over type alias + +Do not use `type`, but rather use `Interfaces`. `type` cannot be extended, and a class cannot implement it. + +> "In general, use what you want ( type alias / interface ) just be consistent" +> "always use interface for public API's definition when authoring a library or 3rd party ambient type definitions" +> +> * https://medium.com/@martin_hotell/interface-vs-type-alias-in-typescript-2-7-2a8f1777af4c + +`Interface` is generally preferred over `type`: https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types + +**Do not:** + +```js +type RelationshipFileInfo = { id: number, target: string }; +``` + +**Do:** + +```js +interface IRelationshipFileInfo { + id: number; + target: string; +} +``` + +## String enums vs type + +To take full advantage of TypeScript's typing system, its best to use `string enums`: + +**Do not:** + +```js +type WeaponType = "bow" | "sword" | "wand"; +``` + +**Do:** + +```js +enum WeaponType = { + BOW = "bow", + SWORD = "sword", + WAND = "wand", +} +``` + +## Spell correctly, full and in American English + +I am not sure where these habit in software development comes from, but I do not believe it is beneficial: + +**Do not:** +```js +readdy // misspelling +perm // abbreviation +conf // abbreviation +cnty // abbreviation +relationFile // abbreviation +colour // U.K. English +``` + +**Do:** +```js +ready +permission +config +country +relationshipFile +color +``` + +## Keep files small (within reason) + +To minimize merge conflicts, reduce complexity, and improve readability, keep the files small. + +## Name files and folders with `/foo-bar/kebab-case.ts` + +To be consistent and in-line with the project, name files `like-this.ts`. + +https://stackoverflow.com/questions/7273316/what-is-the-javascript-filename-naming-convention + ## Testing Please write a test of every file you make and suffix it with `.spec.ts`. @@ -78,3 +183,5 @@ describe("ClassName", () => { }); }); ``` + +Try not to use the `tests/utility.ts` file as this is being deprecated. diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md index 2f43da4c2f..7c226833cd 100644 --- a/docs/usage/table-of-contents.md +++ b/docs/usage/table-of-contents.md @@ -22,9 +22,9 @@ const toc = new TableOfContents("Summary", { doc.addTableOfContents(toc); ``` -## Table of Contents Properties +## Table of Contents Options -Here is the list of all properties that you can use to generate your tables of contents: +Here is the list of all options that you can use to generate your tables of contents: | Option | Type | TOC Field Switch | Description | | --- | --- | --- | --- | @@ -48,7 +48,7 @@ Here is the list of all properties that you can use to generate your tables of c ## Examples ```js -// Let's define the properties for generate a TOC for heading 1-5 and MySpectacularStyle, +// Let's define the options for generate a TOC for heading 1-5 and MySpectacularStyle, // making the entries be hyperlinks for the paragraph const toc = new TableOfContents("Summary", { hyperlink: true, diff --git a/package.json b/package.json index 56b5b1951c..5ab20d5c8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docx", - "version": "4.1.0", + "version": "4.3.0", "description": "Generate .docx documents with JavaScript (formerly Office-Clippy)", "main": "build/index.js", "scripts": { diff --git a/src/file/paragraph/formatting/spacing.ts b/src/file/paragraph/formatting/spacing.ts index 292864bf53..608a3fe1ab 100644 --- a/src/file/paragraph/formatting/spacing.ts +++ b/src/file/paragraph/formatting/spacing.ts @@ -1,5 +1,5 @@ // http://officeopenxml.com/WPspacing.php -import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components"; export interface ISpacingProperties { after?: number; @@ -23,3 +23,14 @@ export class Spacing extends XmlComponent { this.root.push(new SpacingAttributes(opts)); } } + +export class ContextualSpacing extends XmlComponent { + constructor(value: boolean) { + super("w:contextualSpacing"); + this.root.push( + new Attributes({ + val: value === false ? 0 : 1, + }), + ); + } +} diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index b7930970b5..263064d2fa 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -10,13 +10,13 @@ import { Border, ThematicBreak } from "./formatting/border"; import { IIndentAttributesProperties, Indent } from "./formatting/indent"; import { KeepLines, KeepNext } from "./formatting/keep"; import { PageBreak, PageBreakBefore } from "./formatting/page-break"; -import { ISpacingProperties, Spacing } from "./formatting/spacing"; +import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spacing"; import { Style } from "./formatting/style"; import { CenterTabStop, LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; import { Bookmark, Hyperlink } from "./links"; import { ParagraphProperties } from "./properties"; -import { PictureRun, Run, TextRun } from "./run"; +import { PictureRun, Run, SequentialIdentifier, TextRun } from "./run"; export class Paragraph extends XmlComponent { private readonly properties: ParagraphProperties; @@ -211,6 +211,11 @@ export class Paragraph extends XmlComponent { return this; } + public contextualSpacing(value: boolean): Paragraph { + this.properties.push(new ContextualSpacing(value)); + return this; + } + public keepNext(): Paragraph { this.properties.push(new KeepNext()); return this; @@ -240,4 +245,9 @@ export class Paragraph extends XmlComponent { this.root.splice(1, 0, run); return this; } + + public addSequentialIdentifier(identifier: string): Paragraph { + this.root.push(new SequentialIdentifier(identifier)); + return this; + } } diff --git a/src/file/paragraph/run/index.ts b/src/file/paragraph/run/index.ts index 6ac823e9bb..a30a1d6037 100644 --- a/src/file/paragraph/run/index.ts +++ b/src/file/paragraph/run/index.ts @@ -1,3 +1,4 @@ export * from "./run"; export * from "./text-run"; export * from "./picture-run"; +export * from "./sequential-identifier"; diff --git a/src/file/paragraph/run/sequential-identifier-instruction.ts b/src/file/paragraph/run/sequential-identifier-instruction.ts new file mode 100644 index 0000000000..6f48738db7 --- /dev/null +++ b/src/file/paragraph/run/sequential-identifier-instruction.ts @@ -0,0 +1,19 @@ +// http://officeopenxml.com/WPfieldInstructions.php +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +enum SpaceType { + DEFAULT = "default", + PRESERVE = "preserve", +} + +class TextAttributes extends XmlAttributeComponent<{ space: SpaceType }> { + protected xmlKeys = { space: "xml:space" }; +} + +export class SequentialIdentifierInstruction extends XmlComponent { + constructor(identifier: string) { + super("w:instrText"); + this.root.push(new TextAttributes({ space: SpaceType.PRESERVE })); + this.root.push(`SEQ ${identifier}`); + } +} diff --git a/src/file/paragraph/run/sequential-identifier.spec.ts b/src/file/paragraph/run/sequential-identifier.spec.ts new file mode 100644 index 0000000000..1b34997b9c --- /dev/null +++ b/src/file/paragraph/run/sequential-identifier.spec.ts @@ -0,0 +1,60 @@ +import { expect } from "chai"; + +import { Formatter } from "../../../export/formatter"; +import { SequentialIdentifier } from "./sequential-identifier"; + +describe("Sequential Identifier", () => { + describe("#constructor", () => { + it("should construct a SEQ without options", () => { + const seq = new SequentialIdentifier("Figure"); + const tree = new Formatter().format(seq); + expect(tree).to.be.deep.equal(DEFAULT_SEQ); + }); + }); +}); + +const DEFAULT_SEQ = { + "w:r": [ + { + "w:rPr": [], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "begin", + "w:dirty": true, + }, + }, + ], + }, + { + "w:instrText": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "SEQ Figure", + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "separate", + }, + }, + ], + }, + { + "w:fldChar": [ + { + _attr: { + "w:fldCharType": "end", + }, + }, + ], + }, + ], +}; diff --git a/src/file/paragraph/run/sequential-identifier.ts b/src/file/paragraph/run/sequential-identifier.ts new file mode 100644 index 0000000000..5536eca4ae --- /dev/null +++ b/src/file/paragraph/run/sequential-identifier.ts @@ -0,0 +1,13 @@ +import { Run } from "file/paragraph/run"; +import { Begin, End, Separate } from "file/paragraph/run/field"; +import { SequentialIdentifierInstruction } from "./sequential-identifier-instruction"; + +export class SequentialIdentifier extends Run { + constructor(identifier: string) { + super(); + this.root.push(new Begin(true)); + this.root.push(new SequentialIdentifierInstruction(identifier)); + this.root.push(new Separate()); + this.root.push(new End()); + } +} diff --git a/src/file/table-of-contents/table-of-contents-instruction.ts b/src/file/table-of-contents/field-instruction.ts similarity index 92% rename from src/file/table-of-contents/table-of-contents-instruction.ts rename to src/file/table-of-contents/field-instruction.ts index 1183ebd3de..7194cf3f16 100644 --- a/src/file/table-of-contents/table-of-contents-instruction.ts +++ b/src/file/table-of-contents/field-instruction.ts @@ -1,3 +1,4 @@ +// http://officeopenxml.com/WPfieldInstructions.php import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { ITableOfContentsOptions } from "./table-of-contents-properties"; @@ -10,7 +11,7 @@ class TextAttributes extends XmlAttributeComponent<{ space: SpaceType }> { protected xmlKeys = { space: "xml:space" }; } -export class TableOfContentsInstruction extends XmlComponent { +export class FieldInstruction extends XmlComponent { private readonly properties: ITableOfContentsOptions; constructor(properties: ITableOfContentsOptions = {}) { @@ -40,19 +41,19 @@ export class TableOfContentsInstruction extends XmlComponent { instruction = `${instruction} \\h`; } if (this.properties.tcFieldLevelRange) { - instruction = `${instruction} \\l "${this.properties.tcFieldLevelRange}`; + instruction = `${instruction} \\l "${this.properties.tcFieldLevelRange}"`; } if (this.properties.pageNumbersEntryLevelsRange) { - instruction = `${instruction} \\n "${this.properties.pageNumbersEntryLevelsRange}`; + instruction = `${instruction} \\n "${this.properties.pageNumbersEntryLevelsRange}"`; } if (this.properties.headingStyleRange) { - instruction = `${instruction} \\o "${this.properties.headingStyleRange}`; + instruction = `${instruction} \\o "${this.properties.headingStyleRange}"`; } if (this.properties.entryAndPageNumberSeparator) { - instruction = `${instruction} \\p "${this.properties.entryAndPageNumberSeparator}`; + instruction = `${instruction} \\p "${this.properties.entryAndPageNumberSeparator}"`; } if (this.properties.seqFieldIdentifierForPrefix) { - instruction = `${instruction} \\s "${this.properties.seqFieldIdentifierForPrefix}`; + instruction = `${instruction} \\s "${this.properties.seqFieldIdentifierForPrefix}"`; } if (this.properties.stylesWithLevels && this.properties.stylesWithLevels.length) { const styles = this.properties.stylesWithLevels.map((sl) => `${sl.styleName},${sl.level}`).join(","); diff --git a/src/file/table-of-contents/sdt-content.ts b/src/file/table-of-contents/sdt-content.ts index e668da6052..5228ff447f 100644 --- a/src/file/table-of-contents/sdt-content.ts +++ b/src/file/table-of-contents/sdt-content.ts @@ -1,6 +1,6 @@ import { XmlComponent } from "file/xml-components"; -export class SdtContent extends XmlComponent { +export class StructuredDocumentTagContent extends XmlComponent { constructor() { super("w:sdtContent"); } diff --git a/src/file/table-of-contents/sdt-properties.ts b/src/file/table-of-contents/sdt-properties.ts index bb8ef77a6b..3f7019f561 100644 --- a/src/file/table-of-contents/sdt-properties.ts +++ b/src/file/table-of-contents/sdt-properties.ts @@ -1,7 +1,8 @@ +// http://www.datypic.com/sc/ooxml/e-w_sdtPr-1.html import { XmlComponent } from "file/xml-components"; import { Alias } from "./alias"; -export class SdtProperties extends XmlComponent { +export class StructuredDocumentTagProperties extends XmlComponent { constructor(alias: string) { super("w:sdtPr"); this.root.push(new Alias(alias)); diff --git a/src/file/table-of-contents/table-of-contents.spec.ts b/src/file/table-of-contents/table-of-contents.spec.ts index 7d7d4a55b6..6729f004ba 100644 --- a/src/file/table-of-contents/table-of-contents.spec.ts +++ b/src/file/table-of-contents/table-of-contents.spec.ts @@ -174,7 +174,7 @@ const COMPLETE_TOC = { "xml:space": "preserve", }, }, - 'TOC \\a "A" \\b "B" \\c "C" \\d "D" \\f "F" \\h \\l "L \\n "N \\o "O \\p "P \\s "S \\t "SL,1,SL,2" \\u \\w \\x \\z', + 'TOC \\a "A" \\b "B" \\c "C" \\d "D" \\f "F" \\h \\l "L" \\n "N" \\o "O" \\p "P" \\s "S" \\t "SL,1,SL,2" \\u \\w \\x \\z', ], }, { diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index fb7b709c4f..7b1ff0f66d 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -1,23 +1,25 @@ +// http://officeopenxml.com/WPtableOfContents.php +// http://www.datypic.com/sc/ooxml/e-w_sdt-1.html import { Paragraph } from "file/paragraph"; import { Run } from "file/paragraph/run"; import { Begin, End, Separate } from "file/paragraph/run/field"; import { XmlComponent } from "file/xml-components"; -import { SdtContent } from "./sdt-content"; -import { SdtProperties } from "./sdt-properties"; -import { TableOfContentsInstruction } from "./table-of-contents-instruction"; +import { FieldInstruction } from "./field-instruction"; +import { StructuredDocumentTagContent } from "./sdt-content"; +import { StructuredDocumentTagProperties } from "./sdt-properties"; import { ITableOfContentsOptions } from "./table-of-contents-properties"; export class TableOfContents extends XmlComponent { constructor(alias: string = "Table of Contents", properties?: ITableOfContentsOptions) { super("w:sdt"); - this.root.push(new SdtProperties(alias)); + this.root.push(new StructuredDocumentTagProperties(alias)); - const content = new SdtContent(); + const content = new StructuredDocumentTagContent(); const beginParagraph = new Paragraph(); const beginRun = new Run(); beginRun.addChildElement(new Begin(true)); - beginRun.addChildElement(new TableOfContentsInstruction(properties)); + beginRun.addChildElement(new FieldInstruction(properties)); beginRun.addChildElement(new Separate()); beginParagraph.addRun(beginRun); content.addChildElement(beginParagraph); diff --git a/src/file/table/grid.ts b/src/file/table/grid.ts index d9338e7dc4..0e477df946 100644 --- a/src/file/table/grid.ts +++ b/src/file/table/grid.ts @@ -1,3 +1,4 @@ +// http://officeopenxml.com/WPtableGrid.php import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export class TableGrid extends XmlComponent { diff --git a/src/file/table/table.ts b/src/file/table/table.ts index bc81520157..3220bce537 100644 --- a/src/file/table/table.ts +++ b/src/file/table/table.ts @@ -1,3 +1,4 @@ +// http://officeopenxml.com/WPtableGrid.php import { GridSpan, TableCellBorders, @@ -60,7 +61,13 @@ export class Table extends XmlComponent { } public getRow(ix: number): TableRow { - return this.rows[ix]; + const row = this.rows[ix]; + + if (!row) { + throw Error("Index out of bounds when trying to get row on table"); + } + + return row; } public getCell(row: number, col: number): TableCell { @@ -93,17 +100,29 @@ export class TableRow extends XmlComponent { } public getCell(ix: number): TableCell { - return this.cells[ix]; + const cell = this.cells[ix]; + + if (!cell) { + throw Error("Index out of bounds when trying to get cell on row"); + } + + return cell; } - public addGridSpan(ix: number, cellSpan: number): TableCell { - const remainCell = this.cells[ix]; + public addGridSpan(index: number, cellSpan: number): TableCell { + const remainCell = this.cells[index]; remainCell.CellProperties.addGridSpan(cellSpan); - this.cells.splice(ix + 1, cellSpan - 1); - this.root.splice(ix + 2, cellSpan - 1); + this.cells.splice(index + 1, cellSpan - 1); + this.root.splice(index + 2, cellSpan - 1); return remainCell; } + + public mergeCells(startIndex: number, endIndex: number): TableCell { + const cellSpan = endIndex - startIndex + 1; + + return this.addGridSpan(startIndex, cellSpan); + } } export class TableRowProperties extends XmlComponent {