Merge branch 'master' of https://github.com/dolanmiu/docx into feat/floating-images

# Conflicts:
#	src/file/drawing/drawing.ts
This commit is contained in:
Dolan
2019-01-09 02:04:06 +00:00
78 changed files with 2656 additions and 1168 deletions

View File

@ -64,6 +64,12 @@ Check the `examples` section in the [documentation](https://docx.js.org/#/exampl
Read the contribution guidelines [here](https://docx.js.org/#/contribution-guidelines). Read the contribution guidelines [here](https://docx.js.org/#/contribution-guidelines).
# Used by
[<img src="https://i.imgur.com/zy5qWmI.png" alt="drawing" height="200"/>](https://hfour.com/)
[<img src="https://i.imgur.com/OYP5tgS.png" alt="drawing" height="200"/>](https://fuzzproductions.com/)
[<img src="https://i.imgur.com/zUDMfZ3.png" alt="drawing" height="200"/>](https://www.mettzer.com/)
--- ---
Made with 💖 Made with 💖

View File

@ -233,7 +233,7 @@ class DocumentCreator {
public createRoleText(roleText: string): Paragraph { public createRoleText(roleText: string): Paragraph {
const paragraph = new Paragraph(); const paragraph = new Paragraph();
const role = new TextRun(roleText).italic(); const role = new TextRun(roleText).italics();
paragraph.addRun(role); paragraph.addRun(role);

View File

@ -84,8 +84,7 @@ doc.Styles.createParagraphStyle("ListParagraph", "List Paragraph")
.basedOn("Normal"); .basedOn("Normal");
doc.createImage(fs.readFileSync("./demo/images/pizza.gif")); doc.createImage(fs.readFileSync("./demo/images/pizza.gif"));
doc doc.createParagraph("HEADING")
.createParagraph("HEADING")
.heading1() .heading1()
.center(); .center();
@ -111,8 +110,8 @@ const table = new Table(4, 4);
table table
.getRow(0) .getRow(0)
.getCell(0) .getCell(0)
.addContent(new Paragraph("Pole No.")); .addParagraph(new Paragraph("Pole No."));
// table.Properties.width = 10000;
doc.addTable(table); doc.addTable(table);
const arrboth = [ const arrboth = [
@ -129,8 +128,6 @@ const arrboth = [
arrboth.forEach((item) => { arrboth.forEach((item) => {
const para = doc.createParagraph(); const para = doc.createParagraph();
para.addImage(doc.createImage(fs.readFileSync(item.image))); para.addImage(doc.createImage(fs.readFileSync(item.image)));
// para.Properties.width = 60;
// para.Properties.height = 90;
doc.createParagraph(item.comment).style("normalPara2"); doc.createParagraph(item.comment).style("normalPara2");
}); });

View File

@ -1,7 +1,7 @@
// Multiple sections and headers // Multiple sections and headers
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, PageNumberFormat, PageOrientation, Paragraph } from "../build"; import { Document, Packer, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build";
const doc = new Document(); const doc = new Document();
@ -41,6 +41,40 @@ doc.addSection({
doc.createParagraph("hello in landscape"); doc.createParagraph("hello in landscape");
const header2 = doc.createHeader();
const pageNumber = new TextRun("Page number: ").pageNumber();
header2.createParagraph().addRun(pageNumber);
doc.addSection({
headers: {
default: header2,
},
orientation: PageOrientation.PORTRAIT,
});
doc.createParagraph("Page number in the header must be 2, because it continues from the previous section.");
doc.addSection({
headers: {
default: header2,
},
pageNumberFormatType: PageNumberFormat.UPPER_ROMAN,
orientation: PageOrientation.PORTRAIT,
});
doc.createParagraph("Page number in the header must be III, because it continues from the previous section, but is defined as upper roman.");
doc.addSection({
headers: {
default: header2,
},
pageNumberFormatType: PageNumberFormat.DECIMAL,
pageNumberStart: 25,
orientation: PageOrientation.PORTRAIT,
});
doc.createParagraph("Page number in the header must be 25, because it is defined to start at 25 and to be decimal in this section.");
const packer = new Packer(); const packer = new Packer();
packer.toBuffer(doc).then((buffer) => { packer.toBuffer(doc).then((buffer) => {

View File

@ -8,8 +8,8 @@ const doc = new Document();
const table = doc.createTable(4, 4); const table = doc.createTable(4, 4);
table table
.getCell(2, 2) .getCell(2, 2)
.addContent(new Paragraph("Hello")) .addParagraph(new Paragraph("Hello"))
.CellProperties.Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red") .Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red")
.addBottomBorder(BorderStyle.DOUBLE, 3, "blue") .addBottomBorder(BorderStyle.DOUBLE, 3, "blue")
.addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green") .addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green")
.addEndBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000"); .addEndBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000");

View File

@ -16,7 +16,7 @@ paragraph2.addRun(textRun2);
doc.addParagraph(paragraph2); doc.addParagraph(paragraph2);
const paragraph3 = new Paragraph().bidirectional(); const paragraph3 = new Paragraph().bidirectional();
const textRun3 = new TextRun("שלום עולם").italic().rightToLeft(); const textRun3 = new TextRun("שלום עולם").italics().rightToLeft();
paragraph3.addRun(textRun3); paragraph3.addRun(textRun3);
doc.addParagraph(paragraph3); doc.addParagraph(paragraph3);

View File

@ -6,10 +6,10 @@ import { Document, Media, Packer, Paragraph } from "../build";
const doc = new Document(); const doc = new Document();
const table = doc.createTable(4, 4); const table = doc.createTable(4, 4);
table.getCell(2, 2).addContent(new Paragraph("Hello")); table.getCell(2, 2).addParagraph(new Paragraph("Hello"));
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg")); const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
table.getCell(1, 1).addContent(image.Paragraph); table.getCell(1, 1).addParagraph(image.Paragraph);
const packer = new Packer(); const packer = new Packer();

View File

@ -8,12 +8,12 @@ const doc = new Document();
const table = doc.createTable(2, 2); const table = doc.createTable(2, 2);
table table
.getCell(1, 1) .getCell(1, 1)
.addContent(new Paragraph("This text should be in the middle of the cell")) .addParagraph(new Paragraph("This text should be in the middle of the cell"))
.CellProperties.setVerticalAlign(VerticalAlign.CENTER); .setVerticalAlign(VerticalAlign.CENTER);
table table
.getCell(1, 0) .getCell(1, 0)
.addContent( .addParagraph(
new Paragraph( 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", "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(), ).heading1(),

View File

@ -1,4 +1,4 @@
// Example of how you would create a table and add data to it // Example of how you would merge cells together
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph } from "../build"; import { Document, Packer, Paragraph } from "../build";
@ -7,24 +7,24 @@ const doc = new Document();
let table = doc.createTable(2, 2); let table = doc.createTable(2, 2);
table.getCell(0, 0).addContent(new Paragraph("Hello")); table.getCell(0, 0).addParagraph(new Paragraph("Hello"));
table.getRow(0).mergeCells(0, 1); table.getRow(0).mergeCells(0, 1);
doc.createParagraph("Another table").heading2(); doc.createParagraph("Another table").heading2();
table = doc.createTable(2, 3); table = doc.createTable(2, 3);
table.getCell(0, 0).addContent(new Paragraph("World")); table.getCell(0, 0).addParagraph(new Paragraph("World"));
table.getRow(0).mergeCells(0, 2); table.getRow(0).mergeCells(0, 2);
doc.createParagraph("Another table").heading2(); doc.createParagraph("Another table").heading2();
table = doc.createTable(2, 4); table = doc.createTable(2, 4);
table.getCell(0, 0).addContent(new Paragraph("Foo")); table.getCell(0, 0).addParagraph(new Paragraph("Foo"));
table.getCell(1, 0).addContent(new Paragraph("Bar1")); table.getCell(1, 0).addParagraph(new Paragraph("Bar1"));
table.getCell(1, 1).addContent(new Paragraph("Bar2")); table.getCell(1, 1).addParagraph(new Paragraph("Bar2"));
table.getCell(1, 2).addContent(new Paragraph("Bar3")); table.getCell(1, 2).addParagraph(new Paragraph("Bar3"));
table.getCell(1, 3).addContent(new Paragraph("Bar4")); table.getCell(1, 3).addParagraph(new Paragraph("Bar4"));
table.getRow(0).mergeCells(0, 3); table.getRow(0).mergeCells(0, 3);

View File

@ -20,9 +20,9 @@ const table = doc.createTable(2, 2).float({
relativeVerticalPosition: RelativeVerticalPosition.BOTTOM, relativeVerticalPosition: RelativeVerticalPosition.BOTTOM,
}); });
table.setFixedWidthLayout(); table.setFixedWidthLayout();
table.setWidth(WidthType.DXA, 4535); table.setWidth(4535, WidthType.DXA);
table.getCell(0, 0).addContent(new Paragraph("Hello")); table.getCell(0, 0).addParagraph(new Paragraph("Hello"));
table.getRow(0).mergeCells(0, 1); table.getRow(0).mergeCells(0, 1);
const packer = new Packer(); const packer = new Packer();

17
demo/demo35.ts Normal file
View File

@ -0,0 +1,17 @@
// Simple example to add text to a document
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, Paragraph, TextRun } from "../build";
var doc = new Document();
var paragraph = new Paragraph();
var link = doc.createHyperlink('http://www.example.com', 'Hyperlink');
link.bold();
paragraph.addHyperLink(link);
doc.addParagraph(paragraph);
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

23
demo/demo36.ts Normal file
View File

@ -0,0 +1,23 @@
// Add images to header and footer
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Media, Packer, Table } from "../build";
const doc = new Document();
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
const table = new Table(2, 2);
table.getCell(1, 1).addContent(image.Paragraph);
// doc.createParagraph("Hello World");
doc.addTable(table);
// doc.Header.createImage(fs.readFileSync("./demo/images/pizza.gif"));
doc.Header.addTable(table);
// doc.Footer.createImage(fs.readFileSync("./demo/images/pizza.gif"));
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

18
demo/demo37.ts Normal file
View File

@ -0,0 +1,18 @@
// Add images to header and footer
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Media, Packer } from "../build";
const doc = new Document();
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
doc.createParagraph("Hello World");
doc.Header.addImage(image);
doc.Header.createImage(fs.readFileSync("./demo/images/pizza.gif"));
doc.Header.createImage(fs.readFileSync("./demo/images/image1.jpeg"));
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -6,7 +6,7 @@ import { Document, Packer, Paragraph } from "../build";
const doc = new Document(); const doc = new Document();
const table = doc.createTable(4, 4); const table = doc.createTable(4, 4);
table.getCell(2, 2).addContent(new Paragraph("Hello")); table.getCell(2, 2).addParagraph(new Paragraph("Hello"));
const packer = new Packer(); const packer = new Packer();

View File

@ -29,6 +29,7 @@ import * as docx from "docx";
## Basic Usage ## Basic Usage
```js ```js
var fs = require("fs");
var docx = require("docx"); var docx = require("docx");
// Create document // Create document
@ -41,11 +42,12 @@ paragraph.addRun(new docx.TextRun("Lorem Ipsum Foo Bar"));
doc.addParagraph(paragraph); doc.addParagraph(paragraph);
// Used to export the file into a .docx file // Used to export the file into a .docx file
var exporter = new docx.LocalPacker(doc); var packer = new docx.Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My First Document.docx", buffer);
});
exporter.pack("My First Document"); // Done! A file called 'My First Document.docx' will be in your file system.
// Done! A file called 'My First Document.docx' will be in your file system if you used LocalPacker
``` ```
## Honoured Mentions ## Honoured Mentions

View File

@ -12,7 +12,7 @@
## Always think about the user ## Always think about the user
The number one pillar for contribution is to **ALWAYS** think about how the user will use the library. The number one pillar for contribution to `docx` is to **ALWAYS** think about how the user will use `docx`.
Put yourself in their position, and imagine how they would feel about your feature you wrote. Put yourself in their position, and imagine how they would feel about your feature you wrote.
@ -37,13 +37,13 @@ Unesesary coment removed // Make sure to use correct spelling
## No leaky components in API interface ## No leaky components in API interface
This mainly applies to the API the end user will consume. > This mainly applies to the API the end user will consume.
Try to make method parameters accept primatives, or `json` objects, so that child components are created **inside** the component, rather than being **injected** in. Try to make method parameters of the outside API accept primatives, or `json` objects, so that child components are created **inside** the component, rather than being **injected** in.
This is so that: This is so that:
1. Imports are much cleaner, no need for: 1. Imports are much cleaner for the end user, no need for:
```js ```js
import { ChildComponent } from "./my-feature/sub-component/deeper/.../my-deep.component"; import { ChildComponent } from "./my-feature/sub-component/deeper/.../my-deep.component";
``` ```
@ -52,13 +52,17 @@ This is so that:
3. It means the end user does not need to import and create the child component to be injected. 3. It means the end user does not need to import and create the child component to be injected.
**Do not** **Do not**
`TableFloatProperties` is a class. The outside world would have to construct the object, and inject it in
`TableFloatProperties` is a class. The outside world would have to `new` up the object, and inject it in like so:
```js ```js
public float(tableFloatProperties: TableFloatProperties): Table public float(tableFloatProperties: TableFloatProperties): Table
``` ```
**Do** **Do**
`ITableFloatOptions` is an interface for a JSON of primatives.
`ITableFloatOptions` is an interface for a JSON of primatives. The end user would need to pass in a json object and not need to worry about the internals:
```js ```js
public float(tableFloatOptions: ITableFloatOptions): Table public float(tableFloatOptions: ITableFloatOptions): Table
``` ```
@ -76,7 +80,8 @@ This is just a guideline, and the rules can sometimes be broken.
} }
``` ```
* Use `add` if you add the element into the method as a parameter: * Use `add` if you add the element into the method as a parameter.
*Note:* This may look like its breaking the previous guideline, but it has semantically different meanings. The previous one is using data to construct an object, whereas this one is simply adding elements into the document:
```js ```js
public addParagraph(paragraph: Paragraph) { public addParagraph(paragraph: Paragraph) {

View File

@ -158,7 +158,8 @@ _Source: https://github.com/dolanmiu/docx/blob/master/demo/demo15.ts_
## Sections ## Sections
Example of how sections work. Sections allow multiple headers and footers, and `landscape`/`portrait` inside the same document Example of how sections work. Sections allow multiple headers and footers, and `landscape`/`portrait` inside the same document.
Also you can have different page number formats and starts for different sections.
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo16.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo16.ts ":include")

View File

@ -25,6 +25,7 @@
<script src="//unpkg.com/docsify/lib/plugins/emoji.min.js"></script> <script src="//unpkg.com/docsify/lib/plugins/emoji.min.js"></script>
<script src="https://unpkg.com/docsify-copy-code@2"></script> <script src="https://unpkg.com/docsify-copy-code@2"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script> <script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-typescript.min.js"></script>
</body> </body>
</html> </html>

View File

@ -5,7 +5,7 @@
To create a new document, it is very easy: To create a new document, it is very easy:
```js ```js
var doc = new docx.Document(); const doc = new docx.Document();
``` ```
## Document properties ## Document properties
@ -13,7 +13,7 @@ var doc = new docx.Document();
You can add properties to the Word document by specifying options, for example: You can add properties to the Word document by specifying options, for example:
```js ```js
var doc = new docx.Document({ const doc = new docx.Document({
creator: "Dolan Miu", creator: "Dolan Miu",
description: "My extremely interesting document", description: "My extremely interesting document",
title: "My Document", title: "My Document",
@ -22,14 +22,18 @@ var doc = new docx.Document({
### Full list of options: ### Full list of options:
```
creator * creator
description * description
title * title
subject * subject
keywords * keywords
lastModifiedBy * lastModifiedBy
revision * revision
```
You can mix and match whatever properties you want, or provide no properties. You can mix and match whatever properties you want, or provide no properties.
### units for positioning
Various parts of the API require positioning arguments. The units are "20ths of a point" from the [OOXML](http://officeopenxml.com/index.php) specification.
See [Lars Corneliussen's blog post](https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/) for more information and how to convert units.

View File

@ -37,8 +37,12 @@ Also all the supported section properties are implemented according to: http://o
// Add new section with another header and footer // Add new section with another header and footer
doc.addSection({ doc.addSection({
headerId: header.Header.ReferenceId, headers: {
footerId: footer.Footer.ReferenceId, default: header
},
footers: {
default: footer
},
pageNumberStart: 1, pageNumberStart: 1,
pageNumberFormatType: docx.PageNumberFormat.DECIMAL, pageNumberFormatType: docx.PageNumberFormat.DECIMAL,
}); });

View File

@ -14,7 +14,7 @@ const name = new TextRun("Name:")
## Available methods ## Available methods
* For run formatting: * For run formatting:
* `.bold()`, `.italic()`, `.smallCaps()`, `.allCaps()`, `.strike()`, `.doubleStrike()`, `.subScript()`, `.superScript()`: Set the formatting property to true * `.bold()`, `.italics()`, `.smallCaps()`, `.allCaps()`, `.strike()`, `.doubleStrike()`, `.subScript()`, `.superScript()`: Set the formatting property to true
* `.underline(style="single", color=null)`: Set the underline style and color * `.underline(style="single", color=null)`: Set the underline style and color
* `.color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`) * `.color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`)
* `.size(halfPts)`: Set the font size, measured in half-points * `.size(halfPts)`: Set the font size, measured in half-points

View File

@ -2,7 +2,7 @@
> Tab stops are useful, if you are unclear of what they are, [here is a link explaining](https://en.wikipedia.org/wiki/Tab_stop). It enables side by side text which is nicely laid out without the need for tables, or constantly pressing space bar. > Tab stops are useful, if you are unclear of what they are, [here is a link explaining](https://en.wikipedia.org/wiki/Tab_stop). It enables side by side text which is nicely laid out without the need for tables, or constantly pressing space bar.
**Note**: At the moment, the unit of measurement for a tab stop is counter intuitive for a human. It is using OpenXMLs own measuring system. For example, 2268 roughly translates to 3cm. Therefore in the future, I may consider changing it to percentages or even cm. !> **Note**: At the moment, the unit of measurement for a tab stop is counter intuitive for a human. It is using OpenXMLs own measuring system. For example, 2268 roughly translates to 3cm. Therefore in the future, I may consider changing it to percentages or even cm.
![Word 2013 Tabs](http://www.teachucomp.com/wp-content/uploads/blog-4-22-2015-UsingTabStopsInWord-1024x577.png "Word 2013 Tab Stops") ![Word 2013 Tabs](http://www.teachucomp.com/wp-content/uploads/blog-4-22-2015-UsingTabStopsInWord-1024x577.png "Word 2013 Tab Stops")

154
docs/usage/tables.md Normal file
View File

@ -0,0 +1,154 @@
# Tables
You can create tables with `docx`. More information can be found [here](http://officeopenxml.com/WPtable.php).
## Create Table
To create a table, simply use the `createTable()` method on a `document`.
```ts
const table = doc.createTable([NUMBER OF ROWS], [NUMBER OF COLUMNS]);
```
Alternatively, you can create a table object directly, and then add it in the `document`
```ts
const table = new Table(4, 4);
doc.addTable(table);
```
The snippet below creates a table of 2 rows and 4 columns.
```ts
const table = doc.createTable(2, 4);
// Or
const table = new Table(2, 4);
doc.addTable(table);
```
## Cells
The above section created a table with cells. To access the cell, use the `getCell` method.
```ts
const cell = table.getCell([ROW INDEX], [COLUMN INDEX]);
```
For example:
```ts
const cell = table.getCell(0, 2);
```
### Add paragraph to a cell
Once you have got the cell, you can add data to it with the `addParagraph` method.
```ts
cell.addParagraph(new Paragraph("Hello"));
```
## Borders
BorderStyle can be imported from `docx`. Size determines the thickness. HTML color can be a hex code or alias such as `red`.
```ts
cell.Borders.addTopBorder([BorderStyle], [SIZE], [HTML COLOR]);
```
```ts
cell.Borders.addBottomBorder([BorderStyle], [SIZE], [HTML COLOR]);
```
```ts
cell.Borders.addStartBorder([[BorderStyle]], [SIZE], [HTML COLOR]);
```
```ts
cell.Borders.addEndBorder([BorderStyle], [SIZE], [HTML COLOR]);
```
### Example
```ts
import { BorderStyle } from "docx";
cell.Borders.addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green");
cell.Borders.addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000");
```
## Set Width
```ts
import { WidthType } from "docx";
table.setWidth([WIDTH], [OPTIONAL WidthType. Defaults to DXA]);
```
For example:
```ts
table.setWidth(4535, WidthType.DXA);
```
## Vertical Align
Sets the vertical alignment of the contents of the cell
```ts
import { VerticalAlign } from "docx";
cell.setVerticalAlign([VerticalAlign TYPE]);
```
For example, to center align a cell:
```ts
cell.setVerticalAlign(VerticalAlign.CENTER);
```
## Rows
To get a row, use the `getRow` method on a `table`. There are a handful of methods which you can apply to a row which will be explained below.
```ts
table.getRow([ROW INDEX]);
```
## Merge cells together
### Merging on a row
First obtain the row, and call `mergeCells()`. The first argument is where the merge should start. The second argument is where the merge should end.
```ts
table.getRow(0).mergeCells([FROM INDEX], [TO INDEX]);
```
#### Example
This will merge 3 cells together starting from index `0`:
```ts
table.getRow(0).mergeCells(0, 2);
```
### Merging on a column
It has not been implemented yet, but it will follow a similar structure as merging a row.
## Nested Tables
To have a table within a table
```ts
cell.addTable(new Table(1, 1));
```
## Examples
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo4.ts ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo4.ts_

View File

@ -22,7 +22,7 @@ text.bold();
### Italics ### Italics
```js ```js
text.italic(); text.italics();
``` ```
### Underline ### Underline
@ -80,5 +80,5 @@ text.break();
What if you want to create a paragraph which is **_bold_** and **_italic_**? What if you want to create a paragraph which is **_bold_** and **_italic_**?
```js ```js
paragraph.bold().italic(); paragraph.bold().italics();
``` ```

View File

@ -73,7 +73,7 @@
"mocha-webpack": "^1.0.1", "mocha-webpack": "^1.0.1",
"nyc": "^13.1.0", "nyc": "^13.1.0",
"pre-commit": "^1.2.2", "pre-commit": "^1.2.2",
"prettier": "^1.12.1", "prettier": "^1.15.2",
"prompt": "^1.0.0", "prompt": "^1.0.0",
"replace-in-file": "^3.1.0", "replace-in-file": "^3.1.0",
"rimraf": "^2.5.2", "rimraf": "^2.5.2",

View File

@ -0,0 +1,17 @@
import { IMediaData, Media } from "file/media";
export class ImageReplacer {
public replace(xmlData: string, mediaData: IMediaData[], offset: number): string {
let currentXmlData = xmlData;
mediaData.forEach((image, i) => {
currentXmlData = currentXmlData.replace(`{${image.fileName}}`, (offset + i).toString());
});
return currentXmlData;
}
public getMediaData(xmlData: string, media: Media): IMediaData[] {
return media.Array.filter((image) => xmlData.search(`{${image.fileName}}`) > 0);
}
}

View File

@ -17,7 +17,7 @@ describe("Compiler", () => {
describe("#compile()", () => { describe("#compile()", () => {
it("should pack all the content", async function() { it("should pack all the content", async function() {
this.timeout(99999999); this.timeout(99999999);
const zipFile = await compiler.compile(file); const zipFile = compiler.compile(file);
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array); expect(fileNames).is.an.instanceof(Array);
@ -46,7 +46,7 @@ describe("Compiler", () => {
this.timeout(99999999); this.timeout(99999999);
const zipFile = await compiler.compile(file); const zipFile = compiler.compile(file);
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name); const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array); expect(fileNames).is.an.instanceof(Array);

View File

@ -3,6 +3,7 @@ import * as xml from "xml";
import { File } from "file"; import { File } from "file";
import { Formatter } from "../formatter"; import { Formatter } from "../formatter";
import { ImageReplacer } from "./image-replacer";
interface IXmlifyedFile { interface IXmlifyedFile {
readonly data: string; readonly data: string;
@ -28,14 +29,15 @@ interface IXmlifyedFileMapping {
export class Compiler { export class Compiler {
private readonly formatter: Formatter; private readonly formatter: Formatter;
private readonly imageReplacer: ImageReplacer;
constructor() { constructor() {
this.formatter = new Formatter(); this.formatter = new Formatter();
this.imageReplacer = new ImageReplacer();
} }
public async compile(file: File): Promise<JSZip> { public compile(file: File): JSZip {
const zip = new JSZip(); const zip = new JSZip();
const xmlifiedFileMapping = this.xmlifyFile(file); const xmlifiedFileMapping = this.xmlifyFile(file);
for (const key in xmlifiedFileMapping) { for (const key in xmlifiedFileMapping) {
@ -59,26 +61,39 @@ export class Compiler {
zip.file(`word/media/${data.fileName}`, mediaData); zip.file(`word/media/${data.fileName}`, mediaData);
} }
for (const header of file.Headers) {
for (const data of header.Media.Array) {
zip.file(`word/media/${data.fileName}`, data.stream);
}
}
for (const footer of file.Footers) {
for (const data of footer.Media.Array) {
zip.file(`word/media/${data.fileName}`, data.stream);
}
}
return zip; return zip;
} }
private xmlifyFile(file: File): IXmlifyedFileMapping { private xmlifyFile(file: File): IXmlifyedFileMapping {
file.verifyUpdateFields(); file.verifyUpdateFields();
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
return { return {
Relationships: {
data: (() => {
const xmlData = xml(this.formatter.format(file.Document));
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
file.DocumentRelationships.createRelationship(
documentRelationshipCount + i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
});
return xml(this.formatter.format(file.DocumentRelationships));
})(),
path: "word/_rels/document.xml.rels",
},
Document: { Document: {
data: xml(this.formatter.format(file.Document), true), data: (() => {
const tempXmlData = xml(this.formatter.format(file.Document), true);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, documentRelationshipCount);
return xmlData;
})(),
path: "word/document.xml", path: "word/document.xml",
}, },
Styles: { Styles: {
@ -98,30 +113,66 @@ export class Compiler {
data: xml(this.formatter.format(file.Numbering)), data: xml(this.formatter.format(file.Numbering)),
path: "word/numbering.xml", path: "word/numbering.xml",
}, },
Relationships: {
data: xml(this.formatter.format(file.DocumentRelationships)),
path: "word/_rels/document.xml.rels",
},
FileRelationships: { FileRelationships: {
data: xml(this.formatter.format(file.FileRelationships)), data: xml(this.formatter.format(file.FileRelationships)),
path: "_rels/.rels", path: "_rels/.rels",
}, },
Headers: file.Headers.map((headerWrapper, index) => ({ HeaderRelationships: file.Headers.map((headerWrapper, index) => {
data: xml(this.formatter.format(headerWrapper.Header)), const xmlData = xml(this.formatter.format(headerWrapper.Header));
path: `word/header${index + 1}.xml`, const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
})),
Footers: file.Footers.map((footerWrapper, index) => ({ mediaDatas.forEach((mediaData, i) => {
data: xml(this.formatter.format(footerWrapper.Footer)), headerWrapper.Relationships.createRelationship(
path: `word/footer${index + 1}.xml`, i,
})), "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
HeaderRelationships: file.Headers.map((headerWrapper, index) => ({ `media/${mediaData.fileName}`,
);
});
return {
data: xml(this.formatter.format(headerWrapper.Relationships)), data: xml(this.formatter.format(headerWrapper.Relationships)),
path: `word/_rels/header${index + 1}.xml.rels`, path: `word/_rels/header${index + 1}.xml.rels`,
})), };
FooterRelationships: file.Footers.map((footerWrapper, index) => ({ }),
FooterRelationships: file.Footers.map((footerWrapper, index) => {
const xmlData = xml(this.formatter.format(footerWrapper.Footer));
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
footerWrapper.Relationships.createRelationship(
i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
});
return {
data: xml(this.formatter.format(footerWrapper.Relationships)), data: xml(this.formatter.format(footerWrapper.Relationships)),
path: `word/_rels/footer${index + 1}.xml.rels`, path: `word/_rels/footer${index + 1}.xml.rels`,
})), };
}),
Headers: file.Headers.map((headerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(headerWrapper.Header));
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
return {
data: xmlData,
path: `word/header${index + 1}.xml`,
};
}),
Footers: file.Footers.map((footerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(footerWrapper.Footer));
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
return {
data: xmlData,
path: `word/footer${index + 1}.xml`,
};
}),
ContentTypes: { ContentTypes: {
data: xml(this.formatter.format(file.ContentTypes)), data: xml(this.formatter.format(file.ContentTypes)),
path: "[Content_Types].xml", path: "[Content_Types].xml",

View File

@ -46,4 +46,24 @@ describe("Packer", () => {
}); });
}); });
}); });
describe("#toBase64String()", () => {
it("should create a standard docx file", async function() {
this.timeout(99999999);
const str = await packer.toBase64String(file);
assert.isDefined(str);
assert.isTrue(str.length > 0);
});
it("should handle exception if it throws any", () => {
// tslint:disable-next-line:no-any
const compiler = stub((packer as any).compiler, "compile");
compiler.throwsException();
return packer.toBase64String(file).catch((error) => {
assert.isDefined(error);
});
});
});
}); });

View File

@ -9,7 +9,7 @@ export class Packer {
} }
public async toBuffer(file: File): Promise<Buffer> { public async toBuffer(file: File): Promise<Buffer> {
const zip = await this.compiler.compile(file); const zip = this.compiler.compile(file);
const zipData = (await zip.generateAsync({ const zipData = (await zip.generateAsync({
type: "nodebuffer", type: "nodebuffer",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@ -19,7 +19,7 @@ export class Packer {
} }
public async toBase64String(file: File): Promise<string> { public async toBase64String(file: File): Promise<string> {
const zip = await this.compiler.compile(file); const zip = this.compiler.compile(file);
const zipData = (await zip.generateAsync({ const zipData = (await zip.generateAsync({
type: "base64", type: "base64",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@ -29,7 +29,7 @@ export class Packer {
} }
public async toBlob(file: File): Promise<Blob> { public async toBlob(file: File): Promise<Blob> {
const zip = await this.compiler.compile(file); const zip = this.compiler.compile(file);
const zipData = (await zip.generateAsync({ const zipData = (await zip.generateAsync({
type: "blob", type: "blob",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",

View File

@ -17,7 +17,7 @@ describe("Body", () => {
expect(formatted) expect(formatted)
.to.have.property("w:sectPr") .to.have.property("w:sectPr")
.and.to.be.an.instanceof(Array); .and.to.be.an.instanceof(Array);
expect(formatted["w:sectPr"]).to.have.length(5); expect(formatted["w:sectPr"]).to.have.length(4);
}); });
}); });
@ -76,7 +76,6 @@ describe("Body", () => {
}, },
{ "w:cols": [{ _attr: { "w:space": 708 } }] }, { "w:cols": [{ _attr: { "w:space": 708 } }] },
{ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] }, { "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] },
{ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] },
], ],
}, },
], ],
@ -104,7 +103,6 @@ describe("Body", () => {
}, },
{ "w:cols": [{ _attr: { "w:space": 708 } }] }, { "w:cols": [{ _attr: { "w:space": 708 } }] },
{ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] }, { "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] },
{ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] },
], ],
}, },
], ],

View File

@ -88,7 +88,6 @@ describe("SectionProperties", () => {
}); });
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": [{ _attr: { "w:space": 708 } }] }); expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": [{ _attr: { "w:space": 708 } }] });
expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] }); expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] });
expect(tree["w:sectPr"][4]).to.deep.equal({ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] });
}); });
it("should create section properties with changed options", () => { it("should create section properties with changed options", () => {
@ -183,5 +182,25 @@ describe("SectionProperties", () => {
"w:pgBorders": [{ _attr: { "w:offsetFrom": "page" } }], "w:pgBorders": [{ _attr: { "w:offsetFrom": "page" } }],
}); });
}); });
it("should create section properties with page number type, but without start attribute", () => {
const properties = new SectionProperties({
pageNumberFormatType: PageNumberFormat.UPPER_ROMAN,
});
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);
const pgNumType = tree["w:sectPr"].find((item) => item["w:pgNumType"] !== undefined);
expect(pgNumType).to.deep.equal({
"w:pgNumType": [{ _attr: { "w:fmt": "upperRoman" } }],
});
});
it("should create section properties without page number type", () => {
const properties = new SectionProperties({});
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);
const pgNumType = tree["w:sectPr"].find((item) => item["w:pgNumType"] !== undefined);
expect(pgNumType).to.equal(undefined);
});
}); });
}); });

View File

@ -14,7 +14,7 @@ import { HeaderReference } from "./header-reference/header-reference";
import { IPageBordersOptions, PageBorders } from "./page-border"; import { IPageBordersOptions, PageBorders } from "./page-border";
import { PageMargin } from "./page-margin/page-margin"; import { PageMargin } from "./page-margin/page-margin";
import { IPageMarginAttributes } from "./page-margin/page-margin-attributes"; import { IPageMarginAttributes } from "./page-margin/page-margin-attributes";
import { IPageNumberTypeAttributes, PageNumberFormat, PageNumberType } from "./page-number"; import { IPageNumberTypeAttributes, PageNumberType } from "./page-number";
import { PageSize } from "./page-size/page-size"; import { PageSize } from "./page-size/page-size";
import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes"; import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes";
import { TitlePage } from "./title-page/title-page"; import { TitlePage } from "./title-page/title-page";
@ -69,7 +69,7 @@ export class SectionProperties extends XmlComponent {
orientation = PageOrientation.PORTRAIT, orientation = PageOrientation.PORTRAIT,
headers, headers,
footers, footers,
pageNumberFormatType = PageNumberFormat.DECIMAL, pageNumberFormatType,
pageNumberStart, pageNumberStart,
pageBorders, pageBorders,
pageBorderTop, pageBorderTop,
@ -88,7 +88,9 @@ export class SectionProperties extends XmlComponent {
this.addHeaders(headers); this.addHeaders(headers);
this.addFooters(footers); this.addFooters(footers);
if (pageNumberStart || pageNumberFormatType) {
this.root.push(new PageNumberType(pageNumberStart, pageNumberFormatType)); this.root.push(new PageNumberType(pageNumberStart, pageNumberFormatType));
}
if (pageBorders || pageBorderTop || pageBorderRight || pageBorderBottom || pageBorderLeft) { if (pageBorders || pageBorderTop || pageBorderRight || pageBorderBottom || pageBorderLeft) {
this.root.push( this.root.push(

View File

@ -8,7 +8,20 @@ import { Anchor } from "./anchor";
function createAnchor(drawingOptions: IDrawingOptions): Anchor { function createAnchor(drawingOptions: IDrawingOptions): Anchor {
return new Anchor( return new Anchor(
1, {
fileName: "test.png",
stream: new Buffer(""),
dimensions: {
pixels: {
x: 0,
y: 0,
},
emus: {
x: 0,
y: 0,
},
},
},
{ {
pixels: { pixels: {
x: 100, x: 100,

View File

@ -1,5 +1,5 @@
// http://officeopenxml.com/drwPicFloating.php // http://officeopenxml.com/drwPicFloating.php
import { IMediaDataDimensions } from "file/media"; import { IMediaData, IMediaDataDimensions } from "file/media";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { IDrawingOptions } from "../drawing"; import { IDrawingOptions } from "../drawing";
import { HorizontalPosition, IFloating, SimplePos, VerticalPosition } from "../floating"; import { HorizontalPosition, IFloating, SimplePos, VerticalPosition } from "../floating";
@ -21,7 +21,7 @@ const defaultOptions: IFloating = {
}; };
export class Anchor extends XmlComponent { export class Anchor extends XmlComponent {
constructor(referenceId: number, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) { constructor(mediaData: IMediaData, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
super("wp:anchor"); super("wp:anchor");
const floating = { const floating = {
@ -70,6 +70,6 @@ export class Anchor extends XmlComponent {
this.root.push(new DocProperties()); this.root.push(new DocProperties());
this.root.push(new GraphicFrameProperties()); this.root.push(new GraphicFrameProperties());
this.root.push(new Graphic(referenceId, dimensions.emus.x, dimensions.emus.y)); this.root.push(new Graphic(mediaData, dimensions.emus.x, dimensions.emus.y));
} }
} }

View File

@ -11,7 +11,6 @@ function createDrawing(drawingOptions?: IDrawingOptions): Drawing {
return new Drawing( return new Drawing(
{ {
fileName: "test.jpg", fileName: "test.jpg",
referenceId: 1,
stream: Buffer.from(imageBase64Data, "base64"), stream: Buffer.from(imageBase64Data, "base64"),
path: path, path: path,
dimensions: { dimensions: {

View File

@ -28,10 +28,10 @@ export class Drawing extends XmlComponent {
} }
if (!drawingOptions.floating) { if (!drawingOptions.floating) {
this.inline = new Inline(imageData.referenceId, imageData.dimensions); this.inline = new Inline(imageData, imageData.dimensions);
this.root.push(this.inline); this.root.push(this.inline);
} else { } else {
this.root.push(new Anchor(imageData.referenceId, imageData.dimensions, drawingOptions)); this.root.push(new Anchor(imageData, imageData.dimensions, drawingOptions));
} }
} }

View File

@ -1,11 +1,13 @@
import { IMediaData } from "file/media";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { GraphicDataAttributes } from "./graphic-data-attribute"; import { GraphicDataAttributes } from "./graphic-data-attribute";
import { Pic } from "./pic"; import { Pic } from "./pic";
export class GraphicData extends XmlComponent { export class GraphicData extends XmlComponent {
private readonly pic: Pic; private readonly pic: Pic;
constructor(referenceId: number, x: number, y: number) { constructor(mediaData: IMediaData, x: number, y: number) {
super("a:graphicData"); super("a:graphicData");
this.root.push( this.root.push(
@ -14,7 +16,7 @@ export class GraphicData extends XmlComponent {
}), }),
); );
this.pic = new Pic(referenceId, x, y); this.pic = new Pic(mediaData, x, y);
this.root.push(this.pic); this.root.push(this.pic);
} }

View File

@ -1,12 +1,15 @@
import { IMediaData } from "file/media";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { Blip } from "./blip"; import { Blip } from "./blip";
import { SourceRectangle } from "./source-rectangle"; import { SourceRectangle } from "./source-rectangle";
import { Stretch } from "./stretch"; import { Stretch } from "./stretch";
export class BlipFill extends XmlComponent { export class BlipFill extends XmlComponent {
constructor(referenceId: number) { constructor(mediaData: IMediaData) {
super("pic:blipFill"); super("pic:blipFill");
this.root.push(new Blip(referenceId));
this.root.push(new Blip(mediaData));
this.root.push(new SourceRectangle()); this.root.push(new SourceRectangle());
this.root.push(new Stretch()); this.root.push(new Stretch());
} }

View File

@ -1,3 +1,4 @@
import { IMediaData } from "file/media";
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
interface IBlipProperties { interface IBlipProperties {
@ -13,11 +14,11 @@ class BlipAttributes extends XmlAttributeComponent<IBlipProperties> {
} }
export class Blip extends XmlComponent { export class Blip extends XmlComponent {
constructor(referenceId: number) { constructor(mediaData: IMediaData) {
super("a:blip"); super("a:blip");
this.root.push( this.root.push(
new BlipAttributes({ new BlipAttributes({
embed: `rId${referenceId}`, embed: `rId{${mediaData.fileName}}`,
cstate: "none", cstate: "none",
}), }),
); );

View File

@ -1,5 +1,7 @@
// http://officeopenxml.com/drwPic.php // http://officeopenxml.com/drwPic.php
import { IMediaData } from "file/media";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { BlipFill } from "./blip/blip-fill"; import { BlipFill } from "./blip/blip-fill";
import { NonVisualPicProperties } from "./non-visual-pic-properties/non-visual-pic-properties"; import { NonVisualPicProperties } from "./non-visual-pic-properties/non-visual-pic-properties";
import { PicAttributes } from "./pic-attributes"; import { PicAttributes } from "./pic-attributes";
@ -8,7 +10,7 @@ import { ShapeProperties } from "./shape-properties/shape-properties";
export class Pic extends XmlComponent { export class Pic extends XmlComponent {
private readonly shapeProperties: ShapeProperties; private readonly shapeProperties: ShapeProperties;
constructor(referenceId: number, x: number, y: number) { constructor(mediaData: IMediaData, x: number, y: number) {
super("pic:pic"); super("pic:pic");
this.root.push( this.root.push(
@ -20,7 +22,7 @@ export class Pic extends XmlComponent {
this.shapeProperties = new ShapeProperties(x, y); this.shapeProperties = new ShapeProperties(x, y);
this.root.push(new NonVisualPicProperties()); this.root.push(new NonVisualPicProperties());
this.root.push(new BlipFill(referenceId)); this.root.push(new BlipFill(mediaData));
this.root.push(new ShapeProperties(x, y)); this.root.push(new ShapeProperties(x, y));
} }

View File

@ -1,4 +1,6 @@
import { IMediaData } from "file/media";
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { GraphicData } from "./graphic-data"; import { GraphicData } from "./graphic-data";
interface IGraphicProperties { interface IGraphicProperties {
@ -14,7 +16,7 @@ class GraphicAttributes extends XmlAttributeComponent<IGraphicProperties> {
export class Graphic extends XmlComponent { export class Graphic extends XmlComponent {
private readonly data: GraphicData; private readonly data: GraphicData;
constructor(referenceId: number, x: number, y: number) { constructor(mediaData: IMediaData, x: number, y: number) {
super("a:graphic"); super("a:graphic");
this.root.push( this.root.push(
new GraphicAttributes({ new GraphicAttributes({
@ -22,7 +24,7 @@ export class Graphic extends XmlComponent {
}), }),
); );
this.data = new GraphicData(referenceId, x, y); this.data = new GraphicData(mediaData, x, y);
this.root.push(this.data); this.root.push(this.data);
} }

View File

@ -1,5 +1,5 @@
// http://officeopenxml.com/drwPicInline.php // http://officeopenxml.com/drwPicInline.php
import { IMediaDataDimensions } from "file/media"; import { IMediaData, IMediaDataDimensions } from "file/media";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { DocProperties } from "./../doc-properties/doc-properties"; import { DocProperties } from "./../doc-properties/doc-properties";
import { EffectExtent } from "./../effect-extent/effect-extent"; import { EffectExtent } from "./../effect-extent/effect-extent";
@ -12,7 +12,7 @@ export class Inline extends XmlComponent {
private readonly extent: Extent; private readonly extent: Extent;
private readonly graphic: Graphic; private readonly graphic: Graphic;
constructor(referenceId: number, private readonly dimensions: IMediaDataDimensions) { constructor(readonly mediaData: IMediaData, private readonly dimensions: IMediaDataDimensions) {
super("wp:inline"); super("wp:inline");
this.root.push( this.root.push(
@ -25,7 +25,7 @@ export class Inline extends XmlComponent {
); );
this.extent = new Extent(dimensions.emus.x, dimensions.emus.y); this.extent = new Extent(dimensions.emus.x, dimensions.emus.y);
this.graphic = new Graphic(referenceId, dimensions.emus.x, dimensions.emus.y); this.graphic = new Graphic(mediaData, dimensions.emus.x, dimensions.emus.y);
this.root.push(this.extent); this.root.push(this.extent);
this.root.push(new EffectExtent()); this.root.push(new EffectExtent());

View File

@ -1,12 +1,15 @@
import { expect } from "chai"; import { expect } from "chai";
import * as sinon from "sinon";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { File } from "./file"; import { File } from "./file";
import { Paragraph } from "./paragraph";
import { Table } from "./table";
describe("File", () => { describe("File", () => {
describe("#constructor", () => { describe("#constructor", () => {
it("should create with correct headers", () => { it("should create with correct headers and footers", () => {
const doc = new File(); const doc = new File();
const header = doc.createHeader(); const header = doc.createHeader();
const footer = doc.createFooter(); const footer = doc.createFooter();
@ -26,6 +29,26 @@ describe("File", () => {
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"][0]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"][0]._attr["w:type"]).to.equal("default");
}); });
it("should create with first headers and footers", () => {
const doc = new File();
const header = doc.createHeader();
const footer = doc.createFooter();
doc.addSection({
headers: {
first: header,
},
footers: {
first: footer,
},
});
const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"][0]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"][0]._attr["w:type"]).to.equal("first");
});
it("should create with correct headers", () => { it("should create with correct headers", () => {
const doc = new File(); const doc = new File();
const header = doc.createHeader(); const header = doc.createHeader();
@ -55,4 +78,88 @@ describe("File", () => {
expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"][0]._attr["w:type"]).to.equal("even"); expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"][0]._attr["w:type"]).to.equal("even");
}); });
}); });
describe("#addParagraph", () => {
it("should call the underlying document's addParagraph", () => {
const file = new File();
const spy = sinon.spy(file.Document, "addParagraph");
file.addParagraph(new Paragraph());
expect(spy.called).to.equal(true);
});
});
describe("#addTable", () => {
it("should call the underlying document's addTable", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.Document, "addTable");
wrapper.addTable(new Table(1, 1));
expect(spy.called).to.equal(true);
});
});
describe("#createTable", () => {
it("should call the underlying document's createTable", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.Document, "createTable");
wrapper.createTable(1, 1);
expect(spy.called).to.equal(true);
});
});
describe("#addTableOfContents", () => {
it("should call the underlying document's addTableOfContents", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.Document, "addTableOfContents");
// tslint:disable-next-line:no-any
wrapper.addTableOfContents({} as any);
expect(spy.called).to.equal(true);
});
});
describe("#createParagraph", () => {
it("should call the underlying document's createParagraph", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.Document, "createParagraph");
wrapper.createParagraph("test");
expect(spy.called).to.equal(true);
});
});
describe("#addImage", () => {
it("should call the underlying document's addImage", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.Document, "addParagraph");
// tslint:disable-next-line:no-any
wrapper.addImage({} as any);
expect(spy.called).to.equal(true);
});
});
describe("#createImage", () => {
it("should call the underlying document's createImage", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.Media, "addMedia");
const wrapperSpy = sinon.spy(wrapper.Document, "addParagraph");
wrapper.createImage("");
expect(spy.called).to.equal(true);
expect(wrapperSpy.called).to.equal(true);
});
});
describe("#createFootnote", () => {
it("should call the underlying document's createFootnote", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.FootNotes, "createFootNote");
wrapper.createFootnote(new Paragraph(""));
expect(spy.called).to.equal(true);
});
});
}); });

View File

@ -18,6 +18,7 @@ import { Image, Media } from "./media";
import { Numbering } from "./numbering"; import { Numbering } from "./numbering";
import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { TargetModeType } from "./relationships/relationship/relationship";
import { Settings } from "./settings"; import { Settings } from "./settings";
import { Styles } from "./styles"; import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory"; import { ExternalStylesFactory } from "./styles/external-styles-factory";
@ -153,7 +154,7 @@ export class File {
hyperlink.linkId, hyperlink.linkId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
link, link,
"External", TargetModeType.EXTERNAL,
); );
return hyperlink; return hyperlink;
} }
@ -299,12 +300,6 @@ export class File {
for (const header of headers) { for (const header of headers) {
switch (header.type) { switch (header.type) {
case HeaderReferenceType.DEFAULT:
newGroup = {
...newGroup,
default: header.header,
};
break;
case HeaderReferenceType.FIRST: case HeaderReferenceType.FIRST:
newGroup = { newGroup = {
...newGroup, ...newGroup,
@ -317,6 +312,7 @@ export class File {
even: header.header, even: header.header,
}; };
break; break;
case HeaderReferenceType.DEFAULT:
default: default:
newGroup = { newGroup = {
...newGroup, ...newGroup,
@ -334,12 +330,6 @@ export class File {
for (const footer of footers) { for (const footer of footers) {
switch (footer.type) { switch (footer.type) {
case FooterReferenceType.DEFAULT:
newGroup = {
...newGroup,
default: footer.footer,
};
break;
case FooterReferenceType.FIRST: case FooterReferenceType.FIRST:
newGroup = { newGroup = {
...newGroup, ...newGroup,
@ -352,6 +342,7 @@ export class File {
even: footer.footer, even: footer.footer,
}; };
break; break;
case FooterReferenceType.DEFAULT:
default: default:
newGroup = { newGroup = {
...newGroup, ...newGroup,

View File

@ -0,0 +1,83 @@
import { expect } from "chai";
import * as sinon from "sinon";
import { FooterWrapper } from "./footer-wrapper";
import { Media } from "./media";
import { Paragraph } from "./paragraph";
import { Table } from "./table";
describe("FooterWrapper", () => {
describe("#addParagraph", () => {
it("should call the underlying footer's addParagraph", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "addParagraph");
file.addParagraph(new Paragraph());
expect(spy.called).to.equal(true);
});
});
describe("#addTable", () => {
it("should call the underlying footer's addParagraph", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "addTable");
file.addTable(new Table(1, 1));
expect(spy.called).to.equal(true);
});
});
describe("#createTable", () => {
it("should call the underlying footer's createTable", () => {
const wrapper = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(wrapper.Footer, "createTable");
wrapper.createTable(1, 1);
expect(spy.called).to.equal(true);
});
});
describe("#createParagraph", () => {
it("should call the underlying footer's createParagraph", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "addParagraph");
file.createParagraph();
expect(spy.called).to.equal(true);
});
});
describe("#addImage", () => {
it("should call the underlying footer's addImage", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "addParagraph");
// tslint:disable-next-line:no-any
file.addImage({} as any);
expect(spy.called).to.equal(true);
});
});
describe("#createImage", () => {
it("should call the underlying footer's createImage", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Media, "addMedia");
const fileSpy = sinon.spy(file, "addImage");
file.createImage("");
expect(spy.called).to.equal(true);
expect(fileSpy.called).to.equal(true);
});
});
describe("#addChildElement", () => {
it("should call the underlying footer's addChildElement", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "addChildElement");
// tslint:disable-next-line:no-any
file.addChildElement({} as any);
expect(spy.called).to.equal(true);
});
});
});

View File

@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
import { FooterReferenceType } from "./document"; import { FooterReferenceType } from "./document";
import { Footer } from "./footer/footer"; import { Footer } from "./footer/footer";
import { Image, IMediaData, Media } from "./media"; import { Image, Media } from "./media";
import { ImageParagraph, Paragraph } from "./paragraph"; import { ImageParagraph, Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
@ -43,29 +43,8 @@ export class FooterWrapper {
this.footer.addChildElement(childElement); this.footer.addChildElement(childElement);
} }
public addImageRelationship(image: Buffer, refId: number, width?: number, height?: number): IMediaData {
const mediaData = this.media.addMedia(image, refId, width, height);
this.relationships.createRelationship(
mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
return mediaData;
}
public addHyperlinkRelationship(target: string, refId: number, targetMode?: "External" | undefined): void {
this.relationships.createRelationship(
refId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
target,
targetMode,
);
}
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void { public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
// TODO const mediaData = this.media.addMedia(image, width, height);
// tslint:disable-next-line:no-any
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
this.addImage(new Image(new ImageParagraph(mediaData))); this.addImage(new Image(new ImageParagraph(mediaData)));
} }

View File

@ -9,19 +9,73 @@ import { Table } from "./table";
describe("HeaderWrapper", () => { describe("HeaderWrapper", () => {
describe("#addParagraph", () => { describe("#addParagraph", () => {
it("should call the underlying header's addParagraph", () => { it("should call the underlying header's addParagraph", () => {
const file = new HeaderWrapper(new Media(), 1); const wrapper = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(file.Header, "addParagraph"); const spy = sinon.spy(wrapper.Header, "addParagraph");
file.addParagraph(new Paragraph()); wrapper.addParagraph(new Paragraph());
expect(spy.called).to.equal(true); expect(spy.called).to.equal(true);
}); });
}); });
describe("#addTable", () => { describe("#addTable", () => {
it("should call the underlying header's addParagraph", () => { it("should call the underlying header's addTable", () => {
const wrapper = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(wrapper.Header, "addTable");
wrapper.addTable(new Table(1, 1));
expect(spy.called).to.equal(true);
});
});
describe("#createTable", () => {
it("should call the underlying header's createTable", () => {
const wrapper = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(wrapper.Header, "createTable");
wrapper.createTable(1, 1);
expect(spy.called).to.equal(true);
});
});
describe("#createParagraph", () => {
it("should call the underlying header's createParagraph", () => {
const file = new HeaderWrapper(new Media(), 1); const file = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(file.Header, "addTable"); const spy = sinon.spy(file.Header, "addParagraph");
file.addTable(new Table(1, 1)); file.createParagraph();
expect(spy.called).to.equal(true);
});
});
describe("#addImage", () => {
it("should call the underlying header's addImage", () => {
const file = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(file.Header, "addParagraph");
// tslint:disable-next-line:no-any
file.addImage({} as any);
expect(spy.called).to.equal(true);
});
});
describe("#createImage", () => {
it("should call the underlying header's createImage", () => {
const file = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(file.Media, "addMedia");
const fileSpy = sinon.spy(file, "addImage");
file.createImage("");
expect(spy.called).to.equal(true);
expect(fileSpy.called).to.equal(true);
});
});
describe("#addChildElement", () => {
it("should call the underlying header's addChildElement", () => {
const file = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(file.Header, "addChildElement");
// tslint:disable-next-line:no-any
file.addChildElement({} as any);
expect(spy.called).to.equal(true); expect(spy.called).to.equal(true);
}); });

View File

@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
import { HeaderReferenceType } from "./document"; import { HeaderReferenceType } from "./document";
import { Header } from "./header/header"; import { Header } from "./header/header";
import { Image, IMediaData, Media } from "./media"; import { Image, Media } from "./media";
import { ImageParagraph, Paragraph } from "./paragraph"; import { ImageParagraph, Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
@ -43,29 +43,8 @@ export class HeaderWrapper {
this.header.addChildElement(childElement); this.header.addChildElement(childElement);
} }
public addImageRelationship(image: Buffer, refId: number, width?: number, height?: number): IMediaData {
const mediaData = this.media.addMedia(image, refId, width, height);
this.relationships.createRelationship(
mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
return mediaData;
}
public addHyperlinkRelationship(target: string, refId: number, targetMode?: "External" | undefined): void {
this.relationships.createRelationship(
refId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
target,
targetMode,
);
}
public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void { public createImage(image: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): void {
// TODO const mediaData = this.media.addMedia(image, width, height);
// tslint:disable-next-line:no-any
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
this.addImage(new Image(new ImageParagraph(mediaData))); this.addImage(new Image(new ImageParagraph(mediaData)));
} }

View File

@ -10,7 +10,6 @@ export interface IMediaDataDimensions {
} }
export interface IMediaData { export interface IMediaData {
readonly referenceId: number;
readonly stream: Buffer | Uint8Array | ArrayBuffer; readonly stream: Buffer | Uint8Array | ArrayBuffer;
readonly path?: string; readonly path?: string;
readonly fileName: string; readonly fileName: string;

View File

@ -1,5 +1,6 @@
// tslint:disable:object-literal-key-quotes // tslint:disable:object-literal-key-quotes
import { expect } from "chai"; import { expect } from "chai";
import { stub } from "sinon";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
@ -20,16 +21,18 @@ describe("Media", () => {
}); });
it("should ensure the correct relationship id is used when adding image", () => { it("should ensure the correct relationship id is used when adding image", () => {
// tslint:disable-next-line:no-any
stub(Media as any, "generateId").callsFake(() => "testId");
const file = new File(); const file = new File();
const image1 = Media.addImage(file, "test"); const image1 = Media.addImage(file, "test");
const tree = new Formatter().format(image1.Paragraph); const tree = new Formatter().format(image1.Paragraph);
const inlineElements = tree["w:p"][1]["w:r"][1]["w:drawing"][0]["wp:inline"]; const inlineElements = tree["w:p"][1]["w:r"][1]["w:drawing"][0]["wp:inline"];
const graphicData = inlineElements.find((x) => x["a:graphic"]); const graphicData = inlineElements.find((x) => x["a:graphic"]);
expect(graphicData["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({ expect(graphicData["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({
_attr: { _attr: {
"r:embed": `rId${file.DocumentRelationships.RelationshipCount}`, "r:embed": `rId{testId.png}`,
cstate: "none", cstate: "none",
}, },
}); });
@ -41,7 +44,7 @@ describe("Media", () => {
expect(graphicData2["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({ expect(graphicData2["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"][0]).to.deep.equal({
_attr: { _attr: {
"r:embed": `rId${file.DocumentRelationships.RelationshipCount}`, "r:embed": `rId{testId.png}`,
cstate: "none", cstate: "none",
}, },
}); });
@ -53,9 +56,8 @@ describe("Media", () => {
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
(Media as any).generateId = () => "test"; (Media as any).generateId = () => "test";
const image = new Media().addMedia("", 1); const image = new Media().addMedia("");
expect(image.fileName).to.equal("test.png"); expect(image.fileName).to.equal("test.png");
expect(image.referenceId).to.equal(1);
expect(image.dimensions).to.deep.equal({ expect(image.dimensions).to.deep.equal({
pixels: { pixels: {
x: 100, x: 100,
@ -74,7 +76,7 @@ describe("Media", () => {
// tslint:disable-next-line:no-any // tslint:disable-next-line:no-any
(Media as any).generateId = () => "test"; (Media as any).generateId = () => "test";
const image = new Media().addMedia("", 1); const image = new Media().addMedia("");
expect(image.stream).to.be.an.instanceof(Uint8Array); expect(image.stream).to.be.an.instanceof(Uint8Array);
}); });
}); });
@ -85,12 +87,11 @@ describe("Media", () => {
(Media as any).generateId = () => "test"; (Media as any).generateId = () => "test";
const media = new Media(); const media = new Media();
media.addMedia("", 1); media.addMedia("");
const image = media.getMedia("test.png"); const image = media.getMedia("test.png");
expect(image.fileName).to.equal("test.png"); expect(image.fileName).to.equal("test.png");
expect(image.referenceId).to.equal(1);
expect(image.dimensions).to.deep.equal({ expect(image.dimensions).to.deep.equal({
pixels: { pixels: {
x: 100, x: 100,
@ -116,7 +117,7 @@ describe("Media", () => {
(Media as any).generateId = () => "test"; (Media as any).generateId = () => "test";
const media = new Media(); const media = new Media();
media.addMedia("", 1); media.addMedia("");
const array = media.Array; const array = media.Array;
expect(array).to.be.an.instanceof(Array); expect(array).to.be.an.instanceof(Array);
@ -124,7 +125,6 @@ describe("Media", () => {
const image = array[0]; const image = array[0];
expect(image.fileName).to.equal("test.png"); expect(image.fileName).to.equal("test.png");
expect(image.referenceId).to.equal(1);
expect(image.dimensions).to.deep.equal({ expect(image.dimensions).to.deep.equal({
pixels: { pixels: {
x: 100, x: 100,

View File

@ -4,11 +4,6 @@ import { ImageParagraph } from "../paragraph";
import { IMediaData } from "./data"; import { IMediaData } from "./data";
import { Image } from "./image"; import { Image } from "./image";
interface IHackedFile {
// tslint:disable-next-line:readonly-keyword
currentRelationshipId: number;
}
export class Media { export class Media {
public static addImage( public static addImage(
file: File, file: File,
@ -18,14 +13,7 @@ export class Media {
drawingOptions?: IDrawingOptions, drawingOptions?: IDrawingOptions,
): Image { ): Image {
// Workaround to expose id without exposing to API // Workaround to expose id without exposing to API
const exposedFile = (file as {}) as IHackedFile; const mediaData = file.Media.addMedia(buffer, width, height);
const mediaData = file.Media.addMedia(buffer, exposedFile.currentRelationshipId++, width, height);
file.DocumentRelationships.createRelationship(
mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
return new Image(new ImageParagraph(mediaData, drawingOptions)); return new Image(new ImageParagraph(mediaData, drawingOptions));
} }
@ -57,17 +45,11 @@ export class Media {
return data; return data;
} }
public addMedia( public addMedia(buffer: Buffer | string | Uint8Array | ArrayBuffer, width: number = 100, height: number = 100): IMediaData {
buffer: Buffer | string | Uint8Array | ArrayBuffer,
referenceId: number,
width: number = 100,
height: number = 100,
): IMediaData {
const key = `${Media.generateId()}.png`; const key = `${Media.generateId()}.png`;
return this.createMedia( return this.createMedia(
key, key,
referenceId,
{ {
width: width, width: width,
height: height, height: height,
@ -78,7 +60,6 @@ export class Media {
private createMedia( private createMedia(
key: string, key: string,
relationshipsCount: number,
dimensions: { readonly width: number; readonly height: number }, dimensions: { readonly width: number; readonly height: number },
data: Buffer | string | Uint8Array | ArrayBuffer, data: Buffer | string | Uint8Array | ArrayBuffer,
filePath?: string, filePath?: string,
@ -86,7 +67,6 @@ export class Media {
const newData = typeof data === "string" ? this.convertDataURIToBinary(data) : data; const newData = typeof data === "string" ? this.convertDataURIToBinary(data) : data;
const imageData: IMediaData = { const imageData: IMediaData = {
referenceId: relationshipsCount,
stream: newData, stream: newData,
path: filePath, path: filePath,
fileName: key, fileName: key,

View File

@ -1,3 +1,4 @@
export * from "./numbering"; export * from "./numbering";
export * from "./abstract-numbering"; export * from "./abstract-numbering";
export * from "./level"; export * from "./level";
export * from "./num";

View File

@ -10,10 +10,9 @@ describe("Image", () => {
beforeEach(() => { beforeEach(() => {
image = new ImageParagraph({ image = new ImageParagraph({
referenceId: 0,
stream: new Buffer(""), stream: new Buffer(""),
path: "", path: "",
fileName: "", fileName: "test.png",
dimensions: { dimensions: {
pixels: { pixels: {
x: 10, x: 10,
@ -171,7 +170,7 @@ describe("Image", () => {
{ {
_attr: { _attr: {
cstate: "none", cstate: "none",
"r:embed": "rId0", "r:embed": "rId{test.png}",
}, },
}, },
], ],

View File

@ -21,9 +21,9 @@ describe("Run", () => {
}); });
}); });
describe("#italic()", () => { describe("#italics()", () => {
it("it should add italics to the properties", () => { it("it should add italics to the properties", () => {
run.italic(); run.italics();
const newJson = Utility.jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:i"); assert.equal(newJson.root[0].root[0].rootKey, "w:i");
assert.equal(newJson.root[0].root[1].rootKey, "w:iCs"); assert.equal(newJson.root[0].root[1].rootKey, "w:iCs");

View File

@ -39,7 +39,7 @@ export class Run extends XmlComponent {
return this; return this;
} }
public italic(): Run { public italics(): Run {
this.properties.push(new Italics()); this.properties.push(new Italics());
this.properties.push(new ItalicsComplexScript()); this.properties.push(new ItalicsComplexScript());
return this; return this;

View File

@ -17,7 +17,9 @@ export type RelationshipType =
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"; | "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
export type TargetModeType = "External"; export enum TargetModeType {
EXTERNAL = "External",
}
export class Relationship extends XmlComponent { export class Relationship extends XmlComponent {
constructor(id: string, type: RelationshipType, target: string, targetMode?: TargetModeType) { constructor(id: string, type: RelationshipType, target: string, targetMode?: TargetModeType) {

View File

@ -0,0 +1,296 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { CharacterStyle } from "./character-style";
describe("CharacterStyle", () => {
describe("#constructor", () => {
it("should set the style type to character and use the given style id", () => {
const style = new CharacterStyle("myStyleId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{ "w:rPr": [] },
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
it("should set the name of the style, if given", () => {
const style = new CharacterStyle("myStyleId", "Style Name");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{ "w:name": [{ _attr: { "w:val": "Style Name" } }] },
{ "w:rPr": [] },
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
});
describe("formatting methods: style attributes", () => {
it("#basedOn", () => {
const style = new CharacterStyle("myStyleId").basedOn("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{ "w:rPr": [] },
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
{ "w:basedOn": [{ _attr: { "w:val": "otherId" } }] },
],
});
});
});
describe("formatting methods: run properties", () => {
it("#size", () => {
const style = new CharacterStyle("myStyleId").size(24);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [{ "w:sz": [{ _attr: { "w:val": 24 } }] }, { "w:szCs": [{ _attr: { "w:val": 24 } }] }],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const style = new CharacterStyle("myStyleId").underline();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "single" } }] }],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
it("should set the style if given", () => {
const style = new CharacterStyle("myStyleId").underline("double");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "double" } }] }],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
it("should set the style and color if given", () => {
const style = new CharacterStyle("myStyleId").underline("double", "005599");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "double", "w:color": "005599" } }] }],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
});
it("#superScript", () => {
const style = new CharacterStyle("myStyleId").superScript();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [
{
"w:vertAlign": [
{
_attr: {
"w:val": "superscript",
},
},
],
},
],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
it("#color", () => {
const style = new CharacterStyle("myStyleId").color("123456");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [{ "w:color": [{ _attr: { "w:val": "123456" } }] }],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
it("#link", () => {
const style = new CharacterStyle("myStyleId").link("MyLink");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
{ "w:link": [{ _attr: { "w:val": "MyLink" } }] },
],
});
});
it("#semiHidden", () => {
const style = new CharacterStyle("myStyleId").semiHidden();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
{
"w:rPr": [],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{ "w:unhideWhenUsed": [] },
{ "w:semiHidden": [] },
],
});
});
});
});

View File

@ -0,0 +1,53 @@
import * as formatting from "file/paragraph/run/formatting";
import { RunProperties } from "file/paragraph/run/properties";
import { XmlComponent } from "file/xml-components";
import { BasedOn, Link, SemiHidden, UiPriority, UnhideWhenUsed } from "./components";
import { Style } from "./style";
export class CharacterStyle extends Style {
private readonly runProperties: RunProperties;
constructor(styleId: string, name?: string) {
super({ type: "character", styleId: styleId }, name);
this.runProperties = new RunProperties();
this.root.push(this.runProperties);
this.root.push(new UiPriority("99"));
this.root.push(new UnhideWhenUsed());
}
public basedOn(parentId: string): CharacterStyle {
this.root.push(new BasedOn(parentId));
return this;
}
public addRunProperty(property: XmlComponent): CharacterStyle {
this.runProperties.push(property);
return this;
}
public color(color: string): CharacterStyle {
return this.addRunProperty(new formatting.Color(color));
}
public underline(underlineType?: string, color?: string): CharacterStyle {
return this.addRunProperty(new formatting.Underline(underlineType, color));
}
public superScript(): CharacterStyle {
return this.addRunProperty(new formatting.SuperScript());
}
public size(twips: number): CharacterStyle {
return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips));
}
public link(link: string): CharacterStyle {
this.root.push(new Link(link));
return this;
}
public semiHidden(): CharacterStyle {
this.root.push(new SemiHidden());
return this;
}
}

View File

@ -0,0 +1,53 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import * as components from "./components";
describe("Style components", () => {
it("Name#constructor", () => {
const style = new components.Name("Style Name");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:name": [{ _attr: { "w:val": "Style Name" } }] });
});
it("BasedOn#constructor", () => {
const style = new components.BasedOn("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:basedOn": [{ _attr: { "w:val": "otherId" } }] });
});
it("Next#constructor", () => {
const style = new components.Next("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:next": [{ _attr: { "w:val": "otherId" } }] });
});
it("Link#constructor", () => {
const style = new components.Link("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:link": [{ _attr: { "w:val": "otherId" } }] });
});
it("UiPriority#constructor", () => {
const style = new components.UiPriority("123");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:uiPriority": [{ _attr: { "w:val": "123" } }] });
});
it("UnhideWhenUsed#constructor", () => {
const style = new components.UnhideWhenUsed();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:unhideWhenUsed": [] });
});
it("QuickFormat#constructor", () => {
const style = new components.QuickFormat();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:qFormat": [] });
});
it("SemiHidden#constructor", () => {
const style = new components.SemiHidden();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:semiHidden": [] });
});
});

View File

@ -0,0 +1,331 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import * as defaultStyels from "./default-styles";
describe("Default Styles", () => {
it("HeadingStyle#constructor", () => {
const style = new defaultStyels.HeadingStyle("Heading1", "Heading 1");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Heading1" } },
{ "w:name": [{ _attr: { "w:val": "Heading 1" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("TitleStyle#constructor", () => {
const style = new defaultStyels.TitleStyle();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Title" } },
{ "w:name": [{ _attr: { "w:val": "Title" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("Heading1Style#constructor", () => {
const style = new defaultStyels.Heading1Style();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Heading1" } },
{ "w:name": [{ _attr: { "w:val": "Heading 1" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("Heading2Style#constructor", () => {
const style = new defaultStyels.Heading2Style();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Heading2" } },
{ "w:name": [{ _attr: { "w:val": "Heading 2" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("Heading3Style#constructor", () => {
const style = new defaultStyels.Heading3Style();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Heading3" } },
{ "w:name": [{ _attr: { "w:val": "Heading 3" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("Heading4Style#constructor", () => {
const style = new defaultStyels.Heading4Style();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Heading4" } },
{ "w:name": [{ _attr: { "w:val": "Heading 4" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("Heading5Style#constructor", () => {
const style = new defaultStyels.Heading5Style();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Heading5" } },
{ "w:name": [{ _attr: { "w:val": "Heading 5" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("Heading6Style#constructor", () => {
const style = new defaultStyels.Heading6Style();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "Heading6" } },
{ "w:name": [{ _attr: { "w:val": "Heading 6" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:next": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("ListParagraph#constructor", () => {
const style = new defaultStyels.ListParagraph();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "ListParagraph" } },
{ "w:name": [{ _attr: { "w:val": "List Paragraph" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:qFormat": [] },
],
});
});
it("FootnoteText#constructor", () => {
const style = new defaultStyels.FootnoteText();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "FootnoteText" } },
{ "w:name": [{ _attr: { "w:val": "footnote text" } }] },
{
"w:pPr": [
{
"w:spacing": [
{
_attr: {
"w:after": 0,
"w:line": 240,
"w:lineRule": "auto",
},
},
],
},
],
},
{
"w:rPr": [
{
"w:sz": [
{
_attr: {
"w:val": 20,
},
},
],
},
{
"w:szCs": [
{
_attr: {
"w:val": 20,
},
},
],
},
],
},
{ "w:basedOn": [{ _attr: { "w:val": "Normal" } }] },
{ "w:link": [{ _attr: { "w:val": "FootnoteTextChar" } }] },
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:semiHidden": [],
},
{
"w:unhideWhenUsed": [],
},
],
});
});
it("FootnoteReferenceStyle#constructor", () => {
const style = new defaultStyels.FootnoteReferenceStyle();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "FootnoteReference" } },
{ "w:name": [{ _attr: { "w:val": "footnote reference" } }] },
{
"w:rPr": [
{
"w:vertAlign": [
{
_attr: {
"w:val": "superscript",
},
},
],
},
],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
{ "w:basedOn": [{ _attr: { "w:val": "DefaultParagraphFont" } }] },
{
"w:semiHidden": [],
},
],
});
});
it("FootnoteTextChar#constructor", () => {
const style = new defaultStyels.FootnoteTextChar();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "FootnoteTextChar" } },
{ "w:name": [{ _attr: { "w:val": "Footnote Text Char" } }] },
{
"w:rPr": [
{
"w:sz": [
{
_attr: {
"w:val": 20,
},
},
],
},
{
"w:szCs": [
{
_attr: {
"w:val": 20,
},
},
],
},
],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
{ "w:basedOn": [{ _attr: { "w:val": "DefaultParagraphFont" } }] },
{ "w:link": [{ _attr: { "w:val": "FootnoteText" } }] },
{
"w:semiHidden": [],
},
],
});
});
it("HyperlinkStyle#constructor", () => {
const style = new defaultStyels.HyperlinkStyle();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "Hyperlink" } },
{ "w:name": [{ _attr: { "w:val": "Hyperlink" } }] },
{
"w:rPr": [{ "w:color": [{ _attr: { "w:val": "0563C1" } }] }, { "w:u": [{ _attr: { "w:val": "single" } }] }],
},
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
{
"w:unhideWhenUsed": [],
},
{ "w:basedOn": [{ _attr: { "w:val": "DefaultParagraphFont" } }] },
],
});
});
});

View File

@ -0,0 +1,106 @@
import { CharacterStyle } from "./character-style";
import { ParagraphStyle } from "./paragraph-style";
export class HeadingStyle extends ParagraphStyle {
constructor(styleId: string, name: string) {
super(styleId, name);
this.basedOn("Normal");
this.next("Normal");
this.quickFormat();
}
}
export class TitleStyle extends HeadingStyle {
constructor() {
super("Title", "Title");
}
}
export class Heading1Style extends HeadingStyle {
constructor() {
super("Heading1", "Heading 1");
}
}
export class Heading2Style extends HeadingStyle {
constructor() {
super("Heading2", "Heading 2");
}
}
export class Heading3Style extends HeadingStyle {
constructor() {
super("Heading3", "Heading 3");
}
}
export class Heading4Style extends HeadingStyle {
constructor() {
super("Heading4", "Heading 4");
}
}
export class Heading5Style extends HeadingStyle {
constructor() {
super("Heading5", "Heading 5");
}
}
export class Heading6Style extends HeadingStyle {
constructor() {
super("Heading6", "Heading 6");
}
}
export class ListParagraph extends ParagraphStyle {
constructor() {
super("ListParagraph", "List Paragraph");
this.basedOn("Normal");
this.quickFormat();
}
}
export class FootnoteText extends ParagraphStyle {
constructor() {
super("FootnoteText", "footnote text");
this.basedOn("Normal")
.link("FootnoteTextChar")
.uiPriority("99")
.semiHidden()
.unhideWhenUsed()
.spacing({
after: 0,
line: 240,
lineRule: "auto",
})
.size(20);
}
}
export class FootnoteReferenceStyle extends CharacterStyle {
constructor() {
super("FootnoteReference", "footnote reference");
this.basedOn("DefaultParagraphFont")
.semiHidden()
.superScript();
}
}
export class FootnoteTextChar extends CharacterStyle {
constructor() {
super("FootnoteTextChar", "Footnote Text Char");
this.basedOn("DefaultParagraphFont")
.link("FootnoteText")
.semiHidden()
.size(20);
}
}
export class HyperlinkStyle extends CharacterStyle {
constructor() {
super("Hyperlink", "Hyperlink");
this.basedOn("DefaultParagraphFont")
.color("0563C1")
.underline("single");
}
}

View File

@ -1,335 +1,4 @@
import { export * from "./style";
Alignment, export * from "./paragraph-style";
AlignmentOptions, export * from "./character-style";
Indent, export * from "./default-styles";
ISpacingProperties,
KeepLines,
KeepNext,
LeftTabStop,
MaxRightTabStop,
ParagraphProperties,
Spacing,
ThematicBreak,
} from "file/paragraph";
import * as formatting from "file/paragraph/run/formatting";
import { RunProperties } from "file/paragraph/run/properties";
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { BasedOn, Link, Name, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components";
export interface IStyleAttributes {
readonly type?: string;
readonly styleId?: string;
readonly default?: boolean;
readonly customStyle?: string;
}
class StyleAttributes extends XmlAttributeComponent<IStyleAttributes> {
protected readonly xmlKeys = {
type: "w:type",
styleId: "w:styleId",
default: "w:default",
customStyle: "w:customStyle",
};
}
export class Style extends XmlComponent {
constructor(attributes: IStyleAttributes, name?: string) {
super("w:style");
this.root.push(new StyleAttributes(attributes));
if (name) {
this.root.push(new Name(name));
}
}
public push(styleSegment: XmlComponent): void {
this.root.push(styleSegment);
}
}
export class ParagraphStyle extends Style {
private readonly paragraphProperties: ParagraphProperties;
private readonly runProperties: RunProperties;
constructor(styleId: string, name?: string) {
super({ type: "paragraph", styleId: styleId }, name);
this.paragraphProperties = new ParagraphProperties();
this.runProperties = new RunProperties();
this.root.push(this.paragraphProperties);
this.root.push(this.runProperties);
}
public addParagraphProperty(property: XmlComponent): ParagraphStyle {
this.paragraphProperties.push(property);
return this;
}
public addRunProperty(property: XmlComponent): ParagraphStyle {
this.runProperties.push(property);
return this;
}
public basedOn(parentId: string): ParagraphStyle {
this.root.push(new BasedOn(parentId));
return this;
}
public quickFormat(): ParagraphStyle {
this.root.push(new QuickFormat());
return this;
}
public next(nextId: string): ParagraphStyle {
this.root.push(new Next(nextId));
return this;
}
// ---------- Run formatting ---------------------- //
public size(twips: number): ParagraphStyle {
return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips));
}
public bold(): ParagraphStyle {
return this.addRunProperty(new formatting.Bold());
}
public italics(): ParagraphStyle {
return this.addRunProperty(new formatting.Italics());
}
public smallCaps(): ParagraphStyle {
return this.addRunProperty(new formatting.SmallCaps());
}
public allCaps(): ParagraphStyle {
return this.addRunProperty(new formatting.Caps());
}
public strike(): ParagraphStyle {
return this.addRunProperty(new formatting.Strike());
}
public doubleStrike(): ParagraphStyle {
return this.addRunProperty(new formatting.DoubleStrike());
}
public subScript(): ParagraphStyle {
return this.addRunProperty(new formatting.SubScript());
}
public superScript(): ParagraphStyle {
return this.addRunProperty(new formatting.SuperScript());
}
public underline(underlineType?: string, color?: string): ParagraphStyle {
return this.addRunProperty(new formatting.Underline(underlineType, color));
}
public color(color: string): ParagraphStyle {
return this.addRunProperty(new formatting.Color(color));
}
public font(fontName: string): ParagraphStyle {
return this.addRunProperty(new formatting.RunFonts(fontName));
}
public characterSpacing(value: number): ParagraphStyle {
return this.addRunProperty(new formatting.CharacterSpacing(value));
}
// --------------------- Paragraph formatting ------------------------ //
public center(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.CENTER));
}
public left(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.LEFT));
}
public right(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.RIGHT));
}
public justified(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.BOTH));
}
public thematicBreak(): ParagraphStyle {
return this.addParagraphProperty(new ThematicBreak());
}
public maxRightTabStop(): ParagraphStyle {
return this.addParagraphProperty(new MaxRightTabStop());
}
public leftTabStop(position: number): ParagraphStyle {
return this.addParagraphProperty(new LeftTabStop(position));
}
public indent(attrs: object): ParagraphStyle {
return this.addParagraphProperty(new Indent(attrs));
}
public spacing(params: ISpacingProperties): ParagraphStyle {
return this.addParagraphProperty(new Spacing(params));
}
public keepNext(): ParagraphStyle {
return this.addParagraphProperty(new KeepNext());
}
public keepLines(): ParagraphStyle {
return this.addParagraphProperty(new KeepLines());
}
}
export class HeadingStyle extends ParagraphStyle {
constructor(styleId: string, name: string) {
super(styleId, name);
this.basedOn("Normal");
this.next("Normal");
this.quickFormat();
}
}
export class TitleStyle extends HeadingStyle {
constructor() {
super("Title", "Title");
}
}
export class Heading1Style extends HeadingStyle {
constructor() {
super("Heading1", "Heading 1");
}
}
export class Heading2Style extends HeadingStyle {
constructor() {
super("Heading2", "Heading 2");
}
}
export class Heading3Style extends HeadingStyle {
constructor() {
super("Heading3", "Heading 3");
}
}
export class Heading4Style extends HeadingStyle {
constructor() {
super("Heading4", "Heading 4");
}
}
export class Heading5Style extends HeadingStyle {
constructor() {
super("Heading5", "Heading 5");
}
}
export class Heading6Style extends HeadingStyle {
constructor() {
super("Heading6", "Heading 6");
}
}
export class ListParagraph extends ParagraphStyle {
constructor() {
super("ListParagraph");
this.root.push(new Name("List Paragraph"));
this.root.push(new BasedOn("Normal"));
this.root.push(new QuickFormat());
}
}
export class CharacterStyle extends Style {
private readonly runProperties: RunProperties;
constructor(styleId: string, name?: string) {
super({ type: "character", styleId: styleId }, name);
this.runProperties = new RunProperties();
this.root.push(this.runProperties);
this.root.push(new UiPriority("99"));
this.root.push(new UnhideWhenUsed());
}
public basedOn(parentId: string): CharacterStyle {
this.root.push(new BasedOn(parentId));
return this;
}
public addRunProperty(property: XmlComponent): CharacterStyle {
this.runProperties.push(property);
return this;
}
public color(color: string): CharacterStyle {
return this.addRunProperty(new formatting.Color(color));
}
public underline(underlineType?: string, color?: string): CharacterStyle {
return this.addRunProperty(new formatting.Underline(underlineType, color));
}
public size(twips: number): CharacterStyle {
return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips));
}
}
export class HyperlinkStyle extends CharacterStyle {
constructor() {
super("Hyperlink", "Hyperlink");
this.basedOn("DefaultParagraphFont")
.color("0563C1")
.underline("single");
}
}
export class FootnoteReferenceStyle extends Style {
private readonly runProperties: RunProperties;
constructor() {
super({ type: "character", styleId: "FootnoteReference" });
this.root.push(new Name("footnote reference"));
this.root.push(new BasedOn("DefaultParagraphFont"));
this.root.push(new UiPriority("99"));
this.root.push(new SemiHidden());
this.root.push(new UnhideWhenUsed());
this.runProperties = new RunProperties();
this.runProperties.addChildElement(new formatting.SuperScript());
this.root.push(this.runProperties);
}
}
export class FootnoteText extends ParagraphStyle {
constructor() {
super("FootnoteText");
this.root.push(new Name("footnote text"));
this.root.push(new BasedOn("Normal"));
this.root.push(new Link("FootnoteTextChar"));
this.root.push(new UiPriority("99"));
this.root.push(new SemiHidden());
this.root.push(new UnhideWhenUsed());
this.spacing({
after: 0,
line: 240,
lineRule: "auto",
});
this.size(20);
}
}
export class FootnoteTextChar extends CharacterStyle {
constructor() {
super("FootnoteTextChar", "Footnote Text Char");
this.basedOn("DefaultParagraphFont");
this.root.push(new Link("FootnoteText"));
this.root.push(new UiPriority("99"));
this.root.push(new SemiHidden());
this.size(20);
}
}

View File

@ -0,0 +1,524 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { ParagraphStyle } from "./paragraph-style";
describe("ParagraphStyle", () => {
describe("#constructor", () => {
it("should set the style type to paragraph and use the given style id", () => {
const style = new ParagraphStyle("myStyleId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [] }, { "w:rPr": [] }],
});
});
it("should set the name of the style, if given", () => {
const style = new ParagraphStyle("myStyleId", "Style Name");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:name": [{ _attr: { "w:val": "Style Name" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] },
],
});
});
});
describe("formatting methods: style attributes", () => {
it("#basedOn", () => {
const style = new ParagraphStyle("myStyleId").basedOn("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "otherId" } }] },
],
});
});
it("#quickFormat", () => {
const style = new ParagraphStyle("myStyleId").quickFormat();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:qFormat": [] },
],
});
});
it("#next", () => {
const style = new ParagraphStyle("myStyleId").next("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:next": [{ _attr: { "w:val": "otherId" } }] },
],
});
});
});
describe("formatting methods: paragraph properties", () => {
it("#indent", () => {
const style = new ParagraphStyle("myStyleId").indent({ left: 720 });
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:ind": [{ _attr: { "w:left": 720 } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#spacing", () => {
const style = new ParagraphStyle("myStyleId").spacing({ before: 50, after: 150 });
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:spacing": [{ _attr: { "w:before": 50, "w:after": 150 } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#center", () => {
const style = new ParagraphStyle("myStyleId").center();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "center" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#character spacing", () => {
const style = new ParagraphStyle("myStyleId").characterSpacing(24);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:spacing": [{ _attr: { "w:val": 24 } }] }],
},
],
});
});
it("#left", () => {
const style = new ParagraphStyle("myStyleId").left();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "left" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#right", () => {
const style = new ParagraphStyle("myStyleId").right();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "right" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#justified", () => {
const style = new ParagraphStyle("myStyleId").justified();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "both" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#thematicBreak", () => {
const style = new ParagraphStyle("myStyleId").thematicBreak();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [
{
"w:pBdr": [
{
"w:bottom": [
{
_attr: {
"w:color": "auto",
"w:space": "1",
"w:val": "single",
"w:sz": "6",
},
},
],
},
],
},
],
},
{ "w:rPr": [] },
],
});
});
it("#leftTabStop", () => {
const style = new ParagraphStyle("myStyleId").leftTabStop(1200);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [
{
"w:tabs": [{ "w:tab": [{ _attr: { "w:val": "left", "w:pos": 1200 } }] }],
},
],
},
{ "w:rPr": [] },
],
});
});
it("#maxRightTabStop", () => {
const style = new ParagraphStyle("myStyleId").maxRightTabStop();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [
{
"w:tabs": [{ "w:tab": [{ _attr: { "w:val": "right", "w:pos": 9026 } }] }],
},
],
},
{ "w:rPr": [] },
],
});
});
it("#keepLines", () => {
const style = new ParagraphStyle("myStyleId").keepLines();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [{ "w:keepLines": [] }] },
{ "w:rPr": [] },
],
});
});
it("#keepNext", () => {
const style = new ParagraphStyle("myStyleId").keepNext();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [{ "w:keepNext": [] }] },
{ "w:rPr": [] },
],
});
});
});
describe("formatting methods: run properties", () => {
it("#size", () => {
const style = new ParagraphStyle("myStyleId").size(24);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:sz": [{ _attr: { "w:val": 24 } }] }, { "w:szCs": [{ _attr: { "w:val": 24 } }] }],
},
],
});
});
it("#smallCaps", () => {
const style = new ParagraphStyle("myStyleId").smallCaps();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:smallCaps": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#allCaps", () => {
const style = new ParagraphStyle("myStyleId").allCaps();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:caps": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#strike", () => {
const style = new ParagraphStyle("myStyleId").strike();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:strike": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#doubleStrike", () => {
const style = new ParagraphStyle("myStyleId").doubleStrike();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:dstrike": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#subScript", () => {
const style = new ParagraphStyle("myStyleId").subScript();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "subscript" } }] }],
},
],
});
});
it("#superScript", () => {
const style = new ParagraphStyle("myStyleId").superScript();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "superscript" } }] }],
},
],
});
});
it("#font", () => {
const style = new ParagraphStyle("myStyleId").font("Times");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [
{ "w:rFonts": [{ _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } }] },
],
},
],
});
});
it("#bold", () => {
const style = new ParagraphStyle("myStyleId").bold();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:b": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#italics", () => {
const style = new ParagraphStyle("myStyleId").italics();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:i": [{ _attr: { "w:val": true } }] }],
},
],
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const style = new ParagraphStyle("myStyleId").underline();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "single" } }] }],
},
],
});
});
it("should set the style if given", () => {
const style = new ParagraphStyle("myStyleId").underline("double");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "double" } }] }],
},
],
});
});
it("should set the style and color if given", () => {
const style = new ParagraphStyle("myStyleId").underline("double", "005599");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "double", "w:color": "005599" } }] }],
},
],
});
});
});
it("#color", () => {
const style = new ParagraphStyle("myStyleId").color("123456");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:color": [{ _attr: { "w:val": "123456" } }] }],
},
],
});
});
it("#link", () => {
const style = new ParagraphStyle("myStyleId").link("MyLink");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:link": [{ _attr: { "w:val": "MyLink" } }] },
],
});
});
it("#semiHidden", () => {
const style = new ParagraphStyle("myStyleId").semiHidden();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:semiHidden": [] },
],
});
});
it("#uiPriority", () => {
const style = new ParagraphStyle("myStyleId").uiPriority("99");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{
"w:uiPriority": [
{
_attr: {
"w:val": "99",
},
},
],
},
],
});
});
it("#unhideWhenUsed", () => {
const style = new ParagraphStyle("myStyleId").unhideWhenUsed();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:unhideWhenUsed": [] },
],
});
});
});
});

View File

@ -0,0 +1,178 @@
import {
Alignment,
AlignmentOptions,
Indent,
ISpacingProperties,
KeepLines,
KeepNext,
LeftTabStop,
MaxRightTabStop,
ParagraphProperties,
Spacing,
ThematicBreak,
} from "file/paragraph";
import * as formatting from "file/paragraph/run/formatting";
import { RunProperties } from "file/paragraph/run/properties";
import { XmlComponent } from "file/xml-components";
import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components";
import { Style } from "./style";
export class ParagraphStyle extends Style {
private readonly paragraphProperties: ParagraphProperties;
private readonly runProperties: RunProperties;
constructor(styleId: string, name?: string) {
super({ type: "paragraph", styleId: styleId }, name);
this.paragraphProperties = new ParagraphProperties();
this.runProperties = new RunProperties();
this.root.push(this.paragraphProperties);
this.root.push(this.runProperties);
}
public addParagraphProperty(property: XmlComponent): ParagraphStyle {
this.paragraphProperties.push(property);
return this;
}
public addRunProperty(property: XmlComponent): ParagraphStyle {
this.runProperties.push(property);
return this;
}
public basedOn(parentId: string): ParagraphStyle {
this.root.push(new BasedOn(parentId));
return this;
}
public quickFormat(): ParagraphStyle {
this.root.push(new QuickFormat());
return this;
}
public next(nextId: string): ParagraphStyle {
this.root.push(new Next(nextId));
return this;
}
// ---------- Run formatting ---------------------- //
public size(twips: number): ParagraphStyle {
return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips));
}
public bold(): ParagraphStyle {
return this.addRunProperty(new formatting.Bold());
}
public italics(): ParagraphStyle {
return this.addRunProperty(new formatting.Italics());
}
public smallCaps(): ParagraphStyle {
return this.addRunProperty(new formatting.SmallCaps());
}
public allCaps(): ParagraphStyle {
return this.addRunProperty(new formatting.Caps());
}
public strike(): ParagraphStyle {
return this.addRunProperty(new formatting.Strike());
}
public doubleStrike(): ParagraphStyle {
return this.addRunProperty(new formatting.DoubleStrike());
}
public subScript(): ParagraphStyle {
return this.addRunProperty(new formatting.SubScript());
}
public superScript(): ParagraphStyle {
return this.addRunProperty(new formatting.SuperScript());
}
public underline(underlineType?: string, color?: string): ParagraphStyle {
return this.addRunProperty(new formatting.Underline(underlineType, color));
}
public color(color: string): ParagraphStyle {
return this.addRunProperty(new formatting.Color(color));
}
public font(fontName: string): ParagraphStyle {
return this.addRunProperty(new formatting.RunFonts(fontName));
}
public characterSpacing(value: number): ParagraphStyle {
return this.addRunProperty(new formatting.CharacterSpacing(value));
}
// --------------------- Paragraph formatting ------------------------ //
public center(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.CENTER));
}
public left(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.LEFT));
}
public right(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.RIGHT));
}
public justified(): ParagraphStyle {
return this.addParagraphProperty(new Alignment(AlignmentOptions.BOTH));
}
public thematicBreak(): ParagraphStyle {
return this.addParagraphProperty(new ThematicBreak());
}
public maxRightTabStop(): ParagraphStyle {
return this.addParagraphProperty(new MaxRightTabStop());
}
public leftTabStop(position: number): ParagraphStyle {
return this.addParagraphProperty(new LeftTabStop(position));
}
public indent(attrs: object): ParagraphStyle {
return this.addParagraphProperty(new Indent(attrs));
}
public spacing(params: ISpacingProperties): ParagraphStyle {
return this.addParagraphProperty(new Spacing(params));
}
public keepNext(): ParagraphStyle {
return this.addParagraphProperty(new KeepNext());
}
public keepLines(): ParagraphStyle {
return this.addParagraphProperty(new KeepLines());
}
/*-------------- Style Properties -----------------*/
public link(link: string): ParagraphStyle {
this.root.push(new Link(link));
return this;
}
public semiHidden(): ParagraphStyle {
this.root.push(new SemiHidden());
return this;
}
public uiPriority(priority: string): ParagraphStyle {
this.root.push(new UiPriority(priority));
return this;
}
public unhideWhenUsed(): ParagraphStyle {
this.root.push(new UnhideWhenUsed());
return this;
}
}

View File

@ -0,0 +1,36 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Style } from "./style";
describe("Style", () => {
describe("#constructor()", () => {
it("should set the given properties", () => {
const style = new Style({
type: "paragraph",
styleId: "myStyleId",
default: true,
});
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId", "w:default": true } }],
});
});
it("should set the name of the style, if given", () => {
const style = new Style(
{
type: "paragraph",
styleId: "myStyleId",
},
"Style Name",
);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:name": [{ _attr: { "w:val": "Style Name" } }] },
],
});
});
});
});

View File

@ -0,0 +1,32 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { Name } from "./components";
export interface IStyleAttributes {
readonly type?: string;
readonly styleId?: string;
readonly default?: boolean;
readonly customStyle?: string;
}
class StyleAttributes extends XmlAttributeComponent<IStyleAttributes> {
protected readonly xmlKeys = {
type: "w:type",
styleId: "w:styleId",
default: "w:default",
customStyle: "w:customStyle",
};
}
export class Style extends XmlComponent {
constructor(attributes: IStyleAttributes, name?: string) {
super("w:style");
this.root.push(new StyleAttributes(attributes));
if (name) {
this.root.push(new Name(name));
}
}
public push(styleSegment: XmlComponent): void {
this.root.push(styleSegment);
}
}

View File

@ -2,8 +2,8 @@ import { assert, expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { ParagraphStyle, Style } from "./style"; import { CharacterStyle, ParagraphStyle } from "./style";
import * as components from "./style/components";
import { Styles } from "./styles"; import { Styles } from "./styles";
describe("Styles", () => { describe("Styles", () => {
@ -22,7 +22,8 @@ describe("Styles", () => {
describe("#createParagraphStyle", () => { describe("#createParagraphStyle", () => {
it("should create a new paragraph style and push it onto this collection", () => { it("should create a new paragraph style and push it onto this collection", () => {
styles.createParagraphStyle("pStyleId"); const pStyle = styles.createParagraphStyle("pStyleId");
expect(pStyle).to.instanceOf(ParagraphStyle);
const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr);
expect(tree).to.deep.equal([ expect(tree).to.deep.equal([
{ {
@ -32,7 +33,8 @@ describe("Styles", () => {
}); });
it("should set the paragraph name if given", () => { it("should set the paragraph name if given", () => {
styles.createParagraphStyle("pStyleId", "Paragraph Style"); const pStyle = styles.createParagraphStyle("pStyleId", "Paragraph Style");
expect(pStyle).to.instanceOf(ParagraphStyle);
const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr);
expect(tree).to.deep.equal([ expect(tree).to.deep.equal([
{ {
@ -46,528 +48,59 @@ describe("Styles", () => {
]); ]);
}); });
}); });
});
describe("Style", () => { describe("#createCharacterStyle", () => {
describe("#constructor()", () => { it("should create a new character style and push it onto this collection", () => {
it("should set the given properties", () => { const cStyle = styles.createCharacterStyle("pStyleId");
const style = new Style({ expect(cStyle).to.instanceOf(CharacterStyle);
type: "paragraph", const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr);
styleId: "myStyleId", expect(tree).to.deep.equal([
default: true,
});
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId", "w:default": true } }],
});
});
it("should set the name of the style, if given", () => {
const style = new Style(
{ {
type: "paragraph",
styleId: "myStyleId",
},
"Style Name",
);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [ "w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { _attr: { "w:type": "character", "w:styleId": "pStyleId" } },
{ "w:name": [{ _attr: { "w:val": "Style Name" } }] },
],
});
});
});
});
describe("Style components", () => {
it("Name#constructor", () => {
const style = new components.Name("Style Name");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:name": [{ _attr: { "w:val": "Style Name" } }] });
});
it("BasedOn#constructor", () => {
const style = new components.BasedOn("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:basedOn": [{ _attr: { "w:val": "otherId" } }] });
});
it("Next#constructor", () => {
const style = new components.Next("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:next": [{ _attr: { "w:val": "otherId" } }] });
});
it("Link#constructor", () => {
const style = new components.Link("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:link": [{ _attr: { "w:val": "otherId" } }] });
});
it("UiPriority#constructor", () => {
const style = new components.UiPriority("123");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({ "w:uiPriority": [{ _attr: { "w:val": "123" } }] });
});
});
describe("ParagraphStyle", () => {
describe("#constructor", () => {
it("should set the style type to paragraph and use the given style id", () => {
const style = new ParagraphStyle("myStyleId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [] }, { "w:rPr": [] }],
});
});
it("should set the name of the style, if given", () => {
const style = new ParagraphStyle("myStyleId", "Style Name");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:name": [{ _attr: { "w:val": "Style Name" } }] },
{ "w:pPr": [] },
{ "w:rPr": [] }, { "w:rPr": [] },
],
});
});
});
describe("formatting methods: style attributes", () => {
it("#basedOn", () => {
const style = new ParagraphStyle("myStyleId").basedOn("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:basedOn": [{ _attr: { "w:val": "otherId" } }] },
],
});
});
it("#quickFormat", () => {
const style = new ParagraphStyle("myStyleId").quickFormat();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:qFormat": [] },
],
});
});
it("#next", () => {
const style = new ParagraphStyle("myStyleId").next("otherId");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{ "w:rPr": [] },
{ "w:next": [{ _attr: { "w:val": "otherId" } }] },
],
});
});
});
describe("formatting methods: paragraph properties", () => {
it("#indent", () => {
const style = new ParagraphStyle("myStyleId").indent({ left: 720 });
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ {
"w:pPr": [{ "w:ind": [{ _attr: { "w:left": 720 } }] }], "w:uiPriority": [
},
{ "w:rPr": [] },
],
});
});
it("#spacing", () => {
const style = new ParagraphStyle("myStyleId").spacing({ before: 50, after: 150 });
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:spacing": [{ _attr: { "w:before": 50, "w:after": 150 } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#center", () => {
const style = new ParagraphStyle("myStyleId").center();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "center" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#character spacing", () => {
const style = new ParagraphStyle("myStyleId").characterSpacing(24);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:spacing": [{ _attr: { "w:val": 24 } }] }],
},
],
});
});
it("#left", () => {
const style = new ParagraphStyle("myStyleId").left();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "left" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#right", () => {
const style = new ParagraphStyle("myStyleId").right();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "right" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#justified", () => {
const style = new ParagraphStyle("myStyleId").justified();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [{ "w:jc": [{ _attr: { "w:val": "both" } }] }],
},
{ "w:rPr": [] },
],
});
});
it("#thematicBreak", () => {
const style = new ParagraphStyle("myStyleId").thematicBreak();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{
"w:pPr": [
{
"w:pBdr": [
{
"w:bottom": [
{ {
_attr: { _attr: {
"w:color": "auto", "w:val": "99",
"w:space": "1",
"w:val": "single",
"w:sz": "6",
}, },
}, },
], ],
}, },
], {
"w:unhideWhenUsed": [],
}, },
], ],
}, },
]);
});
it("should set the character name if given", () => {
const cStyle = styles.createCharacterStyle("pStyleId", "Character Style");
expect(cStyle).to.instanceOf(CharacterStyle);
const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr);
expect(tree).to.deep.equal([
{
"w:style": [
{ _attr: { "w:type": "character", "w:styleId": "pStyleId" } },
{ "w:name": [{ _attr: { "w:val": "Character Style" } }] },
{ "w:rPr": [] }, { "w:rPr": [] },
],
});
});
it("#leftTabStop", () => {
const style = new ParagraphStyle("myStyleId").leftTabStop(1200);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ {
"w:pPr": [ "w:uiPriority": [
{ {
"w:tabs": [{ "w:tab": [{ _attr: { "w:val": "left", "w:pos": 1200 } }] }], _attr: {
"w:val": "99",
},
}, },
], ],
}, },
{ "w:rPr": [] },
],
});
});
it("#maxRightTabStop", () => {
const style = new ParagraphStyle("myStyleId").maxRightTabStop();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ {
"w:pPr": [ "w:unhideWhenUsed": [],
{
"w:tabs": [{ "w:tab": [{ _attr: { "w:val": "right", "w:pos": 9026 } }] }],
}, },
], ],
}, },
{ "w:rPr": [] }, ]);
],
});
});
it("#keepLines", () => {
const style = new ParagraphStyle("myStyleId").keepLines();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [{ "w:keepLines": [] }] },
{ "w:rPr": [] },
],
});
});
it("#keepNext", () => {
const style = new ParagraphStyle("myStyleId").keepNext();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [{ "w:keepNext": [] }] },
{ "w:rPr": [] },
],
});
});
});
describe("formatting methods: run properties", () => {
it("#size", () => {
const style = new ParagraphStyle("myStyleId").size(24);
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:sz": [{ _attr: { "w:val": 24 } }] }, { "w:szCs": [{ _attr: { "w:val": 24 } }] }],
},
],
});
});
it("#smallCaps", () => {
const style = new ParagraphStyle("myStyleId").smallCaps();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:smallCaps": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#allCaps", () => {
const style = new ParagraphStyle("myStyleId").allCaps();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:caps": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#strike", () => {
const style = new ParagraphStyle("myStyleId").strike();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:strike": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#doubleStrike", () => {
const style = new ParagraphStyle("myStyleId").doubleStrike();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:dstrike": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#subScript", () => {
const style = new ParagraphStyle("myStyleId").subScript();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "subscript" } }] }],
},
],
});
});
it("#superScript", () => {
const style = new ParagraphStyle("myStyleId").superScript();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:vertAlign": [{ _attr: { "w:val": "superscript" } }] }],
},
],
});
});
it("#font", () => {
const style = new ParagraphStyle("myStyleId").font("Times");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [
{ "w:rFonts": [{ _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } }] },
],
},
],
});
});
it("#bold", () => {
const style = new ParagraphStyle("myStyleId").bold();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:b": [{ _attr: { "w:val": true } }] }],
},
],
});
});
it("#italics", () => {
const style = new ParagraphStyle("myStyleId").italics();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:i": [{ _attr: { "w:val": true } }] }],
},
],
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const style = new ParagraphStyle("myStyleId").underline();
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "single" } }] }],
},
],
});
});
it("should set the style if given", () => {
const style = new ParagraphStyle("myStyleId").underline("double");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "double" } }] }],
},
],
});
});
it("should set the style and color if given", () => {
const style = new ParagraphStyle("myStyleId").underline("double", "005599");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:u": [{ _attr: { "w:val": "double", "w:color": "005599" } }] }],
},
],
});
});
});
it("#color", () => {
const style = new ParagraphStyle("myStyleId").color("123456");
const tree = new Formatter().format(style);
expect(tree).to.deep.equal({
"w:style": [
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
{ "w:pPr": [] },
{
"w:rPr": [{ "w:color": [{ _attr: { "w:val": "123456" } }] }],
},
],
});
}); });
}); });
}); });

View File

@ -1,6 +1,6 @@
import { BaseXmlComponent, XmlComponent } from "file/xml-components"; import { BaseXmlComponent, XmlComponent } from "file/xml-components";
import { DocumentDefaults } from "./defaults"; import { DocumentDefaults } from "./defaults";
import { ParagraphStyle } from "./style"; import { CharacterStyle, ParagraphStyle } from "./style";
export * from "./border"; export * from "./border";
export class Styles extends XmlComponent { export class Styles extends XmlComponent {
@ -23,8 +23,14 @@ export class Styles extends XmlComponent {
} }
public createParagraphStyle(styleId: string, name?: string): ParagraphStyle { public createParagraphStyle(styleId: string, name?: string): ParagraphStyle {
const para = new ParagraphStyle(styleId, name); const paragraphStyle = new ParagraphStyle(styleId, name);
this.push(para); this.push(paragraphStyle);
return para; return paragraphStyle;
}
public createCharacterStyle(styleId: string, name?: string): CharacterStyle {
const characterStyle = new CharacterStyle(styleId, name);
this.push(characterStyle);
return characterStyle;
} }
} }

View File

@ -1,6 +1,7 @@
import { expect } from "chai"; import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { BorderStyle } from "file/styles";
import { VerticalAlign, VMergeType, WidthType } from "./table-cell-components"; import { VerticalAlign, VMergeType, WidthType } from "./table-cell-components";
import { TableCellProperties } from "./table-cell-properties"; import { TableCellProperties } from "./table-cell-properties";
@ -42,12 +43,19 @@ describe("TableCellProperties", () => {
}); });
describe("#setWidth", () => { describe("#setWidth", () => {
it("sets width", () => { it("should set width", () => {
const cellMargain = new TableCellProperties(); const cellMargain = new TableCellProperties();
cellMargain.setWidth(1, WidthType.DXA); cellMargain.setWidth(1, WidthType.DXA);
const tree = new Formatter().format(cellMargain); const tree = new Formatter().format(cellMargain);
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:tcW": [{ _attr: { "w:type": "dxa", "w:w": 1 } }] }] }); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:tcW": [{ _attr: { "w:type": "dxa", "w:w": 1 } }] }] });
}); });
it("should set width using default of AUTO", () => {
const cellMargain = new TableCellProperties();
cellMargain.setWidth(1);
const tree = new Formatter().format(cellMargain);
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:tcW": [{ _attr: { "w:type": "auto", "w:w": 1 } }] }] });
});
}); });
describe("#setShading", () => { describe("#setShading", () => {
@ -61,4 +69,18 @@ describe("TableCellProperties", () => {
expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": [{ _attr: { "w:fill": "test", "w:color": "000" } }] }] }); expect(tree).to.deep.equal({ "w:tcPr": [{ "w:shd": [{ _attr: { "w:fill": "test", "w:color": "000" } }] }] });
}); });
}); });
describe("#Borders", () => {
it("should return the TableCellBorders if Border has borders", () => {
const cellMargain = new TableCellProperties();
cellMargain.Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red");
const borders = cellMargain.Borders;
const tree = new Formatter().format(borders);
expect(tree).to.deep.equal({
"w:tcBorders": [{ "w:top": [{ _attr: { "w:val": "dashDotStroked", "w:sz": 3, "w:color": "red" } }] }],
});
});
});
}); });

View File

@ -44,7 +44,7 @@ export class TableCellProperties extends XmlComponent {
return this; return this;
} }
public setWidth(width: string | number, type: WidthType): TableCellProperties { public setWidth(width: string | number, type: WidthType = WidthType.AUTO): TableCellProperties {
this.root.push(new TableCellWidth(width, type)); this.root.push(new TableCellWidth(width, type));
return this; return this;

View File

@ -3,6 +3,7 @@ import { Paragraph } from "file/paragraph";
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Table } from "../table"; import { Table } from "../table";
import { TableCellBorders, VerticalAlign } from "./table-cell-components";
import { TableCellProperties } from "./table-cell-properties"; import { TableCellProperties } from "./table-cell-properties";
export class TableCell extends XmlComponent { export class TableCell extends XmlComponent {
@ -14,7 +15,12 @@ export class TableCell extends XmlComponent {
this.root.push(this.properties); this.root.push(this.properties);
} }
public addContent(content: Paragraph | Table): TableCell { public addParagraph(content: Paragraph): TableCell {
this.root.push(content);
return this;
}
public addTable(content: Table): TableCell {
this.root.push(content); this.root.push(content);
return this; return this;
} }
@ -35,11 +41,24 @@ export class TableCell extends XmlComponent {
public createParagraph(text?: string): Paragraph { public createParagraph(text?: string): Paragraph {
const para = new Paragraph(text); const para = new Paragraph(text);
this.addContent(para); this.addParagraph(para);
return para; return para;
} }
public get CellProperties(): TableCellProperties { public setVerticalAlign(type: VerticalAlign): TableCell {
return this.properties; this.properties.setVerticalAlign(type);
return this;
}
public addGridSpan(cellSpan: number): TableCell {
this.properties.addGridSpan(cellSpan);
return this;
}
public get Borders(): TableCellBorders {
return this.properties.Borders;
} }
} }

View File

@ -15,13 +15,21 @@ describe("TableProperties", () => {
}); });
describe("#setWidth", () => { describe("#setWidth", () => {
it("adds a table width property", () => { it("should add a table width property", () => {
const tp = new TableProperties().setWidth(WidthType.DXA, 1234); const tp = new TableProperties().setWidth(1234, WidthType.DXA);
const tree = new Formatter().format(tp); const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:tblPr": [{ "w:tblW": [{ _attr: { "w:type": "dxa", "w:w": 1234 } }] }], "w:tblPr": [{ "w:tblW": [{ _attr: { "w:type": "dxa", "w:w": 1234 } }] }],
}); });
}); });
it("should add a table width property with default of AUTO", () => {
const tp = new TableProperties().setWidth(1234);
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [{ "w:tblW": [{ _attr: { "w:type": "auto", "w:w": 1234 } }] }],
});
});
}); });
describe("#setFixedWidthLayout", () => { describe("#setFixedWidthLayout", () => {

View File

@ -17,8 +17,8 @@ export class TableProperties extends XmlComponent {
this.root.push(this.cellMargin); this.root.push(this.cellMargin);
} }
public setWidth(type: WidthType, w: number | string): TableProperties { public setWidth(width: number | string, type: WidthType = WidthType.AUTO): TableProperties {
this.root.push(new PreferredTableWidth(type, w)); this.root.push(new PreferredTableWidth(type, width));
return this; return this;
} }

View File

@ -25,7 +25,7 @@ export class TableRow extends XmlComponent {
public addGridSpan(index: number, cellSpan: number): TableCell { public addGridSpan(index: number, cellSpan: number): TableCell {
const remainCell = this.cells[index]; const remainCell = this.cells[index];
remainCell.CellProperties.addGridSpan(cellSpan); remainCell.addGridSpan(cellSpan);
this.cells.splice(index + 1, cellSpan - 1); this.cells.splice(index + 1, cellSpan - 1);
this.root.splice(index + 2, cellSpan - 1); this.root.splice(index + 2, cellSpan - 1);

View File

@ -111,19 +111,19 @@ describe("Table", () => {
table table
.getRow(0) .getRow(0)
.getCell(0) .getCell(0)
.addContent(new Paragraph("A1")); .addParagraph(new Paragraph("A1"));
table table
.getRow(0) .getRow(0)
.getCell(1) .getCell(1)
.addContent(new Paragraph("B1")); .addParagraph(new Paragraph("B1"));
table table
.getRow(1) .getRow(1)
.getCell(0) .getCell(0)
.addContent(new Paragraph("A2")); .addParagraph(new Paragraph("A2"));
table table
.getRow(1) .getRow(1)
.getCell(1) .getCell(1)
.addContent(new Paragraph("B2")); .addParagraph(new Paragraph("B2"));
const tree = new Formatter().format(table); const tree = new Formatter().format(table);
const cell = (c) => ({ const cell = (c) => ({
"w:tc": [ "w:tc": [
@ -149,10 +149,10 @@ describe("Table", () => {
describe("#getCell", () => { describe("#getCell", () => {
it("returns the correct cell", () => { it("returns the correct cell", () => {
const table = new Table(2, 2); const table = new Table(2, 2);
table.getCell(0, 0).addContent(new Paragraph("A1")); table.getCell(0, 0).addParagraph(new Paragraph("A1"));
table.getCell(0, 1).addContent(new Paragraph("B1")); table.getCell(0, 1).addParagraph(new Paragraph("B1"));
table.getCell(1, 0).addContent(new Paragraph("A2")); table.getCell(1, 0).addParagraph(new Paragraph("A2"));
table.getCell(1, 1).addContent(new Paragraph("B2")); table.getCell(1, 1).addParagraph(new Paragraph("B2"));
const tree = new Formatter().format(table); const tree = new Formatter().format(table);
const cell = (c) => ({ const cell = (c) => ({
"w:tc": [ "w:tc": [
@ -176,8 +176,8 @@ describe("Table", () => {
}); });
describe("#setWidth", () => { describe("#setWidth", () => {
it("sets the preferred width on the table", () => { it("should set the preferred width on the table", () => {
const table = new Table(2, 2).setWidth(WidthType.PERCENTAGE, 1000); const table = new Table(2, 2).setWidth(1000, WidthType.PERCENTAGE);
const tree = new Formatter().format(table); const tree = new Formatter().format(table);
expect(tree) expect(tree)
.to.have.property("w:tbl") .to.have.property("w:tbl")
@ -187,6 +187,15 @@ describe("Table", () => {
"w:tblPr": [DEFAULT_TABLE_PROPERTIES, { "w:tblW": [{ _attr: { "w:type": "pct", "w:w": 1000 } }] }], "w:tblPr": [DEFAULT_TABLE_PROPERTIES, { "w:tblW": [{ _attr: { "w:type": "pct", "w:w": 1000 } }] }],
}); });
}); });
it("sets the preferred width on the table with a default of AUTO", () => {
const table = new Table(2, 2).setWidth(1000);
const tree = new Formatter().format(table);
expect(tree["w:tbl"][0]).to.deep.equal({
"w:tblPr": [DEFAULT_TABLE_PROPERTIES, { "w:tblW": [{ _attr: { "w:type": "auto", "w:w": 1000 } }] }],
});
});
}); });
describe("#setFixedWidthLayout", () => { describe("#setFixedWidthLayout", () => {
@ -223,7 +232,7 @@ describe("Table", () => {
it("inserts a paragraph at the end of the cell even if it has a child table", () => { it("inserts a paragraph at the end of the cell even if it has a child table", () => {
const parentTable = new Table(1, 1); const parentTable = new Table(1, 1);
parentTable.getCell(0, 0).addContent(new Table(1, 1)); parentTable.getCell(0, 0).addTable(new Table(1, 1));
const tree = new Formatter().format(parentTable); const tree = new Formatter().format(parentTable);
expect(tree) expect(tree)
.to.have.property("w:tbl") .to.have.property("w:tbl")
@ -242,7 +251,7 @@ describe("Table", () => {
it("does not insert a paragraph if it already ends with one", () => { it("does not insert a paragraph if it already ends with one", () => {
const parentTable = new Table(1, 1); const parentTable = new Table(1, 1);
parentTable.getCell(0, 0).addContent(new Paragraph("Hello")); parentTable.getCell(0, 0).addParagraph(new Paragraph("Hello"));
const tree = new Formatter().format(parentTable); const tree = new Formatter().format(parentTable);
expect(tree) expect(tree)
.to.have.property("w:tbl") .to.have.property("w:tbl")

View File

@ -65,8 +65,8 @@ export class Table extends XmlComponent {
return this.getRow(row).getCell(col); return this.getRow(row).getCell(col);
} }
public setWidth(type: WidthType, width: number | string): Table { public setWidth(width: number | string, type: WidthType = WidthType.AUTO): Table {
this.properties.setWidth(type, width); this.properties.setWidth(width, type);
return this; return this;
} }

View File

@ -5,11 +5,11 @@ import { FooterReferenceType } from "file/document/body/section-properties/foote
import { HeaderReferenceType } from "file/document/body/section-properties/header-reference"; import { HeaderReferenceType } from "file/document/body/section-properties/header-reference";
import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper"; import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper";
import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper"; import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper";
import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components";
import { Media } from "file/media"; import { Media } from "file/media";
import { TargetModeType } from "file/relationships/relationship/relationship";
import { Styles } from "file/styles"; import { Styles } from "file/styles";
import { ExternalStylesFactory } from "file/styles/external-styles-factory"; import { ExternalStylesFactory } from "file/styles/external-styles-factory";
import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components";
const schemeToType = { const schemeToType = {
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header",
@ -23,10 +23,17 @@ interface IDocumentRefs {
readonly footers: Array<{ readonly id: number; readonly type: FooterReferenceType }>; readonly footers: Array<{ readonly id: number; readonly type: FooterReferenceType }>;
} }
enum RelationshipType {
HEADER = "header",
FOOTER = "footer",
IMAGE = "image",
HYPERLINK = "hyperlink",
}
interface IRelationshipFileInfo { interface IRelationshipFileInfo {
readonly id: number; readonly id: number;
readonly target: string; readonly target: string;
readonly type: "header" | "footer" | "image" | "hyperlink"; readonly type: RelationshipType;
} }
// Document Template // Document Template
@ -51,19 +58,69 @@ export class ImportDotx {
const zipContent = await JSZip.loadAsync(data); const zipContent = await JSZip.loadAsync(data);
const stylesContent = await zipContent.files["word/styles.xml"].async("text"); const stylesContent = await zipContent.files["word/styles.xml"].async("text");
const documentContent = await zipContent.files["word/document.xml"].async("text");
const relationshipContent = await zipContent.files["word/_rels/document.xml.rels"].async("text");
const stylesFactory = new ExternalStylesFactory(); const stylesFactory = new ExternalStylesFactory();
const styles = stylesFactory.newInstance(stylesContent); const documentRefs = this.extractDocumentRefs(documentContent);
const documentRelationships = this.findReferenceFiles(relationshipContent);
const documentContent = zipContent.files["word/document.xml"];
const documentRefs: IDocumentRefs = this.extractDocumentRefs(await documentContent.async("text"));
const titlePageIsDefined = this.titlePageIsDefined(await documentContent.async("text"));
const relationshipContent = zipContent.files["word/_rels/document.xml.rels"];
const documentRelationships: IRelationshipFileInfo[] = this.findReferenceFiles(await relationshipContent.async("text"));
const media = new Media(); const media = new Media();
const templateDocument: IDocumentTemplate = {
headers: await this.createHeaders(zipContent, documentRefs, documentRelationships, media),
footers: await this.createFooters(zipContent, documentRefs, documentRelationships, media),
currentRelationshipId: this.currentRelationshipId,
styles: stylesFactory.newInstance(stylesContent),
titlePageIsDefined: this.checkIfTitlePageIsDefined(documentContent),
};
return templateDocument;
}
private async createFooters(
zipContent: JSZip,
documentRefs: IDocumentRefs,
documentRelationships: IRelationshipFileInfo[],
media: Media,
): Promise<IDocumentFooter[]> {
const footers: IDocumentFooter[] = [];
for (const footerRef of documentRefs.footers) {
const relationFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id);
if (relationFileInfo === null || !relationFileInfo) {
throw new Error(`Can not find target file for id ${footerRef.id}`);
}
const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text");
const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
let footerXmlElement: XMLElement | undefined;
for (const xmlElm of xmlObj.elements || []) {
if (xmlElm.name === "w:ftr") {
footerXmlElement = xmlElm;
}
}
if (footerXmlElement === undefined) {
continue;
}
const importedComp = convertToXmlComponent(footerXmlElement) as ImportedXmlComponent;
const footer = new FooterWrapper(media, this.currentRelationshipId++, importedComp);
await this.addRelationshipToWrapper(relationFileInfo, zipContent, footer, media);
footers.push({ type: footerRef.type, footer });
}
return footers;
}
private async createHeaders(
zipContent: JSZip,
documentRefs: IDocumentRefs,
documentRelationships: IRelationshipFileInfo[],
media: Media,
): Promise<IDocumentHeader[]> {
const headers: IDocumentHeader[] = []; const headers: IDocumentHeader[] = [];
for (const headerRef of documentRefs.headers) { for (const headerRef of documentRefs.headers) {
const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id); const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
if (relationFileInfo === null || !relationFileInfo) { if (relationFileInfo === null || !relationFileInfo) {
@ -83,66 +140,52 @@ export class ImportDotx {
} }
const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent; const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent;
const header = new HeaderWrapper(media, this.currentRelationshipId++, importedComp); const header = new HeaderWrapper(media, this.currentRelationshipId++, importedComp);
await this.addRelationToWrapper(relationFileInfo, zipContent, header); // await this.addMedia(zipContent, media, documentRefs, documentRelationships);
await this.addRelationshipToWrapper(relationFileInfo, zipContent, header, media);
headers.push({ type: headerRef.type, header }); headers.push({ type: headerRef.type, header });
} }
const footers: IDocumentFooter[] = []; return headers;
for (const footerRef of documentRefs.footers) {
const relationFileInfo = documentRelationships.find((rel) => rel.id === footerRef.id);
if (relationFileInfo === null || !relationFileInfo) {
throw new Error(`Can not find target file for id ${footerRef.id}`);
}
const xmlData = await zipContent.files[`word/${relationFileInfo.target}`].async("text");
const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as XMLElement;
let footerXmlElement: XMLElement | undefined;
for (const xmlElm of xmlObj.elements || []) {
if (xmlElm.name === "w:ftr") {
footerXmlElement = xmlElm;
}
}
if (footerXmlElement === undefined) {
continue;
}
const importedComp = convertToXmlComponent(footerXmlElement) as ImportedXmlComponent;
const footer = new FooterWrapper(media, this.currentRelationshipId++, importedComp);
await this.addRelationToWrapper(relationFileInfo, zipContent, footer);
footers.push({ type: footerRef.type, footer });
} }
const templateDocument: IDocumentTemplate = { private async addRelationshipToWrapper(
headers,
footers,
currentRelationshipId: this.currentRelationshipId,
styles,
titlePageIsDefined,
};
return templateDocument;
}
public async addRelationToWrapper(
relationhipFile: IRelationshipFileInfo, relationhipFile: IRelationshipFileInfo,
zipContent: JSZip, zipContent: JSZip,
wrapper: HeaderWrapper | FooterWrapper, wrapper: HeaderWrapper | FooterWrapper,
media: Media,
): Promise<void> { ): Promise<void> {
let wrapperImagesReferences: IRelationshipFileInfo[] = [];
let hyperLinkReferences: IRelationshipFileInfo[] = [];
const refFile = zipContent.files[`word/_rels/${relationhipFile.target}.rels`]; const refFile = zipContent.files[`word/_rels/${relationhipFile.target}.rels`];
if (refFile) {
const xmlRef = await refFile.async("text"); if (!refFile) {
wrapperImagesReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "image"); return;
hyperLinkReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "hyperlink");
} }
const xmlRef = await refFile.async("text");
const wrapperImagesReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === RelationshipType.IMAGE);
const hyperLinkReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === RelationshipType.HYPERLINK);
for (const r of wrapperImagesReferences) { for (const r of wrapperImagesReferences) {
const buffer = await zipContent.files[`word/${r.target}`].async("nodebuffer"); const buffer = await zipContent.files[`word/${r.target}`].async("nodebuffer");
wrapper.addImageRelationship(buffer, r.id); const mediaData = media.addMedia(buffer);
wrapper.Relationships.createRelationship(
r.id,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
} }
for (const r of hyperLinkReferences) { for (const r of hyperLinkReferences) {
wrapper.addHyperlinkRelationship(r.target, r.id, "External"); wrapper.Relationships.createRelationship(
r.id,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
r.target,
TargetModeType.EXTERNAL,
);
} }
} }
public findReferenceFiles(xmlData: string): IRelationshipFileInfo[] { private findReferenceFiles(xmlData: string): IRelationshipFileInfo[] {
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact; const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship) const relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
? xmlObj.Relationships.Relationship ? xmlObj.Relationships.Relationship
@ -162,7 +205,7 @@ export class ImportDotx {
return relationships; return relationships;
} }
public extractDocumentRefs(xmlData: string): IDocumentRefs { private extractDocumentRefs(xmlData: string): IDocumentRefs {
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact; const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"]; const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
@ -208,13 +251,14 @@ export class ImportDotx {
return { headers, footers }; return { headers, footers };
} }
public titlePageIsDefined(xmlData: string): boolean { private checkIfTitlePageIsDefined(xmlData: string): boolean {
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact; const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"]; const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
return sectionProp["w:titlePg"] !== undefined; return sectionProp["w:titlePg"] !== undefined;
} }
public parseRefId(str: string): number { private parseRefId(str: string): number {
const match = /^rId(\d+)$/.exec(str); const match = /^rId(\d+)$/.exec(str);
if (match === null) { if (match === null) {
throw new Error("Invalid ref id"); throw new Error("Invalid ref id");