Merge branch 'master' of https://github.com/dolanmiu/docx into feat/floating-images
# Conflicts: # src/file/drawing/drawing.ts
This commit is contained in:
@ -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).
|
||||
|
||||
# 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 💖
|
||||
|
@ -233,7 +233,7 @@ class DocumentCreator {
|
||||
|
||||
public createRoleText(roleText: string): Paragraph {
|
||||
const paragraph = new Paragraph();
|
||||
const role = new TextRun(roleText).italic();
|
||||
const role = new TextRun(roleText).italics();
|
||||
|
||||
paragraph.addRun(role);
|
||||
|
||||
|
@ -84,8 +84,7 @@ doc.Styles.createParagraphStyle("ListParagraph", "List Paragraph")
|
||||
.basedOn("Normal");
|
||||
|
||||
doc.createImage(fs.readFileSync("./demo/images/pizza.gif"));
|
||||
doc
|
||||
.createParagraph("HEADING")
|
||||
doc.createParagraph("HEADING")
|
||||
.heading1()
|
||||
.center();
|
||||
|
||||
@ -111,8 +110,8 @@ const table = new Table(4, 4);
|
||||
table
|
||||
.getRow(0)
|
||||
.getCell(0)
|
||||
.addContent(new Paragraph("Pole No."));
|
||||
// table.Properties.width = 10000;
|
||||
.addParagraph(new Paragraph("Pole No."));
|
||||
|
||||
doc.addTable(table);
|
||||
|
||||
const arrboth = [
|
||||
@ -129,8 +128,6 @@ const arrboth = [
|
||||
arrboth.forEach((item) => {
|
||||
const para = doc.createParagraph();
|
||||
para.addImage(doc.createImage(fs.readFileSync(item.image)));
|
||||
// para.Properties.width = 60;
|
||||
// para.Properties.height = 90;
|
||||
doc.createParagraph(item.comment).style("normalPara2");
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Multiple sections and headers
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
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();
|
||||
|
||||
@ -41,6 +41,40 @@ doc.addSection({
|
||||
|
||||
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();
|
||||
|
||||
packer.toBuffer(doc).then((buffer) => {
|
||||
|
@ -8,8 +8,8 @@ const doc = new Document();
|
||||
const table = doc.createTable(4, 4);
|
||||
table
|
||||
.getCell(2, 2)
|
||||
.addContent(new Paragraph("Hello"))
|
||||
.CellProperties.Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red")
|
||||
.addParagraph(new Paragraph("Hello"))
|
||||
.Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red")
|
||||
.addBottomBorder(BorderStyle.DOUBLE, 3, "blue")
|
||||
.addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green")
|
||||
.addEndBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000");
|
||||
|
@ -16,7 +16,7 @@ paragraph2.addRun(textRun2);
|
||||
doc.addParagraph(paragraph2);
|
||||
|
||||
const paragraph3 = new Paragraph().bidirectional();
|
||||
const textRun3 = new TextRun("שלום עולם").italic().rightToLeft();
|
||||
const textRun3 = new TextRun("שלום עולם").italics().rightToLeft();
|
||||
paragraph3.addRun(textRun3);
|
||||
doc.addParagraph(paragraph3);
|
||||
|
||||
|
@ -6,10 +6,10 @@ import { Document, Media, Packer, Paragraph } from "../build";
|
||||
const doc = new Document();
|
||||
|
||||
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"));
|
||||
table.getCell(1, 1).addContent(image.Paragraph);
|
||||
table.getCell(1, 1).addParagraph(image.Paragraph);
|
||||
|
||||
const packer = new Packer();
|
||||
|
||||
|
@ -8,12 +8,12 @@ const doc = new Document();
|
||||
const table = doc.createTable(2, 2);
|
||||
table
|
||||
.getCell(1, 1)
|
||||
.addContent(new Paragraph("This text should be in the middle of the cell"))
|
||||
.CellProperties.setVerticalAlign(VerticalAlign.CENTER);
|
||||
.addParagraph(new Paragraph("This text should be in the middle of the cell"))
|
||||
.setVerticalAlign(VerticalAlign.CENTER);
|
||||
|
||||
table
|
||||
.getCell(1, 0)
|
||||
.addContent(
|
||||
.addParagraph(
|
||||
new Paragraph(
|
||||
"Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah",
|
||||
).heading1(),
|
||||
|
@ -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 * as fs from "fs";
|
||||
import { Document, Packer, Paragraph } from "../build";
|
||||
@ -7,24 +7,24 @@ const doc = new Document();
|
||||
|
||||
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);
|
||||
|
||||
doc.createParagraph("Another table").heading2();
|
||||
|
||||
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);
|
||||
|
||||
doc.createParagraph("Another table").heading2();
|
||||
|
||||
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, 1).addContent(new Paragraph("Bar2"));
|
||||
table.getCell(1, 2).addContent(new Paragraph("Bar3"));
|
||||
table.getCell(1, 3).addContent(new Paragraph("Bar4"));
|
||||
table.getCell(1, 0).addParagraph(new Paragraph("Bar1"));
|
||||
table.getCell(1, 1).addParagraph(new Paragraph("Bar2"));
|
||||
table.getCell(1, 2).addParagraph(new Paragraph("Bar3"));
|
||||
table.getCell(1, 3).addParagraph(new Paragraph("Bar4"));
|
||||
|
||||
table.getRow(0).mergeCells(0, 3);
|
||||
|
||||
|
@ -20,9 +20,9 @@ const table = doc.createTable(2, 2).float({
|
||||
relativeVerticalPosition: RelativeVerticalPosition.BOTTOM,
|
||||
});
|
||||
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);
|
||||
|
||||
const packer = new Packer();
|
||||
|
17
demo/demo35.ts
Normal file
17
demo/demo35.ts
Normal 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
23
demo/demo36.ts
Normal 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
18
demo/demo37.ts
Normal 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);
|
||||
});
|
@ -6,7 +6,7 @@ import { Document, Packer, Paragraph } from "../build";
|
||||
const doc = new Document();
|
||||
|
||||
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();
|
||||
|
||||
|
@ -29,6 +29,7 @@ import * as docx from "docx";
|
||||
## Basic Usage
|
||||
|
||||
```js
|
||||
var fs = require("fs");
|
||||
var docx = require("docx");
|
||||
|
||||
// Create document
|
||||
@ -41,11 +42,12 @@ paragraph.addRun(new docx.TextRun("Lorem Ipsum Foo Bar"));
|
||||
doc.addParagraph(paragraph);
|
||||
|
||||
// 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 if you used LocalPacker
|
||||
// Done! A file called 'My First Document.docx' will be in your file system.
|
||||
```
|
||||
|
||||
## Honoured Mentions
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
## 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.
|
||||
|
||||
@ -37,13 +37,13 @@ Unesesary coment removed // Make sure to use correct spelling
|
||||
|
||||
## 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:
|
||||
|
||||
1. Imports are much cleaner, no need for:
|
||||
1. Imports are much cleaner for the end user, no need for:
|
||||
```js
|
||||
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.
|
||||
|
||||
**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
|
||||
public float(tableFloatProperties: TableFloatProperties): Table
|
||||
```
|
||||
|
||||
**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
|
||||
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
|
||||
public addParagraph(paragraph: Paragraph) {
|
||||
|
@ -158,7 +158,8 @@ _Source: https://github.com/dolanmiu/docx/blob/master/demo/demo15.ts_
|
||||
|
||||
## 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")
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
<script src="//unpkg.com/docsify/lib/plugins/emoji.min.js"></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/prismjs/components/prism-typescript.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -5,7 +5,7 @@
|
||||
To create a new document, it is very easy:
|
||||
|
||||
```js
|
||||
var doc = new docx.Document();
|
||||
const doc = new docx.Document();
|
||||
```
|
||||
|
||||
## Document properties
|
||||
@ -13,7 +13,7 @@ var doc = new docx.Document();
|
||||
You can add properties to the Word document by specifying options, for example:
|
||||
|
||||
```js
|
||||
var doc = new docx.Document({
|
||||
const doc = new docx.Document({
|
||||
creator: "Dolan Miu",
|
||||
description: "My extremely interesting document",
|
||||
title: "My Document",
|
||||
@ -22,14 +22,18 @@ var doc = new docx.Document({
|
||||
|
||||
### Full list of options:
|
||||
|
||||
```
|
||||
creator
|
||||
description
|
||||
title
|
||||
subject
|
||||
keywords
|
||||
lastModifiedBy
|
||||
revision
|
||||
```
|
||||
|
||||
* creator
|
||||
* description
|
||||
* title
|
||||
* subject
|
||||
* keywords
|
||||
* lastModifiedBy
|
||||
* revision
|
||||
|
||||
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.
|
@ -37,8 +37,12 @@ Also all the supported section properties are implemented according to: http://o
|
||||
|
||||
// Add new section with another header and footer
|
||||
doc.addSection({
|
||||
headerId: header.Header.ReferenceId,
|
||||
footerId: footer.Footer.ReferenceId,
|
||||
headers: {
|
||||
default: header
|
||||
},
|
||||
footers: {
|
||||
default: footer
|
||||
},
|
||||
pageNumberStart: 1,
|
||||
pageNumberFormatType: docx.PageNumberFormat.DECIMAL,
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ const name = new TextRun("Name:")
|
||||
## Available methods
|
||||
|
||||
* 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
|
||||
* `.color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`)
|
||||
* `.size(halfPts)`: Set the font size, measured in half-points
|
||||
|
@ -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.
|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
|
154
docs/usage/tables.md
Normal file
154
docs/usage/tables.md
Normal 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_
|
@ -22,7 +22,7 @@ text.bold();
|
||||
### Italics
|
||||
|
||||
```js
|
||||
text.italic();
|
||||
text.italics();
|
||||
```
|
||||
|
||||
### Underline
|
||||
@ -80,5 +80,5 @@ text.break();
|
||||
What if you want to create a paragraph which is **_bold_** and **_italic_**?
|
||||
|
||||
```js
|
||||
paragraph.bold().italic();
|
||||
paragraph.bold().italics();
|
||||
```
|
||||
|
@ -73,7 +73,7 @@
|
||||
"mocha-webpack": "^1.0.1",
|
||||
"nyc": "^13.1.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^1.12.1",
|
||||
"prettier": "^1.15.2",
|
||||
"prompt": "^1.0.0",
|
||||
"replace-in-file": "^3.1.0",
|
||||
"rimraf": "^2.5.2",
|
||||
|
17
src/export/packer/image-replacer.ts
Normal file
17
src/export/packer/image-replacer.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ describe("Compiler", () => {
|
||||
describe("#compile()", () => {
|
||||
it("should pack all the content", async function() {
|
||||
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);
|
||||
|
||||
expect(fileNames).is.an.instanceof(Array);
|
||||
@ -46,7 +46,7 @@ describe("Compiler", () => {
|
||||
|
||||
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);
|
||||
|
||||
expect(fileNames).is.an.instanceof(Array);
|
||||
|
@ -3,6 +3,7 @@ import * as xml from "xml";
|
||||
|
||||
import { File } from "file";
|
||||
import { Formatter } from "../formatter";
|
||||
import { ImageReplacer } from "./image-replacer";
|
||||
|
||||
interface IXmlifyedFile {
|
||||
readonly data: string;
|
||||
@ -28,14 +29,15 @@ interface IXmlifyedFileMapping {
|
||||
|
||||
export class Compiler {
|
||||
private readonly formatter: Formatter;
|
||||
private readonly imageReplacer: ImageReplacer;
|
||||
|
||||
constructor() {
|
||||
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 xmlifiedFileMapping = this.xmlifyFile(file);
|
||||
|
||||
for (const key in xmlifiedFileMapping) {
|
||||
@ -59,26 +61,39 @@ export class Compiler {
|
||||
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;
|
||||
}
|
||||
|
||||
private xmlifyFile(file: File): IXmlifyedFileMapping {
|
||||
file.verifyUpdateFields();
|
||||
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
|
||||
|
||||
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: {
|
||||
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",
|
||||
},
|
||||
Styles: {
|
||||
@ -98,30 +113,66 @@ export class Compiler {
|
||||
data: xml(this.formatter.format(file.Numbering)),
|
||||
path: "word/numbering.xml",
|
||||
},
|
||||
Relationships: {
|
||||
data: xml(this.formatter.format(file.DocumentRelationships)),
|
||||
path: "word/_rels/document.xml.rels",
|
||||
},
|
||||
FileRelationships: {
|
||||
data: xml(this.formatter.format(file.FileRelationships)),
|
||||
path: "_rels/.rels",
|
||||
},
|
||||
Headers: file.Headers.map((headerWrapper, index) => ({
|
||||
data: xml(this.formatter.format(headerWrapper.Header)),
|
||||
path: `word/header${index + 1}.xml`,
|
||||
})),
|
||||
Footers: file.Footers.map((footerWrapper, index) => ({
|
||||
data: xml(this.formatter.format(footerWrapper.Footer)),
|
||||
path: `word/footer${index + 1}.xml`,
|
||||
})),
|
||||
HeaderRelationships: file.Headers.map((headerWrapper, index) => ({
|
||||
data: xml(this.formatter.format(headerWrapper.Relationships)),
|
||||
path: `word/_rels/header${index + 1}.xml.rels`,
|
||||
})),
|
||||
FooterRelationships: file.Footers.map((footerWrapper, index) => ({
|
||||
data: xml(this.formatter.format(footerWrapper.Relationships)),
|
||||
path: `word/_rels/footer${index + 1}.xml.rels`,
|
||||
})),
|
||||
HeaderRelationships: file.Headers.map((headerWrapper, index) => {
|
||||
const xmlData = xml(this.formatter.format(headerWrapper.Header));
|
||||
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
|
||||
|
||||
mediaDatas.forEach((mediaData, i) => {
|
||||
headerWrapper.Relationships.createRelationship(
|
||||
i,
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
||||
`media/${mediaData.fileName}`,
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
data: xml(this.formatter.format(headerWrapper.Relationships)),
|
||||
path: `word/_rels/header${index + 1}.xml.rels`,
|
||||
};
|
||||
}),
|
||||
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)),
|
||||
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: {
|
||||
data: xml(this.formatter.format(file.ContentTypes)),
|
||||
path: "[Content_Types].xml",
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,7 +9,7 @@ export class Packer {
|
||||
}
|
||||
|
||||
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({
|
||||
type: "nodebuffer",
|
||||
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
@ -19,7 +19,7 @@ export class Packer {
|
||||
}
|
||||
|
||||
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({
|
||||
type: "base64",
|
||||
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
@ -29,7 +29,7 @@ export class Packer {
|
||||
}
|
||||
|
||||
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({
|
||||
type: "blob",
|
||||
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
|
@ -17,7 +17,7 @@ describe("Body", () => {
|
||||
expect(formatted)
|
||||
.to.have.property("w:sectPr")
|
||||
.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:docGrid": [{ _attr: { "w:linePitch": 360 } }] },
|
||||
{ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -104,7 +103,6 @@ describe("Body", () => {
|
||||
},
|
||||
{ "w:cols": [{ _attr: { "w:space": 708 } }] },
|
||||
{ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] },
|
||||
{ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -88,7 +88,6 @@ describe("SectionProperties", () => {
|
||||
});
|
||||
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"][4]).to.deep.equal({ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] });
|
||||
});
|
||||
|
||||
it("should create section properties with changed options", () => {
|
||||
@ -183,5 +182,25 @@ describe("SectionProperties", () => {
|
||||
"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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ import { HeaderReference } from "./header-reference/header-reference";
|
||||
import { IPageBordersOptions, PageBorders } from "./page-border";
|
||||
import { PageMargin } from "./page-margin/page-margin";
|
||||
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 { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes";
|
||||
import { TitlePage } from "./title-page/title-page";
|
||||
@ -69,7 +69,7 @@ export class SectionProperties extends XmlComponent {
|
||||
orientation = PageOrientation.PORTRAIT,
|
||||
headers,
|
||||
footers,
|
||||
pageNumberFormatType = PageNumberFormat.DECIMAL,
|
||||
pageNumberFormatType,
|
||||
pageNumberStart,
|
||||
pageBorders,
|
||||
pageBorderTop,
|
||||
@ -88,7 +88,9 @@ export class SectionProperties extends XmlComponent {
|
||||
this.addHeaders(headers);
|
||||
this.addFooters(footers);
|
||||
|
||||
this.root.push(new PageNumberType(pageNumberStart, pageNumberFormatType));
|
||||
if (pageNumberStart || pageNumberFormatType) {
|
||||
this.root.push(new PageNumberType(pageNumberStart, pageNumberFormatType));
|
||||
}
|
||||
|
||||
if (pageBorders || pageBorderTop || pageBorderRight || pageBorderBottom || pageBorderLeft) {
|
||||
this.root.push(
|
||||
|
@ -8,7 +8,20 @@ import { Anchor } from "./anchor";
|
||||
|
||||
function createAnchor(drawingOptions: IDrawingOptions): Anchor {
|
||||
return new Anchor(
|
||||
1,
|
||||
{
|
||||
fileName: "test.png",
|
||||
stream: new Buffer(""),
|
||||
dimensions: {
|
||||
pixels: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
emus: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pixels: {
|
||||
x: 100,
|
||||
|
@ -1,5 +1,5 @@
|
||||
// http://officeopenxml.com/drwPicFloating.php
|
||||
import { IMediaDataDimensions } from "file/media";
|
||||
import { IMediaData, IMediaDataDimensions } from "file/media";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
import { IDrawingOptions } from "../drawing";
|
||||
import { HorizontalPosition, IFloating, SimplePos, VerticalPosition } from "../floating";
|
||||
@ -21,7 +21,7 @@ const defaultOptions: IFloating = {
|
||||
};
|
||||
|
||||
export class Anchor extends XmlComponent {
|
||||
constructor(referenceId: number, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
|
||||
constructor(mediaData: IMediaData, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
|
||||
super("wp:anchor");
|
||||
|
||||
const floating = {
|
||||
@ -70,6 +70,6 @@ export class Anchor extends XmlComponent {
|
||||
|
||||
this.root.push(new DocProperties());
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ function createDrawing(drawingOptions?: IDrawingOptions): Drawing {
|
||||
return new Drawing(
|
||||
{
|
||||
fileName: "test.jpg",
|
||||
referenceId: 1,
|
||||
stream: Buffer.from(imageBase64Data, "base64"),
|
||||
path: path,
|
||||
dimensions: {
|
||||
|
@ -28,10 +28,10 @@ export class Drawing extends XmlComponent {
|
||||
}
|
||||
|
||||
if (!drawingOptions.floating) {
|
||||
this.inline = new Inline(imageData.referenceId, imageData.dimensions);
|
||||
this.inline = new Inline(imageData, imageData.dimensions);
|
||||
this.root.push(this.inline);
|
||||
} else {
|
||||
this.root.push(new Anchor(imageData.referenceId, imageData.dimensions, drawingOptions));
|
||||
this.root.push(new Anchor(imageData, imageData.dimensions, drawingOptions));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { IMediaData } from "file/media";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { GraphicDataAttributes } from "./graphic-data-attribute";
|
||||
import { Pic } from "./pic";
|
||||
|
||||
export class GraphicData extends XmlComponent {
|
||||
private readonly pic: Pic;
|
||||
|
||||
constructor(referenceId: number, x: number, y: number) {
|
||||
constructor(mediaData: IMediaData, x: number, y: number) {
|
||||
super("a:graphicData");
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { IMediaData } from "file/media";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { Blip } from "./blip";
|
||||
import { SourceRectangle } from "./source-rectangle";
|
||||
import { Stretch } from "./stretch";
|
||||
|
||||
export class BlipFill extends XmlComponent {
|
||||
constructor(referenceId: number) {
|
||||
constructor(mediaData: IMediaData) {
|
||||
super("pic:blipFill");
|
||||
this.root.push(new Blip(referenceId));
|
||||
|
||||
this.root.push(new Blip(mediaData));
|
||||
this.root.push(new SourceRectangle());
|
||||
this.root.push(new Stretch());
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { IMediaData } from "file/media";
|
||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
|
||||
interface IBlipProperties {
|
||||
@ -13,11 +14,11 @@ class BlipAttributes extends XmlAttributeComponent<IBlipProperties> {
|
||||
}
|
||||
|
||||
export class Blip extends XmlComponent {
|
||||
constructor(referenceId: number) {
|
||||
constructor(mediaData: IMediaData) {
|
||||
super("a:blip");
|
||||
this.root.push(
|
||||
new BlipAttributes({
|
||||
embed: `rId${referenceId}`,
|
||||
embed: `rId{${mediaData.fileName}}`,
|
||||
cstate: "none",
|
||||
}),
|
||||
);
|
||||
|
@ -1,5 +1,7 @@
|
||||
// http://officeopenxml.com/drwPic.php
|
||||
import { IMediaData } from "file/media";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { BlipFill } from "./blip/blip-fill";
|
||||
import { NonVisualPicProperties } from "./non-visual-pic-properties/non-visual-pic-properties";
|
||||
import { PicAttributes } from "./pic-attributes";
|
||||
@ -8,7 +10,7 @@ import { ShapeProperties } from "./shape-properties/shape-properties";
|
||||
export class Pic extends XmlComponent {
|
||||
private readonly shapeProperties: ShapeProperties;
|
||||
|
||||
constructor(referenceId: number, x: number, y: number) {
|
||||
constructor(mediaData: IMediaData, x: number, y: number) {
|
||||
super("pic:pic");
|
||||
|
||||
this.root.push(
|
||||
@ -20,7 +22,7 @@ export class Pic extends XmlComponent {
|
||||
this.shapeProperties = new ShapeProperties(x, y);
|
||||
|
||||
this.root.push(new NonVisualPicProperties());
|
||||
this.root.push(new BlipFill(referenceId));
|
||||
this.root.push(new BlipFill(mediaData));
|
||||
this.root.push(new ShapeProperties(x, y));
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { IMediaData } from "file/media";
|
||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { GraphicData } from "./graphic-data";
|
||||
|
||||
interface IGraphicProperties {
|
||||
@ -14,7 +16,7 @@ class GraphicAttributes extends XmlAttributeComponent<IGraphicProperties> {
|
||||
export class Graphic extends XmlComponent {
|
||||
private readonly data: GraphicData;
|
||||
|
||||
constructor(referenceId: number, x: number, y: number) {
|
||||
constructor(mediaData: IMediaData, x: number, y: number) {
|
||||
super("a:graphic");
|
||||
this.root.push(
|
||||
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);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// http://officeopenxml.com/drwPicInline.php
|
||||
import { IMediaDataDimensions } from "file/media";
|
||||
import { IMediaData, IMediaDataDimensions } from "file/media";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
import { DocProperties } from "./../doc-properties/doc-properties";
|
||||
import { EffectExtent } from "./../effect-extent/effect-extent";
|
||||
@ -12,7 +12,7 @@ export class Inline extends XmlComponent {
|
||||
private readonly extent: Extent;
|
||||
private readonly graphic: Graphic;
|
||||
|
||||
constructor(referenceId: number, private readonly dimensions: IMediaDataDimensions) {
|
||||
constructor(readonly mediaData: IMediaData, private readonly dimensions: IMediaDataDimensions) {
|
||||
super("wp:inline");
|
||||
|
||||
this.root.push(
|
||||
@ -25,7 +25,7 @@ export class Inline extends XmlComponent {
|
||||
);
|
||||
|
||||
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(new EffectExtent());
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { expect } from "chai";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { File } from "./file";
|
||||
import { Paragraph } from "./paragraph";
|
||||
import { Table } from "./table";
|
||||
|
||||
describe("File", () => {
|
||||
describe("#constructor", () => {
|
||||
it("should create with correct headers", () => {
|
||||
it("should create with correct headers and footers", () => {
|
||||
const doc = new File();
|
||||
const header = doc.createHeader();
|
||||
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");
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const doc = new File();
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -18,6 +18,7 @@ import { Image, Media } from "./media";
|
||||
import { Numbering } from "./numbering";
|
||||
import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
|
||||
import { Relationships } from "./relationships";
|
||||
import { TargetModeType } from "./relationships/relationship/relationship";
|
||||
import { Settings } from "./settings";
|
||||
import { Styles } from "./styles";
|
||||
import { ExternalStylesFactory } from "./styles/external-styles-factory";
|
||||
@ -153,7 +154,7 @@ export class File {
|
||||
hyperlink.linkId,
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||
link,
|
||||
"External",
|
||||
TargetModeType.EXTERNAL,
|
||||
);
|
||||
return hyperlink;
|
||||
}
|
||||
@ -299,12 +300,6 @@ export class File {
|
||||
|
||||
for (const header of headers) {
|
||||
switch (header.type) {
|
||||
case HeaderReferenceType.DEFAULT:
|
||||
newGroup = {
|
||||
...newGroup,
|
||||
default: header.header,
|
||||
};
|
||||
break;
|
||||
case HeaderReferenceType.FIRST:
|
||||
newGroup = {
|
||||
...newGroup,
|
||||
@ -317,6 +312,7 @@ export class File {
|
||||
even: header.header,
|
||||
};
|
||||
break;
|
||||
case HeaderReferenceType.DEFAULT:
|
||||
default:
|
||||
newGroup = {
|
||||
...newGroup,
|
||||
@ -334,12 +330,6 @@ export class File {
|
||||
|
||||
for (const footer of footers) {
|
||||
switch (footer.type) {
|
||||
case FooterReferenceType.DEFAULT:
|
||||
newGroup = {
|
||||
...newGroup,
|
||||
default: footer.footer,
|
||||
};
|
||||
break;
|
||||
case FooterReferenceType.FIRST:
|
||||
newGroup = {
|
||||
...newGroup,
|
||||
@ -352,6 +342,7 @@ export class File {
|
||||
even: footer.footer,
|
||||
};
|
||||
break;
|
||||
case FooterReferenceType.DEFAULT:
|
||||
default:
|
||||
newGroup = {
|
||||
...newGroup,
|
||||
|
83
src/file/footer-wrapper.spec.ts
Normal file
83
src/file/footer-wrapper.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { FooterReferenceType } from "./document";
|
||||
import { Footer } from "./footer/footer";
|
||||
import { Image, IMediaData, Media } from "./media";
|
||||
import { Image, Media } from "./media";
|
||||
import { ImageParagraph, Paragraph } from "./paragraph";
|
||||
import { Relationships } from "./relationships";
|
||||
import { Table } from "./table";
|
||||
@ -43,29 +43,8 @@ export class FooterWrapper {
|
||||
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 {
|
||||
// TODO
|
||||
// tslint:disable-next-line:no-any
|
||||
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
|
||||
const mediaData = this.media.addMedia(image, width, height);
|
||||
this.addImage(new Image(new ImageParagraph(mediaData)));
|
||||
}
|
||||
|
||||
|
@ -9,19 +9,73 @@ import { Table } from "./table";
|
||||
describe("HeaderWrapper", () => {
|
||||
describe("#addParagraph", () => {
|
||||
it("should call the underlying header's addParagraph", () => {
|
||||
const file = new HeaderWrapper(new Media(), 1);
|
||||
const spy = sinon.spy(file.Header, "addParagraph");
|
||||
file.addParagraph(new Paragraph());
|
||||
const wrapper = new HeaderWrapper(new Media(), 1);
|
||||
const spy = sinon.spy(wrapper.Header, "addParagraph");
|
||||
wrapper.addParagraph(new Paragraph());
|
||||
|
||||
expect(spy.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
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 spy = sinon.spy(file.Header, "addTable");
|
||||
file.addTable(new Table(1, 1));
|
||||
const spy = sinon.spy(file.Header, "addParagraph");
|
||||
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);
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { HeaderReferenceType } from "./document";
|
||||
import { Header } from "./header/header";
|
||||
import { Image, IMediaData, Media } from "./media";
|
||||
import { Image, Media } from "./media";
|
||||
import { ImageParagraph, Paragraph } from "./paragraph";
|
||||
import { Relationships } from "./relationships";
|
||||
import { Table } from "./table";
|
||||
@ -43,29 +43,8 @@ export class HeaderWrapper {
|
||||
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 {
|
||||
// TODO
|
||||
// tslint:disable-next-line:no-any
|
||||
const mediaData = this.addImageRelationship(image as any, this.relationships.RelationshipCount, width, height);
|
||||
const mediaData = this.media.addMedia(image, width, height);
|
||||
this.addImage(new Image(new ImageParagraph(mediaData)));
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ export interface IMediaDataDimensions {
|
||||
}
|
||||
|
||||
export interface IMediaData {
|
||||
readonly referenceId: number;
|
||||
readonly stream: Buffer | Uint8Array | ArrayBuffer;
|
||||
readonly path?: string;
|
||||
readonly fileName: string;
|
||||
|
@ -1,5 +1,6 @@
|
||||
// tslint:disable:object-literal-key-quotes
|
||||
import { expect } from "chai";
|
||||
import { stub } from "sinon";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
@ -20,16 +21,18 @@ describe("Media", () => {
|
||||
});
|
||||
|
||||
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 image1 = Media.addImage(file, "test");
|
||||
|
||||
const tree = new Formatter().format(image1.Paragraph);
|
||||
const inlineElements = tree["w:p"][1]["w:r"][1]["w:drawing"][0]["wp:inline"];
|
||||
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({
|
||||
_attr: {
|
||||
"r:embed": `rId${file.DocumentRelationships.RelationshipCount}`,
|
||||
"r:embed": `rId{testId.png}`,
|
||||
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({
|
||||
_attr: {
|
||||
"r:embed": `rId${file.DocumentRelationships.RelationshipCount}`,
|
||||
"r:embed": `rId{testId.png}`,
|
||||
cstate: "none",
|
||||
},
|
||||
});
|
||||
@ -53,9 +56,8 @@ describe("Media", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
(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.referenceId).to.equal(1);
|
||||
expect(image.dimensions).to.deep.equal({
|
||||
pixels: {
|
||||
x: 100,
|
||||
@ -74,7 +76,7 @@ describe("Media", () => {
|
||||
// tslint:disable-next-line:no-any
|
||||
(Media as any).generateId = () => "test";
|
||||
|
||||
const image = new Media().addMedia("", 1);
|
||||
const image = new Media().addMedia("");
|
||||
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
||||
});
|
||||
});
|
||||
@ -85,12 +87,11 @@ describe("Media", () => {
|
||||
(Media as any).generateId = () => "test";
|
||||
|
||||
const media = new Media();
|
||||
media.addMedia("", 1);
|
||||
media.addMedia("");
|
||||
|
||||
const image = media.getMedia("test.png");
|
||||
|
||||
expect(image.fileName).to.equal("test.png");
|
||||
expect(image.referenceId).to.equal(1);
|
||||
expect(image.dimensions).to.deep.equal({
|
||||
pixels: {
|
||||
x: 100,
|
||||
@ -116,7 +117,7 @@ describe("Media", () => {
|
||||
(Media as any).generateId = () => "test";
|
||||
|
||||
const media = new Media();
|
||||
media.addMedia("", 1);
|
||||
media.addMedia("");
|
||||
|
||||
const array = media.Array;
|
||||
expect(array).to.be.an.instanceof(Array);
|
||||
@ -124,7 +125,6 @@ describe("Media", () => {
|
||||
|
||||
const image = array[0];
|
||||
expect(image.fileName).to.equal("test.png");
|
||||
expect(image.referenceId).to.equal(1);
|
||||
expect(image.dimensions).to.deep.equal({
|
||||
pixels: {
|
||||
x: 100,
|
||||
|
@ -4,11 +4,6 @@ import { ImageParagraph } from "../paragraph";
|
||||
import { IMediaData } from "./data";
|
||||
import { Image } from "./image";
|
||||
|
||||
interface IHackedFile {
|
||||
// tslint:disable-next-line:readonly-keyword
|
||||
currentRelationshipId: number;
|
||||
}
|
||||
|
||||
export class Media {
|
||||
public static addImage(
|
||||
file: File,
|
||||
@ -18,14 +13,7 @@ export class Media {
|
||||
drawingOptions?: IDrawingOptions,
|
||||
): Image {
|
||||
// Workaround to expose id without exposing to API
|
||||
const exposedFile = (file as {}) as IHackedFile;
|
||||
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}`,
|
||||
);
|
||||
|
||||
const mediaData = file.Media.addMedia(buffer, width, height);
|
||||
return new Image(new ImageParagraph(mediaData, drawingOptions));
|
||||
}
|
||||
|
||||
@ -57,17 +45,11 @@ export class Media {
|
||||
return data;
|
||||
}
|
||||
|
||||
public addMedia(
|
||||
buffer: Buffer | string | Uint8Array | ArrayBuffer,
|
||||
referenceId: number,
|
||||
width: number = 100,
|
||||
height: number = 100,
|
||||
): IMediaData {
|
||||
public addMedia(buffer: Buffer | string | Uint8Array | ArrayBuffer, width: number = 100, height: number = 100): IMediaData {
|
||||
const key = `${Media.generateId()}.png`;
|
||||
|
||||
return this.createMedia(
|
||||
key,
|
||||
referenceId,
|
||||
{
|
||||
width: width,
|
||||
height: height,
|
||||
@ -78,7 +60,6 @@ export class Media {
|
||||
|
||||
private createMedia(
|
||||
key: string,
|
||||
relationshipsCount: number,
|
||||
dimensions: { readonly width: number; readonly height: number },
|
||||
data: Buffer | string | Uint8Array | ArrayBuffer,
|
||||
filePath?: string,
|
||||
@ -86,7 +67,6 @@ export class Media {
|
||||
const newData = typeof data === "string" ? this.convertDataURIToBinary(data) : data;
|
||||
|
||||
const imageData: IMediaData = {
|
||||
referenceId: relationshipsCount,
|
||||
stream: newData,
|
||||
path: filePath,
|
||||
fileName: key,
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./numbering";
|
||||
export * from "./abstract-numbering";
|
||||
export * from "./level";
|
||||
export * from "./num";
|
||||
|
@ -10,10 +10,9 @@ describe("Image", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
image = new ImageParagraph({
|
||||
referenceId: 0,
|
||||
stream: new Buffer(""),
|
||||
path: "",
|
||||
fileName: "",
|
||||
fileName: "test.png",
|
||||
dimensions: {
|
||||
pixels: {
|
||||
x: 10,
|
||||
@ -171,7 +170,7 @@ describe("Image", () => {
|
||||
{
|
||||
_attr: {
|
||||
cstate: "none",
|
||||
"r:embed": "rId0",
|
||||
"r:embed": "rId{test.png}",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -21,9 +21,9 @@ describe("Run", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#italic()", () => {
|
||||
describe("#italics()", () => {
|
||||
it("it should add italics to the properties", () => {
|
||||
run.italic();
|
||||
run.italics();
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:i");
|
||||
assert.equal(newJson.root[0].root[1].rootKey, "w:iCs");
|
||||
|
@ -39,7 +39,7 @@ export class Run extends XmlComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public italic(): Run {
|
||||
public italics(): Run {
|
||||
this.properties.push(new Italics());
|
||||
this.properties.push(new ItalicsComplexScript());
|
||||
return this;
|
||||
|
@ -17,7 +17,9 @@ export type RelationshipType =
|
||||
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
||||
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
|
||||
|
||||
export type TargetModeType = "External";
|
||||
export enum TargetModeType {
|
||||
EXTERNAL = "External",
|
||||
}
|
||||
|
||||
export class Relationship extends XmlComponent {
|
||||
constructor(id: string, type: RelationshipType, target: string, targetMode?: TargetModeType) {
|
||||
|
296
src/file/styles/style/character-style.spec.ts
Normal file
296
src/file/styles/style/character-style.spec.ts
Normal 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": [] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
53
src/file/styles/style/character-style.ts
Normal file
53
src/file/styles/style/character-style.ts
Normal 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;
|
||||
}
|
||||
}
|
53
src/file/styles/style/components.spec.ts
Normal file
53
src/file/styles/style/components.spec.ts
Normal 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": [] });
|
||||
});
|
||||
});
|
331
src/file/styles/style/default-styles.spec.ts
Normal file
331
src/file/styles/style/default-styles.spec.ts
Normal 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" } }] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
106
src/file/styles/style/default-styles.ts
Normal file
106
src/file/styles/style/default-styles.ts
Normal 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");
|
||||
}
|
||||
}
|
@ -1,335 +1,4 @@
|
||||
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 { 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);
|
||||
}
|
||||
}
|
||||
export * from "./style";
|
||||
export * from "./paragraph-style";
|
||||
export * from "./character-style";
|
||||
export * from "./default-styles";
|
||||
|
524
src/file/styles/style/paragraph-style.spec.ts
Normal file
524
src/file/styles/style/paragraph-style.spec.ts
Normal 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": [] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
178
src/file/styles/style/paragraph-style.ts
Normal file
178
src/file/styles/style/paragraph-style.ts
Normal 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;
|
||||
}
|
||||
}
|
36
src/file/styles/style/style.spec.ts
Normal file
36
src/file/styles/style/style.spec.ts
Normal 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" } }] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
32
src/file/styles/style/style.ts
Normal file
32
src/file/styles/style/style.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@ import { assert, expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { ParagraphStyle, Style } from "./style";
|
||||
import * as components from "./style/components";
|
||||
import { CharacterStyle, ParagraphStyle } from "./style";
|
||||
|
||||
import { Styles } from "./styles";
|
||||
|
||||
describe("Styles", () => {
|
||||
@ -22,7 +22,8 @@ describe("Styles", () => {
|
||||
|
||||
describe("#createParagraphStyle", () => {
|
||||
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);
|
||||
expect(tree).to.deep.equal([
|
||||
{
|
||||
@ -32,7 +33,8 @@ describe("Styles", () => {
|
||||
});
|
||||
|
||||
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);
|
||||
expect(tree).to.deep.equal([
|
||||
{
|
||||
@ -46,528 +48,59 @@ describe("Styles", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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(
|
||||
describe("#createCharacterStyle", () => {
|
||||
it("should create a new character style and push it onto this collection", () => {
|
||||
const cStyle = styles.createCharacterStyle("pStyleId");
|
||||
expect(cStyle).to.instanceOf(CharacterStyle);
|
||||
const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr);
|
||||
expect(tree).to.deep.equal([
|
||||
{
|
||||
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" } }] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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": [] },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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:style": [
|
||||
{ _attr: { "w:type": "character", "w:styleId": "pStyleId" } },
|
||||
{ "w:rPr": [] },
|
||||
{
|
||||
"w:uiPriority": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "99",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ "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" } }] }],
|
||||
"w:unhideWhenUsed": [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
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("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:uiPriority": [
|
||||
{
|
||||
_attr: {
|
||||
"w:val": "99",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:unhideWhenUsed": [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BaseXmlComponent, XmlComponent } from "file/xml-components";
|
||||
import { DocumentDefaults } from "./defaults";
|
||||
import { ParagraphStyle } from "./style";
|
||||
import { CharacterStyle, ParagraphStyle } from "./style";
|
||||
export * from "./border";
|
||||
|
||||
export class Styles extends XmlComponent {
|
||||
@ -23,8 +23,14 @@ export class Styles extends XmlComponent {
|
||||
}
|
||||
|
||||
public createParagraphStyle(styleId: string, name?: string): ParagraphStyle {
|
||||
const para = new ParagraphStyle(styleId, name);
|
||||
this.push(para);
|
||||
return para;
|
||||
const paragraphStyle = new ParagraphStyle(styleId, name);
|
||||
this.push(paragraphStyle);
|
||||
return paragraphStyle;
|
||||
}
|
||||
|
||||
public createCharacterStyle(styleId: string, name?: string): CharacterStyle {
|
||||
const characterStyle = new CharacterStyle(styleId, name);
|
||||
this.push(characterStyle);
|
||||
return characterStyle;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
import { BorderStyle } from "file/styles";
|
||||
|
||||
import { VerticalAlign, VMergeType, WidthType } from "./table-cell-components";
|
||||
import { TableCellProperties } from "./table-cell-properties";
|
||||
@ -42,12 +43,19 @@ describe("TableCellProperties", () => {
|
||||
});
|
||||
|
||||
describe("#setWidth", () => {
|
||||
it("sets width", () => {
|
||||
it("should set width", () => {
|
||||
const cellMargain = new TableCellProperties();
|
||||
cellMargain.setWidth(1, WidthType.DXA);
|
||||
const tree = new Formatter().format(cellMargain);
|
||||
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", () => {
|
||||
@ -61,4 +69,18 @@ describe("TableCellProperties", () => {
|
||||
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" } }] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -44,7 +44,7 @@ export class TableCellProperties extends XmlComponent {
|
||||
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));
|
||||
|
||||
return this;
|
||||
|
@ -3,6 +3,7 @@ import { Paragraph } from "file/paragraph";
|
||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { Table } from "../table";
|
||||
import { TableCellBorders, VerticalAlign } from "./table-cell-components";
|
||||
import { TableCellProperties } from "./table-cell-properties";
|
||||
|
||||
export class TableCell extends XmlComponent {
|
||||
@ -14,7 +15,12 @@ export class TableCell extends XmlComponent {
|
||||
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);
|
||||
return this;
|
||||
}
|
||||
@ -35,11 +41,24 @@ export class TableCell extends XmlComponent {
|
||||
|
||||
public createParagraph(text?: string): Paragraph {
|
||||
const para = new Paragraph(text);
|
||||
this.addContent(para);
|
||||
this.addParagraph(para);
|
||||
|
||||
return para;
|
||||
}
|
||||
|
||||
public get CellProperties(): TableCellProperties {
|
||||
return this.properties;
|
||||
public setVerticalAlign(type: VerticalAlign): TableCell {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,21 @@ describe("TableProperties", () => {
|
||||
});
|
||||
|
||||
describe("#setWidth", () => {
|
||||
it("adds a table width property", () => {
|
||||
const tp = new TableProperties().setWidth(WidthType.DXA, 1234);
|
||||
it("should add a table width property", () => {
|
||||
const tp = new TableProperties().setWidth(1234, WidthType.DXA);
|
||||
const tree = new Formatter().format(tp);
|
||||
expect(tree).to.deep.equal({
|
||||
"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", () => {
|
||||
|
@ -17,8 +17,8 @@ export class TableProperties extends XmlComponent {
|
||||
this.root.push(this.cellMargin);
|
||||
}
|
||||
|
||||
public setWidth(type: WidthType, w: number | string): TableProperties {
|
||||
this.root.push(new PreferredTableWidth(type, w));
|
||||
public setWidth(width: number | string, type: WidthType = WidthType.AUTO): TableProperties {
|
||||
this.root.push(new PreferredTableWidth(type, width));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class TableRow extends XmlComponent {
|
||||
|
||||
public addGridSpan(index: number, cellSpan: number): TableCell {
|
||||
const remainCell = this.cells[index];
|
||||
remainCell.CellProperties.addGridSpan(cellSpan);
|
||||
remainCell.addGridSpan(cellSpan);
|
||||
this.cells.splice(index + 1, cellSpan - 1);
|
||||
this.root.splice(index + 2, cellSpan - 1);
|
||||
|
||||
|
@ -111,19 +111,19 @@ describe("Table", () => {
|
||||
table
|
||||
.getRow(0)
|
||||
.getCell(0)
|
||||
.addContent(new Paragraph("A1"));
|
||||
.addParagraph(new Paragraph("A1"));
|
||||
table
|
||||
.getRow(0)
|
||||
.getCell(1)
|
||||
.addContent(new Paragraph("B1"));
|
||||
.addParagraph(new Paragraph("B1"));
|
||||
table
|
||||
.getRow(1)
|
||||
.getCell(0)
|
||||
.addContent(new Paragraph("A2"));
|
||||
.addParagraph(new Paragraph("A2"));
|
||||
table
|
||||
.getRow(1)
|
||||
.getCell(1)
|
||||
.addContent(new Paragraph("B2"));
|
||||
.addParagraph(new Paragraph("B2"));
|
||||
const tree = new Formatter().format(table);
|
||||
const cell = (c) => ({
|
||||
"w:tc": [
|
||||
@ -149,10 +149,10 @@ describe("Table", () => {
|
||||
describe("#getCell", () => {
|
||||
it("returns the correct cell", () => {
|
||||
const table = new Table(2, 2);
|
||||
table.getCell(0, 0).addContent(new Paragraph("A1"));
|
||||
table.getCell(0, 1).addContent(new Paragraph("B1"));
|
||||
table.getCell(1, 0).addContent(new Paragraph("A2"));
|
||||
table.getCell(1, 1).addContent(new Paragraph("B2"));
|
||||
table.getCell(0, 0).addParagraph(new Paragraph("A1"));
|
||||
table.getCell(0, 1).addParagraph(new Paragraph("B1"));
|
||||
table.getCell(1, 0).addParagraph(new Paragraph("A2"));
|
||||
table.getCell(1, 1).addParagraph(new Paragraph("B2"));
|
||||
const tree = new Formatter().format(table);
|
||||
const cell = (c) => ({
|
||||
"w:tc": [
|
||||
@ -176,8 +176,8 @@ describe("Table", () => {
|
||||
});
|
||||
|
||||
describe("#setWidth", () => {
|
||||
it("sets the preferred width on the table", () => {
|
||||
const table = new Table(2, 2).setWidth(WidthType.PERCENTAGE, 1000);
|
||||
it("should set the preferred width on the table", () => {
|
||||
const table = new Table(2, 2).setWidth(1000, WidthType.PERCENTAGE);
|
||||
const tree = new Formatter().format(table);
|
||||
expect(tree)
|
||||
.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 } }] }],
|
||||
});
|
||||
});
|
||||
|
||||
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", () => {
|
||||
@ -223,7 +232,7 @@ describe("Table", () => {
|
||||
|
||||
it("inserts a paragraph at the end of the cell even if it has a child table", () => {
|
||||
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);
|
||||
expect(tree)
|
||||
.to.have.property("w:tbl")
|
||||
@ -242,7 +251,7 @@ describe("Table", () => {
|
||||
|
||||
it("does not insert a paragraph if it already ends with one", () => {
|
||||
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);
|
||||
expect(tree)
|
||||
.to.have.property("w:tbl")
|
||||
|
@ -65,8 +65,8 @@ export class Table extends XmlComponent {
|
||||
return this.getRow(row).getCell(col);
|
||||
}
|
||||
|
||||
public setWidth(type: WidthType, width: number | string): Table {
|
||||
this.properties.setWidth(type, width);
|
||||
public setWidth(width: number | string, type: WidthType = WidthType.AUTO): Table {
|
||||
this.properties.setWidth(width, type);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,11 @@ import { FooterReferenceType } from "file/document/body/section-properties/foote
|
||||
import { HeaderReferenceType } from "file/document/body/section-properties/header-reference";
|
||||
import { FooterWrapper, IDocumentFooter } from "file/footer-wrapper";
|
||||
import { HeaderWrapper, IDocumentHeader } from "file/header-wrapper";
|
||||
import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components";
|
||||
|
||||
import { Media } from "file/media";
|
||||
import { TargetModeType } from "file/relationships/relationship/relationship";
|
||||
import { Styles } from "file/styles";
|
||||
import { ExternalStylesFactory } from "file/styles/external-styles-factory";
|
||||
import { convertToXmlComponent, ImportedXmlComponent } from "file/xml-components";
|
||||
|
||||
const schemeToType = {
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header": "header",
|
||||
@ -23,10 +23,17 @@ interface IDocumentRefs {
|
||||
readonly footers: Array<{ readonly id: number; readonly type: FooterReferenceType }>;
|
||||
}
|
||||
|
||||
enum RelationshipType {
|
||||
HEADER = "header",
|
||||
FOOTER = "footer",
|
||||
IMAGE = "image",
|
||||
HYPERLINK = "hyperlink",
|
||||
}
|
||||
|
||||
interface IRelationshipFileInfo {
|
||||
readonly id: number;
|
||||
readonly target: string;
|
||||
readonly type: "header" | "footer" | "image" | "hyperlink";
|
||||
readonly type: RelationshipType;
|
||||
}
|
||||
|
||||
// Document Template
|
||||
@ -51,19 +58,69 @@ export class ImportDotx {
|
||||
const zipContent = await JSZip.loadAsync(data);
|
||||
|
||||
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 styles = stylesFactory.newInstance(stylesContent);
|
||||
|
||||
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 documentRefs = this.extractDocumentRefs(documentContent);
|
||||
const documentRelationships = this.findReferenceFiles(relationshipContent);
|
||||
|
||||
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[] = [];
|
||||
|
||||
for (const headerRef of documentRefs.headers) {
|
||||
const relationFileInfo = documentRelationships.find((rel) => rel.id === headerRef.id);
|
||||
if (relationFileInfo === null || !relationFileInfo) {
|
||||
@ -83,66 +140,52 @@ export class ImportDotx {
|
||||
}
|
||||
const importedComp = convertToXmlComponent(headerXmlElement) as ImportedXmlComponent;
|
||||
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 });
|
||||
}
|
||||
|
||||
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.addRelationToWrapper(relationFileInfo, zipContent, footer);
|
||||
footers.push({ type: footerRef.type, footer });
|
||||
}
|
||||
|
||||
const templateDocument: IDocumentTemplate = {
|
||||
headers,
|
||||
footers,
|
||||
currentRelationshipId: this.currentRelationshipId,
|
||||
styles,
|
||||
titlePageIsDefined,
|
||||
};
|
||||
return templateDocument;
|
||||
return headers;
|
||||
}
|
||||
|
||||
public async addRelationToWrapper(
|
||||
private async addRelationshipToWrapper(
|
||||
relationhipFile: IRelationshipFileInfo,
|
||||
zipContent: JSZip,
|
||||
wrapper: HeaderWrapper | FooterWrapper,
|
||||
media: Media,
|
||||
): Promise<void> {
|
||||
let wrapperImagesReferences: IRelationshipFileInfo[] = [];
|
||||
let hyperLinkReferences: IRelationshipFileInfo[] = [];
|
||||
const refFile = zipContent.files[`word/_rels/${relationhipFile.target}.rels`];
|
||||
if (refFile) {
|
||||
const xmlRef = await refFile.async("text");
|
||||
wrapperImagesReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "image");
|
||||
hyperLinkReferences = this.findReferenceFiles(xmlRef).filter((r) => r.type === "hyperlink");
|
||||
|
||||
if (!refFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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 relationXmlArray = Array.isArray(xmlObj.Relationships.Relationship)
|
||||
? xmlObj.Relationships.Relationship
|
||||
@ -162,7 +205,7 @@ export class ImportDotx {
|
||||
return relationships;
|
||||
}
|
||||
|
||||
public extractDocumentRefs(xmlData: string): IDocumentRefs {
|
||||
private extractDocumentRefs(xmlData: string): IDocumentRefs {
|
||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
||||
|
||||
@ -208,13 +251,14 @@ export class ImportDotx {
|
||||
return { headers, footers };
|
||||
}
|
||||
|
||||
public titlePageIsDefined(xmlData: string): boolean {
|
||||
private checkIfTitlePageIsDefined(xmlData: string): boolean {
|
||||
const xmlObj = xml2js(xmlData, { compact: true }) as XMLElementCompact;
|
||||
const sectionProp = xmlObj["w:document"]["w:body"]["w:sectPr"];
|
||||
|
||||
return sectionProp["w:titlePg"] !== undefined;
|
||||
}
|
||||
|
||||
public parseRefId(str: string): number {
|
||||
private parseRefId(str: string): number {
|
||||
const match = /^rId(\d+)$/.exec(str);
|
||||
if (match === null) {
|
||||
throw new Error("Invalid ref id");
|
||||
|
Reference in New Issue
Block a user