Compare commits
201 Commits
Author | SHA1 | Date | |
---|---|---|---|
492face7ab | |||
b6351f0260 | |||
3a7f9053b9 | |||
19b122684c | |||
72e89cfc3c | |||
5ae02c3342 | |||
258adba94c | |||
190208d5df | |||
32cda4dfb3 | |||
b2c3dd2f7b | |||
58eca3ff5b | |||
d5c04f9042 | |||
67ea7c95de | |||
e57fd8fc57 | |||
411c0dadb5 | |||
ee81f3c502 | |||
8263b93c36 | |||
be709d082c | |||
70c4e89a65 | |||
8f632d4ecd | |||
6784dc1f3d | |||
fd63a30298 | |||
b0a29e26c9 | |||
8826fb010d | |||
97101adb10 | |||
2ec171d4a8 | |||
8876bb1fea | |||
96ca9d9c23 | |||
74a65f02fa | |||
339c017940 | |||
969b52ae05 | |||
eb8f0f4033 | |||
d7229eb049 | |||
644819ed81 | |||
96fd61d3e4 | |||
b96cfe7b19 | |||
d0e7c97a88 | |||
0ede54cfdc | |||
5be1163549 | |||
580f6eb633 | |||
321be35918 | |||
45a18742d7 | |||
7e81da404a | |||
6c2abb4abc | |||
e8b0dbf93b | |||
e59c255d85 | |||
17b28cb724 | |||
410152441b | |||
dfff4b96bd | |||
72cb75a486 | |||
53fe1dd988 | |||
043219f005 | |||
0453f28951 | |||
94716e081b | |||
30f826fd3d | |||
99b7a03b8a | |||
b1c8b2beb8 | |||
a706455a7c | |||
8c388a95c4 | |||
9a41d78ecb | |||
6fc4ad782a | |||
16b9057ac6 | |||
28c62c1ee4 | |||
91a757619e | |||
f89049e4c1 | |||
086f5f5d03 | |||
a2694f1d7d | |||
fd350d53db | |||
af48af2854 | |||
15009884e3 | |||
ad462dfe10 | |||
68fe380575 | |||
5b2a311ad4 | |||
5db2503e7b | |||
55a1d23f29 | |||
0bfd753a0d | |||
22adabd2d9 | |||
6d5c89ff47 | |||
96a9bb1a1e | |||
2be00d3f1e | |||
30b21217a9 | |||
260a680f56 | |||
486777b108 | |||
3730e2b60e | |||
e7e6d02cc5 | |||
c7b7b585be | |||
0c128ae4a5 | |||
d7a70ffe03 | |||
e1c699ec41 | |||
245f0ae2ea | |||
762fc0ca9d | |||
457d074a59 | |||
ba80440faf | |||
ae41e22df5 | |||
bf2e5201c8 | |||
2662bcc60b | |||
efa4f1933a | |||
8c58ec2ba4 | |||
e6ed3a55ff | |||
69abf509c9 | |||
66eace4a33 | |||
eeb2187db4 | |||
fa44acca1b | |||
34ce662f70 | |||
8d72fa5542 | |||
64d642b593 | |||
d9e533156f | |||
90e5cc3ad5 | |||
9f61ca7abe | |||
6af79a884c | |||
353401d148 | |||
f17360d45a | |||
4bc6ed0eda | |||
bf22866a8d | |||
a6e40d9d92 | |||
0b78e33444 | |||
6689f76c28 | |||
df8ba1e8b8 | |||
b3524971ac | |||
1cd681bc2d | |||
7f4d1bf3e7 | |||
8c6cd012e7 | |||
371398bc2c | |||
9a90818729 | |||
90049a31ee | |||
3dbd917667 | |||
d85c6ce56a | |||
db24f67e34 | |||
fb6a4383ff | |||
c33cdb450c | |||
62b6b33254 | |||
64ab91765a | |||
c0ba937c3a | |||
70f4613e1b | |||
bd9d6b74f5 | |||
bd3eb3e214 | |||
c10b576a3a | |||
210b97d00b | |||
62a238de84 | |||
7b6f5bbaef | |||
a45048a464 | |||
c0b0649f37 | |||
e7e5c61a90 | |||
f1fb0c4f22 | |||
0de30f6c59 | |||
599be5bf2b | |||
9d7573ed9c | |||
738cac0253 | |||
35ddda444a | |||
8d11ec3422 | |||
ea647d84df | |||
8c91c04afa | |||
a89718ea83 | |||
62911d6e44 | |||
766069c7cc | |||
09f1d473c3 | |||
74ea0f699f | |||
b00e4fa5b5 | |||
7ffa59d7fa | |||
d662391a64 | |||
7a9661c55a | |||
f80a5654b4 | |||
001b756866 | |||
fd1aab48f7 | |||
7ff838357a | |||
958b5ea307 | |||
3a6cf81c60 | |||
10684b34aa | |||
2fc9156e93 | |||
c521d0ce63 | |||
50ca71087e | |||
72eff21027 | |||
11bbb5e285 | |||
18bba1870e | |||
c6eef3fd79 | |||
06353a6977 | |||
2b28dd42ee | |||
7022b39d2e | |||
5fac776dca | |||
0e8d92f8bb | |||
30fef5238e | |||
0fe6ff95a2 | |||
11e918114d | |||
879b9163a3 | |||
4a2b8a1e04 | |||
d6f5fe0ae8 | |||
dd4ff944d7 | |||
aac81ffa12 | |||
d93432678b | |||
ac7799a875 | |||
766bcabcb8 | |||
f06094c91d | |||
3b18b8267a | |||
20ec9b679e | |||
449d1bc2c3 | |||
b4dca79d72 | |||
e6e0658812 | |||
18ca93e50a | |||
f5144e6d72 | |||
2684f16579 | |||
77255c9d5e |
@ -38,3 +38,6 @@ build-tests
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# docs
|
||||
docs
|
10
.travis.yml
10
.travis.yml
@ -1,10 +1,16 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "node"
|
||||
- "stable"
|
||||
install:
|
||||
- npm install
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
after_failure:
|
||||
- "cat /home/travis/builds/dolanmiu/docx/npm-debug.log"
|
||||
after_success:
|
||||
- bash ./deploy-docs.sh
|
||||
env:
|
||||
global:
|
||||
- ENCRYPTION_LABEL: "ad385fa3b525"
|
||||
|
||||
|
391
README.md
391
README.md
@ -3,56 +3,38 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
Generate .docx files with JS/TS very easily
|
||||
Generate .docx files with JS/TS very easily, written in TS.
|
||||
</p>
|
||||
---
|
||||
|
||||
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Known Vulnerabilities][snky-image]][snky-url]
|
||||
-----
|
||||
|
||||
# docx
|
||||
> A tool to create Word Documents (.docx) with JS or TS, written in TS.
|
||||
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][gemnasium-image]][gemnasium-url] [![Known Vulnerabilities][snky-image]][snky-url] [![Chat on Gitter][gitter-image]][gitter-url]
|
||||
|
||||
[](https://nodei.co/npm/docx/)
|
||||
|
||||
# Table of Contents
|
||||
- [Install](#)
|
||||
- [Usage](#)
|
||||
- [Create simple Word Document](#)
|
||||
- [Create Paragraph](#)
|
||||
- [Styles](#)
|
||||
- [Heading1 - Heading5](#)
|
||||
- [Title](#)
|
||||
- [Text Alignment](#)
|
||||
- [Example](#)
|
||||
- [Thematic Break (Page Break)](#)
|
||||
- [Text](#)
|
||||
- [Typographical Emphasis](#)
|
||||
- [Bold](#)
|
||||
- [Italics](#)
|
||||
- [Underline](#)
|
||||
- [Break](#)
|
||||
- [Chaining](#)
|
||||
- [Bullet Points](#)
|
||||
- [Tab Stops](#)
|
||||
- [Left Tab Stop](#)
|
||||
- [Center Tab Stop](#)
|
||||
- [Right Tab Stop](#)
|
||||
- [Max Right Tab Stop](#)
|
||||
- [Example](#)
|
||||
- [Exporting](#)
|
||||
- [Express](#)
|
||||
- [Standalone .docx file](#)
|
||||
- [Examples](#)
|
||||
- [License](#)
|
||||
# docx
|
||||
|
||||
# Install
|
||||
## Install
|
||||
|
||||
```sh
|
||||
$ npm install --save docx
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
# Usage
|
||||
```sh
|
||||
$ npm run demo
|
||||
```
|
||||
|
||||
will run the demo selector app in the `demo` folder. It will prompt you to select a demo number, which will run a demo from that folder.
|
||||
|
||||
## Guide
|
||||
|
||||
Please refer to [the Wiki](https://github.com/dolanmiu/docx/wiki) for details on how to use this library, examples and much more!
|
||||
|
||||
Full documentation can be found here: [http://dolanmiu.github.io/docx](http://dolanmiu.github.io/docx)
|
||||
|
||||
## Simple Usage
|
||||
|
||||
```js
|
||||
// Used to create docx files
|
||||
@ -61,326 +43,34 @@ var docx = require('docx');
|
||||
// Create document
|
||||
var doc = new docx.Document();
|
||||
|
||||
// Add some content in the document
|
||||
var paragraph = new docx.Paragraph("Some cool text here.");
|
||||
// Add more text into the paragraph if you wish
|
||||
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);
|
||||
|
||||
// Or use the express packer to make the file downloadable.
|
||||
// res is express' Response object
|
||||
var exporter = new docx.ExpressPacker(doc, res);
|
||||
var exporter = new docx.LocalPacker(doc);
|
||||
```
|
||||
## Create simple Word Document
|
||||
```js
|
||||
var doc = new docx.Document();
|
||||
|
||||
var paragraph = new docx.Paragraph();
|
||||
var text = new docx.TextRun('Hello World');
|
||||
paragraph.addText(text);
|
||||
doc.addParagraph(paragraph);
|
||||
exporter.pack('My First Document');
|
||||
|
||||
// done! A file called 'My First Document.docx'
|
||||
// will be in your file system if you used LocalPacker
|
||||
// Or it will start downloading if you are using Express
|
||||
```
|
||||
|
||||
### Document properties
|
||||
You can add properties to the Word document by specifying options, for example:
|
||||
```js
|
||||
var doc = new docx.Document({
|
||||
creator: 'Dolan Miu',
|
||||
description: 'My extremely interesting document',
|
||||
title: 'My Document'
|
||||
});
|
||||
```
|
||||
## Examples
|
||||
Check [the Wiki](https://github.com/dolanmiu/docx/wiki/Examples) for examples.
|
||||
|
||||
#### Full list of options:
|
||||
```
|
||||
creator
|
||||
description
|
||||
title
|
||||
subject
|
||||
keywords
|
||||
lastModifiedBy
|
||||
revision
|
||||
```
|
||||
-----
|
||||
|
||||
You can mix and match whatever properties you want, or provide no properties.
|
||||
Made with 💖
|
||||
|
||||
## Create Paragraph
|
||||
Every text block in OpenXML is organised in paragraphs. You can add more text to the paragraph by doing this:
|
||||
```js
|
||||
var paragraph = new docx.Paragraph(),
|
||||
```
|
||||
```js
|
||||
var text = new docx.TextRun('Lorem Ipsum Foo Bar');
|
||||
var paragraph = new docx.Paragraph();
|
||||
paragraph.addText(text);
|
||||
```
|
||||
```js
|
||||
var paragraph = new docx.Paragraph("Short hand notation for adding text.");
|
||||
```
|
||||
|
||||
After you create the paragraph, you must add the paragraph into the `document`:
|
||||
```js
|
||||
doc.addParagraph(paragraph);
|
||||
```
|
||||
|
||||
### Styles
|
||||
Styles is a very important part of the look of a word document. At the moment, only headings and title is supported, but son the rest will be supported along with custom styles!
|
||||
|
||||

|
||||
|
||||
#### Heading1 - Heading5
|
||||
```js
|
||||
paragraph.heading1();
|
||||
paragraph.heading2();
|
||||
paragraph.heading3();
|
||||
paragraph.heading4();
|
||||
paragraph.heading5();
|
||||
```
|
||||
|
||||
#### Title
|
||||
```js
|
||||
paragraph.title();
|
||||
```
|
||||
|
||||
### Text Alignment
|
||||
To change the text alignment of a paragraph, for center, left, right or justified:
|
||||
```js
|
||||
paragraph.center();
|
||||
```
|
||||
```js
|
||||
paragraph.left();
|
||||
```
|
||||
```js
|
||||
paragraph.right();
|
||||
```
|
||||
```js
|
||||
paragraph.justified();
|
||||
```
|
||||
|
||||
#### Example
|
||||
```js
|
||||
paragraph.heading1().center();
|
||||
```
|
||||
The above will create a `heading 1` which is `centered`.
|
||||
|
||||
### Thematic Break
|
||||
To add a break in the page, simply add `.thematicBreak()` on a paragraph:
|
||||
|
||||
```js
|
||||
var paragraph = new docx.Paragraph("Amazing Heading").heading1().thematicBreak();
|
||||
```
|
||||
The above example will create a heading with a page break directly under it.
|
||||
|
||||
### Page Break
|
||||
To move to a new page (insert a page break), simply add `.pageBreak()` on a paragraph:
|
||||
|
||||
```js
|
||||
var paragraph = new docx.Paragraph("Amazing Heading").heading1().pageBreak();
|
||||
```
|
||||
The above example will create a heading and start a new page immediately afterwards.
|
||||
|
||||
## Text
|
||||
Paragraphs need `text run` objects. To create text:
|
||||
```js
|
||||
var text = new docx.TextRun("My awesome text here for my university dissertation");
|
||||
paragraph.addText(text);
|
||||
```
|
||||
Text objects have methods inside which changes the way the text is displayed.
|
||||
|
||||
### Typographical Emphasis
|
||||
More info [here](https://english.stackexchange.com/questions/97081/what-is-the-typography-term-which-refers-to-the-usage-of-bold-italics-and-unde)
|
||||
#### Bold
|
||||
```js
|
||||
text.bold();
|
||||
```
|
||||
|
||||
#### Italics
|
||||
```js
|
||||
text.italic();
|
||||
```
|
||||
|
||||
#### Underline
|
||||
```js
|
||||
text.underline();
|
||||
```
|
||||
|
||||
#### Strike through
|
||||
```js
|
||||
text.strike();
|
||||
```
|
||||
|
||||
#### Double strike through
|
||||
```js
|
||||
text.doubleStrike();
|
||||
```
|
||||
|
||||
#### Superscript
|
||||
```js
|
||||
text.superScript();
|
||||
```
|
||||
|
||||
#### Subscript
|
||||
```js
|
||||
text.subScript();
|
||||
```
|
||||
|
||||
#### All Capitals
|
||||
```js
|
||||
text.allCaps();
|
||||
```
|
||||
|
||||
#### Small Capitals
|
||||
```js
|
||||
text.smallCaps();
|
||||
```
|
||||
|
||||
### Break
|
||||
Sometimes you would want to put text underneath another line of text but inside the same paragraph.
|
||||
```js
|
||||
text.break();
|
||||
```
|
||||
|
||||
### Chaining
|
||||
What if you want to create a paragraph which is ***bold*** and ***italic***?
|
||||
```js
|
||||
paragraph.bold().italic();
|
||||
```
|
||||
|
||||
## Bullet Points
|
||||
To make a bullet point, simply make a paragraph into a bullet point:
|
||||
```js
|
||||
var text = new docx.TextRun("Bullet points");
|
||||
var paragraph = new docx.Paragraph(text).bullet();
|
||||
|
||||
var text2 = new docx.TextRun("Are awesome");
|
||||
var paragraph2 = new docx.Paragraph(text2).bullet();
|
||||
|
||||
doc.addParagraph(paragraph);
|
||||
doc.addParagraph(paragraph2);
|
||||
```
|
||||
This will produce:
|
||||
* Bullet points
|
||||
* Are awesome
|
||||
|
||||
## Tab Stops
|
||||
If you do not know why tab stops are useful, then I recommend you do a bit of research. 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.
|
||||
|
||||

|
||||
|
||||
Simply call the relevant methods on the paragraph listed below. Then just add a `tab()` method call to a text object. Adding multiple `tabStops` will mean you would have to chain `tab()` until the desired `tabStop` is selected. Example is shown below.
|
||||
|
||||
### Left Tab Stop
|
||||
```js
|
||||
paragraph.leftTabStop(2268);
|
||||
```
|
||||
2268 is the distance from the left side.
|
||||
|
||||
### Center Tab Stop
|
||||
```js
|
||||
paragraph.centerTabStp(2268);
|
||||
```
|
||||
2268 is the distance from the left side.
|
||||
|
||||
### Right Tab Stop
|
||||
```js
|
||||
paragraph.rightTabStop(2268);
|
||||
```
|
||||
2268 is the distance from the left side.
|
||||
|
||||
### Max Right Tab Stop
|
||||
```js
|
||||
paragraph.maxRightTabStop();
|
||||
```
|
||||
This will create a tab stop on the very edge of the right hand side. Handy for right aligning and left aligning text on the same line.
|
||||
|
||||
### Example
|
||||
```js
|
||||
var paragraph = new docx.Paragraph().maxRightTabStop();
|
||||
var leftText = new docx.TextRun("Hey everyone").bold();
|
||||
var rightText = new docx.TextRun("11th November 2015").tab();
|
||||
paragraph.addText(leftText);
|
||||
paragraph.addText(rightText);
|
||||
```
|
||||
The example above will create a left aligned text, and a right aligned text on the same line. The laymans approach to this problem would be to either use text boxes or tables. YUK!
|
||||
|
||||
```js
|
||||
var paragraph = new docx.Paragraph();
|
||||
paragraph.maxRightTabStop();
|
||||
paragraph.leftTabStop(1000);
|
||||
var text = new docx.TextRun("Second tab stop here I come!").tab().tab();
|
||||
paragraph.addText(text);
|
||||
```
|
||||
The above shows the use of two tab stops, and how to select/use it.
|
||||
|
||||
# Exporting
|
||||
I used the express exporter in my [website](http://www.dolan.bio). It's very useful, and is the preferred way if you want to make a downloadable file for a visitor. it is much better than generating a physical file on the server, and then passing a download link to that file.
|
||||
## Express
|
||||
Simply use the exporter, and pass in the necessary parameters:
|
||||
```js
|
||||
var docx = require('docx');
|
||||
|
||||
var doc = new docx.Document();
|
||||
var exporter = new docx.ExpressPacker(doc, res);
|
||||
exporter.pack('My Document');
|
||||
```
|
||||
where `res` is the response object obtained through the Express router. It is that simple. The file will begin downloading in the browser.
|
||||
|
||||
## Standalone .docx file
|
||||
```js
|
||||
var docx = require('docx');
|
||||
|
||||
var doc = new docx.Document();
|
||||
var exporter = new docx.LocalPacker(doc);
|
||||
exporter.pack('My Document');
|
||||
```
|
||||
|
||||
# Examples
|
||||
## In practice
|
||||
I used this library in my personal portfolio/CV website. Click generate CV for a demonstration. [http://www.dolan.bio](http://www.dolan.bio)
|
||||
|
||||
## General
|
||||
#### Simple paragraph
|
||||
```js
|
||||
var doc = new docx.Document();
|
||||
|
||||
var paragraph = new docx.Paragraph("Hello World");
|
||||
var institutionText = new docx.TextRun("University College London").bold(),
|
||||
var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
|
||||
paragraph.addText(institutionText);
|
||||
paragraph.addText(dateText);
|
||||
|
||||
doc.addParagraph(paragraph);
|
||||
var exporter = new docx.LocalPacker(doc);
|
||||
exporter.pack('My Document');
|
||||
```
|
||||
|
||||
Or:
|
||||
```js
|
||||
var doc = new docx.Document();
|
||||
|
||||
var paragraph = new docx.Paragraph("Hello World");
|
||||
var institutionText = new docx.TextRun("University College London").bold(),
|
||||
var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
|
||||
paragraph.addText(institutionText);
|
||||
paragraph.addText(dateText);
|
||||
|
||||
var exporter = new docx.ExpressPacker(doc, res);
|
||||
exporter.pack('My Document');
|
||||
```
|
||||
Would produce:
|
||||
|
||||
***University College London***
|
||||
|
||||
***5th Dec 2015***
|
||||
|
||||
Made with 💖 by Dolan Miu 🍆 💦 😝
|
||||
|
||||
# License
|
||||
|
||||
MIT © [Dolan Miu](http://www.dolan.bio)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contributions to this project
|
||||
|
||||
[npm-image]: https://badge.fury.io/js/docx.svg
|
||||
[npm-url]: https://npmjs.org/package/docx
|
||||
@ -390,3 +80,8 @@ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
||||
[daviddm-url]: https://david-dm.org/dolanmiu/docx
|
||||
[snky-image]: https://snyk.io/test/github/dolanmiu/docx/badge.svg
|
||||
[snky-url]: https://snyk.io/test/github/dolanmiu/docx
|
||||
[gitter-image]: https://badges.gitter.im/dolanmiu/docx.svg
|
||||
[gitter-url]: https://gitter.im/docx-lib/Lobby
|
||||
[gemnasium-image]: https://gemnasium.com/badges/github.com/dolanmiu/docx.svg
|
||||
[gemnasium-url]: https://gemnasium.com/github.com/dolanmiu/docx
|
||||
|
||||
|
22
demo/demo1.js
Normal file
22
demo/demo1.js
Normal file
@ -0,0 +1,22 @@
|
||||
const docx = require('../build');
|
||||
|
||||
var doc = new docx.Document();
|
||||
|
||||
var paragraph = new docx.Paragraph("Hello World");
|
||||
var institutionText = new docx.TextRun("University College London").bold();
|
||||
var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
|
||||
paragraph.addRun(institutionText);
|
||||
paragraph.addRun(dateText);
|
||||
|
||||
doc.addParagraph(paragraph);
|
||||
|
||||
// Feature coming soon
|
||||
// var media = new docx.Media();
|
||||
// media.addMedia("happy-penguins", "./demo/penguins.jpg");
|
||||
// var pictureRun = new docx.PictureRun(media.getMedia("happy-penguins"));
|
||||
|
||||
// var exporter = new docx.LocalPacker(doc);
|
||||
var exporter = new docx.LocalPacker(doc);
|
||||
exporter.pack('My Document');
|
||||
|
||||
console.log('Document created succesfully at project root!');
|
74
demo/demo2.js
Normal file
74
demo/demo2.js
Normal file
@ -0,0 +1,74 @@
|
||||
const docx = require('../build');
|
||||
|
||||
const styles = new docx.Styles();
|
||||
styles.createParagraphStyle('Heading1', 'Heading 1')
|
||||
.basedOn("Normal")
|
||||
.next("Normal")
|
||||
.quickFormat()
|
||||
.size(28)
|
||||
.bold()
|
||||
.italics()
|
||||
.spacing({after: 120});
|
||||
|
||||
styles.createParagraphStyle('Heading2', 'Heading 2')
|
||||
.basedOn("Normal")
|
||||
.next("Normal")
|
||||
.quickFormat()
|
||||
.size(26)
|
||||
.bold()
|
||||
.underline('double', 'FF0000')
|
||||
.spacing({before: 240, after: 120});
|
||||
|
||||
styles.createParagraphStyle('aside', 'Aside')
|
||||
.basedOn('Normal')
|
||||
.next('Normal')
|
||||
.color('999999')
|
||||
.italics()
|
||||
.indent(720)
|
||||
.spacing({line: 276});
|
||||
|
||||
styles.createParagraphStyle('wellSpaced', 'Well Spaced')
|
||||
.basedOn('Normal')
|
||||
.spacing({line: 276, before: 20 * 72 * .1, after: 20 * 72 * .05});
|
||||
|
||||
styles.createParagraphStyle('ListParagraph', 'List Paragraph')
|
||||
.quickFormat()
|
||||
.basedOn('Normal');
|
||||
|
||||
|
||||
const numbering = new docx.Numbering();
|
||||
const numberedAbstract = numbering.createAbstractNumbering();
|
||||
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left");
|
||||
|
||||
const doc = new docx.Document({
|
||||
creator: 'Clippy',
|
||||
title: 'Sample Document',
|
||||
description: 'A brief example of using docx',
|
||||
});
|
||||
|
||||
doc.createParagraph('Test heading1, bold and italicized').heading1();
|
||||
doc.createParagraph('Some simple content');
|
||||
doc.createParagraph('Test heading2 with double red underline').heading2();
|
||||
|
||||
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
|
||||
const letterNumbering5 = numbering.createConcreteNumbering(numberedAbstract);
|
||||
letterNumbering5.overrideLevel(0, 5);
|
||||
|
||||
doc.createParagraph('Option1').setNumbering(letterNumbering, 0);
|
||||
doc.createParagraph('Option5 -- override 2 to 5').setNumbering(letterNumbering5, 0);
|
||||
doc.createParagraph('Option3').setNumbering(letterNumbering, 0);
|
||||
|
||||
doc.createParagraph()
|
||||
.createTextRun('Some monospaced content')
|
||||
.font('Monospace');
|
||||
|
||||
doc.createParagraph('An aside, in light gray italics and indented').style('aside');
|
||||
doc.createParagraph('This is normal, but well-spaced text').style('wellSpaced');
|
||||
const para = doc.createParagraph();
|
||||
para.createTextRun('This is a bold run,').bold();
|
||||
para.createTextRun(' switching to normal ');
|
||||
para.createTextRun('and then underlined ').underline();
|
||||
para.createTextRun('and back to normal.');
|
||||
|
||||
const exporter = new docx.LocalPacker(doc, styles, undefined, numbering);
|
||||
exporter.pack('test.docx');
|
35
demo/demo3.js
Normal file
35
demo/demo3.js
Normal file
@ -0,0 +1,35 @@
|
||||
const docx = require('../build');
|
||||
|
||||
var doc = new docx.Document();
|
||||
|
||||
const numbering = new docx.Numbering();
|
||||
|
||||
const abstractNum = numbering.createAbstractNumbering();
|
||||
abstractNum.createLevel(0, "upperRoman", "%1", "start")
|
||||
.addParagraphProperty(new docx.Indent(720, 260));
|
||||
abstractNum.createLevel(1, "decimal", "%2.", "start")
|
||||
.addParagraphProperty(new docx.Indent(1440, 980));
|
||||
abstractNum.createLevel(2, "lowerLetter", "%3)", "start")
|
||||
.addParagraphProperty(new docx.Indent(2160, 1700));
|
||||
|
||||
const concrete = numbering.createConcreteNumbering(abstractNum);
|
||||
|
||||
var topLevelP = new docx.Paragraph("Hey you");
|
||||
var subP = new docx.Paragraph("What's up fam");
|
||||
var secondSubP = new docx.Paragraph("Hello World 2");
|
||||
var subSubP = new docx.Paragraph("Yeah boi");
|
||||
|
||||
topLevelP.setNumbering(concrete, 0);
|
||||
subP.setNumbering(concrete, 1);
|
||||
secondSubP.setNumbering(concrete, 1);
|
||||
subSubP.setNumbering(concrete, 2);
|
||||
|
||||
doc.addParagraph(topLevelP);
|
||||
doc.addParagraph(subP);
|
||||
doc.addParagraph(secondSubP);
|
||||
doc.addParagraph(subSubP);
|
||||
|
||||
var exporter = new docx.LocalPacker(doc);
|
||||
exporter.pack('My Document');
|
||||
|
||||
console.log('Document created succesfully at project root!');
|
12
demo/demo4.js
Normal file
12
demo/demo4.js
Normal file
@ -0,0 +1,12 @@
|
||||
const docx = require('../build');
|
||||
|
||||
var doc = new docx.Document();
|
||||
|
||||
const table = doc.createTable(4, 4);
|
||||
table.getCell(2, 2).addContent(new docx.Paragraph('Hello'));
|
||||
|
||||
|
||||
var exporter = new docx.LocalPacker(doc);
|
||||
exporter.pack('My Document');
|
||||
|
||||
console.log('Document created succesfully at project root!');
|
29
demo/index.js
Normal file
29
demo/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
var prompt = require('prompt');
|
||||
var shelljs = require('shelljs');
|
||||
var fs = require('fs');
|
||||
|
||||
console.log('What demo do you wish to run? (Enter a number)');
|
||||
|
||||
var schema = {
|
||||
properties: {
|
||||
number: {
|
||||
pattern: /^[0-9]+$/,
|
||||
message: 'Please enter a number.',
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
prompt.start();
|
||||
|
||||
prompt.get(schema, function (err, result) {
|
||||
var demoNumber = result.number;
|
||||
var filePath = `./demo/demo${demoNumber}.js`;
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`demo${demoNumber} does not exist: ${filePath}`);
|
||||
return;
|
||||
}
|
||||
console.log(`Running demo ${demoNumber}`);
|
||||
shelljs.exec(`node ${filePath}`);
|
||||
});
|
BIN
demo/penguins.jpg
Normal file
BIN
demo/penguins.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
68
deploy-docs.sh
Normal file
68
deploy-docs.sh
Normal file
@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit with nonzero exit code if anything fails
|
||||
|
||||
SOURCE_BRANCH="master"
|
||||
TARGET_BRANCH="gh-pages"
|
||||
|
||||
function doCompile {
|
||||
npm run typedoc
|
||||
}
|
||||
|
||||
# Pull requests and commits to other branches shouldn't try to deploy, just build to verify
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "$SOURCE_BRANCH" ]; then
|
||||
echo "Skipping deploy; just doing a build."
|
||||
doCompile
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Save some useful information
|
||||
REPO=`git config remote.origin.url`
|
||||
SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
|
||||
SHA=`git rev-parse --verify HEAD`
|
||||
|
||||
# Clone the existing gh-pages for this repo into docs/
|
||||
# Create a new empty branch if gh-pages doesn't exist yet (should only happen on first deply)
|
||||
git clone $REPO docs
|
||||
cd docs
|
||||
git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH
|
||||
cd ..
|
||||
|
||||
# Clean out existing contents
|
||||
# echo "Cleaning out existing contents."
|
||||
# rm -rf docs/*
|
||||
|
||||
# Run our compile script
|
||||
doCompile
|
||||
|
||||
# Now let's go have some fun with the cloned repo
|
||||
cd docs
|
||||
git config user.name "Travis CI"
|
||||
git config user.email "dolan_miu@hotmail.com"
|
||||
ls
|
||||
|
||||
# add .nojekyll file
|
||||
touch .nojekyll
|
||||
|
||||
# If there are no changes to the compiled out (e.g. this is a README update) then just bail.
|
||||
if [ -z `git diff --exit-code` ]; then
|
||||
echo "No changes to the output on this push; exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Commit the "changes", i.e. the new version.
|
||||
# The delta will show diffs between new and old versions.
|
||||
git add .
|
||||
git commit -m "Deploy to GitHub Pages: ${SHA}"
|
||||
|
||||
# Get the deploy key by using Travis's stored variables to decrypt deploy-key.enc
|
||||
ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
|
||||
ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
|
||||
ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
|
||||
ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
|
||||
openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in deploy-key.enc -out deploy-key -d
|
||||
chmod 600 deploy-key
|
||||
eval `ssh-agent -s`
|
||||
ssh-add deploy-key
|
||||
|
||||
# Now that we're all set up, we can push.
|
||||
git push $SSH_REPO $TARGET_BRANCH
|
BIN
deploy-key.enc
Normal file
BIN
deploy-key.enc
Normal file
Binary file not shown.
30
package.json
30
package.json
@ -1,17 +1,21 @@
|
||||
{
|
||||
"name": "docx",
|
||||
"version": "1.2.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"pretest": "rimraf ./build-tests && tsc -p ts/test-tsconfig.json",
|
||||
"test": "mocha ./build-tests --recursive",
|
||||
"prepublishOnly": "tsc -p ts",
|
||||
"lint": "tslint --project ./ts"
|
||||
"prepublishOnly": "npm run build",
|
||||
"lint": "tslint --project ./ts",
|
||||
"build": "rimraf ./build && tsc -p ts",
|
||||
"demo": "npm run build && node ./demo",
|
||||
"typedoc": "typedoc --out docs/ ts/ --module commonjs --target ES6 --disableOutputCheck"
|
||||
},
|
||||
"files": [
|
||||
"ts",
|
||||
"build"
|
||||
"build",
|
||||
"template"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -19,21 +23,20 @@
|
||||
},
|
||||
"keywords": [
|
||||
"office",
|
||||
"word",
|
||||
"generate",
|
||||
"creator",
|
||||
"create",
|
||||
"document",
|
||||
"doc",
|
||||
"officegen",
|
||||
"clippy"
|
||||
],
|
||||
"types": "./build/index.d.ts",
|
||||
"dependencies": {
|
||||
"@types/archiver": "^0.15.37",
|
||||
"@types/archiver": "^1.3.4",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/lodash": "^4.14.54",
|
||||
"app-root-path": "^2.0.1",
|
||||
"archiver": "^1.3.0",
|
||||
"install": "^0.8.7",
|
||||
"lodash": "^4.6.1",
|
||||
"npm": "^4.3.0",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"author": "Dolan Miu",
|
||||
@ -47,8 +50,11 @@
|
||||
"@types/mocha": "^2.2.39",
|
||||
"chai": "^3.5.0",
|
||||
"mocha": "^3.2.0",
|
||||
"prompt": "^1.0.0",
|
||||
"rimraf": "^2.5.2",
|
||||
"tslint": "^4.5.1",
|
||||
"typescript": "^2.2.1"
|
||||
"shelljs": "^0.7.7",
|
||||
"tslint": "^5.1.0",
|
||||
"typedoc": "^0.5.10",
|
||||
"typescript": "2.4.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
import { Body } from "../../../docx/document/body";
|
||||
import { assert } from "chai";
|
||||
import { SectionProperties } from "../../../docx/document/body/section-properties";
|
||||
import { PageSize } from "../../../docx/document/body/page-size";
|
||||
import { PageMargin } from "../../../docx/document/body/page-margin";
|
||||
import { Columns } from "../../../docx/document/body/columns";
|
||||
import { DocumentGrid } from "../../../docx/document/body/doc-grid";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../../tests/utility";
|
||||
import { Body } from "./";
|
||||
import { Columns } from "./columns";
|
||||
import { DocumentGrid } from "./doc-grid";
|
||||
import { PageMargin } from "./page-margin";
|
||||
import { PageSize } from "./page-size";
|
||||
import { SectionProperties } from "./section-properties";
|
||||
|
||||
describe("Body", () => {
|
||||
let body: Body;
|
||||
@ -26,27 +23,27 @@ describe("Body", () => {
|
||||
describe("#constructor()", () => {
|
||||
|
||||
it("should create the Section Properties", () => {
|
||||
let newJson = jsonify(body);
|
||||
const newJson = Utility.jsonify(body);
|
||||
assert.equal(newJson.root[0].rootKey, "w:sectPr");
|
||||
});
|
||||
|
||||
it("should create the Page Size", () => {
|
||||
let newJson = jsonify(body);
|
||||
const newJson = Utility.jsonify(body);
|
||||
assert.equal(newJson.root[1].rootKey, "w:pgSz");
|
||||
});
|
||||
|
||||
it("should create the Page Margin", () => {
|
||||
let newJson = jsonify(body);
|
||||
const newJson = Utility.jsonify(body);
|
||||
assert.equal(newJson.root[2].rootKey, "w:pgMar");
|
||||
});
|
||||
|
||||
it("should create the Columns", () => {
|
||||
let newJson = jsonify(body);
|
||||
const newJson = Utility.jsonify(body);
|
||||
assert.equal(newJson.root[3].rootKey, "w:cols");
|
||||
});
|
||||
|
||||
it("should create the Document Grid", () => {
|
||||
let newJson = jsonify(body);
|
||||
const newJson = Utility.jsonify(body);
|
||||
assert.equal(newJson.root[4].rootKey, "w:docGrid");
|
||||
});
|
||||
});
|
@ -1,15 +1,12 @@
|
||||
import { Attributes, XmlComponent } from "../../xml-components";
|
||||
import { SectionProperties } from "./section-properties";
|
||||
import { XmlComponent } from "../../xml-components";
|
||||
|
||||
export class Body extends XmlComponent {
|
||||
|
||||
constructor() {
|
||||
super("w:body");
|
||||
// this.root.push(new SectionProperties()); not actually needed
|
||||
}
|
||||
|
||||
public push(component: XmlComponent): void {
|
||||
// this.root.splice(this.body.length - 1, 0, component);
|
||||
this.root.push(component);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { XmlAttributeComponent } from "../xml-components";
|
||||
|
||||
interface IDocumentAttributesProperties {
|
||||
export interface IDocumentAttributesProperties {
|
||||
wpc?: string;
|
||||
mc?: string;
|
||||
o?: string;
|
||||
@ -26,39 +26,30 @@ interface IDocumentAttributesProperties {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export class DocumentAttributes extends XmlAttributeComponent {
|
||||
|
||||
constructor(properties?: IDocumentAttributesProperties) {
|
||||
super({
|
||||
wpc: "xmlns:wpc",
|
||||
mc: "xmlns:mc",
|
||||
o: "xmlns:o",
|
||||
r: "xmlns:r",
|
||||
m: "xmlns:m",
|
||||
v: "xmlns:v",
|
||||
wp14: "xmlns:wp14",
|
||||
wp: "xmlns:wp",
|
||||
w10: "xmlns:w10",
|
||||
w: "xmlns:w",
|
||||
w14: "xmlns:w14",
|
||||
w15: "xmlns:w15",
|
||||
wpg: "xmlns:wpg",
|
||||
wpi: "xmlns:wpi",
|
||||
wne: "xmlns:wne",
|
||||
wps: "xmlns:wps",
|
||||
Ignorable: "mc:Ignorable",
|
||||
cp: "xmlns:cp",
|
||||
dc: "xmlns:dc",
|
||||
dcterms: "xmlns:dcterms",
|
||||
dcmitype: "xmlns:dcmitype",
|
||||
xsi: "xmlns:xsi",
|
||||
type: "xsi:type",
|
||||
}, properties);
|
||||
|
||||
this.root = properties;
|
||||
|
||||
if (!properties) {
|
||||
this.root = {};
|
||||
}
|
||||
}
|
||||
export class DocumentAttributes extends XmlAttributeComponent<IDocumentAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
wpc: "xmlns:wpc",
|
||||
mc: "xmlns:mc",
|
||||
o: "xmlns:o",
|
||||
r: "xmlns:r",
|
||||
m: "xmlns:m",
|
||||
v: "xmlns:v",
|
||||
wp14: "xmlns:wp14",
|
||||
wp: "xmlns:wp",
|
||||
w10: "xmlns:w10",
|
||||
w: "xmlns:w",
|
||||
w14: "xmlns:w14",
|
||||
w15: "xmlns:w15",
|
||||
wpg: "xmlns:wpg",
|
||||
wpi: "xmlns:wpi",
|
||||
wne: "xmlns:wne",
|
||||
wps: "xmlns:wps",
|
||||
Ignorable: "mc:Ignorable",
|
||||
cp: "xmlns:cp",
|
||||
dc: "xmlns:dc",
|
||||
dcterms: "xmlns:dcterms",
|
||||
dcmitype: "xmlns:dcmitype",
|
||||
xsi: "xmlns:xsi",
|
||||
type: "xsi:type",
|
||||
};
|
||||
}
|
||||
|
74
ts/docx/document/document.spec.ts
Normal file
74
ts/docx/document/document.spec.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { assert, expect } from "chai";
|
||||
|
||||
import * as docx from "../../";
|
||||
import { Formatter } from "../../export/formatter";
|
||||
|
||||
describe("Document", () => {
|
||||
let document: docx.Document;
|
||||
|
||||
beforeEach(() => {
|
||||
document = new docx.Document();
|
||||
});
|
||||
|
||||
describe("#constructor()", () => {
|
||||
|
||||
it("should create valid JSON", () => {
|
||||
const stringifiedJson = JSON.stringify(document);
|
||||
let newJson;
|
||||
|
||||
try {
|
||||
newJson = JSON.parse(stringifiedJson);
|
||||
} catch (e) {
|
||||
assert.isTrue(false);
|
||||
}
|
||||
assert.isTrue(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createParagraph", () => {
|
||||
it("should create a new paragraph and append it to body", () => {
|
||||
const para = document.createParagraph();
|
||||
expect(para).to.be.an.instanceof(docx.Paragraph);
|
||||
const body = new Formatter().format(document)["w:document"][1]["w:body"];
|
||||
expect(body).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(body[0]).to.have.property("w:p");
|
||||
});
|
||||
|
||||
it("should use the text given to create a run in the paragraph", () => {
|
||||
const para = document.createParagraph("sample paragraph text");
|
||||
expect(para).to.be.an.instanceof(docx.Paragraph);
|
||||
const body = new Formatter().format(document)["w:document"][1]["w:body"];
|
||||
expect(body).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(body[0]).to.have.property("w:p").which.includes({
|
||||
"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "sample paragraph text"]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createTable", () => {
|
||||
it("should create a new table and append it to body", () => {
|
||||
const table = document.createTable(2, 3);
|
||||
expect(table).to.be.an.instanceof(docx.Table);
|
||||
const body = new Formatter().format(document)["w:document"][1]["w:body"];
|
||||
expect(body).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(body[0]).to.have.property("w:tbl");
|
||||
});
|
||||
|
||||
it("should create a table with the correct dimensions", () => {
|
||||
document.createTable(2, 3);
|
||||
const body = new Formatter().format(document)["w:document"][1]["w:body"];
|
||||
expect(body).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(body[0]).to.have.property("w:tbl").which.includes({
|
||||
"w:tblGrid": [
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
],
|
||||
});
|
||||
expect(body[0]["w:tbl"].filter((x) => x["w:tr"])).to.have.length(2);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,7 +1,9 @@
|
||||
import { Paragraph } from "../paragraph";
|
||||
import { Table } from "../table";
|
||||
import { XmlComponent } from "../xml-components";
|
||||
import { Body } from "./body";
|
||||
import { DocumentAttributes } from "./document-attributes";
|
||||
|
||||
export class Document extends XmlComponent {
|
||||
private body: Body;
|
||||
|
||||
@ -34,8 +36,20 @@ export class Document extends XmlComponent {
|
||||
this.body.push(paragraph);
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
this.body.clearVariables();
|
||||
delete this.body;
|
||||
public createParagraph(text?: string): Paragraph {
|
||||
const para = new Paragraph(text);
|
||||
this.addParagraph(para);
|
||||
return para;
|
||||
}
|
||||
|
||||
public addTable(table: Table): void {
|
||||
this.body.push(table);
|
||||
}
|
||||
|
||||
public createTable(rows: number, cols: number): Table {
|
||||
const table = new Table(rows, cols);
|
||||
this.addTable(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
export { Document } from "./document";
|
||||
export { Paragraph } from "./paragraph";
|
||||
export * from "./paragraph";
|
||||
export { Run } from "./run";
|
||||
export { TextRun } from "./run/text-run";
|
||||
export { PictureRun } from "./run/picture-run";
|
||||
export { Table } from "./table";
|
||||
// Perhaps all run related stuff can be exported from run, instead of exporting Run, TextRun, PictureRun seperately.
|
||||
|
15
ts/docx/paragraph/alignment.ts
Normal file
15
ts/docx/paragraph/alignment.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
export type AlignmentOptions = "left" | "center" | "right" | "both";
|
||||
|
||||
export class AlignmentAttributes extends XmlAttributeComponent<{val: AlignmentOptions}> {
|
||||
protected xmlKeys = {val: "w:val"};
|
||||
}
|
||||
|
||||
export class Alignment extends XmlComponent {
|
||||
|
||||
constructor(type: AlignmentOptions) {
|
||||
super("w:jc");
|
||||
this.root.push(new AlignmentAttributes({val: type}));
|
||||
}
|
||||
}
|
@ -1,13 +1,10 @@
|
||||
import { ThematicBreak } from "../../../docx/paragraph/border";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { ThematicBreak } from "./border";
|
||||
|
||||
describe("Border", () => {
|
||||
|
||||
// TODO: Need tests here
|
||||
});
|
||||
|
||||
describe("ThematicBreak", () => {
|
||||
@ -19,12 +16,12 @@ describe("ThematicBreak", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Thematic Break with correct border properties", () => {
|
||||
let newJson = jsonify(thematicBreak);
|
||||
let attributes = {
|
||||
const newJson = Utility.jsonify(thematicBreak);
|
||||
const attributes = {
|
||||
color: "auto",
|
||||
space: "1",
|
||||
val: "single",
|
||||
sz: "6"
|
||||
sz: "6",
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
|
||||
});
|
10
ts/docx/paragraph/formatting.ts
Normal file
10
ts/docx/paragraph/formatting.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export { Alignment } from "./alignment";
|
||||
export { ThematicBreak } from "./border";
|
||||
export { Indent } from "./indent";
|
||||
export { KeepLines, KeepNext } from "./keep";
|
||||
export { PageBreak } from "./page-break";
|
||||
export { ParagraphProperties } from "./properties";
|
||||
export { ISpacingProperties, Spacing } from "./spacing";
|
||||
export { Style } from "./style";
|
||||
export { LeftTabStop, MaxRightTabStop } from "./tab-stop";
|
||||
export { NumberProperties } from "./unordered-list";
|
27
ts/docx/paragraph/indent.ts
Normal file
27
ts/docx/paragraph/indent.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
interface IIndentAttributesProperties {
|
||||
left?: number;
|
||||
hanging?: number;
|
||||
firstLine?: number;
|
||||
start?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
class IndentAttributes extends XmlAttributeComponent<IIndentAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
left: "w:left",
|
||||
hanging: "w:hanging",
|
||||
firstLine: "w:firstLine",
|
||||
start: "w:start",
|
||||
end: "w:end",
|
||||
};
|
||||
}
|
||||
|
||||
export class Indent extends XmlComponent {
|
||||
|
||||
constructor(attrs: object) {
|
||||
super("w:ind");
|
||||
this.root.push(new IndentAttributes(attrs));
|
||||
}
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
import { IData } from "../../media/data";
|
||||
import { Num } from "../../numbering/num";
|
||||
import { Run } from "../run";
|
||||
import { PictureRun } from "../run/picture-run";
|
||||
import { TextRun } from "../run/text-run";
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
import { XmlComponent } from "../xml-components";
|
||||
|
||||
import { Alignment } from "./alignment";
|
||||
import { ThematicBreak } from "./border";
|
||||
import { Indent } from "./indent";
|
||||
import { KeepLines, KeepNext } from "./keep";
|
||||
import { PageBreak } from "./page-break";
|
||||
import { ParagraphProperties } from "./properties";
|
||||
import { ISpacingProperties, Spacing } from "./spacing";
|
||||
import { Style } from "./style";
|
||||
import { LeftTabStop, MaxRightTabStop } from "./tab-stop";
|
||||
import { NumberProperties } from "./unordered-list";
|
||||
|
||||
class Alignment extends XmlComponent {
|
||||
|
||||
constructor(type: string) {
|
||||
super("w:jc");
|
||||
this.root.push(new Attributes({
|
||||
val: type,
|
||||
}));
|
||||
}
|
||||
}
|
||||
export * from "./formatting";
|
||||
|
||||
export class Paragraph extends XmlComponent {
|
||||
private properties: ParagraphProperties;
|
||||
@ -30,11 +30,23 @@ export class Paragraph extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public addText(run: TextRun): Paragraph {
|
||||
public addRun(run: Run): Paragraph {
|
||||
this.root.push(run);
|
||||
return this;
|
||||
}
|
||||
|
||||
public createTextRun(text: string): TextRun {
|
||||
const run = new TextRun(text);
|
||||
this.addRun(run);
|
||||
return run;
|
||||
}
|
||||
|
||||
public createPictureRun(imageData: IData): PictureRun {
|
||||
const run = new PictureRun(imageData);
|
||||
this.addRun(run);
|
||||
return run;
|
||||
}
|
||||
|
||||
public heading1(): Paragraph {
|
||||
this.properties.push(new Style("Heading1"));
|
||||
return this;
|
||||
@ -91,7 +103,7 @@ export class Paragraph extends XmlComponent {
|
||||
}
|
||||
|
||||
public pageBreak(): Paragraph {
|
||||
this.properties.push(new PageBreak());
|
||||
this.root.push(new PageBreak());
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -116,4 +128,29 @@ export class Paragraph extends XmlComponent {
|
||||
this.properties.push(new NumberProperties(numbering.id, indentLevel));
|
||||
return this;
|
||||
}
|
||||
|
||||
public style(styleId: string): Paragraph {
|
||||
this.properties.push(new Style(styleId));
|
||||
return this;
|
||||
}
|
||||
|
||||
public indent(attrs: object): Paragraph {
|
||||
this.properties.push(new Indent(attrs));
|
||||
return this;
|
||||
}
|
||||
|
||||
public spacing(params: ISpacingProperties): Paragraph {
|
||||
this.properties.push(new Spacing(params));
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepNext(): Paragraph {
|
||||
this.properties.push(new KeepNext());
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepLines(): Paragraph {
|
||||
this.properties.push(new KeepLines());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
13
ts/docx/paragraph/keep.ts
Normal file
13
ts/docx/paragraph/keep.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { XmlComponent } from "../xml-components";
|
||||
|
||||
export class KeepLines extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:keepLines");
|
||||
}
|
||||
}
|
||||
|
||||
export class KeepNext extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:keepNext");
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
import { PageBreak } from "../../../docx/paragraph/page-break";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { PageBreak } from "./page-break";
|
||||
|
||||
describe("PageBreak", () => {
|
||||
let pageBreak: PageBreak;
|
||||
@ -15,20 +12,20 @@ describe("PageBreak", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Page Break with correct attributes", () => {
|
||||
let newJson = jsonify(pageBreak);
|
||||
let attributes = {
|
||||
type: "page"
|
||||
const newJson = Utility.jsonify(pageBreak);
|
||||
const attributes = {
|
||||
type: "page",
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.root[1].root[0].root), JSON.stringify(attributes));
|
||||
});
|
||||
|
||||
it("should create a Page Break with w:r", () => {
|
||||
let newJson = jsonify(pageBreak);
|
||||
const newJson = Utility.jsonify(pageBreak);
|
||||
assert.equal(newJson.rootKey, "w:r");
|
||||
});
|
||||
|
||||
it("should create a Page Break with a Break inside", () => {
|
||||
let newJson = jsonify(pageBreak);
|
||||
const newJson = Utility.jsonify(pageBreak);
|
||||
assert.equal(newJson.root[1].rootKey, "w:br");
|
||||
});
|
||||
});
|
293
ts/docx/paragraph/paragraph.spec.ts
Normal file
293
ts/docx/paragraph/paragraph.spec.ts
Normal file
@ -0,0 +1,293 @@
|
||||
import { assert, expect } from "chai";
|
||||
|
||||
import * as docx from "../../docx";
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { Numbering } from "../../numbering";
|
||||
|
||||
describe("Paragraph", () => {
|
||||
let paragraph: docx.Paragraph;
|
||||
|
||||
beforeEach(() => {
|
||||
paragraph = new docx.Paragraph();
|
||||
});
|
||||
|
||||
describe("#constructor()", () => {
|
||||
|
||||
it("should create valid JSON", () => {
|
||||
const stringifiedJson = JSON.stringify(paragraph);
|
||||
let newJson;
|
||||
|
||||
try {
|
||||
newJson = JSON.parse(stringifiedJson);
|
||||
} catch (e) {
|
||||
assert.isTrue(false);
|
||||
}
|
||||
assert.isTrue(true);
|
||||
});
|
||||
|
||||
it("should create have valid properties", () => {
|
||||
const stringifiedJson = JSON.stringify(paragraph);
|
||||
const newJson = JSON.parse(stringifiedJson);
|
||||
assert.equal(newJson.root[0].rootKey, "w:pPr");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createTextRun", () => {
|
||||
it("should add a new run to the paragraph and return it", () => {
|
||||
const run = paragraph.createTextRun("this is a test run");
|
||||
expect(run).to.be.instanceof(docx.TextRun);
|
||||
const tree = new Formatter().format(paragraph)["w:p"];
|
||||
expect(tree).to.be.an("array").which.includes({
|
||||
"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "this is a test run"]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#heading1()", () => {
|
||||
it("should add heading style to JSON", () => {
|
||||
paragraph.heading1();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Heading1"}}]}],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#heading2()", () => {
|
||||
it("should add heading style to JSON", () => {
|
||||
paragraph.heading2();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Heading2"}}]}],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#heading3()", () => {
|
||||
it("should add heading style to JSON", () => {
|
||||
paragraph.heading3();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Heading3"}}]}],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#title()", () => {
|
||||
it("should add title style to JSON", () => {
|
||||
paragraph.title();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Title"}}]}],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#center()", () => {
|
||||
it("should add center alignment to JSON", () => {
|
||||
paragraph.center();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [{"w:jc": [{_attr: {"w:val": "center"}}]}],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#thematicBreak()", () => {
|
||||
it("should add thematic break to JSON", () => {
|
||||
paragraph.thematicBreak();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [{
|
||||
"w:pPr": [{
|
||||
"w:pBdr": [{
|
||||
"w:bottom": [{
|
||||
_attr: {
|
||||
"w:val": "single",
|
||||
"w:color": "auto",
|
||||
"w:space": "1",
|
||||
"w:sz": "6",
|
||||
},
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#pageBreak()", () => {
|
||||
it("should add page break to JSON", () => {
|
||||
paragraph.pageBreak();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [{
|
||||
"w:pPr": [],
|
||||
}, {
|
||||
"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:br": [{_attr: {"w:type": "page"}}]},
|
||||
],
|
||||
}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#bullet()", () => {
|
||||
it("should add list paragraph style to JSON", () => {
|
||||
paragraph.bullet();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
|
||||
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(1);
|
||||
expect(tree["w:p"][0]["w:pPr"][0]).to.deep.equal({
|
||||
"w:pStyle": [{_attr: {"w:val": "ListParagraph"}}],
|
||||
});
|
||||
});
|
||||
|
||||
it("it should add numbered properties", () => {
|
||||
paragraph.bullet();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
|
||||
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(2);
|
||||
expect(tree["w:p"][0]["w:pPr"][1]).to.deep.equal({
|
||||
"w:numPr": [
|
||||
{"w:ilvl": [{_attr: {"w:val": 0}}]},
|
||||
{"w:numId": [{_attr: {"w:val": 1}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setNumbering", () => {
|
||||
it("should add list paragraph style to JSON", () => {
|
||||
const numbering = new Numbering();
|
||||
const numberedAbstract = numbering.createAbstractNumbering();
|
||||
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
|
||||
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
|
||||
|
||||
paragraph.setNumbering(letterNumbering, 0);
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
|
||||
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(1);
|
||||
expect(tree["w:p"][0]["w:pPr"][0]).to.deep.equal({
|
||||
"w:pStyle": [{_attr: {"w:val": "ListParagraph"}}],
|
||||
});
|
||||
});
|
||||
|
||||
it("it should add numbered properties", () => {
|
||||
const numbering = new Numbering();
|
||||
const numberedAbstract = numbering.createAbstractNumbering();
|
||||
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
|
||||
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
|
||||
|
||||
paragraph.setNumbering(letterNumbering, 0);
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{"w:pStyle": [{_attr: {"w:val": "ListParagraph"}}]},
|
||||
{
|
||||
"w:numPr": [
|
||||
{"w:ilvl": [{_attr: {"w:val": 0}}]},
|
||||
{"w:numId": [{_attr: {"w:val": letterNumbering.id}}]},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#style", () => {
|
||||
it("should set the paragraph style to the given styleId", () => {
|
||||
paragraph.style("myFancyStyle");
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{"w:pStyle": [{_attr: {"w:val": "myFancyStyle"}}]},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#indent", () => {
|
||||
it("should set the paragraph indent to the given values", () => {
|
||||
paragraph.indent({ left: 720 });
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{"w:ind": [{_attr: {"w:left": 720}}]},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#spacing", () => {
|
||||
it("should set the paragraph spacing to the given values", () => {
|
||||
paragraph.spacing({before: 90, line: 50});
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{"w:spacing": [{_attr: {"w:before": 90, "w:line": 50}}]},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#keepLines", () => {
|
||||
it("should set the paragraph keepLines sub-component", () => {
|
||||
paragraph.keepLines();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [{"w:pPr": [{"w:keepLines": []}]}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#keepNext", () => {
|
||||
it("should set the paragraph keepNext sub-component", () => {
|
||||
paragraph.keepNext();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [{"w:pPr": [{"w:keepNext": []}]}],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,10 +1,9 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
import { XmlComponent } from "../xml-components";
|
||||
|
||||
export class ParagraphProperties extends XmlComponent {
|
||||
|
||||
constructor() {
|
||||
super("w:pPr");
|
||||
this.root.push(new Attributes());
|
||||
}
|
||||
|
||||
public push(item: XmlComponent): void {
|
||||
|
24
ts/docx/paragraph/spacing.spec.ts
Normal file
24
ts/docx/paragraph/spacing.spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { Spacing } from "./spacing";
|
||||
|
||||
describe("Spacing", () => {
|
||||
describe("#constructor", () => {
|
||||
it("should set the properties given", () => {
|
||||
const spacing = new Spacing({before: 100, after: 120, line: 150});
|
||||
const tree = new Formatter().format(spacing);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:spacing": [{_attr: {"w:after": 120, "w:before": 100, "w:line": 150}}],
|
||||
});
|
||||
});
|
||||
|
||||
it("should only set the given properties", () => {
|
||||
const spacing = new Spacing({before: 100});
|
||||
const tree = new Formatter().format(spacing);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:spacing": [{_attr: {"w:before": 100}}],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
22
ts/docx/paragraph/spacing.ts
Normal file
22
ts/docx/paragraph/spacing.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
export interface ISpacingProperties {
|
||||
after?: number;
|
||||
before?: number;
|
||||
line?: number;
|
||||
}
|
||||
|
||||
class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
|
||||
protected xmlKeys = {
|
||||
after: "w:after",
|
||||
before: "w:before",
|
||||
line: "w:line",
|
||||
};
|
||||
}
|
||||
|
||||
export class Spacing extends XmlComponent {
|
||||
constructor(opts: ISpacingProperties) {
|
||||
super("w:spacing");
|
||||
this.root.push(new SpacingAttributes(opts));
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
import { Style } from "../../../docx/paragraph/style";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { Style } from "./style";
|
||||
|
||||
describe("ParagraphStyle", () => {
|
||||
let style: Style;
|
||||
@ -12,15 +9,14 @@ describe("ParagraphStyle", () => {
|
||||
describe("#constructor()", () => {
|
||||
it("should create a style with given value", () => {
|
||||
style = new Style("test");
|
||||
let newJson = jsonify(style);
|
||||
const newJson = Utility.jsonify(style);
|
||||
assert.equal(newJson.root[0].root.val, "test");
|
||||
});
|
||||
|
||||
it("should create a style with blank val", () => {
|
||||
style = new Style("");
|
||||
let newJson = jsonify(style);
|
||||
const newJson = Utility.jsonify(style);
|
||||
assert.equal(newJson.root[0].root.val, "");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,10 +1,7 @@
|
||||
import { LeftTabStop, MaxRightTabStop } from "../../../docx/paragraph/tab-stop";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { LeftTabStop, MaxRightTabStop } from "./tab-stop";
|
||||
|
||||
describe("LeftTabStop", () => {
|
||||
let tabStop: LeftTabStop;
|
||||
@ -15,23 +12,23 @@ describe("LeftTabStop", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Tab Stop with correct attributes", () => {
|
||||
let newJson = jsonify(tabStop);
|
||||
let attributes = {
|
||||
const newJson = Utility.jsonify(tabStop);
|
||||
const attributes = {
|
||||
val: "left",
|
||||
pos: 100
|
||||
pos: 100,
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
|
||||
});
|
||||
|
||||
it("should create a Tab Stop with w:tab", () => {
|
||||
let newJson = jsonify(tabStop);
|
||||
const newJson = Utility.jsonify(tabStop);
|
||||
assert.equal(newJson.root[0].rootKey, "w:tab");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("RightTabStop", () => {
|
||||
|
||||
// TODO
|
||||
});
|
||||
|
||||
describe("MaxRightTabStop", () => {
|
||||
@ -43,17 +40,17 @@ describe("MaxRightTabStop", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Tab Stop with correct attributes", () => {
|
||||
let newJson = jsonify(tabStop);
|
||||
const newJson = Utility.jsonify(tabStop);
|
||||
|
||||
let attributes = {
|
||||
const attributes = {
|
||||
val: "right",
|
||||
pos: 9026
|
||||
pos: 9026,
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
|
||||
});
|
||||
|
||||
it("should create a Tab Stop with w:tab", () => {
|
||||
let newJson = jsonify(tabStop);
|
||||
const newJson = Utility.jsonify(tabStop);
|
||||
assert.equal(newJson.root[0].rootKey, "w:tab");
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
class TabStop extends XmlComponent {
|
||||
export class TabStop extends XmlComponent {
|
||||
|
||||
constructor(tab: Tab) {
|
||||
super("w:tabs");
|
||||
@ -8,11 +8,17 @@ class TabStop extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class Tab extends XmlComponent {
|
||||
export type TabOptions = "left" | "right";
|
||||
|
||||
constructor(value: string, position: any) {
|
||||
export class TabAttributes extends XmlAttributeComponent<{val: TabOptions, pos: string | number}> {
|
||||
protected xmlKeys = {val: "w:val", pos: "w:pos"};
|
||||
}
|
||||
|
||||
export class Tab extends XmlComponent {
|
||||
|
||||
constructor(value: TabOptions, position: string | number) {
|
||||
super("w:tab");
|
||||
this.root.push(new Attributes({
|
||||
this.root.push(new TabAttributes({
|
||||
val: value,
|
||||
pos: position,
|
||||
}));
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { NumberProperties } from "../../../docx/paragraph/unordered-list";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { NumberProperties } from "./unordered-list";
|
||||
|
||||
describe("NumberProperties", () => {
|
||||
let numberProperties: NumberProperties;
|
||||
@ -15,18 +12,18 @@ describe("NumberProperties", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Number Properties with correct root key", () => {
|
||||
let newJson = jsonify(numberProperties);
|
||||
const newJson = Utility.jsonify(numberProperties);
|
||||
assert.equal(newJson.rootKey, "w:numPr");
|
||||
});
|
||||
|
||||
it("should create a Page Break with a Indent Level inside", () => {
|
||||
let newJson = jsonify(numberProperties);
|
||||
const newJson = Utility.jsonify(numberProperties);
|
||||
assert.equal(newJson.root[0].rootKey, "w:ilvl");
|
||||
assert.equal(newJson.root[0].root[0].root.val, 10);
|
||||
});
|
||||
|
||||
it("should create a Page Break with a Number Id inside", () => {
|
||||
let newJson = jsonify(numberProperties);
|
||||
const newJson = Utility.jsonify(numberProperties);
|
||||
assert.equal(newJson.root[1].rootKey, "w:numId");
|
||||
assert.equal(newJson.root[1].root[0].root.val, 5);
|
||||
});
|
@ -1,5 +1,4 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
import { Style } from "./style";
|
||||
|
||||
export class NumberProperties extends XmlComponent {
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { Break } from "../../../docx/run/break";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { Break } from "./break";
|
||||
|
||||
describe("Break", () => {
|
||||
let currentBreak: Break;
|
||||
@ -15,7 +12,7 @@ describe("Break", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Break with correct root key", () => {
|
||||
let newJson = jsonify(currentBreak);
|
||||
const newJson = Utility.jsonify(currentBreak);
|
||||
assert.equal(newJson.rootKey, "w:br");
|
||||
});
|
||||
});
|
@ -1,4 +1,7 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
export { Underline } from "./underline";
|
||||
export { SubScript, SuperScript } from "./script";
|
||||
export { RunFonts } from "./run-fonts";
|
||||
|
||||
export class Bold extends XmlComponent {
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Break } from "./break";
|
||||
import { Caps, SmallCaps } from "./caps";
|
||||
import { Bold, Italics } from "./formatting";
|
||||
import { Bold, Color, DoubleStrike, Italics, Size, Strike } from "./formatting";
|
||||
import { RunProperties } from "./properties";
|
||||
import { RunFonts } from "./run-fonts";
|
||||
import { SubScript, SuperScript } from "./script";
|
||||
import { DoubleStrike, Strike } from "./strike";
|
||||
import { Style } from "./style";
|
||||
import { Tab } from "./tab";
|
||||
import { Underline } from "./underline";
|
||||
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
import { XmlComponent } from "../xml-components";
|
||||
|
||||
export class Run extends XmlComponent {
|
||||
private properties: RunProperties;
|
||||
@ -29,8 +29,18 @@ export class Run extends XmlComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public underline(): Run {
|
||||
this.properties.push(new Underline());
|
||||
public underline(underlineType?: string, color?: string): Run {
|
||||
this.properties.push(new Underline(underlineType, color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public color(color: string): Run {
|
||||
this.properties.push(new Color(color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public size(size: number): Run {
|
||||
this.properties.push(new Size(size));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -78,4 +88,9 @@ export class Run extends XmlComponent {
|
||||
this.properties.push(new RunFonts(fontName));
|
||||
return this;
|
||||
}
|
||||
|
||||
public style(styleId: string): Run {
|
||||
this.properties.push(new Style(styleId));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
16
ts/docx/run/picture-run.ts
Normal file
16
ts/docx/run/picture-run.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { IData } from "../../media/data";
|
||||
import { Run } from "../run";
|
||||
import { Drawing } from "./run-components/drawing";
|
||||
|
||||
export class PictureRun extends Run {
|
||||
|
||||
constructor(imageData: IData) {
|
||||
super();
|
||||
|
||||
if (imageData === undefined) {
|
||||
throw new Error("imageData cannot be undefined");
|
||||
}
|
||||
|
||||
this.root.push(new Drawing(imageData));
|
||||
}
|
||||
}
|
27
ts/docx/run/run-components/drawing/drawing.spec.ts
Normal file
27
ts/docx/run/run-components/drawing/drawing.spec.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { assert } from "chai";
|
||||
import * as fs from "fs";
|
||||
|
||||
import { Utility } from "../../../../tests/utility";
|
||||
import { Drawing } from "./";
|
||||
|
||||
describe("Drawing", () => {
|
||||
let currentBreak: Drawing;
|
||||
|
||||
beforeEach(() => {
|
||||
const path = "./demo/penguins.jpg";
|
||||
currentBreak = new Drawing({
|
||||
fileName: "test.jpg",
|
||||
referenceId: 1,
|
||||
stream: fs.createReadStream(path),
|
||||
path: path,
|
||||
});
|
||||
});
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Drawing with correct root key", () => {
|
||||
const newJson = Utility.jsonify(currentBreak);
|
||||
assert.equal(newJson.rootKey, "w:drawing");
|
||||
// console.log(JSON.stringify(newJson, null, 2));
|
||||
});
|
||||
});
|
||||
});
|
16
ts/docx/run/run-components/drawing/index.ts
Normal file
16
ts/docx/run/run-components/drawing/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { IData } from "../../../../media/data";
|
||||
import { XmlComponent } from "../../../xml-components";
|
||||
import { Inline } from "./inline";
|
||||
|
||||
export class Drawing extends XmlComponent {
|
||||
|
||||
constructor(imageData: IData) {
|
||||
super("w:drawing");
|
||||
|
||||
if (imageData === undefined) {
|
||||
throw new Error("imageData cannot be undefined");
|
||||
}
|
||||
|
||||
this.root.push(new Inline(imageData.referenceId));
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import { XmlComponent } from "../../../../../../xml-components";
|
||||
import { Pic } from "./pic";
|
||||
|
||||
export class GraphicData extends XmlComponent {
|
||||
|
||||
constructor(referenceId: number) {
|
||||
super("a:graphicData");
|
||||
this.root.push(new Pic(referenceId));
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { XmlComponent } from "../../../../../../../../xml-components";
|
||||
import { Blip } from "./blip";
|
||||
import { SourceRectangle } from "./source-rectangle";
|
||||
import { Stretch } from "./stretch";
|
||||
|
||||
export class BlipFill extends XmlComponent {
|
||||
|
||||
constructor(referenceId: number) {
|
||||
super("pic:blipFill");
|
||||
this.root.push(new Blip(referenceId));
|
||||
this.root.push(new SourceRectangle());
|
||||
this.root.push(new Stretch());
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../../../../../../../../xml-components";
|
||||
|
||||
interface IBlipProperties {
|
||||
embed: string;
|
||||
}
|
||||
|
||||
class BlipAttributes extends XmlAttributeComponent<IBlipProperties> {
|
||||
protected xmlKeys = {
|
||||
embed: "r:embed",
|
||||
};
|
||||
}
|
||||
|
||||
export class Blip extends XmlComponent {
|
||||
|
||||
constructor(referenceId: number) {
|
||||
super("a:blip");
|
||||
this.root.push(new BlipAttributes({
|
||||
embed: `rId${referenceId}`,
|
||||
}));
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { XmlComponent } from "../../../../../../../../xml-components";
|
||||
|
||||
export class SourceRectangle extends XmlComponent {
|
||||
|
||||
constructor() {
|
||||
super("a:srcRect");
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { XmlComponent } from "../../../../../../../../xml-components";
|
||||
|
||||
class FillRectangle extends XmlComponent {
|
||||
|
||||
constructor() {
|
||||
super("a:fillRect");
|
||||
}
|
||||
}
|
||||
|
||||
export class Stretch extends XmlComponent {
|
||||
|
||||
constructor() {
|
||||
super("a:stretch");
|
||||
this.root.push(new FillRectangle());
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import { XmlComponent } from "../../../../../../../xml-components";
|
||||
import { BlipFill } from "./blip/blip-fill";
|
||||
|
||||
export class Pic extends XmlComponent {
|
||||
|
||||
constructor(referenceId: number) {
|
||||
super("pic:pic");
|
||||
this.root.push(new BlipFill(referenceId));
|
||||
}
|
||||
}
|
23
ts/docx/run/run-components/drawing/inline/graphic/index.ts
Normal file
23
ts/docx/run/run-components/drawing/inline/graphic/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../../../../../xml-components";
|
||||
import { GraphicData } from "./graphic-data";
|
||||
|
||||
interface IGraphicProperties {
|
||||
a: string;
|
||||
}
|
||||
|
||||
class GraphicAttributes extends XmlAttributeComponent<IGraphicProperties> {
|
||||
protected xmlKeys = {
|
||||
a: "xmlns:a",
|
||||
};
|
||||
}
|
||||
|
||||
export class Graphic extends XmlComponent {
|
||||
|
||||
constructor(referenceId: number) {
|
||||
super("a:graphic");
|
||||
this.root.push(new GraphicAttributes({
|
||||
a: "http://schemas.openxmlformats.org/drawingml/2006/main",
|
||||
}));
|
||||
this.root.push(new GraphicData(referenceId));
|
||||
}
|
||||
}
|
10
ts/docx/run/run-components/drawing/inline/index.ts
Normal file
10
ts/docx/run/run-components/drawing/inline/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { XmlComponent } from "../../../../xml-components";
|
||||
import { Graphic } from "./graphic";
|
||||
|
||||
export class Inline extends XmlComponent {
|
||||
|
||||
constructor(referenceId: number) {
|
||||
super("wp:inline");
|
||||
this.root.push(new Graphic(referenceId));
|
||||
}
|
||||
}
|
23
ts/docx/run/run-components/text.spec.ts
Normal file
23
ts/docx/run/run-components/text.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "../../../export/formatter";
|
||||
import { Text } from "./text";
|
||||
|
||||
describe("Text", () => {
|
||||
describe("#constructor", () => {
|
||||
it("creates an empty text run if no text is given", () => {
|
||||
const t = new Text("");
|
||||
const f = new Formatter().format(t);
|
||||
expect(f).to.deep.equal({"w:t": [{_attr: {"xml:space": "preserve"}}]});
|
||||
});
|
||||
|
||||
it("adds the passed in text to the component", () => {
|
||||
const t = new Text(" this is\n text");
|
||||
const f = new Formatter().format(t);
|
||||
expect(f).to.deep.equal({"w:t": [
|
||||
{_attr: {"xml:space": "preserve"}},
|
||||
" this is\n text",
|
||||
]});
|
||||
});
|
||||
});
|
||||
});
|
15
ts/docx/run/run-components/text.ts
Normal file
15
ts/docx/run/run-components/text.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../../xml-components";
|
||||
|
||||
class TextAttributes extends XmlAttributeComponent<{space: "default" | "preserve"}> {
|
||||
protected xmlKeys = {space: "xml:space"};
|
||||
}
|
||||
|
||||
export class Text extends XmlComponent {
|
||||
constructor(text: string) {
|
||||
super("w:t");
|
||||
this.root.push(new TextAttributes({space: "preserve"}));
|
||||
if (text) {
|
||||
this.root.push(text);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { expect } from "chai";
|
||||
import { RunFonts } from "../../../docx/run/run-fonts";
|
||||
import { Formatter } from "../../../export/formatter";
|
||||
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { RunFonts } from "./run-fonts";
|
||||
|
||||
describe("RunFonts", () => {
|
||||
|
@ -6,15 +6,12 @@ interface IRunFontAttributesProperties {
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
class RunFontAttributes extends XmlAttributeComponent {
|
||||
|
||||
constructor(properties: IRunFontAttributesProperties) {
|
||||
super({
|
||||
ascii: "w:ascii",
|
||||
hAnsi: "w:hAnsi",
|
||||
hint: "w:hint",
|
||||
}, properties);
|
||||
}
|
||||
class RunFontAttributes extends XmlAttributeComponent<IRunFontAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
ascii: "w:ascii",
|
||||
hAnsi: "w:hAnsi",
|
||||
hint: "w:hint",
|
||||
};
|
||||
}
|
||||
|
||||
export class RunFonts extends XmlComponent {
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { assert, expect } from "chai";
|
||||
import { Run } from "../../../docx/run";
|
||||
import { TextRun } from "../../../docx/run/text-run";
|
||||
import { Formatter } from "../../../export/formatter";
|
||||
|
||||
function jsonify(obj: object) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { Run } from "./";
|
||||
|
||||
describe("Run", () => {
|
||||
let run: Run;
|
||||
@ -17,7 +14,7 @@ describe("Run", () => {
|
||||
describe("#bold()", () => {
|
||||
it("it should add bold to the properties", () => {
|
||||
run.bold();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:b");
|
||||
});
|
||||
});
|
||||
@ -25,7 +22,7 @@ describe("Run", () => {
|
||||
describe("#italic()", () => {
|
||||
it("it should add italics to the properties", () => {
|
||||
run.italic();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:i");
|
||||
});
|
||||
});
|
||||
@ -33,15 +30,35 @@ describe("Run", () => {
|
||||
describe("#underline()", () => {
|
||||
it("it should add underline to the properties", () => {
|
||||
run.underline();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:u");
|
||||
});
|
||||
|
||||
it("should default to 'single' and no color", () => {
|
||||
run.underline();
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
{"w:rPr": [{"w:u": [{_attr: {"w:val": "single"}}]}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style type and color if given", () => {
|
||||
run.underline("double", "990011");
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
{"w:rPr": [{"w:u": [{_attr: {"w:val": "double", "w:color": "990011"}}]}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#smallCaps()", () => {
|
||||
it("it should add smallCaps to the properties", () => {
|
||||
run.smallCaps();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:smallCaps");
|
||||
});
|
||||
});
|
||||
@ -49,7 +66,7 @@ describe("Run", () => {
|
||||
describe("#caps()", () => {
|
||||
it("it should add caps to the properties", () => {
|
||||
run.allCaps();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:caps");
|
||||
});
|
||||
});
|
||||
@ -57,7 +74,7 @@ describe("Run", () => {
|
||||
describe("#strike()", () => {
|
||||
it("it should add strike to the properties", () => {
|
||||
run.strike();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:strike");
|
||||
});
|
||||
});
|
||||
@ -65,7 +82,7 @@ describe("Run", () => {
|
||||
describe("#doubleStrike()", () => {
|
||||
it("it should add caps to the properties", () => {
|
||||
run.doubleStrike();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[0].root[0].rootKey, "w:dstrike");
|
||||
});
|
||||
});
|
||||
@ -73,7 +90,7 @@ describe("Run", () => {
|
||||
describe("#break()", () => {
|
||||
it("it should add break to the run", () => {
|
||||
run.break();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[1].rootKey, "w:br");
|
||||
});
|
||||
});
|
||||
@ -81,7 +98,7 @@ describe("Run", () => {
|
||||
describe("#tab()", () => {
|
||||
it("it should add break to the run", () => {
|
||||
run.tab();
|
||||
const newJson = jsonify(run);
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[1].rootKey, "w:tab");
|
||||
});
|
||||
});
|
||||
@ -101,4 +118,40 @@ describe("Run", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#color", () => {
|
||||
it("should set the run to the color given", () => {
|
||||
run.color("001122");
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
{"w:rPr": [{"w:color": [{_attr: {"w:val": "001122"}}]}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#size", () => {
|
||||
it("should set the run to the given size", () => {
|
||||
run.size(24);
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
{"w:rPr": [{"w:sz": [{_attr: {"w:val": 24}}]}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#style", () => {
|
||||
it("should set the style to the given styleId", () => {
|
||||
run.style("myRunStyle");
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
{"w:rPr": [{"w:rStyle": [{_attr: {"w:val": "myRunStyle"}}]}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,10 +1,7 @@
|
||||
import { SubScript, SuperScript } from "../../../docx/run/script";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { SubScript, SuperScript } from "./script";
|
||||
|
||||
describe("SubScript", () => {
|
||||
let subScript: SubScript;
|
||||
@ -15,15 +12,15 @@ describe("SubScript", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Sub Script with correct attributes", () => {
|
||||
let newJson = jsonify(subScript);
|
||||
let attributes = {
|
||||
val: "subscript"
|
||||
const newJson = Utility.jsonify(subScript);
|
||||
const attributes = {
|
||||
val: "subscript",
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes));
|
||||
});
|
||||
|
||||
it("should create a Sub Script with correct root key", () => {
|
||||
let newJson = jsonify(subScript);
|
||||
const newJson = Utility.jsonify(subScript);
|
||||
assert.equal(newJson.rootKey, "w:vertAlign");
|
||||
});
|
||||
});
|
||||
@ -38,15 +35,15 @@ describe("SuperScript", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Super Script with correct attributes", () => {
|
||||
let newJson = jsonify(superScript);
|
||||
let attributes = {
|
||||
val: "superscript"
|
||||
const newJson = Utility.jsonify(superScript);
|
||||
const attributes = {
|
||||
val: "superscript",
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes));
|
||||
});
|
||||
|
||||
it("should create a Super Script with correct root key", () => {
|
||||
let newJson = jsonify(superScript);
|
||||
const newJson = Utility.jsonify(superScript);
|
||||
assert.equal(newJson.rootKey, "w:vertAlign");
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
|
||||
abstract class VerticalAlign extends XmlComponent {
|
||||
export abstract class VerticalAlign extends XmlComponent {
|
||||
|
||||
constructor(type: string) {
|
||||
super("w:vertAlign");
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { Strike, DoubleStrike } from "../../../docx/run/strike";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { DoubleStrike, Strike } from "./formatting";
|
||||
|
||||
describe("Strike", () => {
|
||||
let strike: Strike;
|
||||
@ -15,7 +12,7 @@ describe("Strike", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Strike with correct root key", () => {
|
||||
let newJson = jsonify(strike);
|
||||
const newJson = Utility.jsonify(strike);
|
||||
assert.equal(newJson.rootKey, "w:strike");
|
||||
});
|
||||
});
|
||||
@ -30,7 +27,7 @@ describe("DoubleStrike", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Double Strike with correct root key", () => {
|
||||
let newJson = jsonify(strike);
|
||||
const newJson = Utility.jsonify(strike);
|
||||
assert.equal(newJson.rootKey, "w:dstrike");
|
||||
});
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
import { XmlComponent } from "../xml-components";
|
||||
|
||||
export class Strike extends XmlComponent {
|
||||
|
||||
constructor() {
|
||||
super("w:strike");
|
||||
}
|
||||
}
|
||||
|
||||
export class DoubleStrike extends XmlComponent {
|
||||
|
||||
constructor() {
|
||||
super("w:dstrike");
|
||||
}
|
||||
}
|
13
ts/docx/run/style.ts
Normal file
13
ts/docx/run/style.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
class StyleAttributes extends XmlAttributeComponent<{val: string}> {
|
||||
protected xmlKeys = {val: "w:val"};
|
||||
}
|
||||
|
||||
export class Style extends XmlComponent {
|
||||
|
||||
constructor(styleId: string) {
|
||||
super("w:rStyle");
|
||||
this.root.push(new StyleAttributes({val: styleId}));
|
||||
}
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
import { Tab } from "../../../docx/run/tab";
|
||||
import { assert } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { Tab } from "./tab";
|
||||
|
||||
describe("Tab", () => {
|
||||
let tab: Tab;
|
||||
@ -15,7 +12,7 @@ describe("Tab", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should create a Tab with correct root key", () => {
|
||||
let newJson = jsonify(tab);
|
||||
const newJson = Utility.jsonify(tab);
|
||||
assert.equal(newJson.rootKey, "w:tab");
|
||||
});
|
||||
});
|
20
ts/docx/run/text-run.spec.ts
Normal file
20
ts/docx/run/text-run.spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { TextRun } from "./text-run";
|
||||
|
||||
describe("TextRun", () => {
|
||||
let run: TextRun;
|
||||
|
||||
describe("#constructor()", () => {
|
||||
|
||||
it("should add text into run", () => {
|
||||
run = new TextRun("test");
|
||||
const f = new Formatter().format(run);
|
||||
expect(f).to.deep.equal({"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "test"]},
|
||||
]});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import { Run } from "../run";
|
||||
import { Text } from "./text";
|
||||
import { Text } from "./run-components/text";
|
||||
|
||||
export class TextRun extends Run {
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { XmlUnitComponent } from "../xml-components";
|
||||
|
||||
export class Text extends XmlUnitComponent {
|
||||
|
||||
constructor(text: string) {
|
||||
super("w:t");
|
||||
this.root = text;
|
||||
}
|
||||
}
|
@ -1,21 +1,34 @@
|
||||
import * as u from "../../../docx/run/underline";
|
||||
import { TextRun } from "../../../docx/run/text-run";
|
||||
import { assert } from "chai";
|
||||
import { assert, expect } from "chai";
|
||||
|
||||
function jsonify(obj: Object) {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { Utility } from "../../tests/utility";
|
||||
import * as u from "./underline";
|
||||
|
||||
describe("Underline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
|
||||
it("should create a new Underline object with u:u as the rootKey", () => {
|
||||
let underline = new u.Underline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.Underline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.rootKey, "w:u");
|
||||
});
|
||||
|
||||
it("should default to 'single' and no color", () => {
|
||||
const underline = new u.Underline();
|
||||
const tree = new Formatter().format(underline);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:u": [{_attr: {"w:val": "single"}}],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the given style type and color", () => {
|
||||
const underline = new u.Underline("double", "FF00CC");
|
||||
const tree = new Formatter().format(underline);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:u": [{_attr: {"w:val": "double", "w:color": "FF00CC"}}],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -23,14 +36,14 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should have u:u as the rootKey", () => {
|
||||
let underline = new u.DashDotDotHeavyUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DashDotDotHeavyUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.rootKey, "w:u");
|
||||
});
|
||||
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DashDotDotHeavyUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DashDotDotHeavyUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dashDotDotHeavy");
|
||||
});
|
||||
});
|
||||
@ -40,8 +53,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DashDotHeavyUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DashDotHeavyUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dashDotHeavy");
|
||||
});
|
||||
});
|
||||
@ -51,8 +64,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DashLongHeavyUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DashLongHeavyUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dashLongHeavy");
|
||||
});
|
||||
});
|
||||
@ -62,8 +75,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DashLongUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DashLongUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dashLong");
|
||||
});
|
||||
});
|
||||
@ -73,8 +86,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DashUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DashUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dash");
|
||||
});
|
||||
});
|
||||
@ -84,8 +97,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DotDashUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DotDashUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dotDash");
|
||||
});
|
||||
});
|
||||
@ -95,8 +108,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DotDotDashUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DotDotDashUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dotDotDash");
|
||||
});
|
||||
});
|
||||
@ -106,8 +119,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DottedHeavyUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DottedHeavyUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dottedHeavy");
|
||||
});
|
||||
});
|
||||
@ -117,8 +130,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DottedUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DottedUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "dotted");
|
||||
});
|
||||
});
|
||||
@ -128,8 +141,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.DoubleUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.DoubleUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "double");
|
||||
});
|
||||
});
|
||||
@ -139,8 +152,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.SingleUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.SingleUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "single");
|
||||
});
|
||||
});
|
||||
@ -150,8 +163,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.ThickUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.ThickUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "thick");
|
||||
});
|
||||
});
|
||||
@ -161,8 +174,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.WaveUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.WaveUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "wave");
|
||||
});
|
||||
});
|
||||
@ -172,8 +185,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.WavyDoubleUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.WavyDoubleUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "wavyDouble");
|
||||
});
|
||||
});
|
||||
@ -183,8 +196,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.WavyHeavyUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.WavyHeavyUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "wavyHeavy");
|
||||
});
|
||||
});
|
||||
@ -194,8 +207,8 @@ describe("DashDotDotHeavyUnderline", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
let underline = new u.WordsUnderline();
|
||||
let newJson = jsonify(underline);
|
||||
const underline = new u.WordsUnderline();
|
||||
const newJson = Utility.jsonify(underline);
|
||||
assert.equal(newJson.root[0].root.val, "words");
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
|
||||
abstract class BaseUnderline extends XmlComponent {
|
||||
export abstract class BaseUnderline extends XmlComponent {
|
||||
|
||||
constructor(underlineType: string, color?: string) {
|
||||
super("w:u");
|
||||
@ -13,8 +13,8 @@ abstract class BaseUnderline extends XmlComponent {
|
||||
|
||||
export class Underline extends BaseUnderline {
|
||||
|
||||
constructor() {
|
||||
super("");
|
||||
constructor(underlineType: string = "single", color?: string) {
|
||||
super(underlineType, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
export class Table {
|
||||
|
||||
}
|
38
ts/docx/table/grid.spec.ts
Normal file
38
ts/docx/table/grid.spec.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { GridCol, TableGrid } from "./grid";
|
||||
|
||||
describe("GridCol", () => {
|
||||
describe("#constructor", () => {
|
||||
it("sets the width attribute to the value given", () => {
|
||||
const grid = new GridCol(1234);
|
||||
const tree = new Formatter().format(grid);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:gridCol": [{_attr: {"w:w": 1234}}],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not set a width attribute if not given", () => {
|
||||
const grid = new GridCol();
|
||||
const tree = new Formatter().format(grid);
|
||||
expect(tree).to.deep.equal({"w:gridCol": []});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("TableGrid", () => {
|
||||
describe("#constructor", () => {
|
||||
it("creates a column for each width given", () => {
|
||||
const grid = new TableGrid([1234, 321, 123]);
|
||||
const tree = new Formatter().format(grid);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblGrid": [
|
||||
{"w:gridCol": [{_attr: {"w:w": 1234}}]},
|
||||
{"w:gridCol": [{_attr: {"w:w": 321}}]},
|
||||
{"w:gridCol": [{_attr: {"w:w": 123}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
21
ts/docx/table/grid.ts
Normal file
21
ts/docx/table/grid.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
export class TableGrid extends XmlComponent {
|
||||
constructor(cols: number[]) {
|
||||
super("w:tblGrid");
|
||||
cols.forEach((col) => this.root.push(new GridCol(col)));
|
||||
}
|
||||
}
|
||||
|
||||
class GridColAttributes extends XmlAttributeComponent<{w: number}> {
|
||||
protected xmlKeys = {w: "w:w"};
|
||||
}
|
||||
|
||||
export class GridCol extends XmlComponent {
|
||||
constructor(width?: number) {
|
||||
super("w:gridCol");
|
||||
if (width !== undefined) {
|
||||
this.root.push(new GridColAttributes({w: width}));
|
||||
}
|
||||
}
|
||||
}
|
123
ts/docx/table/index.ts
Normal file
123
ts/docx/table/index.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { Paragraph } from "../paragraph";
|
||||
import { XmlComponent } from "../xml-components";
|
||||
import { IXmlableObject } from "../xml-components/xmlable-object";
|
||||
import { TableGrid } from "./grid";
|
||||
import { TableProperties, WidthTypes } from "./properties";
|
||||
|
||||
export class Table extends XmlComponent {
|
||||
private properties: TableProperties;
|
||||
private rows: TableRow[];
|
||||
private grid: TableGrid;
|
||||
|
||||
constructor(rows: number, cols: number) {
|
||||
super("w:tbl");
|
||||
this.properties = new TableProperties();
|
||||
this.root.push(this.properties);
|
||||
|
||||
const gridCols: number[] = [];
|
||||
for (let i = 0; i < cols; i++) {
|
||||
/*
|
||||
0-width columns don't get rendered correctly, so we need
|
||||
to give them some value. A reasonable default would be
|
||||
~6in / numCols, but if we do that it becomes very hard
|
||||
to resize the table using setWidth, unless the layout
|
||||
algorithm is set to 'fixed'. Instead, the approach here
|
||||
means even in 'auto' layout, setting a width on the
|
||||
table will make it look reasonable, as the layout
|
||||
algorithm will expand columns to fit its content
|
||||
*/
|
||||
gridCols.push(1);
|
||||
}
|
||||
this.grid = new TableGrid(gridCols);
|
||||
this.root.push(this.grid);
|
||||
|
||||
this.rows = [];
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const cells: TableCell[] = [];
|
||||
for (let j = 0; j < cols; j++) {
|
||||
cells.push(new TableCell());
|
||||
}
|
||||
const row = new TableRow(cells);
|
||||
this.rows.push(row);
|
||||
this.root.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
public getRow(ix: number): TableRow {
|
||||
return this.rows[ix];
|
||||
}
|
||||
|
||||
public getCell(row: number, col: number): TableCell {
|
||||
return this.getRow(row).getCell(col);
|
||||
}
|
||||
|
||||
public setWidth(type: WidthTypes, width: number | string): Table {
|
||||
this.properties.setWidth(type, width);
|
||||
return this;
|
||||
}
|
||||
|
||||
public fixedWidthLayout(): Table {
|
||||
this.properties.fixedWidthLayout();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class TableRow extends XmlComponent {
|
||||
private properties: TableRowProperties;
|
||||
private cells: TableCell[];
|
||||
|
||||
constructor(cells: TableCell[]) {
|
||||
super("w:tr");
|
||||
this.properties = new TableRowProperties();
|
||||
this.root.push(this.properties);
|
||||
this.cells = cells;
|
||||
cells.forEach((c) => this.root.push(c));
|
||||
}
|
||||
|
||||
public getCell(ix: number): TableCell {
|
||||
return this.cells[ix];
|
||||
}
|
||||
}
|
||||
|
||||
export class TableRowProperties extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:trPr");
|
||||
}
|
||||
}
|
||||
|
||||
export class TableCell extends XmlComponent {
|
||||
private properties: TableCellProperties;
|
||||
|
||||
constructor() {
|
||||
super("w:tc");
|
||||
this.properties = new TableCellProperties();
|
||||
this.root.push(this.properties);
|
||||
}
|
||||
|
||||
public addContent(content: Paragraph | Table): TableCell {
|
||||
this.root.push(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public prepForXml(): IXmlableObject {
|
||||
// Cells must end with a paragraph
|
||||
const retval = super.prepForXml();
|
||||
const content = retval["w:tc"];
|
||||
if (!content[content.length - 1]["w:p"]) {
|
||||
content.push(new Paragraph().prepForXml());
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public createParagraph(text?: string): Paragraph {
|
||||
const para = new Paragraph(text);
|
||||
this.addContent(para);
|
||||
return para;
|
||||
}
|
||||
}
|
||||
|
||||
export class TableCellProperties extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:tcPr");
|
||||
}
|
||||
}
|
38
ts/docx/table/properties.spec.ts
Normal file
38
ts/docx/table/properties.spec.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { TableProperties } from "./properties";
|
||||
|
||||
describe("TableProperties", () => {
|
||||
describe("#constructor", () => {
|
||||
it("creates an initially empty property object", () => {
|
||||
const tp = new TableProperties();
|
||||
const tree = new Formatter().format(tp);
|
||||
expect(tree).to.deep.equal({"w:tblPr": []});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setWidth", () => {
|
||||
it("adds a table width property", () => {
|
||||
const tp = new TableProperties().setWidth("dxa", 1234);
|
||||
const tree = new Formatter().format(tp);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblPr": [
|
||||
{"w:tblW": [{_attr: {"w:type": "dxa", "w:w": 1234}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#fixedWidthLayout", () => {
|
||||
it("sets the table to fixed width layout", () => {
|
||||
const tp = new TableProperties().fixedWidthLayout();
|
||||
const tree = new Formatter().format(tp);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tblPr": [
|
||||
{"w:tblLayout": [{_attr: {"w:type": "fixed"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
48
ts/docx/table/properties.ts
Normal file
48
ts/docx/table/properties.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
export type WidthTypes = "dxa" | "pct" | "nil" | "auto";
|
||||
|
||||
export class TableProperties extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:tblPr");
|
||||
}
|
||||
|
||||
public setWidth(type: WidthTypes, w: number | string): TableProperties {
|
||||
this.root.push(new PreferredTableWidth(type, w));
|
||||
return this;
|
||||
}
|
||||
|
||||
public fixedWidthLayout(): TableProperties {
|
||||
this.root.push(new TableLayout("fixed"));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
interface ITableWidth {
|
||||
type: WidthTypes;
|
||||
w: number | string;
|
||||
}
|
||||
|
||||
class TableWidthAttributes extends XmlAttributeComponent<ITableWidth> {
|
||||
protected xmlKeys = {type: "w:type", w: "w:w"};
|
||||
}
|
||||
|
||||
class PreferredTableWidth extends XmlComponent {
|
||||
constructor(type: WidthTypes, w: number | string) {
|
||||
super("w:tblW");
|
||||
this.root.push(new TableWidthAttributes({type, w}));
|
||||
}
|
||||
}
|
||||
|
||||
type TableLayoutOptions = "autofit" | "fixed";
|
||||
|
||||
class TableLayoutAttributes extends XmlAttributeComponent<{type: TableLayoutOptions}> {
|
||||
protected xmlKeys = {type: "w:type"};
|
||||
}
|
||||
|
||||
class TableLayout extends XmlComponent {
|
||||
constructor(type: TableLayoutOptions) {
|
||||
super("w:tblLayout");
|
||||
this.root.push(new TableLayoutAttributes({type}));
|
||||
}
|
||||
}
|
190
ts/docx/table/table.spec.ts
Normal file
190
ts/docx/table/table.spec.ts
Normal file
@ -0,0 +1,190 @@
|
||||
/* tslint:disable:no-unused-expression */
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "../../export/formatter";
|
||||
import { Paragraph } from "../paragraph";
|
||||
import { Table } from "./";
|
||||
|
||||
describe("Table", () => {
|
||||
describe("#constructor", () => {
|
||||
it("creates a table with the correct number of rows and columns", () => {
|
||||
const table = new Table(3, 2);
|
||||
const tree = new Formatter().format(table);
|
||||
const cell = {"w:tc": [{"w:tcPr": []}, {"w:p": [{"w:pPr": []}]}]};
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tbl": [
|
||||
{"w:tblPr": []},
|
||||
{"w:tblGrid": [
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
]},
|
||||
{"w:tr": [{"w:trPr": []}, cell, cell]},
|
||||
{"w:tr": [{"w:trPr": []}, cell, cell]},
|
||||
{"w:tr": [{"w:trPr": []}, cell, cell]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getRow and Row#getCell", () => {
|
||||
it("returns the correct row", () => {
|
||||
const table = new Table(2, 2);
|
||||
table.getRow(0).getCell(0).addContent(new Paragraph("A1"));
|
||||
table.getRow(0).getCell(1).addContent(new Paragraph("B1"));
|
||||
table.getRow(1).getCell(0).addContent(new Paragraph("A2"));
|
||||
table.getRow(1).getCell(1).addContent(new Paragraph("B2"));
|
||||
const tree = new Formatter().format(table);
|
||||
const cell = (c) => ({"w:tc": [
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, c]}]},
|
||||
]},
|
||||
]});
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tbl": [
|
||||
{"w:tblPr": []},
|
||||
{"w:tblGrid": [
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
]},
|
||||
{"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]},
|
||||
{"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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"));
|
||||
const tree = new Formatter().format(table);
|
||||
const cell = (c) => ({"w:tc": [
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, c]}]},
|
||||
]},
|
||||
]});
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tbl": [
|
||||
{"w:tblPr": []},
|
||||
{"w:tblGrid": [
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
{"w:gridCol": [{_attr: {"w:w": 1}}]},
|
||||
]},
|
||||
{"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]},
|
||||
{"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setWidth", () => {
|
||||
it("sets the preferred width on the table", () => {
|
||||
const table = new Table(2, 2).setWidth("pct", 1000);
|
||||
const tree = new Formatter().format(table);
|
||||
expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1);
|
||||
expect(tree["w:tbl"][0]).to.deep.equal({
|
||||
"w:tblPr": [
|
||||
{"w:tblW": [{_attr: {"w:type": "pct", "w:w": 1000}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#fixedWidthLayout", () => {
|
||||
it("sets the table to fixed width layout", () => {
|
||||
const table = new Table(2, 2).fixedWidthLayout();
|
||||
const tree = new Formatter().format(table);
|
||||
expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1);
|
||||
expect(tree["w:tbl"][0]).to.deep.equal({
|
||||
"w:tblPr": [
|
||||
{"w:tblLayout": [{_attr: {"w:type": "fixed"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cell", () => {
|
||||
describe("#prepForXml", () => {
|
||||
it("inserts a paragraph at the end of the cell if it is empty", () => {
|
||||
const table = new Table(1, 1);
|
||||
const tree = new Formatter().format(table);
|
||||
expect(tree).to.have.property("w:tbl").which.is.an("array");
|
||||
const row = tree["w:tbl"].find((x) => x["w:tr"]);
|
||||
expect(row).not.to.be.undefined;
|
||||
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
|
||||
"w:tc": [
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [{"w:pPr": []}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
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));
|
||||
const tree = new Formatter().format(parentTable);
|
||||
expect(tree).to.have.property("w:tbl").which.is.an("array");
|
||||
const row = tree["w:tbl"].find((x) => x["w:tr"]);
|
||||
expect(row).not.to.be.undefined;
|
||||
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
|
||||
const cell = row["w:tr"].find((x) => x["w:tc"]);
|
||||
expect(cell).not.to.be.undefined;
|
||||
expect(cell["w:tc"][cell["w:tc"].length - 1]).to.deep.equal({
|
||||
"w:p": [{"w:pPr": []}],
|
||||
});
|
||||
});
|
||||
|
||||
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"));
|
||||
const tree = new Formatter().format(parentTable);
|
||||
expect(tree).to.have.property("w:tbl").which.is.an("array");
|
||||
const row = tree["w:tbl"].find((x) => x["w:tr"]);
|
||||
expect(row).not.to.be.undefined;
|
||||
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
|
||||
"w:tc": [
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, "Hello"]}]},
|
||||
]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createParagraph", () => {
|
||||
it("inserts a new paragraph in the cell", () => {
|
||||
const table = new Table(1, 1);
|
||||
const para = table.getCell(0, 0).createParagraph("Test paragraph");
|
||||
expect(para).to.be.an.instanceof(Paragraph);
|
||||
const tree = new Formatter().format(table);
|
||||
expect(tree).to.have.property("w:tbl").which.is.an("array");
|
||||
const row = tree["w:tbl"].find((x) => x["w:tr"]);
|
||||
expect(row).not.to.be.undefined;
|
||||
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
|
||||
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
|
||||
"w:tc": [
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "Test paragraph"]},
|
||||
]},
|
||||
]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
26
ts/docx/xml-components/attribute.spec.ts
Normal file
26
ts/docx/xml-components/attribute.spec.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { assert } from "chai";
|
||||
|
||||
import { Attributes } from "./";
|
||||
|
||||
describe("Attribute", () => {
|
||||
describe("#constructor()", () => {
|
||||
|
||||
it("should have val as defined with populated constructor", () => {
|
||||
const newAttrs = new Attributes({
|
||||
val: "test",
|
||||
});
|
||||
const stringifiedJson = JSON.stringify(newAttrs);
|
||||
const newJson = JSON.parse(stringifiedJson);
|
||||
assert.equal(newJson.root.val, "test");
|
||||
});
|
||||
|
||||
it("should have space value as defined with populated constructor", () => {
|
||||
const newAttrs = new Attributes({
|
||||
space: "spaceTest",
|
||||
});
|
||||
const stringifiedJson = JSON.stringify(newAttrs);
|
||||
const newJson = JSON.parse(stringifiedJson);
|
||||
assert.equal(newJson.root.space, "spaceTest");
|
||||
});
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { XmlAttributeComponent } from "./default-attributes";
|
||||
|
||||
interface IAttributesProperties {
|
||||
export interface IAttributesProperties {
|
||||
val?: string | number | boolean;
|
||||
color?: string;
|
||||
space?: string;
|
||||
@ -19,32 +19,29 @@ interface IAttributesProperties {
|
||||
footer?: string;
|
||||
gutter?: string;
|
||||
linePitch?: string;
|
||||
pos?: string;
|
||||
pos?: string | number; // Little strange. Perhaps it is normal. Need to clarify in the spec.
|
||||
}
|
||||
|
||||
export class Attributes extends XmlAttributeComponent {
|
||||
|
||||
constructor(properties?: IAttributesProperties) {
|
||||
super({
|
||||
val: "w:val",
|
||||
color: "w:color",
|
||||
space: "w:space",
|
||||
sz: "w:sz",
|
||||
type: "w:type",
|
||||
rsidR: "w:rsidR",
|
||||
rsidRPr: "w:rsidRPr",
|
||||
rsidSect: "w:rsidSect",
|
||||
w: "w:w",
|
||||
h: "w:h",
|
||||
top: "w:top",
|
||||
right: "w:right",
|
||||
bottom: "w:bottom",
|
||||
left: "w:left",
|
||||
header: "w:header",
|
||||
footer: "w:footer",
|
||||
gutter: "w:gutter",
|
||||
linePitch: "w:linePitch",
|
||||
pos: "w:pos",
|
||||
}, properties);
|
||||
}
|
||||
export class Attributes extends XmlAttributeComponent<IAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
val: "w:val",
|
||||
color: "w:color",
|
||||
space: "w:space",
|
||||
sz: "w:sz",
|
||||
type: "w:type",
|
||||
rsidR: "w:rsidR",
|
||||
rsidRPr: "w:rsidRPr",
|
||||
rsidSect: "w:rsidSect",
|
||||
w: "w:w",
|
||||
h: "w:h",
|
||||
top: "w:top",
|
||||
right: "w:right",
|
||||
bottom: "w:bottom",
|
||||
left: "w:left",
|
||||
header: "w:header",
|
||||
footer: "w:footer",
|
||||
gutter: "w:gutter",
|
||||
linePitch: "w:linePitch",
|
||||
pos: "w:pos",
|
||||
};
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { IXmlableObject } from "./xmlable-object";
|
||||
|
||||
export abstract class BaseXmlComponent {
|
||||
protected rootKey: string;
|
||||
|
||||
@ -5,9 +7,5 @@ export abstract class BaseXmlComponent {
|
||||
this.rootKey = rootKey;
|
||||
}
|
||||
|
||||
public abstract replaceKey(): void;
|
||||
|
||||
public clearVariables(): void {
|
||||
// Do Nothing
|
||||
}
|
||||
public abstract prepForXml(): IXmlableObject;
|
||||
}
|
||||
|
@ -1,30 +1,26 @@
|
||||
import * as _ from "lodash";
|
||||
import { BaseXmlComponent } from "./base";
|
||||
import { IXmlableObject } from "./xmlable-object";
|
||||
|
||||
export abstract class XmlAttributeComponent extends BaseXmlComponent {
|
||||
protected root: Object;
|
||||
private xmlKeys: Object;
|
||||
export type AttributeMap<T> = {[P in keyof T]: string};
|
||||
|
||||
constructor(xmlKeys: Object, properties: Object) {
|
||||
export abstract class XmlAttributeComponent<T> extends BaseXmlComponent {
|
||||
protected root: T;
|
||||
protected xmlKeys: AttributeMap<T>;
|
||||
|
||||
constructor(properties: T) {
|
||||
super("_attr");
|
||||
this.xmlKeys = xmlKeys;
|
||||
|
||||
this.root = properties;
|
||||
|
||||
if (!properties) {
|
||||
this.root = {};
|
||||
}
|
||||
}
|
||||
|
||||
public replaceKey(): void {
|
||||
if (this.root !== undefined) {
|
||||
_.forOwn(this.root, (value, key) => {
|
||||
public prepForXml(): IXmlableObject {
|
||||
const attrs = {};
|
||||
Object.keys(this.root).forEach((key) => {
|
||||
const value = this.root[key];
|
||||
if (value !== undefined) {
|
||||
const newKey = this.xmlKeys[key];
|
||||
this.root[newKey] = value;
|
||||
delete this.root[key];
|
||||
});
|
||||
this[this.rootKey] = this.root;
|
||||
delete this.root;
|
||||
}
|
||||
attrs[newKey] = value;
|
||||
}
|
||||
});
|
||||
return {_attr: attrs};
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,27 @@
|
||||
import * as _ from "lodash";
|
||||
import { BaseXmlComponent } from "./base";
|
||||
import { IXmlableObject } from "./xmlable-object";
|
||||
export { BaseXmlComponent };
|
||||
|
||||
export abstract class XmlComponent extends BaseXmlComponent {
|
||||
protected root: BaseXmlComponent[];
|
||||
protected root: Array<BaseXmlComponent | string>;
|
||||
|
||||
constructor(rootKey: string) {
|
||||
super(rootKey);
|
||||
this.root = new Array<BaseXmlComponent>();
|
||||
}
|
||||
|
||||
public replaceKey(): void {
|
||||
// console.log(this.rootKey);
|
||||
// console.log(this.root);
|
||||
if (this.root !== undefined) {
|
||||
this.root.forEach((root) => {
|
||||
if (root && root instanceof BaseXmlComponent) {
|
||||
root.replaceKey();
|
||||
}
|
||||
});
|
||||
this[this.rootKey] = this.root;
|
||||
delete this.root;
|
||||
}
|
||||
public prepForXml(): IXmlableObject {
|
||||
const children = this.root.map((comp) => {
|
||||
if (comp instanceof BaseXmlComponent) {
|
||||
return comp.prepForXml();
|
||||
}
|
||||
return comp;
|
||||
}).filter((comp) => comp); // Exclude null, undefined, and empty strings
|
||||
return {
|
||||
[this.rootKey]: children,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export * from "./attributes"
|
||||
export * from "./attributes";
|
||||
export * from "./default-attributes";
|
||||
export * from "./unit";
|
||||
export * from "./property";
|
||||
|
@ -1,57 +0,0 @@
|
||||
import { ParagraphProperties } from "../paragraph/properties";
|
||||
import { RunProperties } from "../run/properties";
|
||||
import { XmlComponent } from "./";
|
||||
|
||||
export class ParagraphPropertyXmlComponent extends XmlComponent {
|
||||
private paragraphProperties: ParagraphProperties;
|
||||
|
||||
constructor(rootKey: string) {
|
||||
super(rootKey);
|
||||
this.paragraphProperties = new ParagraphProperties();
|
||||
this.root.push(this.paragraphProperties);
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
this.paragraphProperties.clearVariables();
|
||||
|
||||
delete this.paragraphProperties;
|
||||
}
|
||||
}
|
||||
|
||||
export class RunPropertyXmlComponent extends XmlComponent {
|
||||
private runProperties: RunProperties;
|
||||
|
||||
constructor(rootKey: string) {
|
||||
super(rootKey);
|
||||
this.runProperties = new RunProperties();
|
||||
this.root.push(this.runProperties);
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
this.runProperties.clearVariables();
|
||||
|
||||
delete this.runProperties;
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiPropertyXmlComponent extends XmlComponent {
|
||||
private runProperties: RunProperties;
|
||||
private paragraphProperties: ParagraphProperties;
|
||||
|
||||
constructor(rootKey: string) {
|
||||
super(rootKey);
|
||||
this.runProperties = new RunProperties();
|
||||
this.root.push(this.runProperties);
|
||||
|
||||
this.paragraphProperties = new ParagraphProperties();
|
||||
this.root.push(this.paragraphProperties);
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
this.runProperties.clearVariables();
|
||||
this.paragraphProperties.clearVariables();
|
||||
|
||||
delete this.runProperties;
|
||||
delete this.paragraphProperties;
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import {BaseXmlComponent} from "./base";
|
||||
|
||||
export abstract class XmlUnitComponent extends BaseXmlComponent {
|
||||
protected root: string;
|
||||
|
||||
constructor(rootKey: string) {
|
||||
super(rootKey);
|
||||
}
|
||||
|
||||
public replaceKey(): void {
|
||||
if (this.root !== undefined) {
|
||||
this[this.rootKey] = this.root;
|
||||
delete this.root;
|
||||
}
|
||||
}
|
||||
}
|
24
ts/docx/xml-components/xml-component.spec.ts
Normal file
24
ts/docx/xml-components/xml-component.spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { assert } from "chai";
|
||||
|
||||
import { Utility } from "../../tests/utility";
|
||||
import { XmlComponent } from "./";
|
||||
|
||||
class TestComponent extends XmlComponent {
|
||||
|
||||
}
|
||||
|
||||
describe("XmlComponent", () => {
|
||||
let xmlComponent: TestComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
xmlComponent = new TestComponent("w:test");
|
||||
});
|
||||
|
||||
describe("#constructor()", () => {
|
||||
|
||||
it("should create an Xml Component which has the correct rootKey", () => {
|
||||
const newJson = Utility.jsonify(xmlComponent);
|
||||
assert.equal(newJson.rootKey, "w:test");
|
||||
});
|
||||
});
|
||||
});
|
3
ts/docx/xml-components/xmlable-object.ts
Normal file
3
ts/docx/xml-components/xmlable-object.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface IXmlableObject extends Object {
|
||||
_attr?: { [key: string]: (string | number | boolean) };
|
||||
}
|
78
ts/export/formatter.spec.ts
Normal file
78
ts/export/formatter.spec.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { assert } from "chai";
|
||||
|
||||
import * as docx from "../docx";
|
||||
import { Attributes } from "../docx/xml-components";
|
||||
import { Formatter } from "../export/formatter";
|
||||
import { Properties } from "../properties";
|
||||
import { Utility } from "../tests/utility";
|
||||
|
||||
describe("Formatter", () => {
|
||||
let formatter: Formatter;
|
||||
|
||||
beforeEach(() => {
|
||||
formatter = new Formatter();
|
||||
});
|
||||
|
||||
describe("#format()", () => {
|
||||
it("should format simple paragraph", () => {
|
||||
const paragraph = new docx.Paragraph();
|
||||
const newJson = formatter.format(paragraph);
|
||||
assert.isDefined(newJson["w:p"]);
|
||||
});
|
||||
|
||||
it("should remove xmlKeys", () => {
|
||||
const paragraph = new docx.Paragraph();
|
||||
const newJson = formatter.format(paragraph);
|
||||
const stringifiedJson = JSON.stringify(newJson);
|
||||
assert(stringifiedJson.indexOf("xmlKeys") < 0);
|
||||
});
|
||||
|
||||
it("should format simple paragraph with bold text", () => {
|
||||
const paragraph = new docx.Paragraph();
|
||||
paragraph.addRun(new docx.TextRun("test").bold());
|
||||
const newJson = formatter.format(paragraph);
|
||||
assert.isDefined(newJson["w:p"][1]["w:r"][0]["w:rPr"][0]["w:b"][0]._attr["w:val"]);
|
||||
});
|
||||
|
||||
it("should format attributes (rsidSect)", () => {
|
||||
const attributes = new Attributes({
|
||||
rsidSect: "test2",
|
||||
});
|
||||
let newJson = formatter.format(attributes);
|
||||
newJson = Utility.jsonify(newJson);
|
||||
if (newJson._attr === undefined) {
|
||||
assert.fail();
|
||||
return;
|
||||
}
|
||||
assert.isDefined(newJson._attr["w:rsidSect"]);
|
||||
});
|
||||
|
||||
it("should format attributes (val)", () => {
|
||||
const attributes = new Attributes({
|
||||
val: "test",
|
||||
});
|
||||
let newJson = formatter.format(attributes);
|
||||
newJson = Utility.jsonify(newJson);
|
||||
if (newJson._attr === undefined) {
|
||||
assert.fail();
|
||||
return;
|
||||
}
|
||||
assert.isDefined(newJson._attr["w:val"]);
|
||||
});
|
||||
|
||||
it("should should change 'p' tag into 'w:p' tag", () => {
|
||||
const paragraph = new docx.Paragraph();
|
||||
const newJson = formatter.format(paragraph);
|
||||
assert.isDefined(newJson["w:p"]);
|
||||
});
|
||||
|
||||
it("should format Properties object correctly", () => {
|
||||
const properties = new Properties({
|
||||
title: "test document",
|
||||
creator: "Dolan",
|
||||
});
|
||||
const newJson = formatter.format(properties);
|
||||
assert.isDefined(newJson["cp:coreProperties"]);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,52 +1,8 @@
|
||||
import * as _ from "lodash";
|
||||
import {XmlComponent} from "../docx/xml-components";
|
||||
import { BaseXmlComponent } from "../docx/xml-components";
|
||||
import { IXmlableObject } from "../docx/xml-components/xmlable-object";
|
||||
|
||||
export class Formatter {
|
||||
|
||||
public format(input: any): Object {
|
||||
input.clearVariables();
|
||||
this.replaceKeys(input);
|
||||
const newJson = this.clense(input);
|
||||
// console.log(JSON.stringify(newJson, null, " "));
|
||||
return newJson;
|
||||
public format(input: BaseXmlComponent): IXmlableObject {
|
||||
return input.prepForXml();
|
||||
}
|
||||
|
||||
private replaceKeys(input: XmlComponent): Object {
|
||||
input.replaceKey();
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private clense(input: any): Object {
|
||||
const newJson = this.jsonify(input);
|
||||
|
||||
this.deepTraverseJson(newJson, (parent, value, key) => {
|
||||
if (key === "properties") {
|
||||
delete parent[key];
|
||||
}
|
||||
if (key === "xmlKeys") {
|
||||
delete parent[key];
|
||||
}
|
||||
if (key === "rootKey") {
|
||||
delete parent[key];
|
||||
}
|
||||
});
|
||||
|
||||
return newJson;
|
||||
}
|
||||
|
||||
private jsonify(obj: Object): Object {
|
||||
let stringifiedJson = JSON.stringify(obj);
|
||||
return JSON.parse(stringifiedJson);
|
||||
}
|
||||
|
||||
private deepTraverseJson(json: Object, lambda: (json: any, value: any, key: any) => void): void {
|
||||
_.forOwn(json, (value, key) => {
|
||||
if (_.isObject(value) && key !== "xmlKeys" && key !== "rootKey") {
|
||||
this.deepTraverseJson(value, lambda);
|
||||
}
|
||||
lambda(json, value, key);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
export { LocalPacker } from "./packer/local";
|
||||
export { ExpressPacker } from "./packer/express";
|
||||
export { Packer } from "./packer/packer";
|
||||
|
@ -1,16 +1,16 @@
|
||||
import * as express from "express";
|
||||
import * as fs from "fs";
|
||||
import { Document } from "../../docx/document";
|
||||
import { Media } from "../../media";
|
||||
import { Numbering } from "../../numbering";
|
||||
import { Properties } from "../../properties";
|
||||
import { Styles } from "../../styles";
|
||||
import { Packer } from "./packer";
|
||||
|
||||
|
||||
export class ExpressPacker extends Packer {
|
||||
private res: express.Response;
|
||||
|
||||
constructor(document: Document, res: express.Response, styles?: any, properties?: Properties, numbering?: Numbering) {
|
||||
super(document, styles, properties, numbering);
|
||||
constructor(document: Document, res: express.Response, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) {
|
||||
super(document, styles, properties, numbering, media);
|
||||
this.res = res;
|
||||
|
||||
this.res.on("close", () => {
|
||||
@ -19,7 +19,7 @@ export class ExpressPacker extends Packer {
|
||||
}
|
||||
|
||||
public pack(name: string): void {
|
||||
this.res.attachment(name + ".docx");
|
||||
super.pack(this.res);
|
||||
this.res.attachment(`${name}.docx`);
|
||||
super.compile(this.res);
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,40 @@
|
||||
import * as fs from "fs";
|
||||
import { LocalPacker } from "../../export/packer/local";
|
||||
import { Document } from "../../docx/document";
|
||||
import { Properties } from "../../properties";
|
||||
import { DefaultStyle } from "../../styles/sample";
|
||||
import { Paragraph } from "../../docx/paragraph";
|
||||
import { DefaultStylesFactory } from "../../styles/factory";
|
||||
import { assert } from "chai";
|
||||
import * as fs from "fs";
|
||||
|
||||
import { Document } from "../../docx/document";
|
||||
import { Paragraph } from "../../docx/paragraph";
|
||||
import { LocalPacker } from "../../export/packer/local";
|
||||
import { Properties } from "../../properties";
|
||||
import { DefaultStylesFactory } from "../../styles/factory";
|
||||
|
||||
describe("Packer", () => {
|
||||
let packer: LocalPacker;
|
||||
let stylesFactory: DefaultStylesFactory;
|
||||
|
||||
beforeEach(() => {
|
||||
let document = new Document();
|
||||
let paragraph = new Paragraph("test text");
|
||||
let heading = new Paragraph("Hello world").heading1();
|
||||
const document = new Document();
|
||||
const paragraph = new Paragraph("test text");
|
||||
const heading = new Paragraph("Hello world").heading1();
|
||||
document.addParagraph(new Paragraph("title").title());
|
||||
document.addParagraph(heading);
|
||||
document.addParagraph(new Paragraph("heading 2").heading2());
|
||||
document.addParagraph(paragraph);
|
||||
let properties = new Properties({
|
||||
const properties = new Properties({
|
||||
creator: "Dolan Miu",
|
||||
revision: "1",
|
||||
lastModifiedBy: "Dolan Miu"
|
||||
lastModifiedBy: "Dolan Miu",
|
||||
});
|
||||
stylesFactory = new DefaultStylesFactory();
|
||||
packer = new LocalPacker(document, stylesFactory.newInstance(), properties);
|
||||
});
|
||||
|
||||
describe("#pack()", () => {
|
||||
/* tslint:disable */
|
||||
it("should create a standard docx file", function (done) {
|
||||
/* tslint:enable */
|
||||
this.timeout(99999999);
|
||||
packer.pack("build-tests/tests/test.docx");
|
||||
let int = setInterval(() => {
|
||||
const int = setInterval(() => {
|
||||
const stats = fs.statSync("build-tests/tests/test.docx");
|
||||
if (stats.size > 2000) {
|
||||
clearInterval(int);
|
||||
@ -40,10 +42,10 @@ describe("Packer", () => {
|
||||
done();
|
||||
}
|
||||
}, 1000);
|
||||
let out = setTimeout(() => {
|
||||
const out = setTimeout(() => {
|
||||
clearInterval(int);
|
||||
try {
|
||||
assert(false, 'did not create a file within the alloted time');
|
||||
assert(false, "did not create a file within the alloted time");
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
import * as fs from "fs";
|
||||
import { Document } from "../../docx/document";
|
||||
import { Media } from "../../media";
|
||||
import { Numbering } from "../../numbering";
|
||||
import { Properties } from "../../properties";
|
||||
import { Styles } from "../../styles";
|
||||
import { Packer } from "./packer";
|
||||
|
||||
|
||||
export class LocalPacker extends Packer {
|
||||
private stream: fs.WriteStream;
|
||||
|
||||
constructor(document: Document, styles?: any, properties?: Properties, numbering?: Numbering) {
|
||||
super(document, styles, properties, numbering);
|
||||
constructor(document: Document, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) {
|
||||
super(document, styles, properties, numbering, media);
|
||||
}
|
||||
|
||||
public pack(path: string): void {
|
||||
this.stream = fs.createWriteStream(path);
|
||||
super.pack(this.stream);
|
||||
path = path.replace(/.docx$/, "");
|
||||
this.stream = fs.createWriteStream(`${path}.docx`);
|
||||
super.compile(this.stream);
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +1,69 @@
|
||||
import * as archiver from "archiver";
|
||||
import * as express from "express";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as xml from "xml";
|
||||
import { Document } from "../../docx";
|
||||
import { Media } from "../../media";
|
||||
import { Numbering } from "../../numbering";
|
||||
import { Properties } from "../../properties";
|
||||
import { Styles } from "../../styles";
|
||||
import { DefaultStylesFactory } from "../../styles/factory";
|
||||
import { Formatter } from "../formatter";
|
||||
|
||||
const appRoot = require("app-root-path");
|
||||
const TEMPLATE_PATH = path.resolve(__dirname, "../../../template");
|
||||
|
||||
export abstract class Packer {
|
||||
protected archive: any;
|
||||
protected document: Document;
|
||||
protected archive: archiver.Archiver;
|
||||
private formatter: Formatter;
|
||||
private style: Styles;
|
||||
private properties: Properties;
|
||||
private numbering: Numbering;
|
||||
|
||||
constructor(document: Document, style?: any, properties?: Properties, numbering?: Numbering) {
|
||||
constructor(
|
||||
protected document: Document,
|
||||
style?: Styles,
|
||||
private properties: Properties = new Properties({
|
||||
creator: "Un-named",
|
||||
revision: "1",
|
||||
lastModifiedBy: "Un-named",
|
||||
}),
|
||||
private numbering: Numbering = new Numbering(),
|
||||
private media: Media = new Media(),
|
||||
) {
|
||||
this.formatter = new Formatter();
|
||||
this.document = document;
|
||||
this.style = style;
|
||||
this.properties = properties;
|
||||
this.numbering = numbering;
|
||||
this.archive = archiver.create("zip", {});
|
||||
|
||||
if (!style) {
|
||||
if (style) {
|
||||
this.style = style;
|
||||
} else {
|
||||
const stylesFactory = new DefaultStylesFactory();
|
||||
this.style = stylesFactory.newInstance();
|
||||
}
|
||||
|
||||
if (!properties) {
|
||||
this.properties = new Properties({
|
||||
creator: "Un-named",
|
||||
revision: "1",
|
||||
lastModifiedBy: "Un-named",
|
||||
});
|
||||
}
|
||||
|
||||
if (!numbering) {
|
||||
this.numbering = new Numbering();
|
||||
}
|
||||
|
||||
this.archive.on("error", (err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
public pack(output: any): void {
|
||||
protected compile(output: fs.WriteStream | express.Response): void {
|
||||
this.archive.pipe(output);
|
||||
console.log(appRoot.path + "/template");
|
||||
this.archive.glob("**", {
|
||||
expand: true,
|
||||
cwd: appRoot.path + "/template",
|
||||
cwd: TEMPLATE_PATH,
|
||||
});
|
||||
|
||||
this.archive.glob("**/.rels", {
|
||||
expand: true,
|
||||
cwd: appRoot.path + "/template",
|
||||
cwd: TEMPLATE_PATH,
|
||||
});
|
||||
|
||||
// this.archive.file(appRoot.path + "/template/[Content_Types].xml", { name: "[Content_Types].xml" });
|
||||
// console.log(__dirname + "/packer.js");
|
||||
// this.archive.file(__dirname + "/packer.js", { name: "/[Content_Types].xml" });
|
||||
|
||||
/*this.archive.directory(appRoot.path + "/template", {
|
||||
name: "/root/g.txt",
|
||||
prefix: "root"
|
||||
});*/
|
||||
const xmlDocument = xml(this.formatter.format(this.document));
|
||||
const xmlStyles = xml(this.formatter.format(this.style));
|
||||
const xmlProperties = xml(this.formatter.format(this.properties), { declaration: { standalone: "yes", encoding: "UTF-8" } });
|
||||
const xmlProperties = xml(this.formatter.format(this.properties), {
|
||||
declaration: {
|
||||
standalone: "yes",
|
||||
encoding: "UTF-8",
|
||||
},
|
||||
});
|
||||
const xmlNumbering = xml(this.formatter.format(this.numbering));
|
||||
// console.log(JSON.stringify(this.numbering, null, " "));
|
||||
console.log(xmlNumbering);
|
||||
|
||||
this.archive.append(xmlDocument, {
|
||||
name: "word/document.xml",
|
||||
});
|
||||
@ -91,6 +80,12 @@ export abstract class Packer {
|
||||
name: "word/numbering.xml",
|
||||
});
|
||||
|
||||
for (const data of this.media.array) {
|
||||
this.archive.append(data.stream, {
|
||||
name: `media/${data.fileName}`,
|
||||
});
|
||||
}
|
||||
|
||||
this.archive.finalize();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
export * from "./docx";
|
||||
export * from "./export";
|
||||
export { Numbering } from "./numbering";
|
||||
export { Styles } from "./styles";
|
||||
export { Media } from "./media";
|
||||
export * from "./export";
|
||||
|
8
ts/media/data.ts
Normal file
8
ts/media/data.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import * as fs from "fs";
|
||||
|
||||
export interface IData {
|
||||
referenceId: number;
|
||||
stream: fs.ReadStream;
|
||||
path: string;
|
||||
fileName: string;
|
||||
}
|
40
ts/media/index.ts
Normal file
40
ts/media/index.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { IData } from "./data";
|
||||
|
||||
export class Media {
|
||||
private map: Map<string, IData>;
|
||||
|
||||
constructor() {
|
||||
this.map = new Map<string, IData>();
|
||||
}
|
||||
|
||||
public getMedia(key: string): IData {
|
||||
const data = this.map.get(key);
|
||||
|
||||
if (data === undefined) {
|
||||
throw new Error(`Cannot find image with the key ${key}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public addMedia(key: string, filePath: string): void {
|
||||
this.map.set(key, {
|
||||
referenceId: this.map.values.length,
|
||||
stream: fs.createReadStream(filePath),
|
||||
path: filePath,
|
||||
fileName: path.basename(filePath),
|
||||
});
|
||||
}
|
||||
|
||||
public get array(): IData[] {
|
||||
const array = new Array<IData>();
|
||||
|
||||
this.map.forEach((data) => {
|
||||
array.push(data);
|
||||
});
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import * as _ from "lodash";
|
||||
import { XmlAttributeComponent, XmlComponent } from "../docx/xml-components";
|
||||
import { Level } from "./level";
|
||||
import { MultiLevelType } from "./multi-level-type";
|
||||
@ -8,14 +7,11 @@ interface IAbstractNumberingAttributesProperties {
|
||||
restartNumberingAfterBreak?: number;
|
||||
}
|
||||
|
||||
class AbstractNumberingAttributes extends XmlAttributeComponent {
|
||||
|
||||
constructor(properties: IAbstractNumberingAttributesProperties) {
|
||||
super({
|
||||
abstractNumId: "w:abstractNumId",
|
||||
restartNumberingAfterBreak: "w15:restartNumberingAfterBreak",
|
||||
}, properties);
|
||||
}
|
||||
class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberingAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
abstractNumId: "w:abstractNumId",
|
||||
restartNumberingAfterBreak: "w15:restartNumberingAfterBreak",
|
||||
};
|
||||
}
|
||||
|
||||
export class AbstractNumbering extends XmlComponent {
|
||||
@ -40,11 +36,4 @@ export class AbstractNumbering extends XmlComponent {
|
||||
this.addLevel(level);
|
||||
return level;
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
_.forEach(this.root, (element) => {
|
||||
element.clearVariables();
|
||||
});
|
||||
delete this.id;
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../docx/xml-components";
|
||||
|
||||
interface IndentAttributesProperties {
|
||||
left: number;
|
||||
hanging: number;
|
||||
}
|
||||
|
||||
class IndentAttributes extends XmlAttributeComponent {
|
||||
|
||||
constructor(properties: IndentAttributesProperties) {
|
||||
super({
|
||||
left: "w:left",
|
||||
hanging: "w:hanging",
|
||||
}, properties);
|
||||
}
|
||||
}
|
||||
|
||||
export class Indent extends XmlComponent {
|
||||
|
||||
constructor(left: number, hanging: number) {
|
||||
super("w:ind");
|
||||
this.root.push(new IndentAttributes({
|
||||
left: left,
|
||||
hanging: hanging,
|
||||
}));
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
import * as _ from "lodash";
|
||||
import { DocumentAttributes } from "../docx/document/document-attributes";
|
||||
import { Indent } from "../docx/paragraph/indent";
|
||||
import { RunFonts } from "../docx/run/run-fonts";
|
||||
import { MultiPropertyXmlComponent } from "../docx/xml-components";
|
||||
import { XmlComponent } from "../docx/xml-components";
|
||||
import { AbstractNumbering } from "./abstract-numbering";
|
||||
import { Indent } from "./indent";
|
||||
import { Level } from "./level";
|
||||
import { Num } from "./num";
|
||||
|
||||
export class Numbering extends MultiPropertyXmlComponent {
|
||||
export class Numbering extends XmlComponent {
|
||||
private nextId: number;
|
||||
|
||||
constructor() {
|
||||
@ -37,39 +35,39 @@ export class Numbering extends MultiPropertyXmlComponent {
|
||||
const abstractNumbering = this.createAbstractNumbering();
|
||||
|
||||
abstractNumbering.createLevel(0, "bullet", "•", "left")
|
||||
.addParagraphProperty(new Indent(720, 360))
|
||||
.addParagraphProperty(new Indent({ left: 720, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Symbol", "default"));
|
||||
|
||||
abstractNumbering.createLevel(1, "bullet", "o", "left")
|
||||
.addParagraphProperty(new Indent(1440, 360))
|
||||
.addParagraphProperty(new Indent({ left: 1440, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Courier New", "default"));
|
||||
|
||||
abstractNumbering.createLevel(2, "bullet", "•", "left")
|
||||
.addParagraphProperty(new Indent(2160, 360))
|
||||
.addParagraphProperty(new Indent({ left: 2160, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Wingdings", "default"));
|
||||
|
||||
abstractNumbering.createLevel(3, "bullet", "•", "left")
|
||||
.addParagraphProperty(new Indent(2880, 360))
|
||||
.addParagraphProperty(new Indent({ left: 2880, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Symbol", "default"));
|
||||
|
||||
abstractNumbering.createLevel(4, "bullet", "o", "left")
|
||||
.addParagraphProperty(new Indent(3600, 360))
|
||||
.addParagraphProperty(new Indent({ left: 3600, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Courier New", "default"));
|
||||
|
||||
abstractNumbering.createLevel(5, "bullet", "•", "left")
|
||||
.addParagraphProperty(new Indent(4320, 360))
|
||||
.addParagraphProperty(new Indent({ left: 4320, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Wingdings", "default"));
|
||||
|
||||
abstractNumbering.createLevel(6, "bullet", "•", "left")
|
||||
.addParagraphProperty(new Indent(5040, 360))
|
||||
.addParagraphProperty(new Indent({ left: 5040, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Symbol", "default"));
|
||||
|
||||
abstractNumbering.createLevel(7, "bullet", "o", "left")
|
||||
.addParagraphProperty(new Indent(5760, 360))
|
||||
.addParagraphProperty(new Indent({ left: 5760, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Courier New", "default"));
|
||||
|
||||
abstractNumbering.createLevel(8, "bullet", "•", "left")
|
||||
.addParagraphProperty(new Indent(6480, 360))
|
||||
.addParagraphProperty(new Indent({ left: 6480, hanging: 360 }))
|
||||
.addRunProperty(new RunFonts("Wingdings", "default"));
|
||||
|
||||
this.createConcreteNumbering(abstractNumbering);
|
||||
@ -86,12 +84,4 @@ export class Numbering extends MultiPropertyXmlComponent {
|
||||
this.root.push(num);
|
||||
return num;
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
super.clearVariables();
|
||||
_.forEach(this.root, (element) => {
|
||||
element.clearVariables();
|
||||
});
|
||||
delete this.nextId;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,19 @@
|
||||
import * as paragraph from "../docx/paragraph/formatting";
|
||||
import { ParagraphProperties } from "../docx/paragraph/properties";
|
||||
import * as formatting from "../docx/run/formatting";
|
||||
import { RunProperties } from "../docx/run/properties";
|
||||
import { Attributes, MultiPropertyXmlComponent, XmlAttributeComponent, XmlComponent } from "../docx/xml-components";
|
||||
import { Attributes, XmlAttributeComponent, XmlComponent } from "../docx/xml-components";
|
||||
|
||||
interface ILevelAttributesProperties {
|
||||
ilvl?: number;
|
||||
tentative?: number;
|
||||
}
|
||||
|
||||
class LevelAttributes extends XmlAttributeComponent {
|
||||
|
||||
constructor(properties: ILevelAttributesProperties) {
|
||||
super({
|
||||
ilvl: "w:ilvl",
|
||||
tentative: "w15:tentative",
|
||||
}, properties);
|
||||
}
|
||||
class LevelAttributes extends XmlAttributeComponent<ILevelAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
ilvl: "w:ilvl",
|
||||
tentative: "w15:tentative",
|
||||
};
|
||||
}
|
||||
|
||||
class Start extends XmlComponent {
|
||||
@ -57,21 +56,29 @@ class LevelJc extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export class Level extends XmlComponent {
|
||||
export class LevelBase extends XmlComponent {
|
||||
private paragraphProperties: ParagraphProperties;
|
||||
private runProperties: RunProperties;
|
||||
|
||||
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) {
|
||||
constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) {
|
||||
super("w:lvl");
|
||||
this.root.push(new LevelAttributes({
|
||||
ilvl: level,
|
||||
tentative: 1,
|
||||
}));
|
||||
|
||||
this.root.push(new Start(1));
|
||||
this.root.push(new NumberFormat(numberFormat));
|
||||
this.root.push(new LevelText(levelText));
|
||||
this.root.push(new LevelJc(lvlJc));
|
||||
if (start !== undefined) {
|
||||
this.root.push(new Start(start));
|
||||
}
|
||||
if (numberFormat !== undefined) {
|
||||
this.root.push(new NumberFormat(numberFormat));
|
||||
}
|
||||
if (levelText !== undefined) {
|
||||
this.root.push(new LevelText(levelText));
|
||||
}
|
||||
if (lvlJc !== undefined) {
|
||||
this.root.push(new LevelJc(lvlJc));
|
||||
}
|
||||
|
||||
this.paragraphProperties = new ParagraphProperties();
|
||||
this.runProperties = new RunProperties();
|
||||
@ -80,14 +87,6 @@ export class Level extends XmlComponent {
|
||||
this.root.push(this.runProperties);
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
this.paragraphProperties.clearVariables();
|
||||
this.runProperties.clearVariables();
|
||||
|
||||
delete this.paragraphProperties;
|
||||
delete this.runProperties;
|
||||
}
|
||||
|
||||
public addParagraphProperty(property: XmlComponent): Level {
|
||||
this.paragraphProperties.push(property);
|
||||
return this;
|
||||
@ -97,4 +96,133 @@ export class Level extends XmlComponent {
|
||||
this.runProperties.push(property);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ---------- Run formatting ---------------------- //
|
||||
|
||||
public size(twips: number): Level {
|
||||
this.addRunProperty(new formatting.Size(twips));
|
||||
return this;
|
||||
}
|
||||
|
||||
public bold(): Level {
|
||||
this.addRunProperty(new formatting.Bold());
|
||||
return this;
|
||||
}
|
||||
|
||||
public italics(): Level {
|
||||
this.addRunProperty(new formatting.Italics());
|
||||
return this;
|
||||
}
|
||||
|
||||
public smallCaps(): Level {
|
||||
this.addRunProperty(new formatting.SmallCaps());
|
||||
return this;
|
||||
}
|
||||
|
||||
public allCaps(): Level {
|
||||
this.addRunProperty(new formatting.Caps());
|
||||
return this;
|
||||
}
|
||||
|
||||
public strike(): Level {
|
||||
this.addRunProperty(new formatting.Strike());
|
||||
return this;
|
||||
}
|
||||
|
||||
public doubleStrike(): Level {
|
||||
this.addRunProperty(new formatting.DoubleStrike());
|
||||
return this;
|
||||
}
|
||||
|
||||
public subScript(): Level {
|
||||
this.addRunProperty(new formatting.SubScript());
|
||||
return this;
|
||||
}
|
||||
|
||||
public superScript(): Level {
|
||||
this.addRunProperty(new formatting.SuperScript());
|
||||
return this;
|
||||
}
|
||||
|
||||
public underline(underlineType?: string, color?: string): Level {
|
||||
this.addRunProperty(new formatting.Underline(underlineType, color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public color(color: string): Level {
|
||||
this.addRunProperty(new formatting.Color(color));
|
||||
return this;
|
||||
}
|
||||
|
||||
public font(fontName: string): Level {
|
||||
this.addRunProperty(new formatting.RunFonts(fontName));
|
||||
return this;
|
||||
}
|
||||
|
||||
// --------------------- Paragraph formatting ------------------------ //
|
||||
|
||||
public center(): Level {
|
||||
this.addParagraphProperty(new paragraph.Alignment("center"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public left(): Level {
|
||||
this.addParagraphProperty(new paragraph.Alignment("left"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public right(): Level {
|
||||
this.addParagraphProperty(new paragraph.Alignment("right"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public justified(): Level {
|
||||
this.addParagraphProperty(new paragraph.Alignment("both"));
|
||||
return this;
|
||||
}
|
||||
|
||||
public thematicBreak(): Level {
|
||||
this.addParagraphProperty(new paragraph.ThematicBreak());
|
||||
return this;
|
||||
}
|
||||
|
||||
public maxRightTabStop(): Level {
|
||||
this.addParagraphProperty(new paragraph.MaxRightTabStop());
|
||||
return this;
|
||||
}
|
||||
|
||||
public leftTabStop(position: number): Level {
|
||||
this.addParagraphProperty(new paragraph.LeftTabStop(position));
|
||||
return this;
|
||||
}
|
||||
|
||||
public indent(attrs: object): Level {
|
||||
this.addParagraphProperty(new paragraph.Indent(attrs));
|
||||
return this;
|
||||
}
|
||||
|
||||
public spacing(params: paragraph.ISpacingProperties): Level {
|
||||
this.addParagraphProperty(new paragraph.Spacing(params));
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepNext(): Level {
|
||||
this.addParagraphProperty(new paragraph.KeepNext());
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepLines(): Level {
|
||||
this.addParagraphProperty(new paragraph.KeepLines());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class Level extends LevelBase {
|
||||
// This is the level that sits under abstractNum. We make a
|
||||
// handful of properties required
|
||||
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) {
|
||||
super(level, 1, numberFormat, levelText, lvlJc);
|
||||
}
|
||||
}
|
||||
|
||||
export class LevelForOverride extends LevelBase {}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Attributes, XmlAttributeComponent, XmlComponent } from "../docx/xml-components";
|
||||
import { LevelForOverride } from "./level";
|
||||
|
||||
class AbstractNumId extends XmlComponent {
|
||||
|
||||
@ -14,13 +15,8 @@ interface INumAttributesProperties {
|
||||
numId: number;
|
||||
}
|
||||
|
||||
class NumAttributes extends XmlAttributeComponent {
|
||||
|
||||
constructor(properties: INumAttributesProperties) {
|
||||
super({
|
||||
numId: "w:numId",
|
||||
}, properties);
|
||||
}
|
||||
class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
|
||||
protected xmlKeys = {numId: "w:numId"};
|
||||
}
|
||||
|
||||
export class Num extends XmlComponent {
|
||||
@ -35,8 +31,50 @@ export class Num extends XmlComponent {
|
||||
this.id = numId;
|
||||
}
|
||||
|
||||
public clearVariables(): void {
|
||||
super.clearVariables();
|
||||
delete this.id;
|
||||
public overrideLevel(num: number, start?: number): LevelOverride {
|
||||
const olvl = new LevelOverride(num, start);
|
||||
this.root.push(olvl);
|
||||
return olvl;
|
||||
}
|
||||
}
|
||||
|
||||
class LevelOverrideAttributes extends XmlAttributeComponent<{ilvl: number}> {
|
||||
protected xmlKeys = {ilvl: "w:ilvl"};
|
||||
}
|
||||
|
||||
export class LevelOverride extends XmlComponent {
|
||||
private levelNum: number;
|
||||
private lvl?: LevelForOverride;
|
||||
|
||||
constructor(levelNum: number, start?: number) {
|
||||
super("w:lvlOverride");
|
||||
this.root.push(new LevelOverrideAttributes({ilvl: levelNum}));
|
||||
if (start !== undefined) {
|
||||
this.root.push(new StartOverride(start));
|
||||
}
|
||||
this.levelNum = levelNum;
|
||||
}
|
||||
|
||||
get level(): LevelForOverride {
|
||||
let lvl: LevelForOverride;
|
||||
if (!this.lvl) {
|
||||
lvl = new LevelForOverride(this.levelNum);
|
||||
this.root.push(lvl);
|
||||
this.lvl = lvl;
|
||||
} else {
|
||||
lvl = this.lvl;
|
||||
}
|
||||
return lvl;
|
||||
}
|
||||
}
|
||||
|
||||
class StartOverrideAttributes extends XmlAttributeComponent<{val: number}> {
|
||||
protected xmlKeys = {val: "w:val"};
|
||||
}
|
||||
|
||||
class StartOverride extends XmlComponent {
|
||||
constructor(start: number) {
|
||||
super("w:startOverride");
|
||||
this.root.push(new StartOverrideAttributes({val: start}));
|
||||
}
|
||||
}
|
||||
|
464
ts/numbering/numbering.spec.ts
Normal file
464
ts/numbering/numbering.spec.ts
Normal file
@ -0,0 +1,464 @@
|
||||
import { expect } from "chai";
|
||||
import { Formatter } from "../export/formatter";
|
||||
import { Numbering } from "./";
|
||||
import { AbstractNumbering } from "./abstract-numbering";
|
||||
import { LevelForOverride } from "./level";
|
||||
import { Num } from "./num";
|
||||
|
||||
describe("Numbering", () => {
|
||||
|
||||
let numbering: Numbering;
|
||||
|
||||
beforeEach(() => {
|
||||
numbering = new Numbering();
|
||||
});
|
||||
|
||||
describe("#constructor", () => {
|
||||
it("creates a default numbering with one abstract and one concrete instance", () => {
|
||||
const tree = new Formatter().format(numbering);
|
||||
expect(Object.keys(tree)).to.deep.equal(["w:numbering"]);
|
||||
const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]);
|
||||
expect(abstractNums).to.have.lengthOf(1);
|
||||
expect(abstractNums[0]["w:abstractNum"]).to.deep.include.members([
|
||||
{ _attr: { "w:abstractNumId": 0, "w15:restartNumberingAfterBreak": 0 } },
|
||||
{ "w:multiLevelType": [{ _attr: { "w:val": "hybridMultilevel" } }] },
|
||||
]);
|
||||
|
||||
abstractNums.filter((el) => el["w:lvl"]).forEach((el, ix) => {
|
||||
expect(Object.keys(el)).to.have.lengthOf(1);
|
||||
expect(Object.keys(el["w:lvl"]).sort()).to.deep.equal([
|
||||
"_attr", "w:start", "w:lvlJc", "w:numFmt", "w:pPr", "w:rPr",
|
||||
]);
|
||||
expect(el["w:lvl"]).to.have.deep.members([
|
||||
{ _attr: { "w:ilvl": ix, "w15:tentative": 1 } },
|
||||
{ "w:start": [{ _attr: { "w:val": 1 } }] },
|
||||
{ "w:lvlJc": [{ _attr: { "w:val": "left" } }] },
|
||||
{ "w:numFmt": [{ _attr: { "w:val": "bullet" } }] },
|
||||
]);
|
||||
// Once chai 4.0.0 lands and #644 is resolved, we can add the following to the test:
|
||||
// {"w:lvlText": [{"_attr": {"w:val": "•"}}]},
|
||||
// {"w:rPr": [{"w:rFonts": [{"_attr": {"w:ascii": "Symbol", "w:hAnsi": "Symbol", "w:hint": "default"}}]}]},
|
||||
// {"w:pPr": [{"_attr": {}},
|
||||
// {"w:ind": [{"_attr": {"w:left": 720, "w:hanging": 360}}]}]},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createAbstractNumbering", () => {
|
||||
it("returns a new AbstractNumbering instance", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
expect(a2).to.be.instanceof(AbstractNumbering);
|
||||
});
|
||||
|
||||
it("assigns a unique ID to each abstract numbering it creates", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
const a3 = numbering.createAbstractNumbering();
|
||||
expect(a2.id).not.to.equal(a3.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createConcreteNumbering", () => {
|
||||
it("returns a new Num instance with its abstract ID set to the AbstractNumbering's ID", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
const n = numbering.createConcreteNumbering(a2);
|
||||
expect(n).to.be.instanceof(Num);
|
||||
const tree = new Formatter().format(numbering);
|
||||
const serializedN = tree["w:numbering"].find((obj) =>
|
||||
obj["w:num"] && obj["w:num"][0]._attr["w:numId"] === n.id,
|
||||
);
|
||||
expect(serializedN["w:num"][1]["w:abstractNumId"][0]._attr["w:val"]).to.equal(a2.id);
|
||||
});
|
||||
|
||||
it("assigns a unique ID to each concrete numbering it creates", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
const n = numbering.createConcreteNumbering(a2);
|
||||
const n2 = numbering.createConcreteNumbering(a2);
|
||||
expect(n.id).not.to.equal(n2.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("AbstractNumbering", () => {
|
||||
it("stores its ID at its .id property", () => {
|
||||
const abstractNumbering = new AbstractNumbering(5);
|
||||
expect(abstractNumbering.id).to.equal(5);
|
||||
});
|
||||
|
||||
describe("#createLevel", () => {
|
||||
it("creates a level with the given characteristics", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)", "end");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:start": [{ _attr: { "w:val": 1 } }] });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlJc": [{ _attr: { "w:val": "end" } }] });
|
||||
expect(tree["w:lvl"]).to.include({ "w:numFmt": [{ _attr: { "w:val": "lowerLetter" } }] });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlText": [{ _attr: { "w:val": "%1)" } }] });
|
||||
});
|
||||
|
||||
it("uses 'start' as the default alignment", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:start": [{ _attr: { "w:val": 1 } }] });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlJc": [{ _attr: { "w:val": "start" } }] });
|
||||
expect(tree["w:lvl"]).to.include({ "w:numFmt": [{ _attr: { "w:val": "lowerLetter" } }] });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlText": [{ _attr: { "w:val": "%1)" } }] });
|
||||
});
|
||||
|
||||
describe("formatting methods: paragraph properties", () => {
|
||||
it("#indent", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.")
|
||||
.indent({ left: 720 });
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{"w:ind": [{_attr: {"w:left": 720}}]}],
|
||||
});
|
||||
});
|
||||
|
||||
it("#spacing", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.")
|
||||
.spacing({before: 50, after: 150});
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:spacing": [{_attr: {"w:before": 50, "w:after": 150}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#center", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.")
|
||||
.center();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:jc": [{_attr: {"w:val": "center"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#left", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.", "left")
|
||||
.left();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:jc": [{_attr: {"w:val": "left"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#right", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.right();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:jc": [{_attr: {"w:val": "right"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#justified", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.justified();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:jc": [{_attr: {"w:val": "both"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#thematicBreak", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.thematicBreak();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:pBdr": [{"w:bottom": [{_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": "1",
|
||||
"w:val": "single",
|
||||
"w:sz": "6",
|
||||
}}]}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#leftTabStop", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.leftTabStop(1200);
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:tabs": [
|
||||
{"w:tab": [{_attr: {"w:val": "left", "w:pos": 1200}}]},
|
||||
]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#maxRightTabStop", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.maxRightTabStop();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{"w:tabs": [
|
||||
{"w:tab": [{_attr: {"w:val": "right", "w:pos": 9026}}]},
|
||||
]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepLines", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.keepLines();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{"w:keepLines": []}],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepNext", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.keepNext();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{"w:keepNext": []}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatting methods: run properties", () => {
|
||||
it("#size", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.size(24);
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:sz": [{_attr: {"w:val": 24}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#smallCaps", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.smallCaps();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:smallCaps": [{_attr: {"w:val": true}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#allCaps", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.allCaps();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:caps": [{_attr: {"w:val": true}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#strike", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.strike();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:strike": [{_attr: {"w:val": true}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#doubleStrike", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.doubleStrike();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:dstrike": [{_attr: {"w:val": true}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#subScript", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.subScript();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:vertAlign": [{_attr: {"w:val": "subscript"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#superScript", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.superScript();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:vertAlign": [{_attr: {"w:val": "superscript"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#font", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.font("Times");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{"w:rFonts": [{_attr: {"w:ascii": "Times", "w:hAnsi": "Times"}}]}],
|
||||
});
|
||||
});
|
||||
|
||||
it("#bold", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.bold();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:b": [{_attr: {"w:val": true}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#italics", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.italics();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:i": [{_attr: {"w:val": true}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("#underline", () => {
|
||||
it("should set underline to 'single' if no arguments are given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.underline();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:u": [{_attr: {"w:val": "single"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style if given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.underline("double");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:u": [{_attr: {"w:val": "double"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style and color if given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.underline("double", "005599");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:u": [{_attr: {"w:val": "double", "w:color": "005599"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("#color", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.color("123456");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{"w:color": [{_attr: {"w:val": "123456"}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("concrete numbering", () => {
|
||||
describe("#overrideLevel", () => {
|
||||
let numbering;
|
||||
let abstractNumbering;
|
||||
let concreteNumbering;
|
||||
beforeEach(() => {
|
||||
numbering = new Numbering();
|
||||
abstractNumbering = numbering.createAbstractNumbering();
|
||||
concreteNumbering = numbering.createConcreteNumbering(abstractNumbering);
|
||||
|
||||
});
|
||||
|
||||
it("sets a new override level for the given level number", () => {
|
||||
concreteNumbering.overrideLevel(3);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({"w:lvlOverride": [{_attr: {"w:ilvl": 3}}]});
|
||||
});
|
||||
|
||||
it("sets the startOverride element if start is given", () => {
|
||||
concreteNumbering.overrideLevel(1, 9);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{_attr: {"w:ilvl": 1}},
|
||||
{"w:startOverride": [{_attr: {"w:val": 9}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the lvl element if overrideLevel.level is accessed", () => {
|
||||
const ol = concreteNumbering.overrideLevel(1);
|
||||
expect(ol.level).to.be.instanceof(LevelForOverride);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{_attr: {"w:ilvl": 1}},
|
||||
{"w:lvl": [
|
||||
{_attr: {"w15:tentative": 1, "w:ilvl": 1}},
|
||||
{"w:pPr": []},
|
||||
{"w:rPr": []},
|
||||
]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,66 +1,64 @@
|
||||
import { DocumentAttributes } from "../docx/document/document-attributes";
|
||||
import { XmlUnitComponent } from "../docx/xml-components";
|
||||
import { XmlComponent } from "../docx/xml-components";
|
||||
|
||||
export class Title extends XmlUnitComponent {
|
||||
export class Title extends XmlComponent {
|
||||
|
||||
constructor(value: string) {
|
||||
super("dc:title");
|
||||
this.root = value;
|
||||
this.root.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class Subject extends XmlUnitComponent {
|
||||
export class Subject extends XmlComponent {
|
||||
|
||||
constructor(value: string) {
|
||||
super("dc:subject");
|
||||
this.root = value;
|
||||
this.root.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class Creator extends XmlUnitComponent {
|
||||
export class Creator extends XmlComponent {
|
||||
|
||||
constructor(value: string) {
|
||||
super("dc:creator");
|
||||
this.root = value;
|
||||
this.root.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class Keywords extends XmlUnitComponent {
|
||||
export class Keywords extends XmlComponent {
|
||||
|
||||
constructor(value: string) {
|
||||
super("cp:keywords");
|
||||
this.root = value;
|
||||
this.root.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class Description extends XmlUnitComponent {
|
||||
export class Description extends XmlComponent {
|
||||
|
||||
constructor(value: string) {
|
||||
super("dc:description");
|
||||
this.root = value;
|
||||
this.root.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class LastModifiedBy extends XmlUnitComponent {
|
||||
export class LastModifiedBy extends XmlComponent {
|
||||
|
||||
constructor(value: string) {
|
||||
super("cp:lastModifiedBy");
|
||||
this.root = value;
|
||||
this.root.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
export class Revision extends XmlUnitComponent {
|
||||
export class Revision extends XmlComponent {
|
||||
|
||||
constructor(value: string) {
|
||||
super("cp:revision");
|
||||
const revision = value;
|
||||
this.root = value;
|
||||
this.root.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DateComponent extends XmlComponent {
|
||||
protected getCurrentDate(): any {
|
||||
export abstract class DateComponent extends XmlComponent {
|
||||
protected getCurrentDate(): string {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = ("0" + (date.getMonth() + 1)).slice(-2);
|
||||
|
@ -2,7 +2,7 @@ import { DocumentAttributes } from "../docx/document/document-attributes";
|
||||
import { XmlComponent } from "../docx/xml-components";
|
||||
import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components";
|
||||
|
||||
interface IPropertiesOptions {
|
||||
export interface IPropertiesOptions {
|
||||
title?: string;
|
||||
subject?: string;
|
||||
creator?: string;
|
||||
@ -23,13 +23,27 @@ export class Properties extends XmlComponent {
|
||||
dcmitype: "http://purl.org/dc/dcmitype/",
|
||||
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
||||
}));
|
||||
this.root.push(new Title(options.title));
|
||||
this.root.push(new Subject(options.subject));
|
||||
this.root.push(new Creator(options.creator));
|
||||
this.root.push(new Keywords(options.keywords));
|
||||
this.root.push(new Description(options.description));
|
||||
this.root.push(new LastModifiedBy(options.lastModifiedBy));
|
||||
this.root.push(new Revision(options.revision));
|
||||
if (options.title) {
|
||||
this.root.push(new Title(options.title));
|
||||
}
|
||||
if (options.subject) {
|
||||
this.root.push(new Subject(options.subject));
|
||||
}
|
||||
if (options.creator) {
|
||||
this.root.push(new Creator(options.creator));
|
||||
}
|
||||
if (options.keywords) {
|
||||
this.root.push(new Keywords(options.keywords));
|
||||
}
|
||||
if (options.description) {
|
||||
this.root.push(new Description(options.description));
|
||||
}
|
||||
if (options.lastModifiedBy) {
|
||||
this.root.push(new LastModifiedBy(options.lastModifiedBy));
|
||||
}
|
||||
if (options.revision) {
|
||||
this.root.push(new Revision(options.revision));
|
||||
}
|
||||
this.root.push(new Created());
|
||||
this.root.push(new Modified());
|
||||
}
|
||||
|
74
ts/properties/properties.spec.ts
Normal file
74
ts/properties/properties.spec.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "../export/formatter";
|
||||
import { Properties } from "./";
|
||||
|
||||
describe("Properties", () => {
|
||||
|
||||
describe("#constructor()", () => {
|
||||
it("sets the appropriate attributes on the top-level", () => {
|
||||
const properties = new Properties({});
|
||||
const tree = new Formatter().format(properties);
|
||||
expect(Object.keys(tree)).to.deep.equal(["cp:coreProperties"]);
|
||||
expect(tree["cp:coreProperties"]).to.be.an.instanceof(Array);
|
||||
expect(tree["cp:coreProperties"][0]).to.deep.equal({
|
||||
_attr: {
|
||||
"xmlns:cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
|
||||
"xmlns:dc": "http://purl.org/dc/elements/1.1/",
|
||||
"xmlns:dcmitype": "http://purl.org/dc/dcmitype/",
|
||||
"xmlns:dcterms": "http://purl.org/dc/terms/",
|
||||
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should create properties with a title", () => {
|
||||
const properties = new Properties({title: "test document"});
|
||||
const tree = new Formatter().format(properties);
|
||||
expect(Object.keys(tree)).to.deep.equal(["cp:coreProperties"]);
|
||||
expect(tree["cp:coreProperties"]).to.be.an.instanceof(Array);
|
||||
expect(Object.keys(tree["cp:coreProperties"][0])).to.deep.equal(["_attr"]);
|
||||
expect(tree["cp:coreProperties"][1]).to.deep.equal(
|
||||
{"dc:title": ["test document"]},
|
||||
);
|
||||
});
|
||||
|
||||
it("should create properties with all the attributes given", () => {
|
||||
const properties = new Properties({
|
||||
title: "test document",
|
||||
subject: "test subject",
|
||||
creator: "me",
|
||||
keywords: "test docx",
|
||||
description: "testing document",
|
||||
lastModifiedBy: "the author",
|
||||
revision: "123",
|
||||
});
|
||||
const tree = new Formatter().format(properties);
|
||||
expect(Object.keys(tree)).to.deep.equal(["cp:coreProperties"]);
|
||||
expect(tree["cp:coreProperties"]).to.be.an.instanceof(Array);
|
||||
const key = (obj) => Object.keys(obj)[0];
|
||||
const props = tree["cp:coreProperties"].map(key).sort();
|
||||
expect(props).to.deep.equal([
|
||||
"_attr",
|
||||
"cp:keywords",
|
||||
"cp:lastModifiedBy",
|
||||
"cp:revision",
|
||||
"dc:creator",
|
||||
"dc:description",
|
||||
"dc:subject",
|
||||
"dc:title",
|
||||
"dcterms:created",
|
||||
"dcterms:modified",
|
||||
]);
|
||||
expect(tree["cp:coreProperties"].slice(1, -2).sort((a, b) => key(a) < key(b) ? -1 : 1)).to.deep.equal([
|
||||
{"cp:keywords": ["test docx"]},
|
||||
{"cp:lastModifiedBy": ["the author"]},
|
||||
{"cp:revision": ["123"]},
|
||||
{"dc:creator": ["me"]},
|
||||
{"dc:description": ["testing document"]},
|
||||
{"dc:subject": ["test subject"]},
|
||||
{"dc:title": ["test document"]},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
11
ts/relationships/attributes.ts
Normal file
11
ts/relationships/attributes.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { XmlAttributeComponent } from "../docx/xml-components";
|
||||
|
||||
export interface IRelationshipsAttributesProperties {
|
||||
xmlns: string;
|
||||
}
|
||||
|
||||
export class RelationshipsAttributes extends XmlAttributeComponent<IRelationshipsAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
xmlns: "xmlns",
|
||||
};
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user