Compare commits

...

194 Commits

Author SHA1 Message Date
b8232f7a02 Version bump 2020-07-11 19:34:29 +01:00
49eadb0efc Merge pull request #559 from wangfengming/master
:fix: `rowSpan` can't work when column index out of range
2020-07-09 01:29:06 +01:00
40dc90e585 :fix: insert the continue cell properly 2020-07-08 12:32:01 +08:00
0de302d192 :typo: update comments 2020-07-08 11:26:24 +08:00
80bab95f6c :fix: rowSpan can't work when column index out of range 2020-07-08 10:55:15 +08:00
ba3d551c9f Version bump 2020-06-29 01:17:13 +01:00
d14fe31f97 Merge pull request #553 from wangfengming/master
Fix: `rowSpan` does not work correctly
2020-06-24 16:39:57 +01:00
057f41e355 :test: more test cases 2020-06-22 12:34:08 +08:00
8c9b61b37a :fix: handle rowSpan by convert between the virtual column index and the root index 2020-06-22 12:25:51 +08:00
11e54b3e2c :fix: handle cell that has both columnSpan and rowSpan 2020-06-20 21:36:35 +08:00
fa7cb0bef1 :test: add test case for columnSpan and rowSpan 2020-06-20 21:01:23 +08:00
3977c8ab3b :fix: rowSpan continue cell should has the same border to the first row cell 2020-06-20 20:20:22 +08:00
e8f92efe05 Merge pull request #2 from dolanmiu/master
merge from origin
2020-06-20 20:05:20 +08:00
994df8531b :fix: rowSpan does not work correctly 2020-06-20 19:47:46 +08:00
3cdf96ee0c Version bump 2020-06-17 14:34:27 +01:00
e2f55d52e9 Merge pull request #550 from wangfengming/feature/enhance-font
Feature/enhance font
2020-06-13 20:38:22 +01:00
d6fa33035a :doc: update demo 53-chinese.ts 2020-06-07 14:58:59 +08:00
f11bca728f :typo: update comment for the demo 2020-06-07 12:46:21 +08:00
596761d78d :doc: doc and demo for "Font for eastAsia" 2020-06-07 12:39:17 +08:00
8a3c8d4664 :test: Font for eastAsia 2020-06-07 12:38:31 +08:00
fdfce79e87 :feat: Font for eastAsia 2020-06-07 12:38:03 +08:00
88340aa336 Merge pull request #1 from dolanmiu/master
merge from origin
2020-06-03 19:04:55 +08:00
d0f53fdd4b Merge branch 'master' of github.com:dolanmiu/docx
# Conflicts:
#	package-lock.json
2020-05-28 18:40:44 +01:00
d657f61e11 Update node types 2020-05-28 18:38:55 +01:00
20e6b98770 Version bump 2020-05-26 21:07:30 +01:00
5ae824358c Revert "Update numbering.ts"
This reverts commit 0ebdcc30ed.
2020-05-26 21:06:43 +01:00
3ea106bd22 Merge pull request #546 from wangfengming/feature/emphasis-mark
Feature/emphasis mark
2020-05-22 13:29:25 +01:00
538264dec5 :demo: support emphasis mark 2020-05-22 12:56:02 +08:00
6bcd1c2c24 :doc: support emphasis mark 2020-05-22 12:32:40 +08:00
120c3a7bbe :feat: support emphasis mark 2020-05-22 12:22:45 +08:00
2654799822 Version bump 2020-05-19 19:24:29 +01:00
6245635b86 Merge pull request #540 from bschwarz/add-tablecell-textdirection
adds textDirection to table cells
2020-05-13 03:00:20 +01:00
b4f1c4dd6a Increase readability for enums 2020-05-13 02:51:47 +01:00
25a7ce3742 adds textDirection to table cells 2020-05-10 10:36:25 -07:00
08bc069cbf Merge pull request #539 from adrielstar/master
Update numbering.ts
2020-05-10 16:45:06 +01:00
0ebdcc30ed Update numbering.ts 2020-05-07 11:49:42 +02:00
3eca81d3f5 Merge branch 'master' of github.com:dolanmiu/docx 2020-05-06 02:15:32 +01:00
faefbae3a1 Add Japanese example 2020-05-06 02:13:25 +01:00
370fb098ac Merge pull request #480 from dolanmiu/dependabot/npm_and_yarn/handlebars-4.5.3
Bump handlebars from 4.1.2 to 4.5.3
2020-05-06 01:58:10 +01:00
c73019d84c Merge branch 'master' of github.com:dolanmiu/docx 2020-04-23 11:51:38 +01:00
250a1de71e Disable e2e temporarily 2020-04-23 11:50:56 +01:00
012963e90a Merge pull request #527 from boopathikumar018/master
Enabled dark and light mode with switch for docs using docsify-darklight-theme with redesigned search bar
2020-04-20 01:22:08 +01:00
632f3cd19b Enabled dark and light mode with switch for docs using docsify-darklight-theme 2020-04-18 01:48:00 +05:30
1c8cd325d7 Add character styles demo 2020-02-27 11:10:00 +00:00
7bcdaab2f2 Updated Hyperlink demo
To test image followed up by hyperlink bug
2020-02-27 10:44:07 +00:00
5b58e520f9 Update CodePen and JSFiddle examples 2020-01-19 02:55:31 +00:00
78f6ea6c44 Update JSFiddle Demo 2020-01-19 02:50:31 +00:00
b5172e73f9 Package lock bump 2020-01-17 21:26:09 +00:00
5cf534ad26 Version bump 2020-01-16 03:04:38 +00:00
d53cdb0558 Merge branch 'master' of github.com:dolanmiu/docx 2020-01-16 02:41:12 +00:00
212adbbffb Add workaround for loading 2020-01-16 02:23:17 +00:00
99ab2f0ef5 Version bump package lock 2020-01-15 22:46:42 +00:00
a8201b2658 Update README.md 2020-01-15 22:33:04 +00:00
f13e676c3b Version bump 2020-01-11 20:52:59 +00:00
61b01836bc Merge pull request #490 from dolanmiu/feat/fix-exports
Add type and node type definition exports
2020-01-11 19:25:41 +00:00
2ee918b845 Add type and node type definition exports 2020-01-11 19:16:36 +00:00
e9579d75c4 Merge pull request #483 from dolanmiu/feat/right-indent
Version 5.0.0
2019-12-31 15:54:07 +00:00
97824f1bb5 Version 5.0.0 2019-12-31 15:52:01 +00:00
884c134b25 Merge pull request #482 from dolanmiu/feat/right-indent
Add new image for README
2019-12-31 15:51:12 +00:00
4f3cb49076 Add new image for README 2019-12-31 15:49:34 +00:00
152285ed5a Merge pull request #481 from dolanmiu/feat/right-indent
Bump up coverage stats
2019-12-27 01:54:09 +00:00
49b7ac212d Bump handlebars from 4.1.2 to 4.5.3
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-27 01:44:24 +00:00
83450e6277 Bump up coverage stats 2019-12-27 01:44:20 +00:00
6d6155c742 Merge pull request #478 from dolanmiu/feat/right-indent
Declarative bookmarks
2019-12-27 01:43:48 +00:00
f8da2c311b Merge branch 'master' of github.com:dolanmiu/docx into feat/right-indent 2019-12-27 01:37:37 +00:00
f6bcaef5b5 Fix test 2019-12-27 01:29:15 +00:00
1a9e71bfa1 Add children and section tests for File 2019-12-27 01:21:19 +00:00
3591e11637 Add hyperlink cache test 2019-12-24 16:31:35 +00:00
47533cf4e2 Bookmark tests 2019-12-24 03:39:55 +00:00
de03f19b46 Remove unused code and improve coverage 2019-12-24 00:44:15 +00:00
d1472368f6 Merge pull request #476 from mustapelto/empty-first-paragraph-fix
Fix for empty first paragraph
2019-12-21 04:21:50 +00:00
b83d2c388f Make Bookmark declarative 2019-12-21 03:59:40 +00:00
ee5425bef7 Add shortId type 2019-12-21 03:58:58 +00:00
3fdbca939e Make internal hyperlink declarative 2019-12-21 03:31:09 +00:00
c68dc8c52a Make hyperlinks declarative 2019-12-18 21:11:15 +00:00
96471ecb45 Fix for empty first paragraph
If there is only one section, remove the extraneous empty paragraph during XML creation.
2019-12-15 22:56:24 +02:00
a13f898ad3 Merge pull request #474 from kalda341/allow-contextual-spacing-paragraph-style
Allow contextual spacing to be specified as an argument to paragraph style
2019-12-13 23:22:39 +00:00
b4cce534a5 Test contextual spacing 2019-12-12 11:44:00 +13:00
e0698554ad Fix style linting error 2019-12-11 15:21:50 +13:00
1649d2a0fa Allow contextual spacing to be specified as an argument to paragraph style 2019-12-11 15:10:46 +13:00
9683e89159 Merge pull request #466 from dolanmiu/feat/right-indent
Make create footnote declarative
2019-12-04 01:41:01 +00:00
2bece0bb61 Make create footnote declarative 2019-12-03 23:04:48 +00:00
deb6c42d86 Merge pull request #465 from dolanmiu/feat/right-indent
Add right indent
2019-12-02 23:57:54 +00:00
2b0953bb19 Add right indent 2019-12-02 23:13:26 +00:00
d25d22508c Merge pull request #460 from dolanmiu/feat/table-alignment
Overlap option for tables
2019-11-24 03:35:26 +00:00
6db37eb4fb Overlap tables 2019-11-24 03:22:50 +00:00
af461f8418 Merge pull request #459 from dolanmiu/feat/table-alignment
Alignment of tables
2019-11-24 02:47:02 +00:00
8bdbd1de39 Alignment of tables 2019-11-24 01:22:17 +00:00
1bdc93ef59 Update numbering demo to add more numbering levels 2019-11-23 23:22:02 +00:00
c12101fbc6 Add decimal to numbering demo 2019-11-23 21:06:15 +00:00
d5ba6578b3 Merge pull request #458 from dolanmiu/feat/remove-tab-method
Make tab and page numbers declarative
2019-11-23 19:22:43 +00:00
b37aefdc8d Correctly exporting methods 2019-11-23 02:26:59 +00:00
0be7c26798 Add companies 2019-11-23 01:21:25 +00:00
bf1d10e893 Fix demos 2019-11-21 01:12:32 +00:00
7dfb016faa Make tab and page numbers declarative 2019-11-21 01:02:46 +00:00
b2aeb2d83c Merge pull request #454 from dolanmiu/feat/table-borders
Add table borders
2019-11-19 22:46:46 +00:00
8f4c78e2a8 Update package lock 2019-11-19 22:08:01 +00:00
bd1f5898a8 Add tests 2019-11-19 22:06:10 +00:00
05a4ef1702 Update package.json 2019-11-18 11:29:32 +00:00
da9e6d6664 Add table borders 2019-11-18 01:04:31 +00:00
e9d3853d93 Merge pull request #448 from Sraleik/master
fix hyperlink id
2019-11-12 00:48:21 +00:00
5a9d6120a5 Merge pull request #447 from dolanmiu/feat/declaritive-numbering
Declarative numbering
2019-11-08 10:53:25 +00:00
e8410ff692 fix lint 2019-11-08 11:22:27 +01:00
3427d220c7 fix hyperlink id 2019-11-08 11:01:26 +01:00
643e3c2f84 Finish making numbering declarative 2019-11-08 03:11:19 +00:00
9b40b5e55e Add work for custom level id 2019-11-06 20:54:39 +00:00
a622c210ef Progress on declaritive numbering 2019-11-04 20:19:09 +00:00
9495f30e2d Merge pull request #446 from dolanmiu/dependabot/npm_and_yarn/lodash-4.17.15
Bump lodash from 4.17.11 to 4.17.15
2019-11-04 01:48:59 +00:00
617af87065 Bump lodash from 4.17.11 to 4.17.15
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.15.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.15)

Signed-off-by: dependabot[bot] <support@github.com>
2019-11-02 15:07:36 +00:00
c1dd421b27 Merge pull request #443 from alex-bogomolov/master
Add ability to vertically align content of section
2019-11-01 02:43:12 +00:00
8eff6bd0cf Declarative numbering work 2019-11-01 01:57:01 +00:00
8566c64eab improve coverage 2019-10-31 17:01:17 +02:00
afd468277e refactor code 2019-10-31 13:54:53 +02:00
ca9c992237 Merge pull request #444 from mloar/master
Allow image reuse
2019-10-31 02:10:55 +00:00
c93b74661b Fix linting error 2019-10-30 08:59:29 -05:00
08ff092638 Allow image reuse 2019-10-29 21:44:04 -05:00
2276572902 add ability to vertically align content of section 2019-10-29 12:55:15 +02:00
43ddeec6e1 Create FUNDING.yml 2019-10-29 01:04:43 +00:00
9eaf04f4c7 Merge pull request #436 from zaunermax/master
Support footnotes on TextRun elements
2019-10-19 14:46:23 +01:00
c3c29bb119 fix(test): added unit test for TextRun 2019-10-18 14:09:33 +02:00
ddb34e6a46 feat(textRun): implemented footnote references on the text run element 2019-10-18 13:33:47 +02:00
ef3055430b Merge pull request #428 from dolanmiu/feat/webpack-export
Put docx in a seperate namespace in the browser version
2019-10-12 22:37:02 +01:00
75cdae1473 Put docx in a seperate namespace in the browser version rather than the global namespace 2019-10-12 22:16:36 +01:00
860afe8f28 Merge pull request #427 from dolanmiu/feat/package-lock
Add lock file
2019-10-12 21:53:07 +01:00
f685dbe0d1 Add lock file 2019-10-12 21:46:18 +01:00
50911fff57 Add color to example 2019-10-12 21:42:10 +01:00
a7064f4728 Merge pull request #424 from seiya-git/master
fix set table cell width
2019-10-12 20:11:30 +01:00
564e9600ea Merge pull request #426 from jamesmontalvo3/nyc14
Bump nyc from 13.1.0 to 14.1.1
2019-10-12 18:50:01 +01:00
fcc4202598 Bump nyc from 13.1.0 to 14.1.1 2019-10-12 00:42:54 -05:00
bfbe59cb84 table cell width test 2019-10-12 03:14:25 +03:00
06abde2425 fix set table cell width 2019-10-12 02:52:30 +03:00
75c3c2f985 Version bump 2019-10-10 21:27:27 +01:00
fefefdd473 Merge pull request #421 from dolanmiu/feat/declaritive-styles-and-tab-stop
Declaritive styles and  multiple tab stops
2019-10-10 21:26:30 +01:00
db59474f1e Remove unused method 2019-10-10 21:10:03 +01:00
1d5e806ff4 Add character style tests 2019-10-10 21:03:38 +01:00
bf4885c7cf Merge branch 'master' of github.com:dolanmiu/docx into feat/declaritive-styles-and-tab-stop 2019-10-10 01:58:13 +01:00
3b289be5ce Fix demo 2019-10-10 01:25:37 +01:00
2bb7e08ade Remove unnecessary method 2019-10-10 01:19:55 +01:00
b571a7550f Fix demo 2019-10-10 01:18:20 +01:00
721de30587 Fix demos 2019-10-10 01:08:01 +01:00
f16126e948 Fix tests 2019-10-09 21:19:41 +01:00
40d1a3a7c2 Multiple tab stops 2019-10-09 20:56:31 +01:00
0d4c7a5fc0 Update documentation 2019-10-09 02:03:39 +01:00
5d401dfb27 Merge pull request #419 from jamesmontalvo3/section-fix
Revert "fix: try to remove unnecessary paragraph", which caused #418
2019-10-05 14:16:06 +01:00
a37c9d8f2f Revert "fix: try to remove unnecessary paragraph", which caused #418
This reverts commit cb74868247.
2019-10-03 22:12:23 -05:00
10ab3c70bf Add back default styles 2019-10-04 02:37:22 +01:00
591b2f4e04 Declarative styles 2019-10-04 01:20:41 +01:00
2536fbe752 Merge branch 'master' of github.com:dolanmiu/docx into feat/declaritive-styles-and-tab-stop
# Conflicts:
#	src/file/paragraph/paragraph.ts
2019-10-03 20:48:34 +01:00
33549d5ec3 Merge pull request #413 from jamesmontalvo3/master
Add SymbolRun to allow adding symbols inline
2019-10-02 21:41:24 +01:00
720c6172e3 Merge pull request #415 from jamesmontalvo3/export-run-fonts
Export RunFonts to be able to pass to Level.addRunProperty()
2019-10-02 21:40:40 +01:00
bbad2f5cde Merge pull request #416 from kalda341/fix-toc-t-flag
Fix table of contents \t flag
2019-10-02 21:27:21 +01:00
d2a0baa221 Fix table of contents \t flag 2019-10-02 14:49:55 +13:00
ad62f5459b Export RunFonts to be able to pass to Level.addRunProperty() 2019-10-01 15:23:01 -05:00
dfb910defb Add SymbolRun to allow adding symbols inline 2019-10-01 12:29:07 -05:00
04b6d8e54a Declarative hyperlinks, bookmarks, tab stops and page breaks 2019-09-30 22:56:21 +01:00
d2dded860d Fix example links 2019-09-30 20:58:18 +01:00
38f2638ea0 Version bump 2019-09-29 04:45:30 +01:00
2b874e3f69 Merge pull request #407 from dolanmiu/feat/declaritive-tables
Feat/declarative tables
2019-09-29 04:43:37 +01:00
b43ed12c84 Fix tests 2019-09-29 04:38:07 +01:00
59be381213 Update travis config 2019-09-29 04:25:40 +01:00
172c333357 Add tests and clean up code 2019-09-29 04:17:21 +01:00
c5eb3d5670 Add addMargin test 2019-09-26 02:24:43 +01:00
44b95f2f15 Add shading test 2019-09-26 02:14:52 +01:00
bd888219fc Amend table documentation 2019-09-26 02:03:17 +01:00
2842619196 Update table documentation 2019-09-25 01:59:30 +01:00
b2de74a0e6 Fix tests 2019-09-25 01:09:53 +01:00
7aa4134e2b Refactor row merging to table level 2019-09-25 00:57:24 +01:00
cc36ea7542 Optimise formatting to not over-format the same files 2019-09-22 20:45:24 +01:00
c11af71ed7 Add test such that it only should call prep once 2019-09-22 19:09:34 +01:00
a9d4ebc898 Add declarative column merge 2019-09-22 02:39:38 +01:00
d2f82052b4 Improve documentation 2019-09-19 22:49:09 +01:00
f88a309d55 Merge branch 'master' of github.com:dolanmiu/docx into feat/declaritive-tables 2019-09-13 00:52:26 +01:00
418adca9f3 Declarative tables 2019-09-13 00:51:20 +01:00
993f8f81f7 Merge pull request #392 from wainstead/parameter-fix
Fix: probable copy/paste error
2019-09-01 16:19:03 +01:00
99718784f1 Fix: probable copy/paste error
Fix the argument to "left:" which should likely be 2160
2019-08-30 09:29:45 -04:00
59fc1ed632 Merge pull request #391 from bokuweb/remove-extra-line
fix: try to remove unnecessary paragraph
2019-08-29 20:34:32 +01:00
cb74868247 fix: try to remove unnecessary paragraph 2019-08-29 20:52:05 +09:00
bd6ae2c0dc Remove unnecessary documentation 2019-08-22 22:49:57 +01:00
60a599a550 Improve bullet points documentation 2019-08-22 00:28:42 +01:00
4ac55a787e Fix includes 2019-08-21 00:53:49 +01:00
cb52a1ef42 Formatting 2019-08-21 00:51:26 +01:00
7827d158d7 Updated documentation 2019-08-21 00:05:46 +01:00
535f2d75b0 Fix embed links in documentation 2019-08-20 22:23:14 +01:00
5ecdb48d43 Stop unnecessary casting 2019-08-15 00:48:36 +01:00
2502fe7f39 Increase thresholds 2019-08-14 15:50:12 +01:00
02487fbb22 Merge pull request #376 from mforman1/Numbering
Add total number of pages in a section
2019-08-14 13:29:35 +01:00
2abff6991f Rename demo 2019-08-14 14:13:28 +03:00
49e85275c3 Fix tests 2019-08-13 10:53:29 +03:00
96efdf8b1a Merge branch 'master' of github.com:dolanmiu/docx into Numbering
# Conflicts:
#	src/file/paragraph/run/run.ts
2019-08-13 10:40:49 +03:00
b52a4adaff Merge pull request #375 from fpirsch/fpirsch-compression
Enable zip compression
2019-08-09 11:17:22 +01:00
a9fc40dad4 Add total number of pages in a section 2019-08-09 11:56:22 +03:00
b609a17362 Update packer.ts 2019-08-09 09:41:58 +02:00
4b49fdbf8e Update packer.ts 2019-08-09 00:19:44 +01:00
fd52c8cf47 Enable zip compression 2019-08-08 16:40:16 +02:00
183 changed files with 17213 additions and 3012 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: dolanmiu
patreon: dolanmiu
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

1
.gitignore vendored
View File

@ -52,7 +52,6 @@ docs/.nojekyll
.idea .idea
# Lock files # Lock files
package-lock.json
yarn.lock yarn.lock
# Documents # Documents

8
.nycrc
View File

@ -1,9 +1,9 @@
{ {
"check-coverage": true, "check-coverage": true,
"lines": 87.54, "lines": 93.53,
"functions": 83.61, "functions": 89.63,
"branches": 72.57, "branches": 88.57,
"statements": 87.32, "statements": 93.34,
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts"
], ],

View File

@ -10,7 +10,7 @@ script:
- npm run style - npm run style
- npm run build - npm run build
- npm run ts-node -- ./demo/1-basic.ts - npm run ts-node -- ./demo/1-basic.ts
- npm run e2e "My Document.docx" # - npm run e2e "My Document.docx"
- npm run ts-node -- ./demo/2-declaritive-styles.ts - npm run ts-node -- ./demo/2-declaritive-styles.ts
- npm run ts-node -- ./demo/3-numbering-and-bullet-points.ts - npm run ts-node -- ./demo/3-numbering-and-bullet-points.ts
- npm run ts-node -- ./demo/4-basic-table.ts - npm run ts-node -- ./demo/4-basic-table.ts
@ -20,7 +20,7 @@ script:
- npm run ts-node -- ./demo/8-header-footer.ts - npm run ts-node -- ./demo/8-header-footer.ts
- npm run ts-node -- ./demo/9-images-in-header-and-footer.ts - npm run ts-node -- ./demo/9-images-in-header-and-footer.ts
- npm run ts-node -- ./demo/10-my-cv.ts - npm run ts-node -- ./demo/10-my-cv.ts
- npm run e2e "My Document.docx" # - npm run e2e "My Document.docx"
- npm run ts-node -- ./demo/11-declaritive-styles-2.ts - npm run ts-node -- ./demo/11-declaritive-styles-2.ts
- npm run ts-node -- ./demo/12-scaling-images.ts - npm run ts-node -- ./demo/12-scaling-images.ts
- npm run ts-node -- ./demo/13-xml-styles.ts - npm run ts-node -- ./demo/13-xml-styles.ts
@ -42,7 +42,7 @@ script:
- npm run ts-node -- ./demo/29-numbered-lists.ts - npm run ts-node -- ./demo/29-numbered-lists.ts
- npm run ts-node -- ./demo/30-template-document.ts - npm run ts-node -- ./demo/30-template-document.ts
- npm run ts-node -- ./demo/31-tables.ts - npm run ts-node -- ./demo/31-tables.ts
- npm run ts-node -- ./demo/32-merge-table-cells.ts - npm run ts-node -- ./demo/32-merge-and-shade-table-cells.ts
# - npm run e2e "My Document.docx" // Need to fix # - npm run e2e "My Document.docx" // Need to fix
- npm run ts-node -- ./demo/33-sequential-captions.ts - npm run ts-node -- ./demo/33-sequential-captions.ts
- npm run ts-node -- ./demo/34-floating-tables.ts - npm run ts-node -- ./demo/34-floating-tables.ts

View File

@ -18,7 +18,7 @@
[![codecov][codecov-image]][codecov-url] [![codecov][codecov-image]][codecov-url]
<p align="center"> <p align="center">
<img src="https://i.imgur.com/H5FA1Qy.gif" alt="drawing" width="800"/> <img src="https://i.imgur.com/TCH0YzD.png" alt="drawing" width="800"/>
</p> </p>
# Demo # Demo
@ -27,8 +27,8 @@
Here are examples of `docx` being used with basic `HTML/JS` in a browser environment: Here are examples of `docx` being used with basic `HTML/JS` in a browser environment:
* https://codepen.io/anon/pen/dqoVgQ * https://codepen.io/dolanmiu/pen/RwNeObg
* https://jsfiddle.net/3xhezb5w/2 * https://jsfiddle.net/dolanmiu/kqxrj35u/1/
Here is an example of `docx` working in `Angular`: Here is an example of `docx` working in `Angular`:
@ -73,6 +73,10 @@ Read the contribution guidelines [here](https://docx.js.org/#/contribution-guide
[<img src="https://i.imgur.com/suiH2zc.png" alt="drawing" height="50"/>](https://www.dabblewriter.com/) [<img src="https://i.imgur.com/suiH2zc.png" alt="drawing" height="50"/>](https://www.dabblewriter.com/)
[<img src="https://i.imgur.com/1LjuK2M.png" alt="drawing" height="50"/>](https://turbopatent.com/) [<img src="https://i.imgur.com/1LjuK2M.png" alt="drawing" height="50"/>](https://turbopatent.com/)
[<img src="https://i.imgur.com/dHMg0wF.gif" alt="drawing" height="50"/>](http://www.madisoncres.com/) [<img src="https://i.imgur.com/dHMg0wF.gif" alt="drawing" height="50"/>](http://www.madisoncres.com/)
[<img src="https://i.imgur.com/QEZXU5b.png" alt="drawing" height="50"/>](https://www.beekast.com/)
[<img src="https://imgur.com/XVU6aoi.png" alt="drawing" height="50"/>](https://herraizsoto.com/)
[<img src="https://i.imgur.com/fn1xccG.png" alt="drawing" height="50"/>](http://www.ativer.com.br/)
...and many more! ...and many more!

View File

@ -16,9 +16,9 @@ doc.addSection({
bold: true, bold: true,
}), }),
new TextRun({ new TextRun({
text: "Github is the best", text: "\tGithub is the best",
bold: true, bold: true,
}).tab(), }),
], ],
}), }),
], ],

View File

@ -1,7 +1,7 @@
// Generate a CV // Generate a CV
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun } from "../build"; import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TabStopPosition, TabStopType, TextRun } from "../build";
// tslint:disable:no-shadowed-variable // tslint:disable:no-shadowed-variable
@ -226,18 +226,21 @@ class DocumentCreator {
public createInstitutionHeader(institutionName: string, dateText: string): Paragraph { public createInstitutionHeader(institutionName: string, dateText: string): Paragraph {
return new Paragraph({ return new Paragraph({
tabStop: { tabStops: [
maxRight: {}, {
}, type: TabStopType.RIGHT,
position: TabStopPosition.MAX,
},
],
children: [ children: [
new TextRun({ new TextRun({
text: institutionName, text: institutionName,
bold: true, bold: true,
}), }),
new TextRun({ new TextRun({
text: dateText, text: `\t${dateText}`,
bold: true, bold: true,
}).tab(), }),
], ],
}); });
} }

View File

@ -1,93 +1,188 @@
// Setting styles with JavaScript configuration // Setting styles with JavaScript configuration
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { AlignmentType, Document, Footer, HeadingLevel, Media, Packer, Paragraph, Table } from "../build"; import {
AlignmentType,
Document,
Footer,
HeadingLevel,
Media,
Packer,
Paragraph,
Table,
TableCell,
TableRow,
TabStopPosition,
UnderlineType,
} from "../build";
const doc = new Document(); const doc = new Document({
styles: {
doc.Styles.createParagraphStyle("Heading1", "Heading 1") paragraphStyles: [
.basedOn("Normal") {
.next("Normal") id: "Heading1",
.quickFormat() name: "Heading 1",
.font("Calibri") basedOn: "Normal",
.size(52) next: "Normal",
.center() quickFormat: true,
.bold() run: {
.color("000000") font: "Calibri",
.spacing({ line: 340 }) size: 52,
.underline("single", "000000"); bold: true,
color: "000000",
doc.Styles.createParagraphStyle("Heading2", "Heading 2") underline: {
.basedOn("Normal") type: UnderlineType.SINGLE,
.next("Normal") color: "000000",
.font("Calibri") },
.quickFormat() },
.size(26) paragraph: {
.bold() alignment: AlignmentType.CENTER,
.spacing({ line: 340 }); spacing: { line: 340 },
},
doc.Styles.createParagraphStyle("Heading3", "Heading 3") },
.basedOn("Normal") {
.next("Normal") id: "Heading2",
.font("Calibri") name: "Heading 2",
.quickFormat() basedOn: "Normal",
.size(26) next: "Normal",
.bold() quickFormat: true,
.spacing({ line: 276 }); run: {
font: "Calibri",
doc.Styles.createParagraphStyle("Heading4", "Heading 4") size: 26,
.basedOn("Normal") bold: true,
.next("Normal") },
.justified() paragraph: {
.font("Calibri") spacing: { line: 340 },
.size(26) },
.bold(); },
{
doc.Styles.createParagraphStyle("normalPara", "Normal Para") id: "Heading3",
.basedOn("Normal") name: "Heading 3",
.next("Normal") basedOn: "Normal",
.font("Calibri") next: "Normal",
.quickFormat() quickFormat: true,
.leftTabStop(453.543307087) run: {
.maxRightTabStop() font: "Calibri",
.size(26) size: 26,
.spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }); bold: true,
},
doc.Styles.createParagraphStyle("normalPara2", "Normal Para2") paragraph: {
.basedOn("Normal") spacing: { line: 276 },
.next("Normal") },
.quickFormat() },
.font("Calibri") {
.size(26) id: "Heading4",
.justified() name: "Heading 4",
.spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }); basedOn: "Normal",
next: "Normal",
doc.Styles.createParagraphStyle("aside", "Aside") quickFormat: true,
.basedOn("Normal") run: {
.next("Normal") font: "Calibri",
.color("999999") size: 26,
.italics() bold: true,
.indent({ left: 720 }) },
.spacing({ line: 276 }); paragraph: {
alignment: AlignmentType.JUSTIFIED,
doc.Styles.createParagraphStyle("wellSpaced", "Well Spaced") },
.basedOn("Normal") },
.spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }); {
id: "normalPara",
doc.Styles.createParagraphStyle("ListParagraph", "List Paragraph") name: "Normal Para",
.quickFormat() basedOn: "Normal",
.basedOn("Normal"); next: "Normal",
quickFormat: true,
run: {
font: "Calibri",
size: 26,
bold: true,
},
paragraph: {
spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 },
rightTabStop: TabStopPosition.MAX,
leftTabStop: 453.543307087,
},
},
{
id: "normalPara2",
name: "Normal Para2",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
run: {
font: "Calibri",
size: 26,
},
paragraph: {
alignment: AlignmentType.JUSTIFIED,
spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 },
},
},
{
id: "aside",
name: "Aside",
basedOn: "Normal",
next: "Normal",
run: {
color: "999999",
italics: true,
},
paragraph: {
spacing: { line: 276 },
indent: { left: 720 },
},
},
{
id: "wellSpaced",
name: "Well Spaced",
basedOn: "Normal",
paragraph: {
spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 },
},
},
{
id: "ListParagraph",
name: "List Paragraph",
basedOn: "Normal",
quickFormat: true,
},
],
},
});
const image = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); const image = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, new TableRow({
children: [
new TableCell({
children: [new Paragraph("Test cell 1.")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Test cell 2.")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Test cell 3.")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Test cell 4.")],
}),
],
}),
],
}); });
table
.getRow(0)
.getCell(0)
.add(new Paragraph("Pole No."));
const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));

View File

@ -1,7 +1,7 @@
// Page numbers // Page numbers
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { AlignmentType, Document, Header, Packer, Paragraph, TextRun } from "../build"; import { AlignmentType, Document, Header, Packer, PageBreak, PageNumber, Paragraph, TextRun } from "../build";
const doc = new Document(); const doc = new Document();
@ -11,7 +11,12 @@ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
alignment: AlignmentType.RIGHT, alignment: AlignmentType.RIGHT,
children: [new TextRun("My Title "), new TextRun("Page ").pageNumber()], children: [
new TextRun("My Title "),
new TextRun({
children: ["Page ", PageNumber.CURRENT],
}),
],
}), }),
], ],
}), }),
@ -19,15 +24,20 @@ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
alignment: AlignmentType.RIGHT, alignment: AlignmentType.RIGHT,
children: [new TextRun("First Page Header "), new TextRun("Page ").pageNumber()], children: [
new TextRun("First Page Header "),
new TextRun({
children: ["Page ", PageNumber.CURRENT],
}),
],
}), }),
], ],
}), }),
}, },
children: [ children: [
new Paragraph({ new Paragraph({
text: "First Page", children: [new TextRun("First Page"), new PageBreak()],
}).pageBreak(), }),
new Paragraph("Second Page"), new Paragraph("Second Page"),
], ],
}); });

View File

@ -1,12 +1,12 @@
// Multiple sections and headers // Multiple sections and headers
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Footer, Header, Packer, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build"; import { Document, Footer, Header, Packer, PageNumber, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build";
const doc = new Document(); const doc = new Document();
doc.addSection({ doc.addSection({
children: [new Paragraph("Hello World").pageBreak()], children: [new Paragraph("Hello World")],
}); });
doc.addSection({ doc.addSection({
@ -53,7 +53,11 @@ doc.addSection({
default: new Header({ default: new Header({
children: [ children: [
new Paragraph({ new Paragraph({
children: [new TextRun("Page number: ").pageNumber()], children: [
new TextRun({
children: ["Page number: ", PageNumber.CURRENT],
}),
],
}), }),
], ],
}), }),
@ -69,7 +73,11 @@ doc.addSection({
default: new Header({ default: new Header({
children: [ children: [
new Paragraph({ new Paragraph({
children: [new TextRun("Page number: ").pageNumber()], children: [
new TextRun({
children: ["Page number: ", PageNumber.CURRENT],
}),
],
}), }),
], ],
}), }),
@ -90,7 +98,11 @@ doc.addSection({
default: new Header({ default: new Header({
children: [ children: [
new Paragraph({ new Paragraph({
children: [new TextRun("Page number: ").pageNumber()], children: [
new TextRun({
children: ["Page number: ", PageNumber.CURRENT],
}),
],
}), }),
], ],
}), }),

View File

@ -1,16 +1,54 @@
// Footnotes // Footnotes
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph } from "../build"; import { Document, FootnoteReferenceRun, Packer, Paragraph, TextRun } from "../build";
const doc = new Document(); const doc = new Document({
footnotes: [
doc.addSection({ new Paragraph("Foo"),
children: [new Paragraph("Hello World").referenceFootnote(1), new Paragraph("Hello World").referenceFootnote(2)], new Paragraph("Test"),
new Paragraph("My amazing reference"),
new Paragraph("Foo1"),
new Paragraph("Test1"),
new Paragraph("My amazing reference1"),
],
}); });
doc.createFootnote(new Paragraph("Test")); doc.addSection({
doc.createFootnote(new Paragraph("My amazing reference")); children: [
new Paragraph({
children: [
new TextRun({
children: ["Hello", new FootnoteReferenceRun(1)],
}),
new TextRun({
children: [" World!", new FootnoteReferenceRun(2)],
}),
],
}),
new Paragraph({
children: [new TextRun("Hello World"), new FootnoteReferenceRun(3)],
}),
],
});
doc.addSection({
children: [
new Paragraph({
children: [
new TextRun({
children: ["Hello", new FootnoteReferenceRun(4)],
}),
new TextRun({
children: [" World!", new FootnoteReferenceRun(5)],
}),
],
}),
new Paragraph({
children: [new TextRun("Hello World"), new FootnoteReferenceRun(6)],
}),
],
});
Packer.toBuffer(doc).then((buffer) => { Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer); fs.writeFileSync("My Document.docx", buffer);

View File

@ -15,9 +15,9 @@ doc.addSection({
bold: true, bold: true,
}), }),
new TextRun({ new TextRun({
text: "Bar", text: "\tBar",
bold: true, bold: true,
}).tab(), }),
], ],
}), }),
], ],

View File

@ -1,55 +1,105 @@
// Example on how to customise the look at feel using Styles // Example on how to customise the look at feel using Styles
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph, TextRun } from "../build"; import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build";
const doc = new Document({ const doc = new Document({
creator: "Clippy", creator: "Clippy",
title: "Sample Document", title: "Sample Document",
description: "A brief example of using docx", description: "A brief example of using docx",
styles: {
paragraphStyles: [
{
id: "Heading1",
name: "Heading 1",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
run: {
size: 28,
bold: true,
italics: true,
color: "red",
},
paragraph: {
spacing: {
after: 120,
},
},
},
{
id: "Heading2",
name: "Heading 2",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
run: {
size: 26,
bold: true,
underline: {
type: UnderlineType.DOUBLE,
color: "FF0000",
},
},
paragraph: {
spacing: {
before: 240,
after: 120,
},
},
},
{
id: "aside",
name: "Aside",
basedOn: "Normal",
next: "Normal",
run: {
color: "999999",
italics: true,
},
paragraph: {
indent: {
left: 720,
},
spacing: {
line: 276,
},
},
},
{
id: "wellSpaced",
name: "Well Spaced",
basedOn: "Normal",
quickFormat: true,
paragraph: {
spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 },
},
},
{
id: "ListParagraph",
name: "List Paragraph",
basedOn: "Normal",
quickFormat: true,
},
],
},
numbering: {
config: [
{
reference: "my-crazy-numbering",
levels: [
{
level: 0,
format: "lowerLetter",
text: "%1)",
alignment: AlignmentType.LEFT,
},
],
},
],
},
}); });
doc.Styles.createParagraphStyle("Heading1", "Heading 1")
.basedOn("Normal")
.next("Normal")
.quickFormat()
.size(28)
.bold()
.italics()
.spacing({ after: 120 });
doc.Styles.createParagraphStyle("Heading2", "Heading 2")
.basedOn("Normal")
.next("Normal")
.quickFormat()
.size(26)
.bold()
.underline("double", "FF0000")
.spacing({ before: 240, after: 120 });
doc.Styles.createParagraphStyle("aside", "Aside")
.basedOn("Normal")
.next("Normal")
.color("999999")
.italics()
.indent({ left: 720 })
.spacing({ line: 276 });
doc.Styles.createParagraphStyle("wellSpaced", "Well Spaced")
.basedOn("Normal")
.spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 });
doc.Styles.createParagraphStyle("ListParagraph", "List Paragraph")
.quickFormat()
.basedOn("Normal");
const numberedAbstract = doc.Numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left");
const letterNumbering = doc.Numbering.createConcreteNumbering(numberedAbstract);
const letterNumbering5 = doc.Numbering.createConcreteNumbering(numberedAbstract);
letterNumbering5.overrideLevel(0, 5);
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
@ -64,32 +114,34 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "Option1", text: "Option1",
numbering: { numbering: {
num: letterNumbering, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Option5 -- override 2 to 5", text: "Option5 -- override 2 to 5",
numbering: { numbering: {
num: letterNumbering, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Option3", text: "Option3",
numbering: { numbering: {
num: letterNumbering, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),
new Paragraph({}).addRun( new Paragraph({
new TextRun({ children: [
text: "Some monospaced content", new TextRun({
font: { text: "Some monospaced content",
name: "Monospace", font: {
}, name: "Monospace",
}), },
), }),
],
}),
new Paragraph({ new Paragraph({
text: "An aside, in light gray italics and indented", text: "An aside, in light gray italics and indented",
style: "aside", style: "aside",
@ -109,6 +161,10 @@ doc.addSection({
text: "and then underlined ", text: "and then underlined ",
underline: {}, underline: {},
}), }),
new TextRun({
text: "and then emphasis-mark ",
emphasisMark: {},
}),
new TextRun({ new TextRun({
text: "and back to normal.", text: "and back to normal.",
}), }),

View File

@ -1,23 +1,102 @@
// Add custom borders to table cell // Add custom borders to table cell
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { BorderStyle, Document, Packer, Paragraph, Table } from "../build"; import { BorderStyle, Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
const doc = new Document(); const doc = new Document();
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("Hello")],
borders: {
top: {
style: BorderStyle.DASH_DOT_STROKED,
size: 3,
color: "red",
},
bottom: {
style: BorderStyle.DOUBLE,
size: 3,
color: "blue",
},
left: {
style: BorderStyle.DASH_DOT_STROKED,
size: 3,
color: "green",
},
right: {
style: BorderStyle.DASH_DOT_STROKED,
size: 3,
color: "#ff8000",
},
},
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
}); });
doc.addSection({ children: [table] }); doc.addSection({ children: [table] });
table
.getCell(2, 2)
.add(new Paragraph("Hello"))
.Borders.addTopBorder(BorderStyle.DASH_DOT_STROKED, 3, "red")
.addBottomBorder(BorderStyle.DOUBLE, 3, "blue")
.addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green")
.addEndBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000");
Packer.toBuffer(doc).then((buffer) => { Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer); fs.writeFileSync("My Document.docx", buffer);

View File

@ -1,7 +1,7 @@
// This demo shows how to create bookmarks then link to them with internal hyperlinks // This demo shows how to create bookmarks then link to them with internal hyperlinks
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph } from "../build"; import { Bookmark, Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph } from "../build";
const LOREM_IPSUM = const LOREM_IPSUM =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante."; "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante.";
@ -10,23 +10,28 @@ const doc = new Document({
creator: "Clippy", creator: "Clippy",
title: "Sample Document", title: "Sample Document",
description: "A brief example of using docx with bookmarks and internal hyperlinks", description: "A brief example of using docx with bookmarks and internal hyperlinks",
hyperlinks: {
myAnchorId: {
text: "Hyperlink",
type: HyperlinkType.INTERNAL,
},
},
}); });
const anchorId = "anchorID";
// First create the bookmark
const bookmark = doc.createBookmark(anchorId, "Lorem Ipsum");
const hyperlink = doc.createInternalHyperLink(anchorId, `Click me!`);
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
heading: HeadingLevel.HEADING_1, heading: HeadingLevel.HEADING_1,
}).addBookmark(bookmark), children: [new Bookmark("myAnchorId", "Lorem Ipsum")],
}),
new Paragraph("\n"), new Paragraph("\n"),
new Paragraph(LOREM_IPSUM), new Paragraph(LOREM_IPSUM),
new Paragraph({}).pageBreak(), new Paragraph({
new Paragraph({}).addHyperLink(hyperlink), children: [new PageBreak()],
}),
new Paragraph({
children: [new HyperlinkRef("myAnchorId")],
}),
], ],
}); });

View File

@ -1,24 +1,85 @@
// Add image to table cell // Add image to table cell
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Media, Packer, Paragraph, Table } from "../build"; import { Document, Media, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
const doc = new Document(); const doc = new Document();
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph(image)],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("Hello")],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
}); });
doc.addSection({ doc.addSection({
children: [table], children: [table],
}); });
table.getCell(2, 2).add(new Paragraph("Hello"));
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
table.getCell(1, 1).add(new Paragraph(image));
Packer.toBuffer(doc).then((buffer) => { Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer); fs.writeFileSync("My Document.docx", buffer);
}); });

View File

@ -1,28 +1,53 @@
// Custom styles using JavaScript configuration // Custom styles using JavaScript configuration
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph } from "../build"; import { Document, HeadingLevel, Packer, Paragraph, UnderlineType } from "../build";
const doc = new Document(); const doc = new Document({
styles: {
// The first argument is an ID you use to apply the style to paragraphs paragraphStyles: [
// The second argument is a human-friendly name to show in the UI {
doc.Styles.createParagraphStyle("myWonkyStyle", "My Wonky Style") id: "myWonkyStyle",
.basedOn("Normal") name: "My Wonky Style",
.next("Normal") basedOn: "Normal",
.color("990000") next: "Normal",
.italics() run: {
.indent({ left: 720 }) // 720 TWIP === 720 / 20 pt === .5 in color: "990000",
.spacing({ line: 276 }); // 276 / 240 = 1.15x line spacing italics: true,
},
doc.Styles.createParagraphStyle("Heading2", "Heading 2") paragraph: {
.basedOn("Normal") indent: {
.next("Normal") left: 720,
.quickFormat() },
.size(26) // 26 half-points === 13pt font spacing: {
.bold() line: 276,
.underline("double", "FF0000") },
.spacing({ before: 240, after: 120 }); // TWIP for both },
},
{
id: "Heading2",
name: "Heading 2",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
run: {
bold: true,
size: 26,
underline: {
type: UnderlineType.DOUBLE,
color: "FF0000",
},
},
paragraph: {
spacing: {
before: 240,
after: 120,
},
},
},
],
},
});
doc.addSection({ doc.addSection({
children: [ children: [

View File

@ -3,15 +3,23 @@
import * as fs from "fs"; import * as fs from "fs";
import { File, HeadingLevel, Packer, Paragraph, StyleLevel, TableOfContents } from "../build"; import { File, HeadingLevel, Packer, Paragraph, StyleLevel, TableOfContents } from "../build";
const doc = new File(); const doc = new File({
styles: {
// The first argument is an ID you use to apply the style to paragraphs paragraphStyles: [
// The second argument is a human-friendly name to show in the UI {
doc.Styles.createParagraphStyle("MySpectacularStyle", "My Spectacular Style") id: "MySpectacularStyle",
.basedOn("Heading1") name: "My Spectacular Style",
.next("Heading1") basedOn: "Heading1",
.color("990000") next: "Heading1",
.italics(); quickFormat: true,
run: {
italics: true,
color: "990000",
},
},
],
},
});
// WordprocessingML docs for TableOfContents can be found here: // WordprocessingML docs for TableOfContents can be found here:
// http://officeopenxml.com/WPtableOfContents.php // http://officeopenxml.com/WPtableOfContents.php

View File

@ -1,23 +1,53 @@
// Numbered lists // Numbered lists
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Numbering, Packer, Paragraph } from "../build"; import { AlignmentType, Document, Packer, Paragraph } from "../build";
const doc = new Document(); const doc = new Document({
numbering: {
const numbering = new Numbering(); config: [
{
const abstractNum = numbering.createAbstractNumbering(); levels: [
abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 }); {
level: 0,
const concrete = numbering.createConcreteNumbering(abstractNum); format: "upperRoman",
text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
},
},
},
],
reference: "my-crazy-reference",
},
{
levels: [
{
level: 0,
format: "decimal",
text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
},
},
},
],
reference: "my-number-numbering-reference",
},
],
},
});
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
text: "line with contextual spacing", text: "line with contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: true, contextualSpacing: true,
@ -28,7 +58,7 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "line with contextual spacing", text: "line with contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: true, contextualSpacing: true,
@ -39,7 +69,7 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "line without contextual spacing", text: "line without contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: false, contextualSpacing: false,
@ -50,7 +80,7 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "line without contextual spacing", text: "line without contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: false, contextualSpacing: false,
@ -58,6 +88,27 @@ doc.addSection({
before: 200, before: 200,
}, },
}), }),
new Paragraph({
text: "Step 1 - Add sugar",
numbering: {
reference: "my-number-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "Step 2 - Add wheat",
numbering: {
reference: "my-number-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "Step 3 - Put in oven",
numbering: {
reference: "my-number-numbering-reference",
level: 0,
},
}),
], ],
}); });

View File

@ -1,46 +1,91 @@
// Numbering and bullet points example // Numbering and bullet points example
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Numbering, Packer, Paragraph } from "../build"; import { AlignmentType, Document, Packer, Paragraph } from "../build";
const doc = new Document(); const doc = new Document({
numbering: {
const numbering = new Numbering(); config: [
{
const abstractNum = numbering.createAbstractNumbering(); reference: "my-crazy-numbering",
abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 }); levels: [
abstractNum.createLevel(1, "decimal", "%2.", "start").indent({ left: 1440, hanging: 980 }); {
abstractNum.createLevel(2, "lowerLetter", "%3)", "start").indent({ left: 14402160, hanging: 1700 }); level: 0,
format: "upperRoman",
const concrete = numbering.createConcreteNumbering(abstractNum); text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
},
},
},
{
level: 1,
format: "decimal",
text: "%2.",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 1440, hanging: 980 },
},
},
},
{
level: 2,
format: "lowerLetter",
text: "%3)",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 2160, hanging: 1700 },
},
},
},
{
level: 3,
format: "upperLetter",
text: "%4)",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 2880, hanging: 2420 },
},
},
},
],
},
],
},
});
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
text: "Hey you", text: "Hey you",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "What's up fam", text: "What's up fam",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 1, level: 1,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Hello World 2", text: "Hello World 2",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 1, level: 1,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Yeah boi", text: "Yeah boi",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 2, level: 2,
}, },
}), }),
@ -68,6 +113,27 @@ doc.addSection({
level: 3, level: 3,
}, },
}), }),
new Paragraph({
text: "101 MSXFM",
numbering: {
reference: "my-crazy-numbering",
level: 3,
},
}),
new Paragraph({
text: "back to level 1",
numbering: {
reference: "my-crazy-numbering",
level: 1,
},
}),
new Paragraph({
text: "back to level 0",
numbering: {
reference: "my-crazy-numbering",
level: 0,
},
}),
], ],
}); });

View File

@ -1,28 +1,72 @@
// Example of how you would create a table and add data to it // Example of how you would create a table and add data to it
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph, Table, VerticalAlign } from "../build"; import { Document, HeadingLevel, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign, TextDirection } from "../build";
const doc = new Document(); const doc = new Document();
const table = new Table({ const table = new Table({
rows: 2, rows: [
columns: 2, new TableRow({
children: [
new TableCell({
children: [new Paragraph({}), new Paragraph({})],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [new Paragraph({}), new Paragraph({})],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [new Paragraph({ text: "bottom to top" }), new Paragraph({})],
textDirection: TextDirection.BOTTOM_TO_TOP_LEFT_TO_RIGHT,
}),
new TableCell({
children: [new Paragraph({ text: "top to bottom" }), new Paragraph({})],
textDirection: TextDirection.TOP_TO_BOTTOM_RIGHT_TO_LEFT,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [
new Paragraph({
text:
"Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah",
heading: HeadingLevel.HEADING_1,
}),
],
}),
new TableCell({
children: [
new Paragraph({
text: "This text should be in the middle of the cell",
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [
new Paragraph({
text: "Text above should be vertical from bottom to top",
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [
new Paragraph({
text: "Text above should be vertical from top to bottom",
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
],
}),
],
}); });
table
.getCell(1, 1)
.add(new Paragraph("This text should be in the middle of the cell"))
.setVerticalAlign(VerticalAlign.CENTER);
table.getCell(1, 0).add(
new Paragraph({
text:
"Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah",
heading: HeadingLevel.HEADING_1,
}),
);
doc.addSection({ doc.addSection({
children: [table], children: [table],
}); });

View File

@ -0,0 +1,397 @@
// Example of how you would merge cells together (Rows and Columns) and apply shading
// Also includes an example on how to center tables
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { AlignmentType, BorderStyle, Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
const doc = new Document();
const table = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Hello")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
});
const table2 = new Table({
alignment: AlignmentType.CENTER,
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("World")],
margins: {
top: 1000,
bottom: 1000,
left: 1000,
right: 1000,
},
columnSpan: 3,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
width: {
size: 100,
type: WidthType.AUTO,
},
columnWidths: [1000, 1000, 1000],
});
const table3 = new Table({
alignment: AlignmentType.CENTER,
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Foo")],
}),
new TableCell({
children: [new Paragraph("v")],
columnSpan: 3,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Bar1")],
shading: {
fill: "b79c2f",
val: ShadingType.REVERSE_DIAGONAL_STRIPE,
color: "auto",
},
}),
new TableCell({
children: [new Paragraph("Bar2")],
shading: {
fill: "42c5f4",
val: ShadingType.PERCENT_95,
color: "auto",
},
}),
new TableCell({
children: [new Paragraph("Bar3")],
shading: {
fill: "880aa8",
val: ShadingType.PERCENT_10,
color: "e2df0b",
},
}),
new TableCell({
children: [new Paragraph("Bar4")],
shading: {
fill: "FF0000",
val: ShadingType.CLEAR,
color: "auto",
},
}),
],
}),
],
width: {
size: 7000,
type: WidthType.DXA,
},
margins: {
top: 400,
bottom: 400,
right: 400,
left: 400,
},
});
const table4 = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("0,0")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("1,0")],
}),
new TableCell({
children: [new Paragraph("1,1")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("2,0")],
columnSpan: 2,
}),
],
}),
],
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
});
const table5 = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("0,0")],
}),
new TableCell({
children: [new Paragraph("0,1")],
rowSpan: 2,
}),
new TableCell({
children: [new Paragraph("0,2")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("1,0")],
}),
new TableCell({
children: [new Paragraph("1,2")],
rowSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("2,0")],
}),
new TableCell({
children: [new Paragraph("2,1")],
}),
],
}),
],
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
});
const borders = {
top: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "red",
},
bottom: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "red",
},
left: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "red",
},
right: {
style: BorderStyle.DASH_SMALL_GAP,
size: 1,
color: "red",
},
};
const table6 = new Table({
rows: [
new TableRow({
children: [
new TableCell({
borders,
children: [new Paragraph("0,0")],
rowSpan: 2,
}),
new TableCell({
borders,
children: [new Paragraph("0,1")],
}),
],
}),
new TableRow({
children: [
new TableCell({
borders,
children: [new Paragraph("1,1")],
rowSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
borders,
children: [new Paragraph("2,0")],
}),
],
}),
],
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
});
const table7 = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("0,0")],
}),
new TableCell({
children: [new Paragraph("0,1")],
}),
new TableCell({
children: [new Paragraph("0,2")],
rowSpan: 2,
}),
new TableCell({
children: [new Paragraph("0,3")],
rowSpan: 3,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("1,0")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("2,0")],
columnSpan: 2,
}),
new TableCell({
children: [new Paragraph("2,2")],
rowSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("3,0")],
}),
new TableCell({
children: [new Paragraph("3,1")],
}),
new TableCell({
children: [new Paragraph("3,3")],
}),
],
}),
],
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
});
const table8 = new Table({
rows: [
new TableRow({
children: [
new TableCell({ children: [new Paragraph("1,1")] }),
new TableCell({ children: [new Paragraph("1,2")] }),
new TableCell({ children: [new Paragraph("1,3")] }),
new TableCell({ children: [new Paragraph("1,4")], rowSpan: 4, borders }),
],
}),
new TableRow({
children: [
new TableCell({ children: [new Paragraph("2,1")] }),
new TableCell({ children: [new Paragraph("2,2")] }),
new TableCell({ children: [new Paragraph("2,3")], rowSpan: 3 }),
],
}),
new TableRow({
children: [
new TableCell({ children: [new Paragraph("3,1")] }),
new TableCell({ children: [new Paragraph("3,2")], rowSpan: 2 }),
],
}),
new TableRow({
children: [
new TableCell({ children: [new Paragraph("4,1")] }),
],
}),
],
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
});
doc.addSection({
children: [
table,
new Paragraph({
text: "Another table",
heading: HeadingLevel.HEADING_2,
}),
table2,
new Paragraph({
text: "Another table",
heading: HeadingLevel.HEADING_2,
}),
table3,
new Paragraph("Merging columns 1"),
table4,
new Paragraph("Merging columns 2"),
table5,
new Paragraph("Merging columns 3"),
table6,
new Paragraph("Merging columns 4"),
table7,
new Paragraph("Merging columns 5"),
table8,
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -1,113 +0,0 @@
// Example of how you would merge cells together
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, WidthType } from "../build";
const doc = new Document();
const table = new Table({
rows: 2,
columns: 2,
});
table.getCell(0, 0).add(new Paragraph("Hello"));
table.getRow(0).mergeCells(0, 1);
const table2 = new Table({
rows: 2,
columns: 3,
width: 100,
widthUnitType: WidthType.AUTO,
columnWidths: [1000, 1000, 1000],
});
table2
.getCell(0, 0)
.add(new Paragraph("World"))
.setMargins({
top: 1000,
bottom: 1000,
left: 1000,
right: 1000,
});
table.getRow(0).mergeCells(0, 2);
const table3 = new Table({
rows: 2,
columns: 4,
width: 7000,
widthUnitType: WidthType.DXA,
margins: {
top: 400,
bottom: 400,
right: 400,
left: 400,
},
});
table3.getCell(0, 0).add(new Paragraph("Foo"));
table3.getCell(0, 1).add(new Paragraph("v"));
table3
.getCell(1, 0)
.add(new Paragraph("Bar1"))
.setShading({
fill: "b79c2f",
val: ShadingType.REVERSE_DIAGONAL_STRIPE,
color: "auto",
});
table3
.getCell(1, 1)
.add(new Paragraph("Bar2"))
.setShading({
fill: "42c5f4",
val: ShadingType.PERCENT_95,
color: "auto",
});
table3
.getCell(1, 2)
.add(new Paragraph("Bar3"))
.setShading({
fill: "880aa8",
val: ShadingType.PERCENT_10,
color: "e2df0b",
});
table3
.getCell(1, 3)
.add(new Paragraph("Bar4"))
.setShading({
fill: "FF0000",
val: ShadingType.CLEAR,
color: "auto",
});
table3.getRow(0).mergeCells(0, 3);
const table4 = new Table({
rows: 2,
columns: 2,
width: 100,
widthUnitType: WidthType.PERCENTAGE,
});
doc.addSection({
children: [
table,
new Paragraph({
text: "Another table",
heading: HeadingLevel.HEADING_2,
}),
table2,
new Paragraph({
text: "Another table",
heading: HeadingLevel.HEADING_2,
}),
table3,
new Paragraph("hi"),
table4,
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -1,28 +1,44 @@
// Sequential Captions // Sequential Captions
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph, TextRun } from "../build"; import { Document, Packer, Paragraph, SequentialIdentifier, TextRun } from "../build";
const doc = new Document(); const doc = new Document();
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph("Hello World 1->") new Paragraph({
.addSequentialIdentifier("Caption") children: [
.addRun(new TextRun(" text after sequencial caption 2->")) new TextRun("Hello World 1->"),
.addSequentialIdentifier("Caption"), new SequentialIdentifier("Caption"),
new Paragraph("Hello World 1->") new TextRun(" text after sequencial caption 2->"),
.addSequentialIdentifier("Label") new SequentialIdentifier("Caption"),
.addRun(new TextRun(" text after sequencial caption 2->")) ],
.addSequentialIdentifier("Label"), }),
new Paragraph("Hello World 1->") new Paragraph({
.addSequentialIdentifier("Another") children: [
.addRun(new TextRun(" text after sequencial caption 3->")) new TextRun("Hello World 1->"),
.addSequentialIdentifier("Label"), new SequentialIdentifier("Label"),
new Paragraph("Hello World 2->") new TextRun(" text after sequencial caption 2->"),
.addSequentialIdentifier("Another") new SequentialIdentifier("Label"),
.addRun(new TextRun(" text after sequencial caption 4->")) ],
.addSequentialIdentifier("Label"), }),
new Paragraph({
children: [
new TextRun("Hello World 1->"),
new SequentialIdentifier("Another"),
new TextRun(" text after sequencial caption 3->"),
new SequentialIdentifier("Label"),
],
}),
new Paragraph({
children: [
new TextRun("Hello World 2->"),
new SequentialIdentifier("Another"),
new TextRun(" text after sequencial caption 4->"),
new SequentialIdentifier("Label"),
],
}),
], ],
}); });

View File

@ -3,35 +3,56 @@
import * as fs from "fs"; import * as fs from "fs";
import { import {
Document, Document,
OverlapType,
Packer, Packer,
Paragraph, Paragraph,
RelativeHorizontalPosition, RelativeHorizontalPosition,
RelativeVerticalPosition, RelativeVerticalPosition,
Table, Table,
TableAnchorType, TableAnchorType,
TableCell,
TableLayoutType, TableLayoutType,
TableRow,
WidthType, WidthType,
} from "../build"; } from "../build";
const doc = new Document(); const doc = new Document();
const table = new Table({ const table = new Table({
rows: 2, rows: [
columns: 2, new TableRow({
children: [
new TableCell({
children: [new Paragraph("Hello")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
float: { float: {
horizontalAnchor: TableAnchorType.MARGIN, horizontalAnchor: TableAnchorType.MARGIN,
verticalAnchor: TableAnchorType.MARGIN, verticalAnchor: TableAnchorType.MARGIN,
relativeHorizontalPosition: RelativeHorizontalPosition.RIGHT, relativeHorizontalPosition: RelativeHorizontalPosition.RIGHT,
relativeVerticalPosition: RelativeVerticalPosition.BOTTOM, relativeVerticalPosition: RelativeVerticalPosition.BOTTOM,
overlap: OverlapType.NEVER,
},
width: {
size: 4535,
type: WidthType.DXA,
}, },
width: 4535,
widthUnitType: WidthType.DXA,
layout: TableLayoutType.FIXED, layout: TableLayoutType.FIXED,
}); });
table.getCell(0, 0).add(new Paragraph("Hello"));
table.getRow(0).mergeCells(0, 1);
doc.addSection({ doc.addSection({
children: [table], children: [table],
}); });

View File

@ -1,15 +1,34 @@
// Example on how to add hyperlinks to websites // Example on how to add hyperlinks to websites
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph } from "../build"; import { Document, HyperlinkRef, HyperlinkType, Packer, Paragraph, Media } from "../build";
const doc = new Document(); const doc = new Document({
const paragraph = new Paragraph({}); hyperlinks: {
const link = doc.createHyperlink("http://www.example.com", "Hyperlink"); myCoolLink: {
link: "http://www.example.com",
text: "Hyperlink",
type: HyperlinkType.EXTERNAL,
},
myOtherLink: {
link: "http://www.google.com",
text: "Google Link",
type: HyperlinkType.EXTERNAL,
},
},
});
const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
paragraph.addHyperLink(link);
doc.addSection({ doc.addSection({
children: [paragraph], children: [
new Paragraph({
children: [new HyperlinkRef("myCoolLink")],
}),
new Paragraph({
children: [image1, new HyperlinkRef("myOtherLink")],
}),
],
}); });
Packer.toBuffer(doc).then((buffer) => { Packer.toBuffer(doc).then((buffer) => {

View File

@ -1,16 +1,61 @@
// Add image to table cell // Add image to table cell in a header and body
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Header, Media, Packer, Paragraph, Table } from "../build"; import { Document, Header, Media, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
const doc = new Document(); const doc = new Document();
const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg")); const image = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
const table = new Table({ const table = new Table({
rows: 2, rows: [
columns: 2, new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph(image)],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
}); });
table.getCell(1, 1).add(new Paragraph(image));
// Adding same table in the body and in the header // Adding same table in the body and in the header
doc.addSection({ doc.addSection({

View File

@ -1,7 +1,7 @@
// Example how to display page numbers // Example how to display page numbers
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { AlignmentType, Document, Footer, Header, Packer, PageNumberFormat, Paragraph, TextRun } from "../build"; import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, PageNumberFormat, Paragraph, TextRun } from "../build";
const doc = new Document({}); const doc = new Document({});
@ -9,9 +9,17 @@ doc.addSection({
headers: { headers: {
default: new Header({ default: new Header({
children: [ children: [
new Paragraph("Foo Bar corp. ") new Paragraph({
.addRun(new TextRun("Page Number ").pageNumber()) children: [
.addRun(new TextRun(" to ").numberOfTotalPages()), new TextRun("Foo Bar corp. "),
new TextRun({
children: ["Page Number ", PageNumber.CURRENT],
}),
new TextRun({
children: [" to ", PageNumber.TOTAL_PAGES],
}),
],
}),
], ],
}), }),
}, },
@ -19,11 +27,17 @@ doc.addSection({
default: new Footer({ default: new Footer({
children: [ children: [
new Paragraph({ new Paragraph({
text: "Foo Bar corp. ",
alignment: AlignmentType.CENTER, alignment: AlignmentType.CENTER,
}) children: [
.addRun(new TextRun("Page Number: ").pageNumber()) new TextRun("Foo Bar corp. "),
.addRun(new TextRun(" to ").numberOfTotalPages()), new TextRun({
children: ["Page Number: ", PageNumber.CURRENT],
}),
new TextRun({
children: [" to ", PageNumber.TOTAL_PAGES],
}),
],
}),
], ],
}), }),
}, },
@ -32,11 +46,21 @@ doc.addSection({
pageNumberFormatType: PageNumberFormat.DECIMAL, pageNumberFormatType: PageNumberFormat.DECIMAL,
}, },
children: [ children: [
new Paragraph("Hello World 1").pageBreak(), new Paragraph({
new Paragraph("Hello World 2").pageBreak(), children: [new TextRun("Hello World 1"), new PageBreak()],
new Paragraph("Hello World 3").pageBreak(), }),
new Paragraph("Hello World 4").pageBreak(), new Paragraph({
new Paragraph("Hello World 5").pageBreak(), children: [new TextRun("Hello World 2"), new PageBreak()],
}),
new Paragraph({
children: [new TextRun("Hello World 3"), new PageBreak()],
}),
new Paragraph({
children: [new TextRun("Hello World 4"), new PageBreak()],
}),
new Paragraph({
children: [new TextRun("Hello World 5"), new PageBreak()],
}),
], ],
}); });

View File

@ -1,17 +1,35 @@
// Example of how you would create a table and add data to it // Example of how you would create a table and add data to it
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph, Table } from "../build"; import { Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
const doc = new Document(); const doc = new Document();
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, new TableRow({
children: [
new TableCell({
children: [new Paragraph("Hello")],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("World")],
}),
],
}),
],
}); });
table.getCell(2, 2).add(new Paragraph("Hello"));
doc.addSection({ doc.addSection({
children: [table], children: [table],
}); });

View File

@ -1,52 +1,259 @@
// Multiple cells merging in the same table // Multiple cells merging in the same table - Rows and Columns
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph, Table } from "../build"; import { Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
const doc = new Document(); const doc = new Document();
const table = new Table({ const table = new Table({
rows: 13, rows: [
columns: 6, new TableRow({
children: [
new TableCell({
children: [new Paragraph("0,0")],
}),
new TableCell({
children: [new Paragraph("0,1")],
columnSpan: 2,
}),
new TableCell({
children: [new Paragraph("0,3")],
}),
new TableCell({
children: [new Paragraph("0,4")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("1,0")],
columnSpan: 2,
}),
new TableCell({
children: [new Paragraph("1,2")],
columnSpan: 2,
}),
new TableCell({
children: [new Paragraph("1,4")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("2,0")],
}),
new TableCell({
children: [new Paragraph("2,1")],
columnSpan: 2,
}),
new TableCell({
children: [new Paragraph("2,3")],
}),
new TableCell({
children: [new Paragraph("2,4")],
columnSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("3,0")],
}),
new TableCell({
children: [new Paragraph("3,1")],
}),
new TableCell({
children: [new Paragraph("3,2")],
}),
new TableCell({
children: [new Paragraph("3,3")],
}),
new TableCell({
children: [new Paragraph("3,4")],
}),
new TableCell({
children: [new Paragraph("3,5")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("4,0")],
columnSpan: 5,
}),
new TableCell({
children: [new Paragraph("4,5")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
}); });
let row = 0; const table2 = new Table({
table.getCell(row, 0).add(new Paragraph("0,0")); rows: [
table.getCell(row, 1).add(new Paragraph("0,1")); new TableRow({
table.getCell(row, 3).add(new Paragraph("0,3")); children: [
table.getCell(row, 4).add(new Paragraph("0,4")); new TableCell({
table.getRow(row).mergeCells(4, 5); children: [new Paragraph("0,0")],
table.getRow(row).mergeCells(1, 2); }),
row = 1; new TableCell({
table.getCell(row, 0).add(new Paragraph("1,0")); children: [new Paragraph("0,1")],
table.getCell(row, 2).add(new Paragraph("1,2")); rowSpan: 2,
table.getCell(row, 4).add(new Paragraph("1,4")); }),
table.getRow(row).mergeCells(4, 5); new TableCell({
table.getRow(row).mergeCells(2, 3); children: [new Paragraph("0,2")],
table.getRow(row).mergeCells(0, 1); }),
new TableCell({
row = 2; children: [new Paragraph("0,3")],
table.getCell(row, 0).add(new Paragraph("2,0")); }),
table.getCell(row, 1).add(new Paragraph("2,1")); new TableCell({
table.getCell(row, 2).add(new Paragraph("2,2")); children: [new Paragraph("0,4")],
table.getCell(row, 3).add(new Paragraph("2,3")); }),
table.getCell(row, 4).add(new Paragraph("2,4")); new TableCell({
table.getRow(row).mergeCells(4, 5); children: [new Paragraph("0,5")],
table.getRow(row).mergeCells(1, 2); }),
row = 3; ],
table.getCell(row, 0).add(new Paragraph("3,0")); }),
table.getCell(row, 1).add(new Paragraph("3,1")); new TableRow({
table.getCell(row, 2).add(new Paragraph("3,2")); children: [
table.getCell(row, 3).add(new Paragraph("3,3")); new TableCell({
table.getCell(row, 4).add(new Paragraph("3,4")); children: [new Paragraph("1,0")],
table.getCell(row, 5).add(new Paragraph("3,5")); }),
row = 4; new TableCell({
table.getCell(row, 0).add(new Paragraph("4,0")); children: [new Paragraph("1,2")],
table.getCell(row, 5).add(new Paragraph("4,5")); }),
table.getRow(row).mergeCells(0, 4); new TableCell({
children: [new Paragraph("1,3")],
}),
new TableCell({
children: [new Paragraph("1,4")],
}),
new TableCell({
children: [new Paragraph("1,5")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("2,0")],
}),
new TableCell({
children: [new Paragraph("2,1")],
}),
new TableCell({
children: [new Paragraph("2,2")],
}),
new TableCell({
children: [new Paragraph("2,3")],
}),
new TableCell({
children: [new Paragraph("2,4")],
}),
new TableCell({
children: [new Paragraph("2,5")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("3,0")],
}),
new TableCell({
children: [new Paragraph("3,1")],
}),
new TableCell({
children: [new Paragraph("3,2")],
}),
new TableCell({
children: [new Paragraph("3,3")],
}),
new TableCell({
children: [new Paragraph("3,4")],
}),
new TableCell({
children: [new Paragraph("3,5")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("4,0")],
}),
new TableCell({
children: [new Paragraph("4,1")],
}),
new TableCell({
children: [new Paragraph("4,2")],
}),
new TableCell({
children: [new Paragraph("4,3")],
}),
new TableCell({
children: [new Paragraph("4,4")],
}),
new TableCell({
children: [new Paragraph("4,5")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
});
doc.addSection({ doc.addSection({
children: [table], children: [table, new Paragraph(""), table2],
}); });
Packer.toBuffer(doc).then((buffer) => { Packer.toBuffer(doc).then((buffer) => {

View File

@ -1,18 +1,80 @@
// Add image to table cell // Add image to table cell
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph, Table } from "../build"; import { Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
const doc = new Document(); const doc = new Document();
const table = new Table({ const table = new Table({
rows: 4, rows: [
columns: 4, new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
rowSpan: 2,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("Hello")],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
new TableCell({
children: [],
}),
],
}),
],
}); });
table.getCell(2, 2).add(new Paragraph("Hello"));
table.getColumn(3).mergeCells(1, 2);
doc.addSection({ doc.addSection({
children: [table], children: [table],
}); });

View File

@ -8,7 +8,7 @@ const doc = new Document();
doc.addSection({ doc.addSection({
properties: { properties: {
column: { column: {
width: 708, space: 708,
count: 2, count: 2,
}, },
}, },
@ -23,7 +23,7 @@ doc.addSection({
doc.addSection({ doc.addSection({
properties: { properties: {
column: { column: {
width: 708, space: 708,
count: 3, count: 3,
}, },
}, },

View File

@ -0,0 +1,67 @@
// Multiple sections with total number of pages in each section
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, PageNumberFormat, Paragraph, TextRun } from "../build";
const doc = new Document();
const header = new Header({
children: [
new Paragraph({
children: [
new TextRun("Header on another page"),
new TextRun({
children: ["Page number: ", PageNumber.CURRENT],
}),
new TextRun({
children: [" to ", PageNumber.TOTAL_PAGES_IN_SECTION],
}),
],
alignment: AlignmentType.CENTER,
}),
],
});
const footer = new Footer({
children: [new Paragraph("Foo Bar corp. ")],
});
doc.addSection({
headers: {
default: header,
},
footers: {
default: footer,
},
properties: {
pageNumberStart: 1,
pageNumberFormatType: PageNumberFormat.DECIMAL,
},
children: [
new Paragraph({
children: [new TextRun("Section 1"), new PageBreak(), new TextRun("Section 1"), new PageBreak()],
}),
],
});
doc.addSection({
headers: {
default: header,
},
footers: {
default: footer,
},
properties: {
pageNumberStart: 1,
pageNumberFormatType: PageNumberFormat.DECIMAL,
},
children: [
new Paragraph({
children: [new TextRun("Section 2"), new PageBreak(), new TextRun("Section 2"), new PageBreak()],
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

31
demo/48-vertical-align.ts Normal file
View File

@ -0,0 +1,31 @@
// Example of making content of section vertically aligned
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, Paragraph, SectionVerticalAlignValue, TextRun } from "../build";
const doc = new Document();
doc.addSection({
properties: {
verticalAlign: SectionVerticalAlignValue.CENTER,
},
children: [
new Paragraph({
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
new TextRun({
text: "\tGithub is the best",
bold: true,
}),
],
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

37
demo/49-table-borders.ts Normal file
View File

@ -0,0 +1,37 @@
// Add custom borders to the table itself
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { BorderStyle, Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
const doc = new Document();
const table = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Hello")],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("World")],
}),
],
}),
],
});
doc.addSection({ children: [table] });
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

61
demo/50-readme-demo.ts Normal file
View File

@ -0,0 +1,61 @@
// Simple example to add text to a document
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, HeadingLevel, Media, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign } from "../build";
const doc = new Document();
const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
const table = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph(image1)],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [
new Paragraph({
text: "Hello",
heading: HeadingLevel.HEADING_1,
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [
new Paragraph({
text: "World",
heading: HeadingLevel.HEADING_1,
}),
],
}),
new TableCell({
children: [new Paragraph(image1)],
}),
],
}),
],
});
doc.addSection({
children: [
new Paragraph({
text: "Hello World",
heading: HeadingLevel.HEADING_1,
}),
table,
new Paragraph(image2),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -0,0 +1,37 @@
// Custom character styles using JavaScript configuration
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, Paragraph, TextRun } from "../build";
const doc = new Document({
styles: {
characterStyles: [
{
id: "myRedStyle",
name: "My Wonky Style",
basedOn: "Normal",
run: {
color: "990000",
italics: true,
},
},
],
},
});
doc.addSection({
children: [
new Paragraph({
children: [
new TextRun({
text: "Foo bar",
style: "myRedStyle",
}),
],
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

37
demo/52-japanese.ts Normal file
View File

@ -0,0 +1,37 @@
// Japanese text - Need to use a Japanese font
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph } from "../build";
const doc = new Document({
styles: {
paragraphStyles: [
{
id: "Normal",
name: "Normal",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
run: {
font: "MS Gothic",
},
},
],
},
});
doc.addSection({
children: [
new Paragraph({
text: "KFCを食べるのが好き",
heading: HeadingLevel.HEADING_1,
}),
new Paragraph({
text: "こんにちは",
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

55
demo/53-chinese.ts Normal file
View File

@ -0,0 +1,55 @@
// Chinese text - Chinese text need to use a Chinese font. And ascii text need to use a ascii font.
// Different from the `52-japanese.ts`.
// `52-japanese.ts` will set all characters to use Japanese font.
// `53-chinese.ts` will set Chinese characters to use Chinese font, and set ascii characters to use ascii font.
// Note that if the OS have not install `KaiTi` font, this demo doesn't work.
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph, TextRun } from "../build";
const doc = new Document({
styles: {
paragraphStyles: [
{
id: "Normal",
name: "Normal",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
run: {
font: {
ascii: "Times",
eastAsia: "KaiTi",
},
},
},
],
},
});
doc.addSection({
children: [
new Paragraph({
text: "中文和英文 Chinese and English",
heading: HeadingLevel.HEADING_1,
}),
new Paragraph({
text: "中文和英文 Chinese and English",
}),
new Paragraph({
children: [
new TextRun({
text: "中文和英文 Chinese and English",
font: { eastAsia: "SimSun" }, // set eastAsia to "SimSun".
// The ascii characters will use the default font ("Times") specified in paragraphStyles
}),
],
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -21,9 +21,9 @@ doc.addSection({
bold: true, bold: true,
}), }),
new TextRun({ new TextRun({
text: "Github is the best", text: "\tGithub is the best",
bold: true, bold: true,
}).tab(), }),
], ],
}), }),
new Paragraph({ new Paragraph({

View File

@ -12,21 +12,21 @@
<script> <script>
function generate() { function generate() {
const doc = new Document(); const doc = new docx.Document();
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new docx.Paragraph({
children: [ children: [
new TextRun("Hello World"), new docx.TextRun("Hello World"),
new TextRun({ new docx.TextRun({
text: "Foo Bar", text: "Foo Bar",
bold: true, bold: true,
}), }),
new TextRun({ new docx.TextRun({
text: "Github is the best", text: "\tGithub is the best",
bold: true, bold: true,
}).tab(), }),
], ],
}), }),
], ],
@ -34,7 +34,7 @@
Packer.toBlob(doc).then((blob) => { docx.Packer.toBlob(doc).then((blob) => {
console.log(blob); console.log(blob);
saveAs(blob, "example.docx"); saveAs(blob, "example.docx");
console.log("Document created successfully"); console.log("Document created successfully");

View File

@ -50,9 +50,9 @@ doc.addSection({
bold: true, bold: true,
}), }),
new TextRun({ new TextRun({
text: "Github is the best", text: "\tGithub is the best",
bold: true, bold: true,
}).tab(), }),
], ],
}), }),
], ],

View File

@ -7,7 +7,12 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Generate .docx documents with JavaScript"> <meta name="description" content="Generate .docx documents with JavaScript">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css"> <link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/style.min.css"
title="docsify-darklight-theme"
type="text/css"
/>
</head> </head>
<body> <body>
@ -26,6 +31,10 @@
<script src="https://unpkg.com/docsify-copy-code@2"></script> <script src="https://unpkg.com/docsify-copy-code@2"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script> <script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-typescript.min.js"></script> <script src="//unpkg.com/prismjs/components/prism-typescript.min.js"></script>
<script
src="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/index.min.js"
type="text/javascript">
</script>
</body> </body>
</html> </html>

View File

@ -5,17 +5,16 @@
To make a bullet point, simply make a paragraph into a bullet point: To make a bullet point, simply make a paragraph into a bullet point:
```ts ```ts
const text = new docx.TextRun("Bullet points"); const text = new TextRun("Bullet points");
const paragraph = new docx.Paragraph(text).bullet(); const paragraph = new Paragraph({
text: "Bullet points",
const text2 = new docx.TextRun("Are awesome"); bullet: {
const paragraph2 = new docx.Paragraph(text2).bullet(); level: 0, // How deep you want the bullet to me
},
doc.add(paragraph); });
doc.add(paragraph2);
``` ```
### This will produce: ### This will produce:
* Bullet points - Bullet points
* Are awesome - Are awesome

View File

@ -2,52 +2,46 @@
!> Headers and Footers requires an understanding of [Sections](usage/sections.md). !> Headers and Footers requires an understanding of [Sections](usage/sections.md).
Every Section has a sections which you can define its Headers and Footers:
```ts
doc.addSection({
headers: {
default: new Header({ // The standard default header
children: [],
}),
first: new Header({ // The first header
children: [],
}),
even: new Header({ // The header on every other page
children: [],
}),
},
footers: {
default: new Footer({ // The standard default footer
children: [],
}),
first: new Footer({ // The first footer
children: [],
}),
even: new Footer({ // The footer on every other page
children: [],
}),
},
children: [],
});
```
If you want more head
## Example ## Example
Creating Headers and footers is simple. Access the `Header` and `Footer` by doing so like this: Example showing basic header and footer
```ts [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/8-header-footer.ts ':include')
doc.Header;
doc.Footer;
```
You can call the same methods as you would with a `File`: _Source: https://github.com/dolanmiu/docx/blob/master/demo/8-header-footer.ts_
```ts
doc.Header.createParagraph("Header text");
doc.Footer.createParagraph("Footer text");
```
Even add images:
```ts
doc.Header.createImage([BUFFER_OF_YOUR_IMAGE]);
doc.Footer.createImage([BUFFER_OF_YOUR_IMAGE]);
```
Refer to [`demo8.ts`](https://github.com/dolanmiu/docx/blob/master/demo/demo8.ts) for more information.
## Multiple Headers and Footers ## Multiple Headers and Footers
Also all the supported section properties are implemented according to: http://officeopenxml.com/WPsection.php More headers and footers can be accomplished by creating more `Section`. New headers and footers can be set per `Section`
### Example
```ts
const header = this.document.createHeader();
const footer = this.document.createFooter();
// Add new section with another header and footer
doc.addSection({
headers: {
default: header
},
footers: {
default: footer
},
pageNumberStart: 1,
pageNumberFormatType: docx.PageNumberFormat.DECIMAL,
});
```

View File

@ -227,22 +227,22 @@ Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
Importing Images from file system path Importing Images from file system path
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo5.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/5-images.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo5.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/5-images.ts_
### Add images to header and footer ### Add images to header and footer
Example showing how to add image to headers and footers Example showing how to add image to headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo9.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/9-images-in-header-and-footer.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo9.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/9-images-in-header-and-footer.ts_
### Floating images ### Floating images
Example showing how to float images on top of text and optimally give a `margin` Example showing how to float images on top of text and optimally give a `margin`
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo38.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/38-text-wrapping.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo38.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/38-text-wrapping.ts_

View File

@ -86,11 +86,3 @@ topLevelP.setNumbering(concrete, 0);
subP.setNumbering(concrete, 1); subP.setNumbering(concrete, 1);
subSubP.setNumbering(concrete, 2); subSubP.setNumbering(concrete, 2);
``` ```
Finally, you need to let your exporter know about your numbering
styles when you're ready to render the document:
```ts
const packer = new Packer(doc, undefined, undefined, numbering);
packer.pack(myOutput);
```

View File

@ -61,6 +61,6 @@ doc.Header.createParagraph().addRun(new TextRun("Page ").pageNumber()).addRun(ne
Adding page numbers to Header and Footer Adding page numbers to Header and Footer
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo39.ts ':include') [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/39-page-numbers.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo39.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/39-page-numbers.ts_

View File

@ -2,7 +2,7 @@
> Everything (text, images, graphs etc) in OpenXML is organised in paragraphs. > Everything (text, images, graphs etc) in OpenXML is organised in paragraphs.
!> Paragraphs requires an understanding of [Sections](usage/sections.md). !> Paragraphs requires an understanding of [Sections](sections.md).
You can create `Paragraphs` in the following ways: You can create `Paragraphs` in the following ways:
@ -16,11 +16,11 @@ const paragraph = new Paragraph("Short hand Hello World");
### Children Method ### Children Method
This method is useful for adding different `text` with different styles or adding `images` inline. This method is useful for adding different [text](text.md) with different styles, [symbols](symbols.md), or adding [images](images.md) inline.
```ts ```ts
const paragraph = new Paragraph({ const paragraph = new Paragraph({
children: [new TextRun("Lorem Ipsum Foo Bar"), new TextRun("Hello World")], children: [new TextRun("Lorem Ipsum Foo Bar"), new TextRun("Hello World"), new SymbolRun("F071")],
}); });
``` ```
@ -56,27 +56,27 @@ doc.addSection({
This is the list of options for a paragraph. A detailed explanation is below: This is the list of options for a paragraph. A detailed explanation is below:
| Property | Type | Mandatory? | Possible Values | | Property | Type | Mandatory? | Possible Values |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
| [text](#text) | `string` | Optional | | | [text](#text) | `string` | Optional | |
| [heading](#heading) | `HeadingLevel` | Optional | `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`, `TITLE` | | [heading](#heading) | `HeadingLevel` | Optional | `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`, `TITLE` |
| [border](#border) | `IBorderOptions` | Optional | `top`, `bottom`, `left`, `right`. Each of these are of type IBorderPropertyOptions. Click here for Example | | [border](#border) | `IBorderOptions` | Optional | `top`, `bottom`, `left`, `right`. Each of these are of type IBorderPropertyOptions. Click here for Example |
| [spacing](#spacing) | `ISpacingProperties` | Optional | See below for ISpacingProperties | | [spacing](#spacing) | `ISpacingProperties` | Optional | See below for ISpacingProperties |
| [outlineLevel](#outline-level) | `number` | Optional | | | [outlineLevel](#outline-level) | `number` | Optional | |
| alignment | `AlignmentType` | Optional | | | alignment | `AlignmentType` | Optional | |
| heading | `HeadingLevel` | Optional | | | heading | `HeadingLevel` | Optional | |
| bidirectional | `boolean` | Optional | | | bidirectional | `boolean` | Optional | |
| thematicBreak | `boolean` | Optional | | | thematicBreak | `boolean` | Optional | |
| pageBreakBefore | `boolean` | Optional | | | pageBreakBefore | `boolean` | Optional | |
| contextualSpacing | `boolean` | Optional | | | contextualSpacing | `boolean` | Optional | |
| indent | `IIndentAttributesProperties` | Optional | | | indent | `IIndentAttributesProperties` | Optional | |
| keepLines | `boolean` | Optional | | | keepLines | `boolean` | Optional | |
| keepNext | `boolean` | Optional | | | keepNext | `boolean` | Optional | |
| children | `(TextRun or PictureRun or Hyperlink)[]` | Optional | | | children | `(TextRun or PictureRun or Hyperlink)[]` | Optional | |
| style | `string` | Optional | | | style | `string` | Optional | |
| tabStop | `{ left?: ITabStopOptions; right?: ITabStopOptions; maxRight?: { leader: LeaderType; }; center?: ITabStopOptions }` | Optional | | | tabStop | `{ left?: ITabStopOptions; right?: ITabStopOptions; maxRight?: { leader: LeaderType; }; center?: ITabStopOptions }` | Optional | |
| bullet | `{ level: number }` | Optional | | | bullet | `{ level: number }` | Optional | |
| numbering | `{ num: Num; level: number; custom?: boolean }` | Optional | | | numbering | `{ num: ConcreteNumbering; level: number; custom?: boolean }` | Optional | |
## Text ## Text
@ -244,10 +244,12 @@ The above example will create a heading with a page break directly under it.
## Page Break ## Page Break
To move to a new page (insert a page break), simply add `.pageBreak()` on a paragraph: To move to a new page (insert a page break):
```ts ```ts
const paragraph = new docx.Paragraph("Amazing Heading").pageBreak(); const paragraph = new docx.Paragraph({
children: [new TextRun("Amazing Heading"), new PageBreak()],
});
``` ```
The above example will create a heading and start a new page immediately afterwards. The above example will create a heading and start a new page immediately afterwards.
@ -265,7 +267,7 @@ const paragraph = new Paragraph({
![Page Break Before in Word](https://user-images.githubusercontent.com/34742290/40176503-df3a8398-59db-11e8-8b9c-d719f13aa8b4.png) ![Page Break Before in Word](https://user-images.githubusercontent.com/34742290/40176503-df3a8398-59db-11e8-8b9c-d719f13aa8b4.png)
Example: https://github.com/dolanmiu/docx/blob/master/demo/demo15.ts Example: https://github.com/dolanmiu/docx/blob/master/demo/15-page-break-before.ts
## Page break control ## Page break control

View File

@ -3,112 +3,148 @@
## Example ## Example
```ts ```ts
const para = new Paragraph("To whom it may concern:").heading2().center(); const para = new Paragraph({
text: "To whom it may concern:",
heading: HeadingLevel.HEADING_2,
alignment: AlignmentType.CENTER,
});
const name = new TextRun("Name:") const name = new TextRun({
.bold() text: "Name:",
.font("Calibri") bold: true,
.allCaps(); font: "Calibri",
allCaps: true,
});
``` ```
## Available methods ## Available Options
* For run formatting: ### Run formatting
* `.bold()`, `.italics()`, `.smallCaps()`, `.allCaps()`, `.strike()`, `.doubleStrike()`, `.subScript()`, `.superScript()`: Set the formatting property to true
* `.underline(style="single", color=null)`: Set the underline style and color
* `.color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`)
* `.size(halfPts)`: Set the font size, measured in half-points
* `.font(name)`: Set the run's font
* `.style(name)`: Apply a named run style
* `.characterSpacing(value)`: Set the character spacing adjustment (in TWIPs)
* For paragraph formatting:
* `.heading1()`, `.heading2()`, `.heading3()`, `.heading4()`, `.heading5()`, `.title()`: apply the appropriate style to the paragraph
* `.left()`, `.center()`, `.right()`, `.justified()`: set the paragraph's alignment
* `.thematicBreak()`, `.pageBreak()`: Insert a thick rule or a page break beneath the paragraph
* `.leftTabStop(position)`: Add a left tab stop (measured in TWIPs from the left)
* `.maxRightTabStop()`: Add a right tab stop at the far right
* `.bullet()`: Use the default bullet style
* `.setNumbering(numbering, indentLevel)`: Use a custom numbering format for the paragraph
* `.style(name)`: Apply a named paragraph style
* `.indent(start, hanging=0)`: Set the paragraph's indent level (in TWIPs)
* `.spacing({before=0, after=0, line=0})`: Set the line and before/after on the paragraph. Before/after is measured in TWIPs, line is measured in 240ths of a line
Paragraph styles have all the run formatting methods, except `style()`, and `.left()`, `.center()`, `.right()`, `.justified()`, `.thematicBreak()`, `.leftTabStop(position)`, `.maxRightTabStop()`, `.indent(start, hanging=0)`, and `.spacing({before=0, after=0, line=0})` methods. - `bold`, `italics`, `smallCaps`, `allCaps`, `strike`, `doubleStrike`, `subScript`, `superScript`: Set the formatting property to true
- `underline({type="single", color=null})`: Set the underline style and color
- `emphasisMark({type="dot"})`: Set the emphasis mark style
- `color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`)
- `size(halfPts)`: Set the font size, measured in half-points
- `font(name)` or `font({ascii, cs, eastAsia, hAnsi, hint})`: Set the run's font
- `style(name)`: Apply a named run style
- `characterSpacing(value)`: Set the character spacing adjustment (in TWIPs)
### Paragraph formatting
- `heading1`, `heading2`, `heading3`, `heading4`, `heading5`, `title`: apply the appropriate style to the paragraph
- `left`, `center`, `right`, `justified`: set the paragraph's alignment
- `thematicBreak`, `pageBreak`: Insert a thick rule or a page break beneath the paragraph
- `leftTabStop(position)`: Add a left tab stop (measured in TWIPs from the left)
- `maxRightTabStop`: Add a right tab stop at the far right
- `bullet`: Use the default bullet style
- `setNumbering(numbering, indentLevel)`: Use a custom numbering format for the paragraph
- `style(name)`: Apply a named paragraph style
- `indent(start, hanging=0)`: Set the paragraph's indent level (in TWIPs)
- `spacing({before=0, after=0, line=0})`: Set the line and before/after on the paragraph. Before/after is measured in TWIPs, line is measured in 240ths of a line
Paragraph styles have all the run formatting methods, except `style()`, and `left()`, `center()`, `right()`, `justified()`, `thematicBreak()`, `leftTabStop(position)`, `maxRightTabStop()`, `indent(start, hanging=0)`, and `spacing({before=0, after=0, line=0})` methods.
## Detailed guide ## Detailed guide
There are 4 items in DOCX that can be styled: There are 4 items in `docx` that can be styled:
* Characters: Attributes that can change within a paragraph. e.g., bold, italics, etc. - Characters: Attributes that can change within a paragraph. e.g., bold, italics, etc.
* Paragraphs: Attributes like indent, text alignment, line spacing, etc. - Paragraphs: Attributes like indent, text alignment, line spacing, etc.
* Tables: Border styles, table formats, etc. - Tables: Border styles, table formats, etc.
* List items: These are the numbers and bullets that are automatically inserted - List items: These are the numbers and bullets that are automatically inserted
There are a few different ways of styling this content in DOCX, which somewhat resemble the HTML/CSS approach. In order of greatest to lowest priority: There are a few different ways of styling this content in `docx`, which somewhat resemble the HTML/CSS approach. In order of greatest to lowest priority:
1. Direct formatting (AKA inline formatting) 1. Direct formatting (inline formatting)
2. Centrally defined styles (similar to external CSS) 2. Declaritive Styles (similar to external CSS)
3. Document defaults (similar to a `*` rule in CSS) 3. Document defaults (similar to a `*` rule in CSS)
Unlike CSS, less specific rules don't _necessarily_ override parent rules. The rules are a bit wonky, but if you're interested, see the [advanced formatting section](#Advanced formatting). Unlike CSS, less specific rules don't _necessarily_ override parent rules. The rules are a bit wonky, but if you're interested, see the [advanced formatting section](#Advanced formatting).
### Direct formatting (AKA inline formatting) ### Direct formatting (inline formatting)
This is the type of formatting that your uncle uses when he types out documents: _N ... a ... m ... e ... :_ Then he grabs the mouse, highlights _Name:_ and moves over to the **B** for bold. This manner of formatting results in markup that is similar to writing `<span style="bold: true">Name:</span>` if you were typing out HTML. DOCX (the format) allows you to specify this for any of the four types of items. `docx` (the library) only supports this type of formatting for paragraphs and characters, using a _fluent_ api. Thus you could do: This is the type of formatting that your uncle uses when he types out documents: _N ... a ... m ... e ... :_ Then he grabs the mouse, highlights _Name:_ and moves over to the **B** for bold. This manner of formatting results in markup that is similar to writing `<span style="bold: true">Name:</span>` if you were typing out HTML. `docx` (the format) allows you to specify this for any of the four types of items. `docx` (the library) only supports this type of formatting for paragraphs and characters, using a _fluent_ api. Thus you could do:
```ts ```ts
const name = new TextRun("Name:") const name = new TextRun({
.bold() text: "Name:",
.font("Calibri") bold: true,
.allCaps(); font: "Calibri",
allCaps: true,
});
``` ```
Or for paragraph formatting: Or for paragraph formatting:
```ts ```ts
const para = new Paragraph("To whom it may concern:").heading2().center(); const para = new Paragraph({
text: "To whom it may concern:",
heading: HeadingLevel.HEADING_2,
alignment: AlignmentType.CENTER,
});
``` ```
### Centrally defined styles (similar to external CSS) ### Declaritive Styles (similar to external CSS)
DOCX files contain a styles section separate from the main content, much like how HTML includes CSS files. Unlike CSS, DOCX distinguishes between styles meant for tables (which show up in the table formatting toolbar), styles for lists (which show up under bullets and numbering), and styles for runs and paragraphs, which show up as dropdowns offering standard styles, like "Heading 1", "Caption", or any custom styles defined in that document. <!-- TODO: add pictures of the panes -->. `docx` allows you to define these styles using a fluent interface as well. `docx` files contain a styles section separate from the main content, much like how HTML includes CSS files. Unlike CSS, `docx` distinguishes between styles meant for tables (which show up in the table formatting toolbar), styles for lists (which show up under bullets and numbering), and styles for runs and paragraphs, which show up as dropdowns offering standard styles, like "Heading 1", "Caption", or any custom styles defined in that document. <!-- TODO: add pictures of the panes -->. `docx` allows you to define these styles using a fluent interface as well.
There are three parts to using custom styles with `docx`: To add styles, define your custom styles in the `document`:
1. Create a container object for the style definitions: ```ts
```ts // The first argument is an ID you use to apply the style to paragraphs
const myStyles = new docx.Styles(); // The second argument is a human-friendly name to show in the UI
``` const doc = new Document({
2. Define your custom styles, similar to the way you would format a paragraph or run creator: "Clippy",
title: "Sample Document",
```ts description: "A brief example of using docx",
// The first argument is an ID you use to apply the style to paragraphs styles: {
// The second argument is a human-friendly name to show in the UI paragraphStyles: [
myStyles {
.createParagraphStyle("myWonkyStyle", "My Wonky Style") id: "myWonkyStyle",
.basedOn("Normal") name: "My Wonky Style",
.next("Normal") basedOn: "Normal",
.color("999999") next: "Normal",
.italics() quickFormat: true,
.indent(720) // 720 TWIP === 720 / 20 pt === .5 in run: {
.spacing({ line: 276 }); // 276 / 240 = 1.15x line spacing italics: true,
color: "999999",
myStyles },
.createParagraphStyle("Heading2", "Heading 2") paragraph: {
.basedOn("Normal") spacing: {
.next("Normal") line: 276,
.quickFormat() },
.size(26) // 26 half-points === 13pt font indent: {
.bold() left: 720,
.underline("double", "FF0000") },
.spacing({ before: 240, after: 120 }); // TWIP for both },
``` },
{
3. When you generate your document, make sure to pass the `styles` container to the `Packer`: id: "Heading2",
name: "Heading 2",
```ts basedOn: "Normal",
Packer.pack(myOutStream); next: "Normal",
``` quickFormat: true,
run: {
size: 26
bold: true,
color: "999999",
{
type: UnderlineType.DOUBLE,
color: "FF0000",
},
},
paragraph: {
spacing: {
before: 240,
after: 120
},
},
},
]
}
});
```
**Note**: If you are using the `.headingX` or `.title` methods of paragraphs, you must make sure to define `HeadingX` or `Title` styles for these. Otherwise they'll show up unstyled :(. If you are using the `.bullet` or `.setNumbering` methods, you need to define a `ListParagraph` style or the numbers may not show up. **Note**: If you are using the `.headingX` or `.title` methods of paragraphs, you must make sure to define `HeadingX` or `Title` styles for these. Otherwise they'll show up unstyled :(. If you are using the `.bullet` or `.setNumbering` methods, you need to define a `ListParagraph` style or the numbers may not show up.
@ -144,19 +180,29 @@ To determine the value of a styling property, you must first identify whether it
The following properties are treated in a special manner; they're called toggle properties: The following properties are treated in a special manner; they're called toggle properties:
* Bold - Bold
* All caps - All caps
* Small caps - Small caps
* Italics - Italics
* Single strike-through - Single strike-through
* Hidden - Hidden
* Imprint - Imprint
* Emboss - Emboss
* Character outline - Character outline
* Character shadow - Character shadow
For these properties, the rules state the following conflict resolution in case the property is specified at multiple points for the same item: For these properties, the rules state the following conflict resolution in case the property is specified at multiple points for the same item:
* Direct formatting trumps all if specified (either true or false) - Direct formatting trumps all if specified (either true or false)
* Otherwise, if the property is true in document defaults, the property is set to true - Otherwise, if the property is true in document defaults, the property is set to true
* Otherwise, the property's value is an XOR of its effective table, paragraph, and character values. (So specifying bold `true` on a table style and a paragraph style would result in non-bold text if a paragraph inside the table had that style) - Otherwise, the property's value is an XOR of its effective table, paragraph, and character values. (So specifying bold `true` on a table style and a paragraph style would result in non-bold text if a paragraph inside the table had that style)
## Examples
### Declaritive styles
Importing Images from file system path
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/2-declaritive-styles.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/2-declaritive-styles.ts_

View File

@ -44,4 +44,4 @@ doc.add(paragraph);
doc.createParagraph("Some normal text"); doc.createParagraph("Some normal text");
``` ```
Example: https://github.com/dolanmiu/docx/blob/master/demo/demo13.ts Example: https://github.com/dolanmiu/docx/blob/master/demo/13-xml-styles.ts

53
docs/usage/symbols.md Normal file
View File

@ -0,0 +1,53 @@
# Symbol Runs
!> SymbolRuns require an understanding of [Paragraphs](paragraph.md).
You can add multiple `symbol runs` in `Paragraphs` along with [text runs](text.md) using the Paragraph's `children` property.
```ts
import { Paragraph, TextRun, SymbolRun } from "docx";
const paragraph = new Paragraph({
children: [
new TextRun("This is a checkbox: "),
new SymbolRun("F071")
],
});
```
## Specifying symbol font
By default symbol runs will use the `Wingdings` font. To switch fonts, pass an object instead of a string to the `SymbolRun` constructor and specify `char` and `symbolfont` properties:
```ts
const symbol = new SymbolRun({
char: "F071",
symbolfont: "Arial",
});
```
## Example symbols
Symbols are specified by their hexidecimal code. Ref http://officeopenxml.com/WPtextSpecialContent-symbol.php. Below are some examples.
- `F071`: empty checkbox
- `F043`: thumbs up
- `F04A`: smile
- `F04C`: frown
- `F022`: scissors
- `F0F0`: right arrow
- `F0FE`: checked box
## Typographical Emphasis
Symbol runs can have their display modified just like text runs. For example, they can be bolded and italicized:
```ts
const symbol = new SymbolRun({
char: "F071",
bold: true,
italics: true,
});
```
See the [text run](text.md) documentation for more info.

View File

@ -2,7 +2,7 @@
> Tab stops are useful, if you are unclear of what they are, [here is a link explaining](https://en.wikipedia.org/wiki/Tab_stop). It enables side by side text which is nicely laid out without the need for tables, or constantly pressing space bar. > Tab stops are useful, if you are unclear of what they are, [here is a link explaining](https://en.wikipedia.org/wiki/Tab_stop). It enables side by side text which is nicely laid out without the need for tables, or constantly pressing space bar.
!> **Note**: At the moment, the unit of measurement for a tab stop is counter intuitive for a human. It is using OpenXMLs own measuring system. For example, 2268 roughly translates to 3cm. Therefore in the future, I may consider changing it to percentages or even cm. !> **Note**: The unit of measurement for a tab stop is in [DXA](https://stackoverflow.com/questions/14360183/default-wordml-unit-measurement-pixel-or-point-or-inches)
![Word 2013 Tabs](http://www.teachucomp.com/wp-content/uploads/blog-4-22-2015-UsingTabStopsInWord-1024x577.png "Word 2013 Tab Stops") ![Word 2013 Tabs](http://www.teachucomp.com/wp-content/uploads/blog-4-22-2015-UsingTabStopsInWord-1024x577.png "Word 2013 Tab Stops")
@ -11,44 +11,111 @@ Simply call the relevant methods on the paragraph listed below. Then just add a
## Example ## Example
```ts ```ts
const paragraph = new docx.Paragraph().maxRightTabStop(); const paragraph = new Paragraph({
const leftText = new docx.TextRun("Hey everyone").bold(); children: [new TextRun("Hey everyone").bold(), new TextRun(\t"11th November 1999")],
const rightText = new docx.TextRun("11th November 2015").tab(); tabStops: [
paragraph.addRun(leftText); {
paragraph.addRun(rightText); type: TabStopType.RIGHT,
position: TabStopPosition.MAX,
},
],
});
``` ```
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!
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. Not ideal!
```ts ```ts
const paragraph = new docx.Paragraph(); const paragraph = new Paragraph({
paragraph.maxRightTabStop(); children: [new TextRun("Second tab stop here I come!")],
paragraph.leftTabStop(1000); tabStops: [
const text = new docx.TextRun("Second tab stop here I come!").tab().tab(); {
paragraph.addRun(text); type: TabStopType.RIGHT,
position: TabStopPosition.MAX,
},
{
type: TabStopType.LEFT,
position: 1000,
},
],
});
``` ```
The above shows the use of two tab stops, and how to select/use it. The above shows the use of two tab stops, and how to select/use it.
## Left Tab Stop You can add multiple tab stops of the same `type` too.
```ts ```ts
paragraph.leftTabStop(2268); const paragraph = new Paragraph({
children: [new TextRun("Multiple tab stops!")],
tabStops: [
{
type: TabStopType.RIGHT,
position: TabStopPosition.MAX,
},
{
type: TabStopType.RIGHT,
position: 1000,
},
],
});
``` ```
## Left Tab Stop
```ts
const paragraph = new Paragraph({
tabStops: [
{
type: TabStopType.LEFT,
position: 2268,
},
],
});
```
2268 is the distance from the left side. 2268 is the distance from the left side.
## Center Tab Stop ## Center Tab Stop
```ts ```ts
paragraph.centerTabStop(2268); const paragraph = new Paragraph({
tabStops: [
{
type: TabStopType.CENTER,
position: 2268,
},
],
});
``` ```
2268 is the distance from the left side.
2268 is the distance from the center.
## Right Tab Stop ## Right Tab Stop
```ts ```ts
paragraph.rightTabStop(2268); const paragraph = new Paragraph({
tabStops: [
{
type: TabStopType.RIGHT,
position: 2268,
},
],
});
``` ```
2268 is the distance from the left side.
2268 is the distance fro0oum the left side.
## Max Right Tab Stop ## Max Right Tab Stop
```ts ```ts
paragraph.maxRightTabStop(); const paragraph = new Paragraph({
tabStops: [
{
type: TabStopType.RIGHT,
position: TabStopPosition.MAX,
},
],
});
``` ```
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. 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.

View File

@ -2,9 +2,9 @@
You can generate table of contents with `docx`. More information can be found [here](http://officeopenxml.com/WPtableOfContents.php). You can generate table of contents with `docx`. More information can be found [here](http://officeopenxml.com/WPtableOfContents.php).
>Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically. > Tables of Contents are fields and, by design, it's content is only generated or updated by Word. We can't do it programatically.
>This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?". > This is why, when you open a the file, Word you will prompt the message "This document contains fields that may refer to other files. Do you want to update the fields in this document?".
>You have say yes to Word generate the content of all table of contents. > You have say yes to Word generate the content of all table of contents.
The complete documentation can be found [here](https://www.ecma-international.org/publications/standards/Ecma-376.htm) (at Part 1, Page 1251). The complete documentation can be found [here](https://www.ecma-international.org/publications/standards/Ecma-376.htm) (at Part 1, Page 1251).
@ -16,7 +16,7 @@ All you need to do is create a `TableOfContents` object and assign it to the doc
const toc = new TableOfContents("Summary", { const toc = new TableOfContents("Summary", {
hyperlink: true, hyperlink: true,
headingStyleRange: "1-5", headingStyleRange: "1-5",
stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)] stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)],
}); });
doc.addTableOfContents(toc); doc.addTableOfContents(toc);
@ -26,51 +26,27 @@ doc.addTableOfContents(toc);
Here is the list of all options that you can use to generate your tables of contents: Here is the list of all options that you can use to generate your tables of contents:
| Option | Type | TOC Field Switch | Description | | Option | Type | TOC Field Switch | Description |
| --- | --- | --- | --- | | ------------------------------- | ------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|captionLabel|string|`\a`|Includes captioned items, but omits caption labels and numbers. The identifier designated by `text` in this switch's field-argument corresponds to the caption label. Use ``\c`` to build a table of captions with labels and numbers.| | captionLabel | string | `\a` | Includes captioned items, but omits caption labels and numbers. The identifier designated by `text` in this switch's field-argument corresponds to the caption label. Use `\c` to build a table of captions with labels and numbers. |
|entriesFromBookmark|string|`\b`|Includes entries only from the portion of the document marked by the bookmark named by `text` in this switch's field-argument.| | entriesFromBookmark | string | `\b` | Includes entries only from the portion of the document marked by the bookmark named by `text` in this switch's field-argument. |
|captionLabelIncludingNumbers|string|`\c`|Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by `text` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field.| | captionLabelIncludingNumbers | string | `\c` | Includes figures, tables, charts, and other items that are numbered by a SEQ field (§17.16.5.56). The sequence identifier designated by `text` in this switch's field-argument, which corresponds to the caption label, shall match the identifier in the corresponding SEQ field. |
|sequenceAndPageNumbersSeparator|string|`\d`|When used with `\s`, the `text` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-).| | sequenceAndPageNumbersSeparator | string | `\d` | When used with `\s`, the `text` in this switch's field-argument defines the separator between sequence and page numbers. The default separator is a hyphen (-). |
|tcFieldIdentifier|string|`\f`|Includes only those TC fields whose identifier exactly matches the `text` in this switch's field-argument (which is typically a letter).| | tcFieldIdentifier | string | `\f` | Includes only those TC fields whose identifier exactly matches the `text` in this switch's field-argument (which is typically a letter). |
|hyperlink|boolean|`\h`|Makes the table of contents entries hyperlinks.| | hyperlink | boolean | `\h` | Makes the table of contents entries hyperlinks. |
|tcFieldLevelRange|string|`\l`|Includes TC fields that assign entries to one of the levels specified by `text` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped.| | tcFieldLevelRange | string | `\l` | Includes TC fields that assign entries to one of the levels specified by `text` in this switch's field-argument as a range having the form startLevel-endLevel, where startLevel and endLevel are integers, and startLevel has a value equal-to or less-than endLevel. TC fields that assign entries to lower levels are skipped. |
|pageNumbersEntryLevelsRange|string|`\n`|Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by `text` in this switch's field-argument. A range is specified as for `\l`.| | pageNumbersEntryLevelsRange | string | `\n` | Without field-argument, omits page numbers from the table of contents. Page numbers are omitted from all levels unless a range of entry levels is specified by `text` in this switch's field-argument. A range is specified as for `\l`. |
|headingStyleRange|string|`\o`|Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by `text` in this switch's field-argument using the notation specified as for `\l`, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed.| | headingStyleRange | string | `\o` | Uses paragraphs formatted with all or the specified range of builtin heading styles. Headings in a style range are specified by `text` in this switch's field-argument using the notation specified as for `\l`, where each integer corresponds to the style with a style ID of HeadingX (e.g. 1 corresponds to Heading1). If no heading range is specified, all heading levels used in the document are listed. |
|entryAndPageNumberSeparator|string|`\p`|`text` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots.| | entryAndPageNumberSeparator | string | `\p` | `text` in this switch's field-argument specifies a sequence of characters that separate an entry and its page number. The default is a tab with leader dots. |
|seqFieldIdentifierForPrefix|string|`\s`|For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. `text` in this switch's field-argument shall match the identifier in the SEQ field.| | seqFieldIdentifierForPrefix | string | `\s` | For entries numbered with a SEQ field (§17.16.5.56), adds a prefix to the page number. The prefix depends on the type of entry. `text` in this switch's field-argument shall match the identifier in the SEQ field. |
|stylesWithLevels|StyleLevel[]|`\t`| Uses paragraphs formatted with styles other than the built-in heading styles. `text` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. `\t` can be combined with `\o`.| | stylesWithLevels | StyleLevel[] | `\t` | Uses paragraphs formatted with styles other than the built-in heading styles. `text` in this switch's field-argument specifies those styles as a set of comma-separated doublets, with each doublet being a comma-separated set of style name and table of content level. `\t` can be combined with `\o`. |
|useAppliedParagraphOutlineLevel|boolean|`\u`|Uses the applied paragraph outline level.| | useAppliedParagraphOutlineLevel | boolean | `\u` | Uses the applied paragraph outline level. |
|preserveTabInEntries|boolean|`\w`|Preserves tab entries within table entries.| | preserveTabInEntries | boolean | `\w` | Preserves tab entries within table entries. |
|preserveNewLineInEntries|boolean|`\x`|Preserves newline characters within table entries.| | preserveNewLineInEntries | boolean | `\x` | Preserves newline characters within table entries. |
|hideTabAndPageNumbersInWebView|boolean|`\z`|Hides tab leader and page numbers in web page view (§17.18.102).| | hideTabAndPageNumbersInWebView | boolean | `\z` | Hides tab leader and page numbers in web page view (§17.18.102). |
## Examples ## Examples
```ts [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/28-table-of-contents.ts ':include')
// Let's define the options for generate a TOC for heading 1-5 and MySpectacularStyle,
// making the entries be hyperlinks for the paragraph
const toc = new TableOfContents("Summary", {
hyperlink: true,
headingStyleRange: "1-5",
stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)]
});
doc.addTableOfContents(toc); _Source: https://github.com/dolanmiu/docx/blob/master/demo/28-table-of-contents.ts_
doc.add(new Paragraph("Header #1").heading1().pageBreakBefore());
doc.add(new Paragraph("I'm a little text, very nicely written.'"));
doc.add(new Paragraph("Header #2").heading1().pageBreakBefore());
doc.add(new Paragraph("I'm another text very nicely written.'"));
doc.add(new Paragraph("Header #2.1").heading2());
doc.add(new Paragraph("I'm another text very nicely written.'"));
doc.add(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pageBreakBefore());
```
### Complete example
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo28.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo28.ts_

View File

@ -1,306 +1,387 @@
# Tables # Tables
You can create tables with `docx`. More information can be found [here](http://officeopenxml.com/WPtable.php). !> Paragraphs requires an understanding of [Sections](usage/sections.md).
## Create Table ## Intro
To create a table, simply create one with `new Table()`, then add it to the document: `doc.add()`. * `Tables` contain a list of `Rows`
* `Rows` contain a list of `TableCells`
* `TableCells` contain a list of `Parahraphs` and/or `Tables`. You can add `Tables` as tables can be nested inside each other
```ts Create a simple table like so:
const table = doc.add(new Table({
rows: [NUMBER OF ROWS],
columns: [NUMBER OF COLUMNS]
});
```
Alternatively, you can create a table object directly, and then add it in the `document`
```ts
const table = new Table(4, 4);
doc.add(table);
```
The snippet below creates a table of 2 rows and 4 columns.
```ts ```ts
const table = new Table({ const table = new Table({
rows: 2, rows: [Array of `TableRow`s]
columns: 4,
}); });
doc.add(table);
``` ```
## Rows and Columns Then add the table in the `section`
You can get a row or a column from a table like so, where `index` is a number:
### Get Row
```ts ```ts
const row = doc.getRow(index); doc.addSection({
children: [table],
});
``` ```
With this, you can merge a row by using the `mergeCells()` method, where `startIndex` is the row number you want to merge from, and `endIndex` is where you want it to merge to: ## Table
### Set Width
```ts ```ts
row.mergeCells(startIndex, endIndex); const table = new Table({
...,
width: {
size: [TABLE_WIDTH],
type: WidthType,
}
});
``` ```
You can get a cell from a `row` by using the `getCell()` method, where `index` is the row index:
```ts
row.getCell(index);
```
### Get Column
```ts
const column = doc.getColumn(index);
```
Again, you can merge a row by using the `mergeCells()` method, where `startIndex` is the row number you want to merge from, and `endIndex` is where you want it to merge to:
```ts
column.mergeCells(startIndex, endIndex);
```
You can get a cell from a `column` by using the `getCell()` method, where `index` is the column index:
```ts
column.getCell(index);
```
## Cells
To access the cell, use the `getCell()` method.
```ts
const cell = table.getCell([ROW INDEX], [COLUMN INDEX]);
```
You can also get a cell from a `column` or a `row` with `getCell()`, mentioned previously.
For example: For example:
```ts ```ts
const cell = table.getCell(0, 2);
const cell = row.getCell(0); const table = new Table({
...,
width: {
size: 4535,
type: WidthType.DXA,
}
});
```
const cell = column.getCell(2); ### Pagination
#### Prevent row pagination
To prevent breaking contents of a row across multiple pages, call `cantSplit`:
```ts
const table = new Table({
rows: [],
cantSplit: true,
});
```
## Table Row
A table consists of multiple `table rows`. Table rows have a list of `children` which accepts a list of `table cells` explained below. You can create a simple `table row` like so:
```ts
const tableRow = new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
});
```
Or preferably, add the tableRow directly into the `table` without declaring a variable:
```ts
const table = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
});
```
### Options
Here is a list of options you can add to the `table row`:
| Property | Type | Notes |
| ----------- | ------------------------------------- | -------- |
| children | `Array<TableCell>` | Required |
| cantSplit | `boolean` | Optional |
| tableHeader | `boolean` | Optional |
| height | `{ value: number, rule: HeightRule }` | Optional |
### Repeat row
If a table is paginated on multiple pages, it is possible to repeat a row at the top of each new page by setting `tableHeader` to `true`:
```ts
const row = new TableRow({
...,
tableHeader: true,
});
```
## Table Cells
Cells need to be added in the `table row`, you can create a table cell like:
```ts
const tableCell = new TableCell({
children: [new Paragraph("hello")],
});
```
Or preferably, add the tableRow directly into the `table row` without declaring a variable:
```ts
const tableRow = new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
});
```
### Options
| Property | Type | Notes |
| ------------- | ----------------------------------- | ----------------------------------------------------------- |
| children | `Array<Paragraph | Table>` | Required. You can nest tables by adding a table into a cell |
| shading | `ITableShadingAttributesProperties` | Optional |
| margins | `ITableCellMarginOptions` | Optional |
| verticalAlign | `VerticalAlign` | Optional |
| columnSpan | `number` | Optional |
| rowSpan | `number` | Optional |
| borders | `BorderOptions` | Optional |
| width | `{ size: number type: WidthType }` | Optional |
#### Border Options
| Property | Type | Notes |
| -------- | ----------------------------------------------------- | -------- |
| top | `{ style: BorderStyle, size: number, color: string }` | Optional |
| bottom | `{ style: BorderStyle, size: number, color: string }` | Optional |
| left | `{ style: BorderStyle, size: number, color: string }` | Optional |
| right | `{ style: BorderStyle, size: number, color: string }` | Optional |
##### Example
```ts
const cell = new TableCell({
...,
borders: {
top: {
style: BorderStyle.DASH_DOT_STROKED,
size: 1,
color: "red",
},
bottom: {
style: BorderStyle.THICK_THIN_MEDIUM_GAP,
size: 5,
color: "889900",
},
},
});
```
##### Google DOCS
Google DOCS does not support start and end borders, instead they use left and right borders. So to set left and right borders for Google DOCS you should use:
```ts
const cell = new TableCell({
...,
borders: {
top: {
style: BorderStyle.DOT_DOT_DASH,
size: 3,
color: "green",
},
bottom: {
style: BorderStyle.DOT_DOT_DASH,
size: 3,
color: "ff8000",
},
},
});
``` ```
### Add paragraph to a cell ### Add paragraph to a cell
Once you have got the cell, you can add data to it with the `add()` method. Once you have got the cell, you can add data to it:
```ts ```ts
cell.add(new Paragraph("Hello")); const cell = new TableCell({
children: [new Paragraph("Hello")],
});
``` ```
### Set width of a cell ### Set width of a cell
You can specify the width of a cell using: You can specify the width of a cell using:
`cell.Properties.setWidth(width, format)` ```ts
const cell = new TableCell({
...,
width: {
size: number,
type: WidthType,
},
});
```
format can be: `WidthType` values can be:
- WidthType.AUTO | Property | Notes |
- WidthType.DXA: value is in twentieths of a point | -------- | --------------------------------- |
- WidthType.NIL: is considered as zero | AUTO | |
- WidthType.PCT: percent of table width | DXA | value is in twentieths of a point |
| NIL | is considered as zero |
| PCT | percent of table width |
### Example #### Example
```ts ```ts
cell.Properties.setWidth(100, WidthType.DXA); cell.Properties.setWidth(100, WidthType.DXA);
``` ```
```ts ### Nested Tables
cell.Properties.setWidth("50%", WidthType.PCT);
```
## Borders To have a table within a table, simply add it in the `children` block of a `table cell`:
BorderStyle can be imported from `docx`. Size determines the thickness. HTML color can be a hex code or alias such as `red`.
```ts ```ts
cell.Borders.addTopBorder([BorderStyle], [SIZE], [HTML COLOR]); const cell = new TableCell({
children: [new Table(...)],
});
``` ```
```ts ### Vertical Align
cell.Borders.addBottomBorder([BorderStyle], [SIZE], [HTML COLOR]);
```
```ts
cell.Borders.addStartBorder([[BorderStyle]], [SIZE], [HTML COLOR]);
```
```ts
cell.Borders.addEndBorder([BorderStyle], [SIZE], [HTML COLOR]);
```
### Example
```ts
import { BorderStyle } from "docx";
cell.Borders.addStartBorder(BorderStyle.DOT_DOT_DASH, 3, "green");
cell.Borders.addEndBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000");
```
### Google DOCS
Google DOCS does not support start and end borders, instead they use left and right borders. So to set left and right borders for Google DOCS you should use:
```ts
import { BorderStyle } from "docx";
cell.Borders.addLeftBorder(BorderStyle.DOT_DOT_DASH, 3, "green");
cell.Borders.addRightBorder(BorderStyle.DOT_DOT_DASH, 3, "#ff8000");
```
## Set Width
```ts
import { WidthType } from "docx";
table.setWidth([WIDTH], [OPTIONAL WidthType. Defaults to DXA]);
```
For example:
```ts
table.setWidth(4535, WidthType.DXA);
```
## Vertical Align
Sets the vertical alignment of the contents of the cell Sets the vertical alignment of the contents of the cell
```ts ```ts
import { VerticalAlign } from "docx"; const cell = new TableCell({
...,
cell.setVerticalAlign([VerticalAlign TYPE]); verticalAlign: VerticalAlign,
});
``` ```
`VerticalAlign` values can be:
| Property | Notes |
| -------- | ------------------------------------------ |
| BOTTOM | Align the contents on the bottom |
| CENTER | Align the contents on the center |
| TOP | Align the contents on the top. The default |
For example, to center align a cell: For example, to center align a cell:
```ts ```ts
cell.setVerticalAlign(VerticalAlign.CENTER); const cell = new TableCell({
verticalAlign: VerticalAlign.CENTER,
});
``` ```
## Rows ## Merging cells together
To get a row, use the `getRow` method on a `table`. There are a handful of methods which you can apply to a row which will be explained below. ### Row Merge
When cell rows are merged, it counts as multiple rows, so be sure to remove excess cells. It is similar to how HTML's `rowspan` works.
https://www.w3schools.com/tags/att_td_rowspan.asp
```ts ```ts
table.getRow([ROW INDEX]); const cell = new TableCell({
``` ...,
rowSpan: [NUMBER_OF_CELLS_TO_MERGE],
## Merge cells together });
### Merging on a row
First obtain the row, and call `mergeCells()`. The first argument is where the merge should start. The second argument is where the merge should end.
```ts
table.getRow(0).mergeCells([FROM INDEX], [TO INDEX]);
``` ```
#### Example #### Example
This will merge 3 cells together starting from index `0`: The example will merge three rows together.
```ts ```ts
table.getRow(0).mergeCells(0, 2); const cell = new TableCell({
...,
rowSpan: 3,
});
``` ```
### Merging on a column ### Column Merge
It has not been implemented yet, but it will follow a similar structure as merging a row. When cell columns are merged, it counts as multiple columns, so be sure to remove excess cells. It is similar to how HTML's `colspan` works.
https://www.w3schools.com/tags/att_td_colspan.asp
## Nested Tables
To have a table within a table
```ts ```ts
cell.add(new Table(1, 1)); const cell = new TableCell({
...,
columnSpan: [NUMBER_OF_CELLS_TO_MERGE],
});
``` ```
## Pagination #### Example
###Prevent row pagination The example will merge three columns together.
To prevent breaking contents of a row across multiple pages, call `cantSplit()`:
```ts ```ts
table.getRow(0).setCantSplit(); const cell = new TableCell({
``` ...,
columnSpan: 3,
###Repeat row });
If a table is paginated on multiple pages, it is possible to repeat a row at the top of each new page calling `setTableHeader()`:
```ts
table.getRow(0).setTableHeader();
``` ```
## Examples ## Examples
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo4.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/4-basic-table.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo4.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/4-basic-table.ts_
### Custom borders ### Custom borders
Example showing how to add colourful borders to tables Example showing how to add colourful borders to tables
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo20.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/20-table-cell-borders.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo20.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/20-table-cell-borders.ts_
### Adding images ### Adding images
Example showing how to add images to tables Example showing how to add images to tables
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo24.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/24-images-to-table-cell.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo24.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/24-images-to-table-cell.ts_
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo36.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/36-image-to-table-cell.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo36.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/36-image-to-table-cell.ts_
### Alignment of text in a cell ### Alignment of text in a cell
Example showing how align text in a table cell Example showing how align text in a table cell
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo31.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/31-tables.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo31.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/31-tables.ts_
### Merging rows ### Shading
Example showing merging of `rows` Example showing merging of columns and rows and shading
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo32.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/32-merge-and-shade-table-cells.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo32.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/32-merge-and-shade-table-cells.ts_
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo41.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/41-merge-table-cells-2.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo41.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/41-merge-table-cells-2.ts_
### Merging columns ### Merging columns
Example showing merging of `columns` Example showing merging of columns and rows
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo43.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/43-images-to-table-cell-2.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo43.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/43-images-to-table-cell-2.ts_
### Floating tables ### Floating tables
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo34.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/34-floating-tables.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo34.ts_ _Source: https://github.com/dolanmiu/docx/blob/master/demo/34-floating-tables.ts_

View File

@ -68,6 +68,15 @@ const text = new TextRun({
}); });
``` ```
### Emphasis Mark
```ts
const text = new TextRun({
text: "and then emphasis mark",
emphasisMark: {},
});
```
### Strike through ### Strike through
```ts ```ts

8453
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "docx", "name": "docx",
"version": "5.0.0-rc5", "version": "5.2.2",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)", "description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.",
"main": "build/index.js", "main": "build/index.js",
"scripts": { "scripts": {
"pretest": "rimraf ./build", "pretest": "rimraf ./build",
@ -50,7 +50,9 @@
"types": "./build/index.d.ts", "types": "./build/index.d.ts",
"dependencies": { "dependencies": {
"@types/jszip": "^3.1.4", "@types/jszip": "^3.1.4",
"@types/node": "^14.0.5",
"jszip": "^3.1.5", "jszip": "^3.1.5",
"shortid": "^2.2.15",
"xml": "^1.0.1", "xml": "^1.0.1",
"xml-js": "^1.6.8" "xml-js": "^1.6.8"
}, },
@ -64,6 +66,7 @@
"@types/chai": "^3.4.35", "@types/chai": "^3.4.35",
"@types/mocha": "^2.2.39", "@types/mocha": "^2.2.39",
"@types/request-promise": "^4.1.42", "@types/request-promise": "^4.1.42",
"@types/shortid": "0.0.29",
"@types/sinon": "^4.3.1", "@types/sinon": "^4.3.1",
"@types/webpack": "^4.4.24", "@types/webpack": "^4.4.24",
"awesome-typescript-loader": "^3.4.1", "awesome-typescript-loader": "^3.4.1",
@ -74,7 +77,7 @@
"jszip": "^3.1.5", "jszip": "^3.1.5",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mocha-webpack": "^1.0.1", "mocha-webpack": "^1.0.1",
"nyc": "^13.1.0", "nyc": "^14.1.1",
"pre-commit": "^1.2.2", "pre-commit": "^1.2.2",
"prettier": "^1.15.2", "prettier": "^1.15.2",
"prompt": "^1.0.0", "prompt": "^1.0.0",

View File

@ -1,7 +1,8 @@
import { assert, expect } from "chai"; import { assert, expect } from "chai";
import * as sinon from "sinon";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import * as file from "file"; import { Paragraph, TextRun } from "file";
import { CoreProperties } from "file/core-properties"; import { CoreProperties } from "file/core-properties";
import { Attributes } from "file/xml-components"; import { Attributes } from "file/xml-components";
@ -14,28 +15,65 @@ describe("Formatter", () => {
describe("#format()", () => { describe("#format()", () => {
it("should format simple paragraph", () => { it("should format simple paragraph", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph("");
const newJson = formatter.format(paragraph); const newJson = formatter.format(paragraph);
assert.isDefined(newJson["w:p"]); assert.isDefined(newJson["w:p"]);
}); });
it("should remove xmlKeys", () => { it("should remove xmlKeys", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph("");
const newJson = formatter.format(paragraph); const newJson = formatter.format(paragraph);
const stringifiedJson = JSON.stringify(newJson); const stringifiedJson = JSON.stringify(newJson);
assert(stringifiedJson.indexOf("xmlKeys") < 0); assert(stringifiedJson.indexOf("xmlKeys") < 0);
}); });
it("should format simple paragraph with bold text", () => { it("should format simple paragraph with bold text", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph({
paragraph.addRun( children: [
new file.TextRun({ new TextRun({
text: "test", text: "test",
bold: true, bold: true,
}), }),
); ],
const newJson = formatter.format(paragraph); });
assert.isDefined(newJson["w:p"][1]["w:r"][0]["w:rPr"][0]["w:b"]._attr["w:val"]);
const tree = formatter.format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:r": [
{
"w:rPr": [
{
"w:b": {
_attr: {
"w:val": true,
},
},
},
{
"w:bCs": {
_attr: {
"w:val": true,
},
},
},
],
},
{
"w:t": [
{
_attr: {
"xml:space": "preserve",
},
},
"test",
],
},
],
},
],
});
}); });
it("should format attributes (rsidSect)", () => { it("should format attributes (rsidSect)", () => {
@ -63,7 +101,7 @@ describe("Formatter", () => {
}); });
it("should should change 'p' tag into 'w:p' tag", () => { it("should should change 'p' tag into 'w:p' tag", () => {
const paragraph = new file.Paragraph(""); const paragraph = new Paragraph("");
const newJson = formatter.format(paragraph); const newJson = formatter.format(paragraph);
assert.isDefined(newJson["w:p"]); assert.isDefined(newJson["w:p"]);
}); });
@ -76,5 +114,13 @@ describe("Formatter", () => {
const newJson = formatter.format(properties); const newJson = formatter.format(properties);
assert.isDefined(newJson["cp:coreProperties"]); assert.isDefined(newJson["cp:coreProperties"]);
}); });
it("should call the prep method only once", () => {
const paragraph = new Paragraph("");
const spy = sinon.spy(paragraph, "prepForXml");
formatter.format(paragraph);
expect(spy.calledOnce).to.equal(true);
});
}); });
}); });

View File

@ -1,8 +1,9 @@
import { BaseXmlComponent, IXmlableObject } from "file/xml-components"; import { BaseXmlComponent, IXmlableObject } from "file/xml-components";
import { File } from "../file";
export class Formatter { export class Formatter {
public format(input: BaseXmlComponent): IXmlableObject { public format(input: BaseXmlComponent, file?: File): IXmlableObject {
const output = input.prepForXml(); const output = input.prepForXml(file);
if (output) { if (output) {
return output; return output;

View File

@ -5,7 +5,7 @@ export class ImageReplacer {
let currentXmlData = xmlData; let currentXmlData = xmlData;
mediaData.forEach((image, i) => { mediaData.forEach((image, i) => {
currentXmlData = currentXmlData.replace(`{${image.fileName}}`, (offset + i).toString()); currentXmlData = currentXmlData.replace(new RegExp(`{${image.fileName}}`, "g"), (offset + i).toString());
}); });
return currentXmlData; return currentXmlData;

View File

@ -1,7 +1,8 @@
/* tslint:disable:typedef space-before-function-paren */ /* tslint:disable:typedef space-before-function-paren */
import { expect } from "chai"; import { expect } from "chai";
import * as sinon from "sinon";
import { File, Footer, Header } from "file"; import { File, Footer, Header, Paragraph } from "file";
import { Compiler } from "./next-compiler"; import { Compiler } from "./next-compiler";
@ -72,5 +73,22 @@ describe("Compiler", () => {
expect(fileNames).to.include("word/footer2.xml"); expect(fileNames).to.include("word/footer2.xml");
expect(fileNames).to.include("word/_rels/footer2.xml.rels"); expect(fileNames).to.include("word/_rels/footer2.xml.rels");
}); });
it("should call the format method X times equalling X files to be formatted", () => {
// This test is required because before, there was a case where Document was formatted twice, which was inefficient
// This also caused issues such as running prepForXml multiple times as format() was ran multiple times.
const paragraph = new Paragraph("");
const doc = new File();
doc.addSection({
properties: {},
children: [paragraph],
});
// tslint:disable-next-line: no-string-literal
const spy = sinon.spy(compiler["formatter"], "format");
compiler.compile(file);
expect(spy.callCount).to.equal(10);
});
}); });
}); });

View File

@ -4,6 +4,7 @@ import * as xml from "xml";
import { File } from "file"; import { File } from "file";
import { Formatter } from "../formatter"; import { Formatter } from "../formatter";
import { ImageReplacer } from "./image-replacer"; import { ImageReplacer } from "./image-replacer";
import { NumberingReplacer } from "./numbering-replacer";
interface IXmlifyedFile { interface IXmlifyedFile {
readonly data: string; readonly data: string;
@ -30,10 +31,12 @@ interface IXmlifyedFileMapping {
export class Compiler { export class Compiler {
private readonly formatter: Formatter; private readonly formatter: Formatter;
private readonly imageReplacer: ImageReplacer; private readonly imageReplacer: ImageReplacer;
private readonly numberingReplacer: NumberingReplacer;
constructor() { constructor() {
this.formatter = new Formatter(); this.formatter = new Formatter();
this.imageReplacer = new ImageReplacer(); this.imageReplacer = new ImageReplacer();
this.numberingReplacer = new NumberingReplacer();
} }
public compile(file: File, prettifyXml?: boolean): JSZip { public compile(file: File, prettifyXml?: boolean): JSZip {
@ -68,13 +71,13 @@ export class Compiler {
file.verifyUpdateFields(); file.verifyUpdateFields();
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1; const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
const documentXmlData = xml(this.formatter.format(file.Document, file), prettify);
const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media);
return { return {
Relationships: { Relationships: {
data: (() => { data: (() => {
const xmlData = xml(this.formatter.format(file.Document), prettify); documentMediaDatas.forEach((mediaData, i) => {
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
file.DocumentRelationships.createRelationship( file.DocumentRelationships.createRelationship(
documentRelationshipCount + i, documentRelationshipCount + i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
@ -82,26 +85,25 @@ export class Compiler {
); );
}); });
return xml(this.formatter.format(file.DocumentRelationships), prettify); return xml(this.formatter.format(file.DocumentRelationships, file), prettify);
})(), })(),
path: "word/_rels/document.xml.rels", path: "word/_rels/document.xml.rels",
}, },
Document: { Document: {
data: (() => { data: (() => {
const tempXmlData = xml(this.formatter.format(file.Document), prettify); const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); const referenedXmlData = this.numberingReplacer.replace(xmlData, file.Numbering.ConcreteNumbering);
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, documentRelationshipCount);
return xmlData; return referenedXmlData;
})(), })(),
path: "word/document.xml", path: "word/document.xml",
}, },
Styles: { Styles: {
data: xml(this.formatter.format(file.Styles), prettify), data: xml(this.formatter.format(file.Styles, file), prettify),
path: "word/styles.xml", path: "word/styles.xml",
}, },
Properties: { Properties: {
data: xml(this.formatter.format(file.CoreProperties), { data: xml(this.formatter.format(file.CoreProperties, file), {
declaration: { declaration: {
standalone: "yes", standalone: "yes",
encoding: "UTF-8", encoding: "UTF-8",
@ -110,15 +112,15 @@ export class Compiler {
path: "docProps/core.xml", path: "docProps/core.xml",
}, },
Numbering: { Numbering: {
data: xml(this.formatter.format(file.Numbering), prettify), data: xml(this.formatter.format(file.Numbering, file), prettify),
path: "word/numbering.xml", path: "word/numbering.xml",
}, },
FileRelationships: { FileRelationships: {
data: xml(this.formatter.format(file.FileRelationships), prettify), data: xml(this.formatter.format(file.FileRelationships, file), prettify),
path: "_rels/.rels", path: "_rels/.rels",
}, },
HeaderRelationships: file.Headers.map((headerWrapper, index) => { HeaderRelationships: file.Headers.map((headerWrapper, index) => {
const xmlData = xml(this.formatter.format(headerWrapper.Header), prettify); const xmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => { mediaDatas.forEach((mediaData, i) => {
@ -130,12 +132,12 @@ export class Compiler {
}); });
return { return {
data: xml(this.formatter.format(headerWrapper.Relationships), prettify), data: xml(this.formatter.format(headerWrapper.Relationships, file), prettify),
path: `word/_rels/header${index + 1}.xml.rels`, path: `word/_rels/header${index + 1}.xml.rels`,
}; };
}), }),
FooterRelationships: file.Footers.map((footerWrapper, index) => { FooterRelationships: file.Footers.map((footerWrapper, index) => {
const xmlData = xml(this.formatter.format(footerWrapper.Footer), prettify); const xmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => { mediaDatas.forEach((mediaData, i) => {
@ -147,12 +149,12 @@ export class Compiler {
}); });
return { return {
data: xml(this.formatter.format(footerWrapper.Relationships), prettify), data: xml(this.formatter.format(footerWrapper.Relationships, file), prettify),
path: `word/_rels/footer${index + 1}.xml.rels`, path: `word/_rels/footer${index + 1}.xml.rels`,
}; };
}), }),
Headers: file.Headers.map((headerWrapper, index) => { Headers: file.Headers.map((headerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(headerWrapper.Header), prettify); const tempXmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own // TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0); const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
@ -163,7 +165,7 @@ export class Compiler {
}; };
}), }),
Footers: file.Footers.map((footerWrapper, index) => { Footers: file.Footers.map((footerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(footerWrapper.Footer), prettify); const tempXmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify);
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media); const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
// TODO: 0 needs to be changed when headers get relationships of their own // TODO: 0 needs to be changed when headers get relationships of their own
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0); const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
@ -174,19 +176,19 @@ export class Compiler {
}; };
}), }),
ContentTypes: { ContentTypes: {
data: xml(this.formatter.format(file.ContentTypes), prettify), data: xml(this.formatter.format(file.ContentTypes, file), prettify),
path: "[Content_Types].xml", path: "[Content_Types].xml",
}, },
AppProperties: { AppProperties: {
data: xml(this.formatter.format(file.AppProperties), prettify), data: xml(this.formatter.format(file.AppProperties, file), prettify),
path: "docProps/app.xml", path: "docProps/app.xml",
}, },
FootNotes: { FootNotes: {
data: xml(this.formatter.format(file.FootNotes), prettify), data: xml(this.formatter.format(file.FootNotes, file), prettify),
path: "word/footnotes.xml", path: "word/footnotes.xml",
}, },
Settings: { Settings: {
data: xml(this.formatter.format(file.Settings), prettify), data: xml(this.formatter.format(file.Settings, file), prettify),
path: "word/settings.xml", path: "word/settings.xml",
}, },
}; };

View File

@ -0,0 +1,17 @@
import { ConcreteNumbering } from "file";
export class NumberingReplacer {
public replace(xmlData: string, concreteNumberings: ConcreteNumbering[]): string {
let currentXmlData = xmlData;
for (const concreteNumbering of concreteNumberings) {
if (!concreteNumbering.reference) {
continue;
}
currentXmlData = currentXmlData.replace(new RegExp(`{${concreteNumbering.reference}}`, "g"), concreteNumbering.id.toString());
}
return currentXmlData;
}
}

View File

@ -4,30 +4,33 @@ import { Compiler } from "./next-compiler";
export class Packer { export class Packer {
public static async toBuffer(file: File, prettify?: boolean): Promise<Buffer> { public static async toBuffer(file: File, prettify?: boolean): Promise<Buffer> {
const zip = this.compiler.compile(file, prettify); const zip = this.compiler.compile(file, prettify);
const zipData = (await zip.generateAsync({ const zipData = await zip.generateAsync({
type: "nodebuffer", type: "nodebuffer",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
})) as Buffer; compression: "DEFLATE",
});
return zipData; return zipData;
} }
public static async toBase64String(file: File, prettify?: boolean): Promise<string> { public static async toBase64String(file: File, prettify?: boolean): Promise<string> {
const zip = this.compiler.compile(file, prettify); const zip = this.compiler.compile(file, prettify);
const zipData = (await zip.generateAsync({ const zipData = await zip.generateAsync({
type: "base64", type: "base64",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
})) as string; compression: "DEFLATE",
});
return zipData; return zipData;
} }
public static async toBlob(file: File, prettify?: boolean): Promise<Blob> { public static async toBlob(file: File, prettify?: boolean): Promise<Blob> {
const zip = this.compiler.compile(file, prettify); const zip = this.compiler.compile(file, prettify);
const zipData = (await zip.generateAsync({ const zipData = await zip.generateAsync({
type: "blob", type: "blob",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
})) as Blob; compression: "DEFLATE",
});
return zipData; return zipData;
} }

View File

@ -1,7 +1,22 @@
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes"; import { DocumentAttributes } from "../document/document-attributes";
import { INumberingOptions } from "../numbering";
import { HyperlinkType, Paragraph } from "../paragraph";
import { IStylesOptions } from "../styles";
import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components";
export interface IInternalHyperlinkDefinition {
readonly text: string;
readonly type: HyperlinkType.INTERNAL;
}
export interface IExternalHyperlinkDefinition {
readonly link: string;
readonly text: string;
readonly type: HyperlinkType.EXTERNAL;
}
export interface IPropertiesOptions { export interface IPropertiesOptions {
readonly title?: string; readonly title?: string;
readonly subject?: string; readonly subject?: string;
@ -11,6 +26,12 @@ export interface IPropertiesOptions {
readonly lastModifiedBy?: string; readonly lastModifiedBy?: string;
readonly revision?: string; readonly revision?: string;
readonly externalStyles?: string; readonly externalStyles?: string;
readonly styles?: IStylesOptions;
readonly numbering?: INumberingOptions;
readonly footnotes?: Paragraph[];
readonly hyperlinks?: {
readonly [key: string]: IInternalHyperlinkDefinition | IExternalHyperlinkDefinition;
};
} }
export class CoreProperties extends XmlComponent { export class CoreProperties extends XmlComponent {

View File

@ -22,9 +22,6 @@ describe("Body", () => {
expect(tree).to.deep.equal({ expect(tree).to.deep.equal({
"w:body": [ "w:body": [
{
"w:p": {},
},
{ {
"w:sectPr": [ "w:sectPr": [
{ "w:pgSz": { _attr: { "w:w": 10000, "w:h": 10000, "w:orient": "portrait" } } }, { "w:pgSz": { _attr: { "w:w": 10000, "w:h": 10000, "w:orient": "portrait" } } },

View File

@ -1,5 +1,6 @@
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Paragraph, ParagraphProperties, TableOfContents } from "../.."; import { Paragraph, ParagraphProperties, TableOfContents } from "../..";
import { File } from "../../../file";
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties"; import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
export class Body extends XmlComponent { export class Body extends XmlComponent {
@ -24,12 +25,13 @@ export class Body extends XmlComponent {
this.sections.push(new SectionProperties(options)); this.sections.push(new SectionProperties(options));
} }
public prepForXml(): IXmlableObject | undefined { public prepForXml(file?: File): IXmlableObject | undefined {
if (this.sections.length === 1) { if (this.sections.length === 1) {
this.root.splice(0, 1);
this.root.push(this.sections.pop() as SectionProperties); this.root.push(this.sections.pop() as SectionProperties);
} }
return super.prepForXml(); return super.prepForXml(file);
} }
public push(component: XmlComponent): void { public push(component: XmlComponent): void {

View File

@ -0,0 +1,42 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { FooterReference } from "./footer-reference";
import { FooterReferenceType } from "./footer-reference-attributes";
describe("footerReference", () => {
it("should create", () => {
const footer = new FooterReference({
footerType: FooterReferenceType.DEFAULT,
footerId: 1,
});
const tree = new Formatter().format(footer);
expect(tree).to.deep.equal({
"w:footerReference": {
_attr: {
"r:id": "rId1",
"w:type": "default",
},
},
});
});
it("should create without a footer type", () => {
const footer = new FooterReference({
footerId: 1,
});
const tree = new Formatter().format(footer);
expect(tree).to.deep.equal({
"w:footerReference": {
_attr: {
"r:id": "rId1",
"w:type": "default",
},
},
});
});
});

View File

@ -0,0 +1,42 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { HeaderReference } from "./header-reference";
import { HeaderReferenceType } from "./header-reference-attributes";
describe("HeaderReference", () => {
it("should create", () => {
const footer = new HeaderReference({
headerType: HeaderReferenceType.DEFAULT,
headerId: 1,
});
const tree = new Formatter().format(footer);
expect(tree).to.deep.equal({
"w:headerReference": {
_attr: {
"r:id": "rId1",
"w:type": "default",
},
},
});
});
it("should create without a header type", () => {
const footer = new HeaderReference({
headerId: 1,
});
const tree = new Formatter().format(footer);
expect(tree).to.deep.equal({
"w:headerReference": {
_attr: {
"r:id": "rId1",
"w:type": "default",
},
},
});
});
});

View File

@ -5,3 +5,4 @@ export * from "./page-size";
export * from "./page-number"; export * from "./page-number";
export * from "./page-border"; export * from "./page-border";
export * from "./line-number"; export * from "./line-number";
export * from "./vertical-align";

View File

@ -8,6 +8,7 @@ import { Media } from "file/media";
import { PageBorderOffsetFrom } from "./page-border"; import { PageBorderOffsetFrom } from "./page-border";
import { PageNumberFormat } from "./page-number"; import { PageNumberFormat } from "./page-number";
import { SectionProperties } from "./section-properties"; import { SectionProperties } from "./section-properties";
import { SectionVerticalAlignValue } from "./vertical-align";
describe("SectionProperties", () => { describe("SectionProperties", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
@ -38,6 +39,8 @@ describe("SectionProperties", () => {
}, },
pageNumberStart: 10, pageNumberStart: 10,
pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT, pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT,
titlePage: true,
verticalAlign: SectionVerticalAlignValue.TOP,
}); });
const tree = new Formatter().format(properties); const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]); expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);

View File

@ -18,6 +18,7 @@ import { IPageNumberTypeAttributes, PageNumberType } from "./page-number";
import { PageSize } from "./page-size/page-size"; import { PageSize } from "./page-size/page-size";
import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes"; import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes";
import { TitlePage } from "./title-page/title-page"; import { TitlePage } from "./title-page/title-page";
import { ISectionVerticalAlignAttributes, SectionVerticalAlign } from "./vertical-align";
export interface IHeaderFooterGroup<T> { export interface IHeaderFooterGroup<T> {
readonly default?: T; readonly default?: T;
@ -45,7 +46,8 @@ export type SectionPropertiesOptions = IPageSizeAttributes &
IPageNumberTypeAttributes & IPageNumberTypeAttributes &
ILineNumberAttributes & ILineNumberAttributes &
IPageBordersOptions & IPageBordersOptions &
ITitlePageOptions & { ITitlePageOptions &
ISectionVerticalAlignAttributes & {
readonly column?: { readonly column?: {
readonly space?: number; readonly space?: number;
readonly count?: number; readonly count?: number;
@ -87,6 +89,7 @@ export class SectionProperties extends XmlComponent {
pageBorderBottom, pageBorderBottom,
pageBorderLeft, pageBorderLeft,
titlePage = false, titlePage = false,
verticalAlign,
} = options; } = options;
this.options = options; this.options = options;
@ -121,6 +124,10 @@ export class SectionProperties extends XmlComponent {
if (titlePage) { if (titlePage) {
this.root.push(new TitlePage()); this.root.push(new TitlePage());
} }
if (verticalAlign) {
this.root.push(new SectionVerticalAlign(verticalAlign));
}
} }
private addHeaders(headers?: IHeaderFooterGroup<HeaderWrapper>): void { private addHeaders(headers?: IHeaderFooterGroup<HeaderWrapper>): void {

View File

@ -0,0 +1,2 @@
export * from "./vertical-align";
export * from "./vertical-align-attributes";

View File

@ -0,0 +1,12 @@
import { XmlAttributeComponent } from "file/xml-components";
import { SectionVerticalAlignValue } from "./vertical-align";
export interface ISectionVerticalAlignAttributes {
readonly verticalAlign?: SectionVerticalAlignValue;
}
export class SectionVerticalAlignAttributes extends XmlAttributeComponent<ISectionVerticalAlignAttributes> {
protected readonly xmlKeys = {
verticalAlign: "w:val",
};
}

View File

@ -0,0 +1,18 @@
// http://officeopenxml.com/WPsection.php
import { XmlComponent } from "file/xml-components";
import { SectionVerticalAlignAttributes } from "./vertical-align-attributes";
export enum SectionVerticalAlignValue {
BOTH = "both",
BOTTOM = "bottom",
CENTER = "center",
TOP = "top",
}
export class SectionVerticalAlign extends XmlComponent {
constructor(value: SectionVerticalAlignValue) {
super("w:vAlign");
this.root.push(new SectionVerticalAlignAttributes({ verticalAlign: value }));
}
}

View File

@ -1,6 +1,6 @@
// http://officeopenxml.com/WPdocument.php // http://officeopenxml.com/WPdocument.php
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { Paragraph } from "../paragraph"; import { Hyperlink, Paragraph } from "../paragraph";
import { Table } from "../table"; import { Table } from "../table";
import { TableOfContents } from "../table-of-contents"; import { TableOfContents } from "../table-of-contents";
import { Body } from "./body"; import { Body } from "./body";
@ -36,7 +36,7 @@ export class Document extends XmlComponent {
this.root.push(this.body); this.root.push(this.body);
} }
public add(item: Paragraph | Table | TableOfContents): Document { public add(item: Paragraph | Table | TableOfContents | Hyperlink): Document {
this.body.push(item); this.body.push(item);
return this; return this;
} }

View File

@ -5,8 +5,8 @@ import { Formatter } from "export/formatter";
import { File } from "./file"; import { File } from "./file";
import { Footer, Header } from "./header"; import { Footer, Header } from "./header";
import { Paragraph } from "./paragraph"; import { HyperlinkRef, Paragraph } from "./paragraph";
import { Table } from "./table"; import { Table, TableCell, TableRow } from "./table";
import { TableOfContents } from "./table-of-contents"; import { TableOfContents } from "./table-of-contents";
describe("File", () => { describe("File", () => {
@ -20,8 +20,8 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
}); });
it("should create with correct headers and footers", () => { it("should create with correct headers and footers", () => {
@ -39,8 +39,8 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
}); });
it("should create with first headers and footers", () => { it("should create with first headers and footers", () => {
@ -58,8 +58,8 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first");
}); });
it("should create with correct headers", () => { it("should create with correct headers", () => {
@ -81,13 +81,98 @@ describe("File", () => {
const tree = new Formatter().format(doc.Document.Body); const tree = new Formatter().format(doc.Document.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even"); expect(tree["w:body"][0]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even");
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default"); expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first"); expect(tree["w:body"][0]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even"); expect(tree["w:body"][0]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even");
});
it("should add child", () => {
const doc = new File(undefined, undefined, [
{
children: [new Paragraph("test")],
},
]);
const tree = new Formatter().format(doc.Document.Body);
expect(tree).to.deep.equal({
"w:body": [
{
"w:p": [
{
"w:r": [
{
"w:t": [
{
_attr: {
"xml:space": "preserve",
},
},
"test",
],
},
],
},
],
},
{
"w:sectPr": [
{
"w:pgSz": {
_attr: {
"w:h": 16838,
"w:orient": "portrait",
"w:w": 11906,
},
},
},
{
"w:pgMar": {
_attr: {
"w:bottom": 1440,
"w:footer": 708,
"w:gutter": 0,
"w:header": 708,
"w:left": 1440,
"w:mirrorMargins": false,
"w:right": 1440,
"w:top": 1440,
},
},
},
{
"w:cols": {
_attr: {
"w:num": 1,
"w:space": 708,
},
},
},
{
"w:docGrid": {
_attr: {
"w:linePitch": 360,
},
},
},
],
},
],
});
});
it("should add hyperlink child", () => {
const doc = new File(undefined, undefined, [
{
children: [new HyperlinkRef("test")],
},
]);
expect(doc.HyperlinkCache).to.deep.equal({});
}); });
}); });
@ -102,14 +187,31 @@ describe("File", () => {
expect(spy.called).to.equal(true); expect(spy.called).to.equal(true);
}); });
it("should add hyperlink child", () => {
const doc = new File();
doc.addSection({
children: [new HyperlinkRef("test")],
});
expect(doc.HyperlinkCache).to.deep.equal({});
});
it("should call the underlying document's add when adding a Table", () => { it("should call the underlying document's add when adding a Table", () => {
const file = new File(); const file = new File();
const spy = sinon.spy(file.Document, "add"); const spy = sinon.spy(file.Document, "add");
file.addSection({ file.addSection({
children: [ children: [
new Table({ new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
}), }),
], ],
}); });
@ -141,13 +243,196 @@ describe("File", () => {
}); });
}); });
describe("#createFootnote", () => { describe("#HyperlinkCache", () => {
it("should call the underlying document's createFootnote", () => { it("should initially have empty hyperlink cache", () => {
const wrapper = new File(); const file = new File();
const spy = sinon.spy(wrapper.FootNotes, "createFootNote");
wrapper.createFootnote(new Paragraph(""));
expect(spy.called).to.equal(true); expect(file.HyperlinkCache).to.deep.equal({});
});
});
describe("#createFootnote", () => {
it("should create footnote", () => {
const wrapper = new File({
footnotes: [new Paragraph("hello")],
});
const tree = new Formatter().format(wrapper.FootNotes);
expect(tree).to.deep.equal({
"w:footnotes": [
{
_attr: {
"mc:Ignorable": "w14 w15 wp14",
"xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
"xmlns:o": "urn:schemas-microsoft-com:office:office",
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:v": "urn:schemas-microsoft-com:vml",
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w10": "urn:schemas-microsoft-com:office:word",
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
"xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml",
"xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
"xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
"xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
"xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
"xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
},
},
{
"w:footnote": [
{
_attr: {
"w:id": -1,
"w:type": "separator",
},
},
{
"w:p": [
{
"w:pPr": [
{
"w:spacing": {
_attr: {
"w:after": 0,
"w:line": 240,
"w:lineRule": "auto",
},
},
},
],
},
{
"w:r": [
{
"w:rPr": [
{
"w:rStyle": {
_attr: {
"w:val": "FootnoteReference",
},
},
},
],
},
{
"w:footnoteRef": {},
},
],
},
{
"w:r": [
{
"w:separator": {},
},
],
},
],
},
],
},
{
"w:footnote": [
{
_attr: {
"w:id": 0,
"w:type": "continuationSeparator",
},
},
{
"w:p": [
{
"w:pPr": [
{
"w:spacing": {
_attr: {
"w:after": 0,
"w:line": 240,
"w:lineRule": "auto",
},
},
},
],
},
{
"w:r": [
{
"w:rPr": [
{
"w:rStyle": {
_attr: {
"w:val": "FootnoteReference",
},
},
},
],
},
{
"w:footnoteRef": {},
},
],
},
{
"w:r": [
{
"w:continuationSeparator": {},
},
],
},
],
},
],
},
{
"w:footnote": [
{
_attr: {
"w:id": 1,
},
},
{
"w:p": [
{
"w:r": [
{
"w:rPr": [
{
"w:rStyle": {
_attr: {
"w:val": "FootnoteReference",
},
},
},
],
},
{
"w:footnoteRef": {},
},
],
},
{
"w:r": [
{
"w:t": [
{
_attr: {
"xml:space": "preserve",
},
},
"hello",
],
},
],
},
],
},
],
},
],
});
}); });
}); });
}); });

View File

@ -1,3 +1,4 @@
import * as shortid from "shortid";
import { AppProperties } from "./app-properties/app-properties"; import { AppProperties } from "./app-properties/app-properties";
import { ContentTypes } from "./content-types/content-types"; import { ContentTypes } from "./content-types/content-types";
import { CoreProperties, IPropertiesOptions } from "./core-properties"; import { CoreProperties, IPropertiesOptions } from "./core-properties";
@ -16,7 +17,7 @@ import { Footer, Header } from "./header";
import { HeaderWrapper, IDocumentHeader } from "./header-wrapper"; import { HeaderWrapper, IDocumentHeader } from "./header-wrapper";
import { Media } from "./media"; import { Media } from "./media";
import { Numbering } from "./numbering"; import { Numbering } from "./numbering";
import { Bookmark, Hyperlink, Paragraph } from "./paragraph"; import { Hyperlink, HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { TargetModeType } from "./relationships/relationship/relationship"; import { TargetModeType } from "./relationships/relationship/relationship";
import { Settings } from "./settings"; import { Settings } from "./settings";
@ -40,14 +41,12 @@ export interface ISectionOptions {
readonly size?: IPageSizeAttributes; readonly size?: IPageSizeAttributes;
readonly margins?: IPageMarginAttributes; readonly margins?: IPageMarginAttributes;
readonly properties?: SectionPropertiesOptions; readonly properties?: SectionPropertiesOptions;
readonly children: Array<Paragraph | Table | TableOfContents>; readonly children: Array<Paragraph | Table | TableOfContents | HyperlinkRef>;
} }
export class File { export class File {
// tslint:disable-next-line:readonly-keyword // tslint:disable-next-line:readonly-keyword
private currentRelationshipId: number = 1; private currentRelationshipId: number = 1;
// tslint:disable-next-line:readonly-keyword
private styles: Styles;
private readonly document: Document; private readonly document: Document;
private readonly headers: IDocumentHeader[] = []; private readonly headers: IDocumentHeader[] = [];
@ -61,6 +60,8 @@ export class File {
private readonly settings: Settings; private readonly settings: Settings;
private readonly contentTypes: ContentTypes; private readonly contentTypes: ContentTypes;
private readonly appProperties: AppProperties; private readonly appProperties: AppProperties;
private readonly styles: Styles;
private readonly hyperlinkCache: { readonly [key: string]: Hyperlink } = {};
constructor( constructor(
options: IPropertiesOptions = { options: IPropertiesOptions = {
@ -72,7 +73,13 @@ export class File {
sections: ISectionOptions[] = [], sections: ISectionOptions[] = [],
) { ) {
this.coreProperties = new CoreProperties(options); this.coreProperties = new CoreProperties(options);
this.numbering = new Numbering(); this.numbering = new Numbering(
options.numbering
? options.numbering
: {
config: [],
},
);
this.docRelationships = new Relationships(); this.docRelationships = new Relationships();
this.fileRelationships = new Relationships(); this.fileRelationships = new Relationships();
this.appProperties = new AppProperties(); this.appProperties = new AppProperties();
@ -97,9 +104,16 @@ export class File {
} else if (options.externalStyles) { } else if (options.externalStyles) {
const stylesFactory = new ExternalStylesFactory(); const stylesFactory = new ExternalStylesFactory();
this.styles = stylesFactory.newInstance(options.externalStyles); this.styles = stylesFactory.newInstance(options.externalStyles);
} else if (options.styles) {
const stylesFactory = new DefaultStylesFactory();
const defaultStyles = stylesFactory.newInstance();
this.styles = new Styles({
...defaultStyles,
...options.styles,
});
} else { } else {
const stylesFactory = new DefaultStylesFactory(); const stylesFactory = new DefaultStylesFactory();
this.styles = stylesFactory.newInstance(); this.styles = new Styles(stylesFactory.newInstance());
} }
this.addDefaultRelationships(); this.addDefaultRelationships();
@ -120,33 +134,42 @@ export class File {
this.document.Body.addSection(section.properties ? section.properties : {}); this.document.Body.addSection(section.properties ? section.properties : {});
for (const child of section.children) { for (const child of section.children) {
if (child instanceof HyperlinkRef) {
const hyperlink = this.hyperlinkCache[child.id];
this.document.add(hyperlink);
continue;
}
this.document.add(child); this.document.add(child);
} }
} }
}
public createHyperlink(link: string, text?: string): Hyperlink { if (options.footnotes) {
const newText = text === undefined ? link : text; for (const paragraph of options.footnotes) {
const hyperlink = new Hyperlink(newText, this.docRelationships.RelationshipCount); this.footNotes.createFootNote(paragraph);
this.docRelationships.createRelationship( }
hyperlink.linkId, }
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
link,
TargetModeType.EXTERNAL,
);
return hyperlink;
}
public createInternalHyperLink(anchor: string, text?: string): Hyperlink { if (options.hyperlinks) {
const newText = text === undefined ? anchor : text; const cache = {};
const hyperlink = new Hyperlink(newText, this.docRelationships.RelationshipCount, anchor);
// NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark
// we don't need to create a new relationship.
return hyperlink;
}
public createBookmark(name: string, text: string = name): Bookmark { for (const key in options.hyperlinks) {
return new Bookmark(name, text, this.docRelationships.RelationshipCount); if (!options.hyperlinks[key]) {
continue;
}
const hyperlinkRef = options.hyperlinks[key];
const hyperlink =
hyperlinkRef.type === HyperlinkType.EXTERNAL
? this.createHyperlink(hyperlinkRef.link, hyperlinkRef.text)
: this.createInternalHyperLink(key, hyperlinkRef.text);
cache[key] = hyperlink;
}
this.hyperlinkCache = cache;
}
} }
public addSection({ public addSection({
@ -174,20 +197,40 @@ export class File {
}); });
for (const child of children) { for (const child of children) {
if (child instanceof HyperlinkRef) {
const hyperlink = this.hyperlinkCache[child.id];
this.document.add(hyperlink);
continue;
}
this.document.add(child); this.document.add(child);
} }
} }
public createFootnote(paragraph: Paragraph): void {
this.footNotes.createFootNote(paragraph);
}
public verifyUpdateFields(): void { public verifyUpdateFields(): void {
if (this.document.getTablesOfContents().length) { if (this.document.getTablesOfContents().length) {
this.settings.addUpdateFields(); this.settings.addUpdateFields();
} }
} }
private createHyperlink(link: string, text: string = link): Hyperlink {
const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase());
this.docRelationships.createRelationship(
hyperlink.linkId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
link,
TargetModeType.EXTERNAL,
);
return hyperlink;
}
private createInternalHyperLink(anchor: string, text: string = anchor): Hyperlink {
const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase(), anchor);
// NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark
// we don't need to create a new relationship.
return hyperlink;
}
private createHeader(header: Header): HeaderWrapper { private createHeader(header: Header): HeaderWrapper {
const wrapper = new HeaderWrapper(this.media, this.currentRelationshipId++); const wrapper = new HeaderWrapper(this.media, this.currentRelationshipId++);
@ -277,10 +320,6 @@ export class File {
return this.styles; return this.styles;
} }
public set Styles(styles: Styles) {
this.styles = styles;
}
public get CoreProperties(): CoreProperties { public get CoreProperties(): CoreProperties {
return this.coreProperties; return this.coreProperties;
} }
@ -324,4 +363,8 @@ export class File {
public get Settings(): Settings { public get Settings(): Settings {
return this.settings; return this.settings;
} }
public get HyperlinkCache(): { readonly [key: string]: Hyperlink } {
return this.hyperlinkCache;
}
} }

View File

@ -4,7 +4,7 @@ import * as sinon from "sinon";
import { FooterWrapper } from "./footer-wrapper"; import { FooterWrapper } from "./footer-wrapper";
import { Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Table } from "./table"; import { Table, TableCell, TableRow } from "./table";
describe("FooterWrapper", () => { describe("FooterWrapper", () => {
describe("#add", () => { describe("#add", () => {
@ -21,22 +21,20 @@ describe("FooterWrapper", () => {
const spy = sinon.spy(file.Footer, "add"); const spy = sinon.spy(file.Footer, "add");
file.add( file.add(
new Table({ new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
}), }),
); );
expect(spy.called).to.equal(true); expect(spy.called).to.equal(true);
}); });
it("should call the underlying footer's addImage", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "add");
// tslint:disable-next-line:no-any
file.addImage({} as any);
expect(spy.called).to.equal(true);
});
}); });
describe("#addChildElement", () => { describe("#addChildElement", () => {

View File

@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
import { FooterReferenceType } from "./document"; import { FooterReferenceType } from "./document";
import { Footer } from "./footer/footer"; import { Footer } from "./footer/footer";
import { Image, Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
@ -25,11 +25,6 @@ export class FooterWrapper {
this.footer.add(item); this.footer.add(item);
} }
public addImage(image: Image): FooterWrapper {
this.footer.add(image.Paragraph);
return this;
}
public addChildElement(childElement: XmlComponent): void { public addChildElement(childElement: XmlComponent): void {
this.footer.addChildElement(childElement); this.footer.addChildElement(childElement);
} }

View File

@ -0,0 +1,47 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Paragraph } from "../paragraph";
import { Footer } from "./footer";
describe("Footer", () => {
it("should create", () => {
const footer = new Footer(1);
const tree = new Formatter().format(footer);
expect(tree).to.deep.equal({
"w:ftr": {
_attr: {
"xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
"xmlns:o": "urn:schemas-microsoft-com:office:office",
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:v": "urn:schemas-microsoft-com:vml",
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w10": "urn:schemas-microsoft-com:office:word",
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
"xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml",
"xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
"xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
"xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
"xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
"xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
},
},
});
});
it("should create with initContent", () => {
const header = new Footer(1, new Paragraph({}));
const tree = new Formatter().format(header);
expect(tree).to.deep.equal({
"w:ftr": {},
});
});
});

View File

@ -0,0 +1 @@
export * from "./run";

View File

@ -0,0 +1 @@
export * from "./reference-run";

View File

@ -44,7 +44,8 @@ export class FootNotes extends XmlComponent {
line: 240, line: 240,
lineRule: "auto", lineRule: "auto",
}, },
}).addRun(new SeperatorRun()), children: [new SeperatorRun()],
}),
); );
this.root.push(begin); this.root.push(begin);
@ -56,7 +57,8 @@ export class FootNotes extends XmlComponent {
line: 240, line: 240,
lineRule: "auto", lineRule: "auto",
}, },
}).addRun(new ContinuationSeperatorRun()), children: [new ContinuationSeperatorRun()],
}),
); );
this.root.push(spacing); this.root.push(spacing);
} }

View File

@ -1 +1,2 @@
export * from "./footnotes"; export * from "./footnotes";
export * from "./footnote";

View File

@ -4,7 +4,7 @@ import * as sinon from "sinon";
import { HeaderWrapper } from "./header-wrapper"; import { HeaderWrapper } from "./header-wrapper";
import { Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Table } from "./table"; import { Table, TableCell, TableRow } from "./table";
describe("HeaderWrapper", () => { describe("HeaderWrapper", () => {
describe("#add", () => { describe("#add", () => {
@ -21,8 +21,15 @@ describe("HeaderWrapper", () => {
const spy = sinon.spy(wrapper.Header, "add"); const spy = sinon.spy(wrapper.Header, "add");
wrapper.add( wrapper.add(
new Table({ new Table({
rows: 1, rows: [
columns: 1, new TableRow({
children: [
new TableCell({
children: [new Paragraph("hello")],
}),
],
}),
],
}), }),
); );
@ -30,17 +37,6 @@ describe("HeaderWrapper", () => {
}); });
}); });
describe("#addImage", () => {
it("should call the underlying header's addImage", () => {
const file = new HeaderWrapper(new Media(), 1);
const spy = sinon.spy(file.Header, "add");
// tslint:disable-next-line:no-any
file.addImage({} as any);
expect(spy.called).to.equal(true);
});
});
describe("#addChildElement", () => { describe("#addChildElement", () => {
it("should call the underlying header's addChildElement", () => { it("should call the underlying header's addChildElement", () => {
const file = new HeaderWrapper(new Media(), 1); const file = new HeaderWrapper(new Media(), 1);

View File

@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
import { HeaderReferenceType } from "./document"; import { HeaderReferenceType } from "./document";
import { Header } from "./header/header"; import { Header } from "./header/header";
import { Image, Media } from "./media"; import { Media } from "./media";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
import { Relationships } from "./relationships"; import { Relationships } from "./relationships";
import { Table } from "./table"; import { Table } from "./table";
@ -27,11 +27,6 @@ export class HeaderWrapper {
return this; return this;
} }
public addImage(image: Image): HeaderWrapper {
this.header.add(image.Paragraph);
return this;
}
public addChildElement(childElement: XmlComponent | string): void { public addChildElement(childElement: XmlComponent | string): void {
this.header.addChildElement(childElement); this.header.addChildElement(childElement);
} }

View File

@ -0,0 +1,58 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Paragraph } from "../paragraph";
import { Header } from "./header";
describe("Header", () => {
it("should create", () => {
const header = new Header(1);
const tree = new Formatter().format(header);
expect(tree).to.deep.equal({
"w:hdr": {
_attr: {
"xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex",
"xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
"xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
"xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
"xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
"xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
"xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
"xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
"xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
"xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
"xmlns:o": "urn:schemas-microsoft-com:office:office",
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:v": "urn:schemas-microsoft-com:vml",
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w10": "urn:schemas-microsoft-com:office:word",
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
"xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid",
"xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex",
"xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml",
"xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
"xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
"xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
"xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
"xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
},
},
});
});
it("should create with initContent", () => {
const header = new Header(1, new Paragraph({}));
const tree = new Formatter().format(header);
expect(tree).to.deep.equal({
"w:hdr": {},
});
});
});

View File

@ -12,3 +12,4 @@ export * from "./xml-components";
export * from "./header-wrapper"; export * from "./header-wrapper";
export * from "./footer-wrapper"; export * from "./footer-wrapper";
export * from "./header"; export * from "./header";
export * from "./footnotes";

View File

@ -1,13 +0,0 @@
import { ImageParagraph, PictureRun } from "../paragraph";
export class Image {
constructor(private readonly paragraph: ImageParagraph) {}
public get Paragraph(): ImageParagraph {
return this.paragraph;
}
public get Run(): PictureRun {
return this.paragraph.Run;
}
}

View File

@ -1,3 +1,2 @@
export * from "./media"; export * from "./media";
export * from "./data"; export * from "./data";
export * from "./image";

View File

@ -80,6 +80,16 @@ describe("Media", () => {
const image = new Media().addMedia(""); const image = new Media().addMedia("");
expect(image.stream).to.be.an.instanceof(Uint8Array); expect(image.stream).to.be.an.instanceof(Uint8Array);
}); });
it("should use data as is if its not a string", () => {
// tslint:disable-next-line
((process as any).atob as any) = () => "atob result";
// tslint:disable-next-line:no-any
(Media as any).generateId = () => "test";
const image = new Media().addMedia(new Buffer(""));
expect(image.stream).to.be.an.instanceof(Uint8Array);
});
}); });
describe("#getMedia", () => { describe("#getMedia", () => {

View File

@ -0,0 +1,687 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { EMPTY_OBJECT } from "file/xml-components";
import { AlignmentType, EmphasisMarkType, TabStopPosition } from "../paragraph";
import { UnderlineType } from "../paragraph/run/underline";
import { ShadingType } from "../table";
import { AbstractNumbering } from "./abstract-numbering";
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, [
{
level: 3,
format: "lowerLetter",
text: "%1)",
alignment: AlignmentType.END,
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
it("uses 'start' as the default alignment", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 3,
format: "lowerLetter",
text: "%1)",
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
describe("formatting methods: paragraph properties", () => {
it("#indent", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
indent: { left: 720 },
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }],
});
});
it("#spacing", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
spacing: { before: 50, after: 150 },
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }],
});
});
it("#center", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.CENTER,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }],
});
});
it("#left", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.LEFT,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }],
});
});
it("#right", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.RIGHT,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }],
});
});
it("#justified", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.JUSTIFIED,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }],
});
});
it("#thematicBreak", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
thematicBreak: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["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, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
leftTabStop: 1200,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }],
},
],
});
});
it("#maxRightTabStop", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
rightTabStop: TabStopPosition.MAX,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }],
},
],
});
});
it("#keepLines", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
keepLines: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:keepLines": EMPTY_OBJECT }],
});
});
it("#keepNext", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
keepNext: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:keepNext": EMPTY_OBJECT }],
});
});
});
describe("formatting methods: run properties", () => {
it("#size", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
size: 24,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }],
});
});
it("#smallCaps", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
smallCaps: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }],
});
});
it("#allCaps", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
allCaps: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }],
});
});
it("#strike", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
strike: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }],
});
});
it("#doubleStrike", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
doubleStrike: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }],
});
});
it("#subScript", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
subScript: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }],
});
});
it("#superScript", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
superScript: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }],
});
});
it("#font by name", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
font: "Times",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [
{
"w:rFonts": {
_attr: {
"w:ascii": "Times",
"w:cs": "Times",
"w:eastAsia": "Times",
"w:hAnsi": "Times",
},
},
},
],
});
});
it("#font for ascii and eastAsia", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
font: {
ascii: "Times",
eastAsia: "KaiTi",
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [
{
"w:rFonts": {
_attr: {
"w:ascii": "Times",
"w:eastAsia": "KaiTi",
},
},
},
],
});
});
it("#bold", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
bold: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:b": { _attr: { "w:val": true } } }],
});
});
it("#italics", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
italics: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:i": { _attr: { "w:val": true } } }],
});
});
it("#highlight", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
highlight: "005599",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
});
});
it("#shadow", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
shadow: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "FF0000",
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
underline: {},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }],
});
});
it("should set the style if given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
underline: {
type: UnderlineType.DOUBLE,
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["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, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
underline: {
type: UnderlineType.DOUBLE,
color: "005599",
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }],
});
});
});
describe("#emphasisMark", () => {
it("should set emphasisMark to 'dot' if no arguments are given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
emphasisMark: {},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
});
});
it("should set the style if given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
emphasisMark: {
type: EmphasisMarkType.DOT,
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
});
});
});
it("#color", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
color: "123456",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }],
});
});
});
});
});

View File

@ -1,5 +1,6 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { Level } from "./level";
import { ILevelsOptions, Level } from "./level";
import { MultiLevelType } from "./multi-level-type"; import { MultiLevelType } from "./multi-level-type";
interface IAbstractNumberingAttributesProperties { interface IAbstractNumberingAttributesProperties {
@ -17,7 +18,7 @@ class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberi
export class AbstractNumbering extends XmlComponent { export class AbstractNumbering extends XmlComponent {
public readonly id: number; public readonly id: number;
constructor(id: number) { constructor(id: number, levelOptions: ILevelsOptions[]) {
super("w:abstractNum"); super("w:abstractNum");
this.root.push( this.root.push(
new AbstractNumberingAttributes({ new AbstractNumberingAttributes({
@ -27,15 +28,9 @@ export class AbstractNumbering extends XmlComponent {
); );
this.root.push(new MultiLevelType("hybridMultilevel")); this.root.push(new MultiLevelType("hybridMultilevel"));
this.id = id; this.id = id;
}
public addLevel(level: Level): void { for (const option of levelOptions) {
this.root.push(level); this.root.push(new Level(option));
} }
public createLevel(num: number, format: string, text: string, align: string = "start"): Level {
const level = new Level(num, format, text, align);
this.addLevel(level);
return level;
} }
} }

View File

@ -0,0 +1,79 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { LevelForOverride } from "./level";
import { ConcreteNumbering } from "./num";
describe("ConcreteNumbering", () => {
describe("#overrideLevel", () => {
let concreteNumbering: ConcreteNumbering;
beforeEach(() => {
concreteNumbering = new ConcreteNumbering(0, 1);
});
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 } },
{
"w:lvl": [
{ _attr: { "w:ilvl": 3, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
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,
},
},
},
{
"w:lvl": [
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
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: { "w:ilvl": 1, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
});
});

View File

@ -2,19 +2,18 @@ import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-compon
import { import {
Alignment, Alignment,
AlignmentType, AlignmentType,
IIndentAttributesProperties,
Indent, Indent,
ISpacingProperties,
KeepLines, KeepLines,
KeepNext, KeepNext,
LeftTabStop,
MaxRightTabStop,
Spacing, Spacing,
TabStop,
TabStopType,
ThematicBreak, ThematicBreak,
} from "../paragraph/formatting"; } from "../paragraph/formatting";
import { ParagraphProperties } from "../paragraph/properties"; import { ParagraphProperties } from "../paragraph/properties";
import * as formatting from "../paragraph/run/formatting"; import * as formatting from "../paragraph/run/formatting";
import { RunProperties } from "../paragraph/run/properties"; import { RunProperties } from "../paragraph/run/properties";
import { IParagraphStyleOptions2, IRunStyleOptions } from "../styles/style-options";
interface ILevelAttributesProperties { interface ILevelAttributesProperties {
readonly ilvl?: number; readonly ilvl?: number;
@ -62,7 +61,7 @@ class LevelText extends XmlComponent {
} }
class LevelJc extends XmlComponent { class LevelJc extends XmlComponent {
constructor(value: string) { constructor(value: AlignmentType) {
super("w:lvlJc"); super("w:lvlJc");
this.root.push( this.root.push(
new Attributes({ new Attributes({
@ -78,6 +77,19 @@ export enum LevelSuffix {
TAB = "tab", TAB = "tab",
} }
export interface ILevelsOptions {
readonly level: number;
readonly format?: string;
readonly text?: string;
readonly alignment?: AlignmentType;
readonly start?: number;
readonly suffix?: LevelSuffix;
readonly style?: {
readonly run?: IRunStyleOptions;
readonly paragraph?: IParagraphStyleOptions2;
};
}
class Suffix extends XmlComponent { class Suffix extends XmlComponent {
constructor(value: LevelSuffix) { constructor(value: LevelSuffix) {
super("w:suff"); super("w:suff");
@ -93,7 +105,7 @@ export class LevelBase extends XmlComponent {
private readonly paragraphProperties: ParagraphProperties; private readonly paragraphProperties: ParagraphProperties;
private readonly runProperties: RunProperties; private readonly runProperties: RunProperties;
constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) { constructor({ level, format, text, alignment = AlignmentType.START, start = 1, style, suffix }: ILevelsOptions) {
super("w:lvl"); super("w:lvl");
this.root.push( this.root.push(
new LevelAttributes({ new LevelAttributes({
@ -102,17 +114,15 @@ export class LevelBase extends XmlComponent {
}), }),
); );
if (start !== undefined) { this.root.push(new Start(start));
this.root.push(new Start(start)); this.root.push(new LevelJc(alignment));
if (format) {
this.root.push(new NumberFormat(format));
} }
if (numberFormat !== undefined) {
this.root.push(new NumberFormat(numberFormat)); if (text) {
} this.root.push(new LevelText(text));
if (levelText !== undefined) {
this.root.push(new LevelText(levelText));
}
if (lvlJc !== undefined) {
this.root.push(new LevelJc(lvlJc));
} }
this.paragraphProperties = new ParagraphProperties({}); this.paragraphProperties = new ParagraphProperties({});
@ -120,157 +130,116 @@ export class LevelBase extends XmlComponent {
this.root.push(this.paragraphProperties); this.root.push(this.paragraphProperties);
this.root.push(this.runProperties); this.root.push(this.runProperties);
}
public setSuffix(value: LevelSuffix): LevelBase { if (suffix) {
this.root.push(new Suffix(value)); this.root.push(new Suffix(suffix));
return this; }
}
public addParagraphProperty(property: XmlComponent): Level { if (style) {
this.paragraphProperties.push(property); if (style.run) {
return this; if (style.run.size) {
} this.runProperties.push(new formatting.Size(style.run.size));
}
public addRunProperty(property: XmlComponent): Level { if (style.run.bold) {
this.runProperties.push(property); this.runProperties.push(new formatting.Bold());
return this; }
}
// ---------- Run formatting ---------------------- // if (style.run.italics) {
this.runProperties.push(new formatting.Italics());
}
public size(twips: number): Level { if (style.run.smallCaps) {
this.addRunProperty(new formatting.Size(twips)); this.runProperties.push(new formatting.SmallCaps());
return this; }
}
public bold(): Level { if (style.run.allCaps) {
this.addRunProperty(new formatting.Bold()); this.runProperties.push(new formatting.Caps());
return this; }
}
public italics(): Level { if (style.run.strike) {
this.addRunProperty(new formatting.Italics()); this.runProperties.push(new formatting.Strike());
return this; }
}
public smallCaps(): Level { if (style.run.doubleStrike) {
this.addRunProperty(new formatting.SmallCaps()); this.runProperties.push(new formatting.DoubleStrike());
return this; }
}
public allCaps(): Level { if (style.run.subScript) {
this.addRunProperty(new formatting.Caps()); this.runProperties.push(new formatting.SubScript());
return this; }
}
public strike(): Level { if (style.run.superScript) {
this.addRunProperty(new formatting.Strike()); this.runProperties.push(new formatting.SuperScript());
return this; }
}
public doubleStrike(): Level { if (style.run.underline) {
this.addRunProperty(new formatting.DoubleStrike()); this.runProperties.push(new formatting.Underline(style.run.underline.type, style.run.underline.color));
return this; }
}
public subScript(): Level { if (style.run.emphasisMark) {
this.addRunProperty(new formatting.SubScript()); this.runProperties.push(new formatting.EmphasisMark(style.run.emphasisMark.type));
return this; }
}
public superScript(): Level { if (style.run.color) {
this.addRunProperty(new formatting.SuperScript()); this.runProperties.push(new formatting.Color(style.run.color));
return this; }
}
public underline(underlineType?: string, color?: string): Level { if (style.run.font) {
this.addRunProperty(new formatting.Underline(underlineType, color)); this.runProperties.push(new formatting.RunFonts(style.run.font));
return this; }
}
public color(color: string): Level { if (style.run.highlight) {
this.addRunProperty(new formatting.Color(color)); this.runProperties.push(new formatting.Highlight(style.run.highlight));
return this; }
}
public font(fontName: string): Level { if (style.run.shadow) {
this.addRunProperty(new formatting.RunFonts(fontName)); this.runProperties.push(new formatting.Shading(style.run.shadow.type, style.run.shadow.fill, style.run.shadow.color));
return this; }
} }
public highlight(color: string): Level { if (style.paragraph) {
this.addRunProperty(new formatting.Highlight(color)); if (style.paragraph.alignment) {
return this; this.paragraphProperties.push(new Alignment(style.paragraph.alignment));
} }
public shadow(value: string, fill: string, color: string): Level { if (style.paragraph.thematicBreak) {
this.addRunProperty(new formatting.Shading(value, fill, color)); this.paragraphProperties.push(new ThematicBreak());
return this; }
}
// --------------------- Paragraph formatting ------------------------ //
public center(): Level { if (style.paragraph.rightTabStop) {
this.addParagraphProperty(new Alignment(AlignmentType.CENTER)); this.paragraphProperties.push(new TabStop(TabStopType.RIGHT, style.paragraph.rightTabStop));
return this; }
}
public left(): Level { if (style.paragraph.leftTabStop) {
this.addParagraphProperty(new Alignment(AlignmentType.LEFT)); this.paragraphProperties.push(new TabStop(TabStopType.LEFT, style.paragraph.leftTabStop));
return this; }
}
public right(): Level { if (style.paragraph.indent) {
this.addParagraphProperty(new Alignment(AlignmentType.RIGHT)); this.paragraphProperties.push(new Indent(style.paragraph.indent));
return this; }
}
public justified(): Level { if (style.paragraph.spacing) {
this.addParagraphProperty(new Alignment(AlignmentType.BOTH)); this.paragraphProperties.push(new Spacing(style.paragraph.spacing));
return this; }
}
public thematicBreak(): Level { if (style.paragraph.keepNext) {
this.addParagraphProperty(new ThematicBreak()); this.paragraphProperties.push(new KeepNext());
return this; }
}
public maxRightTabStop(): Level { if (style.paragraph.keepLines) {
this.addParagraphProperty(new MaxRightTabStop()); this.paragraphProperties.push(new KeepLines());
return this; }
} }
}
public leftTabStop(position: number): Level {
this.addParagraphProperty(new LeftTabStop(position));
return this;
}
public indent(attrs: IIndentAttributesProperties): Level {
this.addParagraphProperty(new Indent(attrs));
return this;
}
public spacing(params: ISpacingProperties): Level {
this.addParagraphProperty(new Spacing(params));
return this;
}
public keepNext(): Level {
this.addParagraphProperty(new KeepNext());
return this;
}
public keepLines(): Level {
this.addParagraphProperty(new KeepLines());
return this;
} }
} }
export class Level extends LevelBase { export class Level extends LevelBase {
// This is the level that sits under abstractNum. We make a // This is the level that sits under abstractNum. We make a
// handful of properties required // handful of properties required
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) { constructor(options: ILevelsOptions) {
super(level, 1, numberFormat, levelText, lvlJc); super(options);
} }
} }

View File

@ -20,10 +20,10 @@ class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
protected readonly xmlKeys = { numId: "w:numId" }; protected readonly xmlKeys = { numId: "w:numId" };
} }
export class Num extends XmlComponent { export class ConcreteNumbering extends XmlComponent {
public readonly id: number; public readonly id: number;
constructor(numId: number, abstractNumId: number) { constructor(numId: number, abstractNumId: number, public readonly reference?: string) {
super("w:num"); super("w:num");
this.root.push( this.root.push(
new NumAttributes({ new NumAttributes({
@ -55,7 +55,9 @@ export class LevelOverride extends XmlComponent {
this.root.push(new StartOverride(start)); this.root.push(new StartOverride(start));
} }
this.lvl = new LevelForOverride(this.levelNum); this.lvl = new LevelForOverride({
level: this.levelNum,
});
this.root.push(this.lvl); this.root.push(this.lvl);
} }

View File

@ -2,22 +2,15 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { AbstractNumbering } from "./abstract-numbering";
import { LevelForOverride } from "./level";
import { Num } from "./num";
import { Numbering } from "./numbering"; import { Numbering } from "./numbering";
import { EMPTY_OBJECT } from "file/xml-components";
describe("Numbering", () => { describe("Numbering", () => {
let numbering: Numbering;
beforeEach(() => {
numbering = new Numbering();
});
describe("#constructor", () => { describe("#constructor", () => {
it("creates a default numbering with one abstract and one concrete instance", () => { it("creates a default numbering with one abstract and one concrete instance", () => {
const numbering = new Numbering({
config: [],
});
const tree = new Formatter().format(numbering); const tree = new Formatter().format(numbering);
expect(Object.keys(tree)).to.deep.equal(["w:numbering"]); expect(Object.keys(tree)).to.deep.equal(["w:numbering"]);
const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]); const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]);
@ -46,418 +39,4 @@ describe("Numbering", () => {
}); });
}); });
}); });
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"]._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": EMPTY_OBJECT }],
});
});
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": EMPTY_OBJECT }],
});
});
});
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:cs": "Times", "w:eastAsia": "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 } } }],
});
});
it("#highlight", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").highlight("005599");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
});
});
it("#shadow", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").shadow("pct10", "00FFFF", "FF0000");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
});
});
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,
},
},
{
"w:lvl": {
_attr: {
"w:ilvl": 3,
"w15:tentative": 1,
},
},
},
],
});
});
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,
},
},
},
{
"w:lvl": {
_attr: {
"w:ilvl": 1,
"w15:tentative": 1,
},
},
},
],
});
});
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 } },
},
],
});
});
});
}); });

View File

@ -1,17 +1,27 @@
import { Indent } from "file/paragraph"; // http://officeopenxml.com/WPnumbering.php
import { AlignmentType } from "file/paragraph";
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes"; import { DocumentAttributes } from "../document/document-attributes";
import { AbstractNumbering } from "./abstract-numbering"; import { AbstractNumbering } from "./abstract-numbering";
import { Num } from "./num"; import { ILevelsOptions } from "./level";
import { ConcreteNumbering } from "./num";
export interface INumberingOptions {
readonly config: Array<{
readonly levels: ILevelsOptions[];
readonly reference: string;
}>;
}
export class Numbering extends XmlComponent { export class Numbering extends XmlComponent {
// tslint:disable-next-line:readonly-keyword // tslint:disable-next-line:readonly-keyword
private nextId: number; private nextId: number;
private readonly abstractNumbering: XmlComponent[] = []; private readonly abstractNumbering: AbstractNumbering[] = [];
private readonly concreteNumbering: XmlComponent[] = []; private readonly concreteNumbering: ConcreteNumbering[] = [];
constructor() { constructor(options: INumberingOptions) {
super("w:numbering"); super("w:numbering");
this.root.push( this.root.push(
new DocumentAttributes({ new DocumentAttributes({
@ -37,39 +47,114 @@ export class Numbering extends XmlComponent {
this.nextId = 0; this.nextId = 0;
const abstractNumbering = this.createAbstractNumbering(); const abstractNumbering = this.createAbstractNumbering([
{
abstractNumbering.createLevel(0, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 720, hanging: 360 })); level: 0,
format: "bullet",
abstractNumbering.createLevel(1, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 1440, hanging: 360 })); text: "\u25CF",
alignment: AlignmentType.LEFT,
abstractNumbering.createLevel(2, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 2160, hanging: 360 })); style: {
paragraph: {
abstractNumbering.createLevel(3, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 2880, hanging: 360 })); indent: { left: 720, hanging: 360 },
},
abstractNumbering.createLevel(4, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 3600, hanging: 360 })); },
},
abstractNumbering.createLevel(5, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 4320, hanging: 360 })); {
level: 1,
abstractNumbering.createLevel(6, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 5040, hanging: 360 })); format: "bullet",
text: "\u25CB",
abstractNumbering.createLevel(7, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 5760, hanging: 360 })); alignment: AlignmentType.LEFT,
style: {
abstractNumbering.createLevel(8, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 6480, hanging: 360 })); paragraph: {
indent: { left: 1440, hanging: 360 },
},
},
},
{
level: 2,
format: "bullet",
text: "\u25A0",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 2160, hanging: 360 },
},
},
},
{
level: 3,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 2880, hanging: 360 },
},
},
},
{
level: 4,
format: "bullet",
text: "\u25CB",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 3600, hanging: 360 },
},
},
},
{
level: 5,
format: "bullet",
text: "\u25A0",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 4320, hanging: 360 },
},
},
},
{
level: 6,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 5040, hanging: 360 },
},
},
},
{
level: 7,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 5760, hanging: 360 },
},
},
},
{
level: 8,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 6480, hanging: 360 },
},
},
},
]);
this.createConcreteNumbering(abstractNumbering); this.createConcreteNumbering(abstractNumbering);
}
public createAbstractNumbering(): AbstractNumbering { for (const con of options.config) {
const num = new AbstractNumbering(this.nextId++); const currentAbstractNumbering = this.createAbstractNumbering(con.levels);
this.abstractNumbering.push(num); this.createConcreteNumbering(currentAbstractNumbering, con.reference);
return num; }
}
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
const num = new Num(this.nextId++, abstractNumbering.id);
this.concreteNumbering.push(num);
return num;
} }
public prepForXml(): IXmlableObject | undefined { public prepForXml(): IXmlableObject | undefined {
@ -77,4 +162,20 @@ export class Numbering extends XmlComponent {
this.concreteNumbering.forEach((x) => this.root.push(x)); this.concreteNumbering.forEach((x) => this.root.push(x));
return super.prepForXml(); return super.prepForXml();
} }
private createConcreteNumbering(abstractNumbering: AbstractNumbering, reference?: string): ConcreteNumbering {
const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id, reference);
this.concreteNumbering.push(num);
return num;
}
private createAbstractNumbering(options: ILevelsOptions[]): AbstractNumbering {
const num = new AbstractNumbering(this.nextId++, options);
this.abstractNumbering.push(num);
return num;
}
public get ConcreteNumbering(): ConcreteNumbering[] {
return this.concreteNumbering;
}
} }

Some files were not shown because too many files have changed in this diff Show More