Compare commits

...

229 Commits
1.2.1 ... 3.1.0

Author SHA1 Message Date
cf1689a3c2 Version bump 2017-12-06 01:39:40 +00:00
66d0bab0a5 Add packPdf reference 2017-12-06 01:39:24 +00:00
5889f20f1e Simplify tests and made compile async 2017-12-06 01:32:57 +00:00
ebec10e312 Refactor 2017-12-06 01:03:14 +00:00
ae8a0c7fd0 Edit Tslint 2017-12-05 23:43:19 +00:00
bb49200fad Clean up code using request promise 2017-12-05 02:24:49 +00:00
a6bba0bc6c Enable pdf export 2017-12-05 00:16:21 +00:00
fb08f79344 Add initial conversion 2017-11-10 14:27:57 +00:00
741c74825e Class definition for Pdf converter wrapper 2017-09-30 18:23:02 +01:00
28539cd47b Initial scaffold of pdf convert 2017-09-30 18:15:33 +01:00
8ca7c5a343 Add test 2017-09-30 18:00:06 +01:00
32b56e7071 Move sub components of Paragraph into own folder 2017-09-30 01:52:37 +01:00
7dad717952 Add documentation links 2017-09-22 14:46:19 +01:00
34e928755f Add documentation links 2017-09-21 14:56:46 +01:00
51e0f311fe Refactor numbering into its own file 2017-09-20 13:37:39 +01:00
cc9dff6b94 Version bump 2017-09-19 16:02:32 +01:00
101cc0fdea Add docx keyword to package 2017-09-19 16:02:06 +01:00
d408262fa8 Remove unused class 2017-09-19 15:58:37 +01:00
ed53c30f42 Remove template file and placed in the Wiki
https://github.com/dolanmiu/docx/wiki/Contributing-Guidelines
2017-09-19 15:56:52 +01:00
ebbf6a99c1 Make exporting more consistent 2017-09-19 15:51:55 +01:00
b98c103e45 Export table into own file 2017-09-19 15:49:27 +01:00
518fec0595 Export like standard style 2017-09-19 15:47:29 +01:00
659936f3f0 Refactor xml component into own class 2017-09-19 15:46:20 +01:00
b0febf5054 Refactor run into own file 2017-09-19 15:42:40 +01:00
56b951a2b1 Refactor body into own file 2017-09-19 15:39:14 +01:00
a3a9958a69 Refactor document into own file 2017-09-19 15:36:41 +01:00
4f36bbf426 Refactor paragraph into its own file
Create barrel
2017-09-19 15:21:07 +01:00
357bc7f377 Add documentation reference 2017-09-19 15:17:58 +01:00
da8405b5b9 Add Right and Center tab stops 2017-09-19 12:51:37 +01:00
492face7ab 3.0.0 2017-09-17 00:20:18 +01:00
b6351f0260 Merge branch 'jacwright-indent-bc' 2017-09-17 00:09:57 +01:00
3a7f9053b9 Allow indent to use other options than left and hanging
This is a breaking change. Existing code using indent will break when padding in the number for left.
2017-09-16 17:05:40 -06:00
19b122684c Add lint to Travis
Fix linting
2017-09-17 00:01:09 +01:00
72e89cfc3c Move tests to respective folders as .spec
This is to keep to standards
2017-09-17 00:00:41 +01:00
5ae02c3342 Merge pull request #43 from jacwright/default-run-styles
Making it easier to work with default styles
2017-09-16 17:48:37 +01:00
258adba94c Version bump 2017-09-16 17:48:15 +01:00
190208d5df Making it easier to work with default styles
Adds the likely common styles, font and size, to be defined in defaults.
2017-09-16 08:24:15 -06:00
32cda4dfb3 Merge pull request #42 from jacwright/patch-1-test
Fix tests
2017-09-15 16:09:18 +01:00
b2c3dd2f7b Fix tests 2017-09-15 09:05:28 -06:00
58eca3ff5b Merge pull request #41 from jacwright/patch-1
Fix page/thematic breaks
2017-09-15 15:57:22 +01:00
d5c04f9042 Pin typescript to 2.4.1 2017-09-15 15:56:21 +01:00
67ea7c95de Just pagebreak, update test 2017-09-15 08:37:57 -06:00
e57fd8fc57 Fix page/thematic breaks
These items are not paragraph properties, but part of the content of the paragraph.
2017-09-15 08:25:43 -06:00
411c0dadb5 Add table demo 2017-07-29 01:58:29 +01:00
ee81f3c502 Version bump 2017-07-29 01:26:34 +01:00
8263b93c36 Add third demo 2017-07-29 01:26:04 +01:00
be709d082c Export formatting from paragraph folder 2017-07-29 01:15:33 +01:00
70c4e89a65 Version bump 2017-07-08 21:37:02 +01:00
8f632d4ecd Made XmlableObject non ambient typings to fix typescript problem 2017-07-08 20:45:19 +01:00
6784dc1f3d Updated archiver types 2017-07-08 01:24:25 +01:00
fd63a30298 Updated definitions 2017-07-07 16:51:22 +01:00
b0a29e26c9 Version bump 2017-07-07 14:36:34 +01:00
8826fb010d Added types to package json 2017-07-07 14:34:03 +01:00
97101adb10 Enabled declarations in tsconfig to be true 2017-07-07 14:31:08 +01:00
2ec171d4a8 Used my updated typings for archiver 1.3 2017-05-09 18:03:31 +01:00
8876bb1fea updated readme about demos 2017-04-17 19:53:53 +01:00
96ca9d9c23 commented out the rm 2017-04-17 16:36:56 +01:00
74a65f02fa remove exit code 2017-04-17 16:07:28 +01:00
339c017940 edited rm function 2017-04-17 15:41:03 +01:00
969b52ae05 added nojekyll to gh-pages 2017-04-17 13:24:43 +01:00
eb8f0f4033 added debugging to deployment 2017-04-17 13:05:02 +01:00
d7229eb049 removed rimraf 2017-04-16 12:41:24 +01:00
644819ed81 added deployment keys and scripts 2017-04-16 12:22:50 +01:00
96fd61d3e4 using stable version of node 2017-04-16 02:25:44 +01:00
b96cfe7b19 git fetch added 2017-04-16 01:29:29 +01:00
d0e7c97a88 added commit message with merge with gh pages 2017-04-16 01:18:22 +01:00
0ede54cfdc added travis doc deployment solution 2017-04-16 01:12:42 +01:00
5be1163549 remove docs in gitignore 2017-04-16 01:07:11 +01:00
580f6eb633 fixed tests 2017-04-16 00:44:35 +01:00
321be35918 added docs in gitignore 2017-04-16 00:30:27 +01:00
45a18742d7 moved documentation to gh-pages branch 2017-04-16 00:22:29 +01:00
7e81da404a updated readme 2017-04-16 00:09:07 +01:00
6c2abb4abc added typedoc generated docs 2017-04-16 00:07:16 +01:00
e8b0dbf93b added docs ignore to npmignore file 2017-04-16 00:04:29 +01:00
e59c255d85 added type to pack param 2017-04-15 23:45:27 +01:00
17b28cb724 fixed more linting issues 2017-04-15 20:11:54 +01:00
410152441b fixed tslinting issues 2017-04-15 17:54:47 +01:00
dfff4b96bd updated tslint to 5.1.0 2017-04-15 17:47:29 +01:00
72cb75a486 added comma 2017-04-15 17:46:56 +01:00
53fe1dd988 added typedoc support for generating documentation 2017-04-15 17:46:11 +01:00
043219f005 Merge pull request #32 from felipeochoa/keep-lines
add keepLines and keepNext support for paragraph formatting
2017-04-15 17:41:11 +01:00
0453f28951 add keepLines and keepNext support for paragraph formatting
This commit adds it to the three places where paragraph formatting can
be applied right now (directly on the paragraph, paragraph styles, and
bullets/numbering styles). I'm separately adding an entry to the wiki
regarding what these methods do (widow/orphan control)
2017-04-14 21:13:11 +02:00
94716e081b Merge pull request #31 from felipeochoa/spacing-bugfix
Spacing bugfix
2017-04-14 17:30:54 +01:00
30f826fd3d made a mini app which you can select the demo you want from a list with an interactive prompt. 2017-04-14 15:48:12 +01:00
99b7a03b8a update demo2 to test space preservation 2017-04-14 11:09:08 +02:00
b1c8b2beb8 add xml:space="preserve" to text runs
In MS Word 2015 (and possibly others), leading and trailing spaces are
ignored in text runs. This means that calling TextRun with
leading/trailing space would result in a document that didn't include
those spaces. The fix here (per http://officeopenxml.com/WPtext.php)
is to include an extra attribute on the "w:t" element that forces word
to recognize those spaces.
2017-04-14 11:08:19 +02:00
a706455a7c Merge pull request #30 from felipeochoa/overrides
Add support for numbering level overrides
2017-04-12 18:34:07 +01:00
8c388a95c4 bump patch version 2017-04-12 16:01:26 +02:00
9a41d78ecb add an additional demo 2017-04-12 16:00:16 +02:00
6fc4ad782a add #overrideLevel method to concrete numberings 2017-04-12 16:00:16 +02:00
16b9057ac6 prepare Level to be extended for overrides 2017-04-12 15:57:26 +02:00
28c62c1ee4 version 2.0.0 2017-04-01 13:31:24 +01:00
91a757619e fixed unit tests 2017-04-01 12:30:27 +01:00
f89049e4c1 version bump 2017-04-01 12:23:01 +01:00
086f5f5d03 Merge branch 'image-support' 2017-04-01 12:22:23 +01:00
a2694f1d7d edited demo code 2017-04-01 12:21:59 +01:00
fd350d53db fixed some typescript compile issues 2017-04-01 12:18:23 +01:00
af48af2854 added error checking and updated demo 2017-03-29 22:57:52 +01:00
15009884e3 made image take in IData instead of image path 2017-03-29 22:47:34 +01:00
ad462dfe10 added relationships 2017-03-27 01:28:52 +01:00
68fe380575 renamed template constant 2017-03-27 01:28:31 +01:00
5b2a311ad4 media now adds object into docx /media folder 2017-03-26 20:57:43 +01:00
5db2503e7b added media class and packer to handle it 2017-03-25 21:50:33 +00:00
55a1d23f29 fixed missing comma 2017-03-14 11:33:46 +00:00
0bfd753a0d fixed json, missing comma 2017-03-14 11:28:05 +00:00
22adabd2d9 updated keywords 2017-03-14 10:51:05 +00:00
6d5c89ff47 corrected underline 2017-03-14 10:48:24 +00:00
96a9bb1a1e corrected underline 2017-03-14 10:47:50 +00:00
2be00d3f1e corrected example 2017-03-14 10:38:05 +00:00
30b21217a9 added create picture run method 2017-03-13 23:41:14 +00:00
260a680f56 Merge branch 'master' into image-support 2017-03-13 23:37:09 +00:00
486777b108 named this addRun because I realised Text can be a Run, but also a Picture can be a run 2017-03-13 23:35:24 +00:00
3730e2b60e clean up of packer 2017-03-13 23:30:26 +00:00
e7e6d02cc5 Merge pull request #28 from felipeochoa/npm-fixes
NPM fixes
2017-03-13 22:38:13 +00:00
c7b7b585be fix path to template when installed via npm
I'm not sure why, buy app-root-path was not picking up the correct
path for this module when installed via NPM. Since we're already using
relative paths for imports, I don't think we gain much from using it
anyway, so I've changed to using path.resolve instead
2017-03-13 20:40:16 +01:00
0c128ae4a5 add template to npm bundle 2017-03-13 20:40:16 +01:00
d7a70ffe03 use gemnasium rather than david-dm 2017-03-13 16:14:03 +00:00
e1c699ec41 Merge pull request #27 from felipeochoa/styles
More styles harmony
2017-03-13 12:31:25 +00:00
245f0ae2ea added gitter integration 2017-03-13 12:30:22 +00:00
762fc0ca9d added run formatting methods to levels 2017-03-13 12:38:16 +01:00
457d074a59 added paragraph formatting methods to numbering Level 2017-03-13 12:38:16 +01:00
ba80440faf formatting and correct heading values 2017-03-13 01:21:26 +00:00
ae41e22df5 moved Documents section to Wiki.
Cleaned up Wiki
Added felipeochoa mention
removed TOC
2017-03-13 01:19:39 +00:00
bf2e5201c8 made usage much more clear 2017-03-13 01:11:11 +00:00
2662bcc60b removed Paragraph from readme, and put it in the Wiki 2017-03-13 01:03:57 +00:00
efa4f1933a moved Text section to Wiki 2017-03-13 00:57:57 +00:00
8c58ec2ba4 moved bullet points to Wiki 2017-03-13 00:54:05 +00:00
e6ed3a55ff updated contents 2017-03-13 00:51:01 +00:00
69abf509c9 removed tab stops and placed it in the Wiki 2017-03-13 00:50:29 +00:00
66eace4a33 added a attribute to graphic 2017-03-13 00:40:39 +00:00
eeb2187db4 added source rectangle and stretch in the blip fill 2017-03-13 00:33:59 +00:00
fa44acca1b added the remaining classes required 2017-03-13 00:02:56 +00:00
34ce662f70 added basic test for drawing 2017-03-12 23:32:02 +00:00
8d72fa5542 added more boilerplate files to drawing 2017-03-12 23:31:43 +00:00
64d642b593 Merge branch 'master' into image-support 2017-03-12 21:44:17 +00:00
d9e533156f fixed the build :P removed .docx extension if one is already present 2017-03-12 21:43:52 +00:00
90e5cc3ad5 Revert "fixed the build :P removed .docx extension if one is already present"
This reverts commit 9f61ca7abe.
2017-03-12 21:43:15 +00:00
9f61ca7abe fixed the build :P removed .docx extension if one is already present 2017-03-12 21:42:45 +00:00
6af79a884c Merge branch 'master' into image-support 2017-03-12 21:38:38 +00:00
353401d148 updated readme to add demo folder 2017-03-12 21:37:36 +00:00
f17360d45a add simple demo page 2017-03-12 21:34:49 +00:00
4bc6ed0eda added more files to drawing 2017-03-12 21:30:46 +00:00
bf22866a8d Merge pull request #26 from felipeochoa/styles
Standardizing formatting methods
2017-03-12 21:26:47 +00:00
a6e40d9d92 tightened type checking for tab stops 2017-03-12 22:06:43 +01:00
0b78e33444 added #leftTabStop and #maxRightTabStop methods to paragraph styles 2017-03-12 22:05:54 +01:00
6689f76c28 added #thematicBreak method to paragraph styles 2017-03-12 21:58:12 +01:00
df8ba1e8b8 added alignment methods to paragraph style 2017-03-12 21:40:01 +01:00
b3524971ac clean up imports into paragraph and paragraph style
(this prep for importing it from styles)
2017-03-12 21:39:29 +01:00
1cd681bc2d added #font method to paragraph styles 2017-03-12 17:53:32 +01:00
7f4d1bf3e7 added #SubScript and #SuperScript methods to ParagraphStyle 2017-03-12 17:52:03 +01:00
8c6cd012e7 eliminated duplicate implementation of strikethrough 2017-03-12 17:51:52 +01:00
371398bc2c added #strike and #doubleStrike methods to paragraphStyle 2017-03-12 17:44:42 +01:00
9a90818729 added #smallCaps and #allCaps methods to ParagraphStyle 2017-03-12 17:43:00 +01:00
90049a31ee added #style method to runs 2017-03-12 17:35:15 +01:00
3dbd917667 added #color and #size methods to Run 2017-03-12 17:31:36 +01:00
d85c6ce56a allow Run#underline to take style and color arguments 2017-03-12 17:28:52 +01:00
db24f67e34 Merge pull request #21 from felipeochoa/tables
Tables!
2017-03-11 20:31:57 +00:00
fb6a4383ff renamed cell#push to cell#addContent 2017-03-11 21:15:45 +01:00
c33cdb450c added initial constructor 2017-03-11 18:21:31 +00:00
62b6b33254 created drawing file and refactored run components in components folder 2017-03-11 16:56:35 +00:00
64ab91765a edited contents 2017-03-11 16:06:09 +00:00
c0ba937c3a simplified readme 2017-03-11 16:05:20 +00:00
70f4613e1b appease the linter 2017-03-11 10:30:25 +01:00
bd9d6b74f5 added Cell#createParagraph method 2017-03-11 10:30:08 +01:00
bd3eb3e214 move the cell-paragraph validation into prepForXml
Instead of forcing table cells to only have a single paragraph as
their content, we now check whether they end in a paragraph (and
insert one if necessary) during #prepForXml
2017-03-11 10:22:30 +01:00
c10b576a3a added fixedWidthLayout option and method 2017-03-11 09:59:29 +01:00
210b97d00b added document#createTable 2017-03-11 09:02:36 +01:00
62a238de84 more sane width management 2017-03-11 09:02:22 +01:00
7b6f5bbaef forgot to test #getCell 2017-03-10 17:48:05 +01:00
a45048a464 fix a handful of strict null errors; make test config also be strict
These errors only arose because I didn't run `npm build`, only `npm
test`. Then, when I tried building the null checks failed. Keeping the
two config fiels in sync will help prevent this issue in the future
2017-03-10 17:45:37 +01:00
c0b0649f37 added tests for table v1 2017-03-10 17:39:37 +01:00
e7e5c61a90 clean up initial table attempt (linter, interfaces, etc.) 2017-03-10 17:11:08 +01:00
f1fb0c4f22 initial attempt at Table 2017-03-10 16:55:39 +01:00
0de30f6c59 Merge pull request #19 from felipeochoa/cleanup
fix unused imports linter warnings
2017-03-10 15:43:19 +00:00
599be5bf2b got a little carried away with the purge! fixed failing errors 2017-03-10 16:35:48 +01:00
9d7573ed9c fix unused imports linter warnings 2017-03-10 16:09:21 +01:00
738cac0253 Merge pull request #17 from felipeochoa/cleanup
More cleanup
2017-03-10 14:53:42 +00:00
35ddda444a rename paragraph#createRun to paragraph#createTextRun 2017-03-10 14:44:25 +01:00
8d11ec3422 add paragraph.createRun method 2017-03-10 14:36:16 +01:00
ea647d84df add document.createParagraph method 2017-03-10 14:31:21 +01:00
8c91c04afa clean up latentstyles to use modern attribute implementation 2017-03-10 14:31:21 +01:00
a89718ea83 fix a couple of compiler null warnings 2017-03-10 14:31:21 +01:00
62911d6e44 make XmlAttributeComponent into a generic class
This change brings increased type safety to uses of
XmlAttributeComponent. Now the compiler is checkign for us that the
properties that get passed in to every subclass match the intended
interface, and also that the xmlKeys property -> xml attribute mapping
has all the right keys
2017-03-10 14:19:45 +01:00
766069c7cc make packers a top-level export 2017-03-10 13:54:49 +01:00
09f1d473c3 remove lodash from dependencies as it's no longer used 2017-03-10 13:54:44 +01:00
74ea0f699f remove npm and install from dependencies
These aren't actually needed at run time or build time
2017-03-10 13:54:37 +01:00
b00e4fa5b5 moved exporting to the wiki 2017-03-10 11:35:43 +00:00
7ffa59d7fa formatting 2017-03-10 11:27:48 +00:00
d662391a64 removed LICENCE in readme.
It is already in the repository as the LICENCE file. No need to repeat
2017-03-10 11:26:04 +00:00
7a9661c55a moved examples to Wiki 2017-03-10 11:24:41 +00:00
f80a5654b4 adding unused variable checking 2017-03-10 10:33:42 +00:00
001b756866 Merge pull request #16 from felipeochoa/cleanup
Cleanup
2017-03-10 10:32:05 +00:00
fd1aab48f7 removed some dead code causing tslint problems 2017-03-10 10:35:38 +01:00
7ff838357a removed empty Attributes from ParagraphProperties
I checked the spec, and "w:pPr" does not take any attributes. I also
converted the paragraph tests to use the deep.equal style to get rid
of some more jsonify!
2017-03-10 10:32:40 +01:00
958b5ea307 improved type annotation for Attributes#prepForXml 2017-03-10 08:58:29 +01:00
3a6cf81c60 use strict null checks for added type safety
From the TypeScript manual "In strict null checking mode, the null and
undefined values are not in the domain of every type and are only
assignable to themselves and any (the one exception being that
undefined is also assignable to void)."

In other words, this setting means the compiler will complain if you
try to use a variable, but it might be null or undefined. Since the
project builds and tests pass with it, I think it's good to turn it
on. If it becomes annoying, you can always turn it off later
2017-03-10 08:54:55 +01:00
10684b34aa clean up unused imports and a linter warning 2017-03-10 08:52:47 +01:00
2fc9156e93 remove console.log statements 2017-03-10 08:25:11 +01:00
c521d0ce63 rename toXml -> prepForXml 2017-03-10 08:23:27 +01:00
50ca71087e reverted back as they’re both buggy 2017-03-10 00:52:43 +00:00
72eff21027 used html as markdown version is buggy 2017-03-10 00:51:42 +00:00
11bbb5e285 linting 2017-03-09 23:32:52 +00:00
18bba1870e added types for app-root-path 2017-03-09 23:20:37 +00:00
c6eef3fd79 tslint fixes 2017-03-09 23:20:28 +00:00
06353a6977 linting 2017-03-09 22:57:37 +00:00
2b28dd42ee unclear variable name. please improve 2017-03-09 22:57:30 +00:00
7022b39d2e linting 2017-03-09 22:56:08 +00:00
5fac776dca listing stuff 2017-03-09 22:31:55 +00:00
0e8d92f8bb Merge pull request #14 from felipeochoa/formatting
Clean up xml formatting (clearVariables) and introduced toXml()
2017-03-09 22:17:04 +00:00
30fef5238e fix some linter warnings 2017-03-09 21:06:54 +01:00
0fe6ff95a2 fix an incorrect test 2017-03-09 20:49:34 +01:00
11e918114d clear out clearVariables! In comes toXml 2017-03-09 20:49:14 +01:00
879b9163a3 Allow XmlComponent to take strings and remove XmlUnit 2017-03-09 20:21:49 +01:00
4a2b8a1e04 remove unused XmlProperty code 2017-03-09 19:04:17 +01:00
d6f5fe0ae8 Remove extraneous multi-property from base numbering object 2017-03-09 17:59:17 +01:00
dd4ff944d7 Merge pull request #12 from felipeochoa/styles
Styles cleanup
2017-03-09 15:25:19 +00:00
aac81ffa12 add Styles to top-level exports 2017-03-09 13:27:53 +01:00
d93432678b added #createParagraphStyle method to Styles 2017-03-09 13:27:40 +01:00
ac7799a875 add an #underline method to ParagraphStyle 2017-03-09 13:19:50 +01:00
766bcabcb8 allow Underline to be called with type and color; fix default 2017-03-09 12:42:19 +01:00
f06094c91d clean up underlineTests linter warnings 2017-03-09 12:36:33 +01:00
3b18b8267a fix paragraphTests.ts linter warnings 2017-03-09 12:25:59 +01:00
20ec9b679e added Spacing for paragraphs and ParagraphStyle 2017-03-09 12:16:49 +01:00
449d1bc2c3 added fluent formatting methods to ParagraphStyle 2017-03-09 11:27:47 +01:00
b4dca79d72 added optional name parameter to style constructor 2017-03-09 11:11:07 +01:00
e6e0658812 clean up style & components attributes 2017-03-09 11:11:07 +01:00
18ca93e50a add paragraph#style method to quickly set the style 2017-03-09 11:11:07 +01:00
f5144e6d72 ts/styles linter warnings 2017-03-09 11:11:07 +01:00
2684f16579 move indent.ts over to paragraph and add a #indent method 2017-03-09 09:59:48 +01:00
147 changed files with 4456 additions and 2247 deletions

View File

@ -38,3 +38,6 @@ build-tests
# vscode # vscode
.vscode .vscode
# docs
docs

View File

@ -1,10 +1,16 @@
language: node_js language: node_js
node_js: node_js:
- "6" - "stable"
- "node"
install: install:
- npm install - npm install
script: script:
- npm run lint
- npm test - npm test
after_failure: after_failure:
- "cat /home/travis/builds/dolanmiu/docx/npm-debug.log" - "cat /home/travis/builds/dolanmiu/docx/npm-debug.log"
after_success:
- bash ./deploy-docs.sh
env:
global:
- ENCRYPTION_LABEL: "ad385fa3b525"

396
README.md
View File

@ -3,56 +3,38 @@
</p> </p>
<p align="center"> <p align="center">
Generate .docx files with JS/TS very easily Generate .docx files with JS/TS very easily, written in TS.
</p> </p>
---
[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Known Vulnerabilities][snky-image]][snky-url] -----
# docx [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][gemnasium-image]][gemnasium-url] [![Known Vulnerabilities][snky-image]][snky-url] [![Chat on Gitter][gitter-image]][gitter-url]
> A tool to create Word Documents (.docx) with JS or TS, written in TS.
[![NPM](https://nodei.co/npm/docx.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/docx/) [![NPM](https://nodei.co/npm/docx.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/docx/)
# Table of Contents # docx
- [Install](#)
- [Usage](#)
- [Create simple Word Document](#)
- [Create Paragraph](#)
- [Styles](#)
- [Heading1 - Heading5](#)
- [Title](#)
- [Text Alignment](#)
- [Example](#)
- [Thematic Break (Page Break)](#)
- [Text](#)
- [Typographical Emphasis](#)
- [Bold](#)
- [Italics](#)
- [Underline](#)
- [Break](#)
- [Chaining](#)
- [Bullet Points](#)
- [Tab Stops](#)
- [Left Tab Stop](#)
- [Center Tab Stop](#)
- [Right Tab Stop](#)
- [Max Right Tab Stop](#)
- [Example](#)
- [Exporting](#)
- [Express](#)
- [Standalone .docx file](#)
- [Examples](#)
- [License](#)
# Install ## Install
```sh ```sh
$ npm install --save docx $ npm install --save docx
``` ```
## Demo
# Usage ```sh
$ npm run demo
```
will run the demo selector app in the `demo` folder. It will prompt you to select a demo number, which will run a demo from that folder.
## Guide
Please refer to [the Wiki](https://github.com/dolanmiu/docx/wiki) for details on how to use this library, examples and much more!
Full documentation can be found here: [http://dolanmiu.github.io/docx](http://dolanmiu.github.io/docx)
## Simple Usage
```js ```js
// Used to create docx files // Used to create docx files
@ -61,326 +43,40 @@ var docx = require('docx');
// Create document // Create document
var doc = new docx.Document(); var doc = new docx.Document();
// Add some content in the document
var paragraph = new docx.Paragraph("Some cool text here.");
// Add more text into the paragraph if you wish
paragraph.addRun(new docx.TextRun('Lorem Ipsum Foo Bar'));
doc.addParagraph(paragraph);
// Used to export the file into a .docx file // Used to export the file into a .docx file
var exporter = new docx.LocalPacker(doc);
// Or use the express packer to make the file downloadable.
// res is express' Response object // res is express' Response object
var exporter = new docx.ExpressPacker(doc, res); var exporter = new docx.ExpressPacker(doc, res);
var exporter = new docx.LocalPacker(doc);
``` exporter.pack('My First Document');
## Create simple Word Document // If you want to export it as a .pdf file instead
```js exporter.packPdf('My First Document');
var doc = new docx.Document();
// done! A file called 'My First Document.docx'
var paragraph = new docx.Paragraph(); // will be in your file system if you used LocalPacker
var text = new docx.TextRun('Hello World'); // Or it will start downloading if you are using Express
paragraph.addText(text);
doc.addParagraph(paragraph);
``` ```
### Document properties ## Examples
You can add properties to the Word document by specifying options, for example: Check [the Wiki](https://github.com/dolanmiu/docx/wiki/Examples) for examples.
```js
var doc = new docx.Document({
creator: 'Dolan Miu',
description: 'My extremely interesting document',
title: 'My Document'
});
```
#### Full list of options: # Contributing
```
creator
description
title
subject
keywords
lastModifiedBy
revision
```
You can mix and match whatever properties you want, or provide no properties. Read the contribution guidelines [here](https://github.com/dolanmiu/docx/wiki/Contributing-Guidelines).
## Create Paragraph -----
Every text block in OpenXML is organised in paragraphs. You can add more text to the paragraph by doing this:
```js
var paragraph = new docx.Paragraph(),
```
```js
var text = new docx.TextRun('Lorem Ipsum Foo Bar');
var paragraph = new docx.Paragraph();
paragraph.addText(text);
```
```js
var paragraph = new docx.Paragraph("Short hand notation for adding text.");
```
After you create the paragraph, you must add the paragraph into the `document`: Made with 💖
```js
doc.addParagraph(paragraph);
```
### Styles Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contributions to this project
Styles is a very important part of the look of a word document. At the moment, only headings and title is supported, but son the rest will be supported along with custom styles!
![Word 2013 Styles menu](http://content.gcflearnfree.org/topics/233/style_apply_choose.png "Word 2013 Styles menu")
#### Heading1 - Heading5
```js
paragraph.heading1();
paragraph.heading2();
paragraph.heading3();
paragraph.heading4();
paragraph.heading5();
```
#### Title
```js
paragraph.title();
```
### Text Alignment
To change the text alignment of a paragraph, for center, left, right or justified:
```js
paragraph.center();
```
```js
paragraph.left();
```
```js
paragraph.right();
```
```js
paragraph.justified();
```
#### Example
```js
paragraph.heading1().center();
```
The above will create a `heading 1` which is `centered`.
### Thematic Break
To add a break in the page, simply add `.thematicBreak()` on a paragraph:
```js
var paragraph = new docx.Paragraph("Amazing Heading").heading1().thematicBreak();
```
The above example will create a heading with a page break directly under it.
### Page Break
To move to a new page (insert a page break), simply add `.pageBreak()` on a paragraph:
```js
var paragraph = new docx.Paragraph("Amazing Heading").heading1().pageBreak();
```
The above example will create a heading and start a new page immediately afterwards.
## Text
Paragraphs need `text run` objects. To create text:
```js
var text = new docx.TextRun("My awesome text here for my university dissertation");
paragraph.addText(text);
```
Text objects have methods inside which changes the way the text is displayed.
### Typographical Emphasis
More info [here](https://english.stackexchange.com/questions/97081/what-is-the-typography-term-which-refers-to-the-usage-of-bold-italics-and-unde)
#### Bold
```js
text.bold();
```
#### Italics
```js
text.italic();
```
#### Underline
```js
text.underline();
```
#### Strike through
```js
text.strike();
```
#### Double strike through
```js
text.doubleStrike();
```
#### Superscript
```js
text.superScript();
```
#### Subscript
```js
text.subScript();
```
#### All Capitals
```js
text.allCaps();
```
#### Small Capitals
```js
text.smallCaps();
```
### Break
Sometimes you would want to put text underneath another line of text but inside the same paragraph.
```js
text.break();
```
### Chaining
What if you want to create a paragraph which is ***bold*** and ***italic***?
```js
paragraph.bold().italic();
```
## Bullet Points
To make a bullet point, simply make a paragraph into a bullet point:
```js
var text = new docx.TextRun("Bullet points");
var paragraph = new docx.Paragraph(text).bullet();
var text2 = new docx.TextRun("Are awesome");
var paragraph2 = new docx.Paragraph(text2).bullet();
doc.addParagraph(paragraph);
doc.addParagraph(paragraph2);
```
This will produce:
* Bullet points
* Are awesome
## Tab Stops
If you do not know why tab stops are useful, then I recommend you do a bit of research. It enables side by side text which is nicely laid out without the need for tables, or constantly pressing space bar.
**Note**: At the moment, the unit of measurement for a tab stop is counter intuitive for a human. It is using OpenXMLs own measuring system. For example, 2268 roughly translates to 3cm. Therefore in the future, I may consider changing it to percentages or even cm.
![Word 2013 Tabs](http://www.teachucomp.com/wp-content/uploads/blog-4-22-2015-UsingTabStopsInWord-1024x577.png "Word 2013 Tab Stops")
Simply call the relevant methods on the paragraph listed below. Then just add a `tab()` method call to a text object. Adding multiple `tabStops` will mean you would have to chain `tab()` until the desired `tabStop` is selected. Example is shown below.
### Left Tab Stop
```js
paragraph.leftTabStop(2268);
```
2268 is the distance from the left side.
### Center Tab Stop
```js
paragraph.centerTabStp(2268);
```
2268 is the distance from the left side.
### Right Tab Stop
```js
paragraph.rightTabStop(2268);
```
2268 is the distance from the left side.
### Max Right Tab Stop
```js
paragraph.maxRightTabStop();
```
This will create a tab stop on the very edge of the right hand side. Handy for right aligning and left aligning text on the same line.
### Example
```js
var paragraph = new docx.Paragraph().maxRightTabStop();
var leftText = new docx.TextRun("Hey everyone").bold();
var rightText = new docx.TextRun("11th November 2015").tab();
paragraph.addText(leftText);
paragraph.addText(rightText);
```
The example above will create a left aligned text, and a right aligned text on the same line. The laymans approach to this problem would be to either use text boxes or tables. YUK!
```js
var paragraph = new docx.Paragraph();
paragraph.maxRightTabStop();
paragraph.leftTabStop(1000);
var text = new docx.TextRun("Second tab stop here I come!").tab().tab();
paragraph.addText(text);
```
The above shows the use of two tab stops, and how to select/use it.
# Exporting
I used the express exporter in my [website](http://www.dolan.bio). It's very useful, and is the preferred way if you want to make a downloadable file for a visitor. it is much better than generating a physical file on the server, and then passing a download link to that file.
## Express
Simply use the exporter, and pass in the necessary parameters:
```js
var docx = require('docx');
var doc = new docx.Document();
var exporter = new docx.ExpressPacker(doc, res);
exporter.pack('My Document');
```
where `res` is the response object obtained through the Express router. It is that simple. The file will begin downloading in the browser.
## Standalone .docx file
```js
var docx = require('docx');
var doc = new docx.Document();
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
```
# Examples
## In practice
I used this library in my personal portfolio/CV website. Click generate CV for a demonstration. [http://www.dolan.bio](http://www.dolan.bio)
## General
#### Simple paragraph
```js
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
var institutionText = new docx.TextRun("University College London").bold(),
var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
paragraph.addText(institutionText);
paragraph.addText(dateText);
doc.addParagraph(paragraph);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
```
Or:
```js
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
var institutionText = new docx.TextRun("University College London").bold(),
var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
paragraph.addText(institutionText);
paragraph.addText(dateText);
var exporter = new docx.ExpressPacker(doc, res);
exporter.pack('My Document');
```
Would produce:
***University College London***
***5th Dec 2015***
Made with 💖 by Dolan Miu 🍆 💦 😝
# License
MIT © [Dolan Miu](http://www.dolan.bio)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[npm-image]: https://badge.fury.io/js/docx.svg [npm-image]: https://badge.fury.io/js/docx.svg
[npm-url]: https://npmjs.org/package/docx [npm-url]: https://npmjs.org/package/docx
@ -389,4 +85,8 @@ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
[daviddm-image]: https://david-dm.org/dolanmiu/docx.svg?theme=shields.io [daviddm-image]: https://david-dm.org/dolanmiu/docx.svg?theme=shields.io
[daviddm-url]: https://david-dm.org/dolanmiu/docx [daviddm-url]: https://david-dm.org/dolanmiu/docx
[snky-image]: https://snyk.io/test/github/dolanmiu/docx/badge.svg [snky-image]: https://snyk.io/test/github/dolanmiu/docx/badge.svg
[snky-url]: https://snyk.io/test/github/dolanmiu/docx [snky-url]: https://snyk.io/test/github/dolanmiu/docx
[gitter-image]: https://badges.gitter.im/dolanmiu/docx.svg
[gitter-url]: https://gitter.im/docx-lib/Lobby
[gemnasium-image]: https://gemnasium.com/badges/github.com/dolanmiu/docx.svg
[gemnasium-url]: https://gemnasium.com/github.com/dolanmiu/docx

22
demo/demo1.js Normal file
View File

@ -0,0 +1,22 @@
const docx = require('../build');
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
var institutionText = new docx.TextRun("University College London").bold();
var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
paragraph.addRun(institutionText);
paragraph.addRun(dateText);
doc.addParagraph(paragraph);
// Feature coming soon
// var media = new docx.Media();
// media.addMedia("happy-penguins", "./demo/penguins.jpg");
// var pictureRun = new docx.PictureRun(media.getMedia("happy-penguins"));
// var exporter = new docx.LocalPacker(doc);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created succesfully at project root!');

74
demo/demo2.js Normal file
View File

@ -0,0 +1,74 @@
const docx = require('../build');
const styles = new docx.Styles();
styles.createParagraphStyle('Heading1', 'Heading 1')
.basedOn("Normal")
.next("Normal")
.quickFormat()
.size(28)
.bold()
.italics()
.spacing({after: 120});
styles.createParagraphStyle('Heading2', 'Heading 2')
.basedOn("Normal")
.next("Normal")
.quickFormat()
.size(26)
.bold()
.underline('double', 'FF0000')
.spacing({before: 240, after: 120});
styles.createParagraphStyle('aside', 'Aside')
.basedOn('Normal')
.next('Normal')
.color('999999')
.italics()
.indent(720)
.spacing({line: 276});
styles.createParagraphStyle('wellSpaced', 'Well Spaced')
.basedOn('Normal')
.spacing({line: 276, before: 20 * 72 * .1, after: 20 * 72 * .05});
styles.createParagraphStyle('ListParagraph', 'List Paragraph')
.quickFormat()
.basedOn('Normal');
const numbering = new docx.Numbering();
const numberedAbstract = numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left");
const doc = new docx.Document({
creator: 'Clippy',
title: 'Sample Document',
description: 'A brief example of using docx',
});
doc.createParagraph('Test heading1, bold and italicized').heading1();
doc.createParagraph('Some simple content');
doc.createParagraph('Test heading2 with double red underline').heading2();
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
const letterNumbering5 = numbering.createConcreteNumbering(numberedAbstract);
letterNumbering5.overrideLevel(0, 5);
doc.createParagraph('Option1').setNumbering(letterNumbering, 0);
doc.createParagraph('Option5 -- override 2 to 5').setNumbering(letterNumbering5, 0);
doc.createParagraph('Option3').setNumbering(letterNumbering, 0);
doc.createParagraph()
.createTextRun('Some monospaced content')
.font('Monospace');
doc.createParagraph('An aside, in light gray italics and indented').style('aside');
doc.createParagraph('This is normal, but well-spaced text').style('wellSpaced');
const para = doc.createParagraph();
para.createTextRun('This is a bold run,').bold();
para.createTextRun(' switching to normal ');
para.createTextRun('and then underlined ').underline();
para.createTextRun('and back to normal.');
const exporter = new docx.LocalPacker(doc, styles, undefined, numbering);
exporter.pack('test.docx');

35
demo/demo3.js Normal file
View File

@ -0,0 +1,35 @@
const docx = require('../build');
var doc = new docx.Document();
const numbering = new docx.Numbering();
const abstractNum = numbering.createAbstractNumbering();
abstractNum.createLevel(0, "upperRoman", "%1", "start")
.addParagraphProperty(new docx.Indent(720, 260));
abstractNum.createLevel(1, "decimal", "%2.", "start")
.addParagraphProperty(new docx.Indent(1440, 980));
abstractNum.createLevel(2, "lowerLetter", "%3)", "start")
.addParagraphProperty(new docx.Indent(2160, 1700));
const concrete = numbering.createConcreteNumbering(abstractNum);
var topLevelP = new docx.Paragraph("Hey you");
var subP = new docx.Paragraph("What's up fam");
var secondSubP = new docx.Paragraph("Hello World 2");
var subSubP = new docx.Paragraph("Yeah boi");
topLevelP.setNumbering(concrete, 0);
subP.setNumbering(concrete, 1);
secondSubP.setNumbering(concrete, 1);
subSubP.setNumbering(concrete, 2);
doc.addParagraph(topLevelP);
doc.addParagraph(subP);
doc.addParagraph(secondSubP);
doc.addParagraph(subSubP);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created succesfully at project root!');

12
demo/demo4.js Normal file
View File

@ -0,0 +1,12 @@
const docx = require('../build');
var doc = new docx.Document();
const table = doc.createTable(4, 4);
table.getCell(2, 2).addContent(new docx.Paragraph('Hello'));
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created succesfully at project root!');

29
demo/index.js Normal file
View File

@ -0,0 +1,29 @@
var prompt = require('prompt');
var shelljs = require('shelljs');
var fs = require('fs');
console.log('What demo do you wish to run? (Enter a number)');
var schema = {
properties: {
number: {
pattern: /^[0-9]+$/,
message: 'Please enter a number.',
required: true
}
}
};
prompt.start();
prompt.get(schema, function (err, result) {
var demoNumber = result.number;
var filePath = `./demo/demo${demoNumber}.js`;
if (!fs.existsSync(filePath)) {
console.error(`demo${demoNumber} does not exist: ${filePath}`);
return;
}
console.log(`Running demo ${demoNumber}`);
shelljs.exec(`node ${filePath}`);
});

BIN
demo/penguins.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

68
deploy-docs.sh Normal file
View File

@ -0,0 +1,68 @@
#!/bin/bash
set -e # Exit with nonzero exit code if anything fails
SOURCE_BRANCH="master"
TARGET_BRANCH="gh-pages"
function doCompile {
npm run typedoc
}
# Pull requests and commits to other branches shouldn't try to deploy, just build to verify
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "$SOURCE_BRANCH" ]; then
echo "Skipping deploy; just doing a build."
doCompile
exit 0
fi
# Save some useful information
REPO=`git config remote.origin.url`
SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
SHA=`git rev-parse --verify HEAD`
# Clone the existing gh-pages for this repo into docs/
# Create a new empty branch if gh-pages doesn't exist yet (should only happen on first deply)
git clone $REPO docs
cd docs
git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH
cd ..
# Clean out existing contents
# echo "Cleaning out existing contents."
# rm -rf docs/*
# Run our compile script
doCompile
# Now let's go have some fun with the cloned repo
cd docs
git config user.name "Travis CI"
git config user.email "dolan_miu@hotmail.com"
ls
# add .nojekyll file
touch .nojekyll
# If there are no changes to the compiled out (e.g. this is a README update) then just bail.
if [ -z `git diff --exit-code` ]; then
echo "No changes to the output on this push; exiting."
exit 0
fi
# Commit the "changes", i.e. the new version.
# The delta will show diffs between new and old versions.
git add .
git commit -m "Deploy to GitHub Pages: ${SHA}"
# Get the deploy key by using Travis's stored variables to decrypt deploy-key.enc
ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in deploy-key.enc -out deploy-key -d
chmod 600 deploy-key
eval `ssh-agent -s`
ssh-add deploy-key
# Now that we're all set up, we can push.
git push $SSH_REPO $TARGET_BRANCH

BIN
deploy-key.enc Normal file

Binary file not shown.

View File

@ -1,6 +1,6 @@
{ {
"name": "docx", "name": "docx",
"version": "1.2.1", "version": "3.1.0",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)", "description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
"main": "build/index.js", "main": "build/index.js",
"scripts": { "scripts": {
@ -8,33 +8,40 @@
"test": "mocha ./build-tests --recursive", "test": "mocha ./build-tests --recursive",
"prepublishOnly": "npm run build", "prepublishOnly": "npm run build",
"lint": "tslint --project ./ts", "lint": "tslint --project ./ts",
"build": "rimraf ./build && tsc -p ts" "build": "rimraf ./build && tsc -p ts",
"demo": "npm run build && node ./demo",
"typedoc": "typedoc --out docs/ ts/ --module commonjs --target ES6 --disableOutputCheck"
}, },
"files": [ "files": [
"ts", "ts",
"build" "build",
"template"
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/dolanmiu/docx.git" "url": "git+https://github.com/dolanmiu/docx.git"
}, },
"keywords": [ "keywords": [
"docx",
"office", "office",
"word",
"generate", "generate",
"creator",
"create",
"document", "document",
"doc", "doc",
"officegen", "officegen",
"clippy" "clippy"
], ],
"types": "./build/index.d.ts",
"dependencies": { "dependencies": {
"@types/archiver": "^0.15.37", "@types/archiver": "^1.3.4",
"@types/express": "^4.0.35", "@types/express": "^4.0.35",
"@types/lodash": "^4.14.54", "@types/request": "^2.0.3",
"app-root-path": "^2.0.1", "@types/request-promise": "^4.1.39",
"archiver": "^1.3.0", "archiver": "^1.3.0",
"install": "^0.8.7", "request": "^2.83.0",
"lodash": "^4.6.1", "request-promise": "^4.2.2",
"npm": "^4.3.0",
"xml": "^1.0.1" "xml": "^1.0.1"
}, },
"author": "Dolan Miu", "author": "Dolan Miu",
@ -48,8 +55,11 @@
"@types/mocha": "^2.2.39", "@types/mocha": "^2.2.39",
"chai": "^3.5.0", "chai": "^3.5.0",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"prompt": "^1.0.0",
"rimraf": "^2.5.2", "rimraf": "^2.5.2",
"tslint": "^4.5.1", "shelljs": "^0.7.7",
"typescript": "^2.2.1" "tslint": "^5.1.0",
"typedoc": "^0.5.10",
"typescript": "2.4.1"
} }
} }

View File

@ -1,15 +1,12 @@
import { Body } from "../../../docx/document/body";
import { assert } from "chai"; import { assert } from "chai";
import { SectionProperties } from "../../../docx/document/body/section-properties";
import { PageSize } from "../../../docx/document/body/page-size";
import { PageMargin } from "../../../docx/document/body/page-margin";
import { Columns } from "../../../docx/document/body/columns";
import { DocumentGrid } from "../../../docx/document/body/doc-grid";
function jsonify(obj: Object) { import { Utility } from "../../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { Body } from "./";
return JSON.parse(stringifiedJson); import { Columns } from "./columns";
} import { DocumentGrid } from "./doc-grid";
import { PageMargin } from "./page-margin";
import { PageSize } from "./page-size";
import { SectionProperties } from "./section-properties";
describe("Body", () => { describe("Body", () => {
let body: Body; let body: Body;
@ -26,28 +23,28 @@ describe("Body", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create the Section Properties", () => { it("should create the Section Properties", () => {
let newJson = jsonify(body); const newJson = Utility.jsonify(body);
assert.equal(newJson.root[0].rootKey, "w:sectPr"); assert.equal(newJson.root[0].rootKey, "w:sectPr");
}); });
it("should create the Page Size", () => { it("should create the Page Size", () => {
let newJson = jsonify(body); const newJson = Utility.jsonify(body);
assert.equal(newJson.root[1].rootKey, "w:pgSz"); assert.equal(newJson.root[1].rootKey, "w:pgSz");
}); });
it("should create the Page Margin", () => { it("should create the Page Margin", () => {
let newJson = jsonify(body); const newJson = Utility.jsonify(body);
assert.equal(newJson.root[2].rootKey, "w:pgMar"); assert.equal(newJson.root[2].rootKey, "w:pgMar");
}); });
it("should create the Columns", () => { it("should create the Columns", () => {
let newJson = jsonify(body); const newJson = Utility.jsonify(body);
assert.equal(newJson.root[3].rootKey, "w:cols"); assert.equal(newJson.root[3].rootKey, "w:cols");
}); });
it("should create the Document Grid", () => { it("should create the Document Grid", () => {
let newJson = jsonify(body); const newJson = Utility.jsonify(body);
assert.equal(newJson.root[4].rootKey, "w:docGrid"); assert.equal(newJson.root[4].rootKey, "w:docGrid");
}); });
}); });
}); });

View File

@ -0,0 +1,12 @@
import { XmlComponent } from "../../xml-components";
export class Body extends XmlComponent {
constructor() {
super("w:body");
}
public push(component: XmlComponent): void {
this.root.push(component);
}
}

View File

@ -1,15 +1 @@
import { Attributes, XmlComponent } from "../../xml-components"; export * from "./body";
import { SectionProperties } from "./section-properties";
export class Body extends XmlComponent {
constructor() {
super("w:body");
// this.root.push(new SectionProperties()); not actually needed
}
public push(component: XmlComponent): void {
// this.root.splice(this.body.length - 1, 0, component);
this.root.push(component);
}
}

View File

@ -1,6 +1,6 @@
import { XmlAttributeComponent } from "../xml-components"; import { XmlAttributeComponent } from "../xml-components";
interface IDocumentAttributesProperties { export interface IDocumentAttributesProperties {
wpc?: string; wpc?: string;
mc?: string; mc?: string;
o?: string; o?: string;
@ -26,39 +26,30 @@ interface IDocumentAttributesProperties {
type?: string; type?: string;
} }
export class DocumentAttributes extends XmlAttributeComponent { export class DocumentAttributes extends XmlAttributeComponent<IDocumentAttributesProperties> {
protected xmlKeys = {
constructor(properties?: IDocumentAttributesProperties) { wpc: "xmlns:wpc",
super({ mc: "xmlns:mc",
wpc: "xmlns:wpc", o: "xmlns:o",
mc: "xmlns:mc", r: "xmlns:r",
o: "xmlns:o", m: "xmlns:m",
r: "xmlns:r", v: "xmlns:v",
m: "xmlns:m", wp14: "xmlns:wp14",
v: "xmlns:v", wp: "xmlns:wp",
wp14: "xmlns:wp14", w10: "xmlns:w10",
wp: "xmlns:wp", w: "xmlns:w",
w10: "xmlns:w10", w14: "xmlns:w14",
w: "xmlns:w", w15: "xmlns:w15",
w14: "xmlns:w14", wpg: "xmlns:wpg",
w15: "xmlns:w15", wpi: "xmlns:wpi",
wpg: "xmlns:wpg", wne: "xmlns:wne",
wpi: "xmlns:wpi", wps: "xmlns:wps",
wne: "xmlns:wne", Ignorable: "mc:Ignorable",
wps: "xmlns:wps", cp: "xmlns:cp",
Ignorable: "mc:Ignorable", dc: "xmlns:dc",
cp: "xmlns:cp", dcterms: "xmlns:dcterms",
dc: "xmlns:dc", dcmitype: "xmlns:dcmitype",
dcterms: "xmlns:dcterms", xsi: "xmlns:xsi",
dcmitype: "xmlns:dcmitype", type: "xsi:type",
xsi: "xmlns:xsi", };
type: "xsi:type",
}, properties);
this.root = properties;
if (!properties) {
this.root = {};
}
}
} }

View File

@ -0,0 +1,74 @@
import { assert, expect } from "chai";
import * as docx from "../../";
import { Formatter } from "../../export/formatter";
describe("Document", () => {
let document: docx.Document;
beforeEach(() => {
document = new docx.Document();
});
describe("#constructor()", () => {
it("should create valid JSON", () => {
const stringifiedJson = JSON.stringify(document);
let newJson;
try {
newJson = JSON.parse(stringifiedJson);
} catch (e) {
assert.isTrue(false);
}
assert.isTrue(true);
});
});
describe("#createParagraph", () => {
it("should create a new paragraph and append it to body", () => {
const para = document.createParagraph();
expect(para).to.be.an.instanceof(docx.Paragraph);
const body = new Formatter().format(document)["w:document"][1]["w:body"];
expect(body).to.be.an("array").which.has.length.at.least(1);
expect(body[0]).to.have.property("w:p");
});
it("should use the text given to create a run in the paragraph", () => {
const para = document.createParagraph("sample paragraph text");
expect(para).to.be.an.instanceof(docx.Paragraph);
const body = new Formatter().format(document)["w:document"][1]["w:body"];
expect(body).to.be.an("array").which.has.length.at.least(1);
expect(body[0]).to.have.property("w:p").which.includes({
"w:r": [
{"w:rPr": []},
{"w:t": [{_attr: {"xml:space": "preserve"}}, "sample paragraph text"]},
],
});
});
});
describe("#createTable", () => {
it("should create a new table and append it to body", () => {
const table = document.createTable(2, 3);
expect(table).to.be.an.instanceof(docx.Table);
const body = new Formatter().format(document)["w:document"][1]["w:body"];
expect(body).to.be.an("array").which.has.length.at.least(1);
expect(body[0]).to.have.property("w:tbl");
});
it("should create a table with the correct dimensions", () => {
document.createTable(2, 3);
const body = new Formatter().format(document)["w:document"][1]["w:body"];
expect(body).to.be.an("array").which.has.length.at.least(1);
expect(body[0]).to.have.property("w:tbl").which.includes({
"w:tblGrid": [
{"w:gridCol": [{_attr: {"w:w": 1}}]},
{"w:gridCol": [{_attr: {"w:w": 1}}]},
{"w:gridCol": [{_attr: {"w:w": 1}}]},
],
});
expect(body[0]["w:tbl"].filter((x) => x["w:tr"])).to.have.length(2);
});
});
});

View File

@ -0,0 +1,56 @@
// http://officeopenxml.com/WPdocument.php
import { Paragraph } from "../paragraph";
import { Table } from "../table";
import { XmlComponent } from "../xml-components";
import { Body } from "./body";
import { DocumentAttributes } from "./document-attributes";
export class Document extends XmlComponent {
private body: Body;
constructor() {
super("w:document");
this.root.push(new DocumentAttributes({
wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
o: "urn:schemas-microsoft-com:office:office",
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
v: "urn:schemas-microsoft-com:vml",
wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
w10: "urn:schemas-microsoft-com:office:word",
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
wne: "http://schemas.microsoft.com/office/word/2006/wordml",
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
Ignorable: "w14 w15 wp14",
}));
this.body = new Body();
this.root.push(this.body);
}
public addParagraph(paragraph: Paragraph): void {
this.body.push(paragraph);
}
public createParagraph(text?: string): Paragraph {
const para = new Paragraph(text);
this.addParagraph(para);
return para;
}
public addTable(table: Table): void {
this.body.push(table);
}
public createTable(rows: number, cols: number): Table {
const table = new Table(rows, cols);
this.addTable(table);
return table;
}
}

View File

@ -1,41 +1 @@
import { Paragraph } from "../paragraph"; export * from "./document";
import { XmlComponent } from "../xml-components";
import { Body } from "./body";
import { DocumentAttributes } from "./document-attributes";
export class Document extends XmlComponent {
private body: Body;
constructor() {
super("w:document");
this.root.push(new DocumentAttributes({
wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
o: "urn:schemas-microsoft-com:office:office",
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
m: "http://schemas.openxmlformats.org/officeDocument/2006/math",
v: "urn:schemas-microsoft-com:vml",
wp14: "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
wp: "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
w10: "urn:schemas-microsoft-com:office:word",
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
wpg: "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
wpi: "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
wne: "http://schemas.microsoft.com/office/word/2006/wordml",
wps: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
Ignorable: "w14 w15 wp14",
}));
this.body = new Body();
this.root.push(this.body);
}
public addParagraph(paragraph: Paragraph): void {
this.body.push(paragraph);
}
public clearVariables(): void {
this.body.clearVariables();
delete this.body;
}
}

View File

@ -1,4 +1,4 @@
export { Document } from "./document"; export * from "./document";
export { Paragraph } from "./paragraph"; export * from "./paragraph";
export { Run } from "./run"; export * from "./run";
export { TextRun } from "./run/text-run"; export { Table } from "./table";

View File

@ -0,0 +1,16 @@
// http://officeopenxml.com/WPalignment.php
import { XmlAttributeComponent, XmlComponent } from "../../xml-components";
export type AlignmentOptions = "left" | "center" | "right" | "both";
export class AlignmentAttributes extends XmlAttributeComponent<{val: AlignmentOptions}> {
protected xmlKeys = {val: "w:val"};
}
export class Alignment extends XmlComponent {
constructor(type: AlignmentOptions) {
super("w:jc");
this.root.push(new AlignmentAttributes({val: type}));
}
}

View File

@ -0,0 +1,41 @@
import { assert } from "chai";
import { Utility } from "../../../tests/utility";
import { ThematicBreak } from "./border";
describe("Border", () => {
// TODO: Need tests here
});
describe("ThematicBreak", () => {
let thematicBreak: ThematicBreak;
beforeEach(() => {
thematicBreak = new ThematicBreak();
});
describe("#constructor()", () => {
it("should create valid JSON", () => {
const stringifiedJson = JSON.stringify(thematicBreak);
let newJson;
try {
newJson = JSON.parse(stringifiedJson);
} catch (e) {
assert.isTrue(false);
}
assert.isTrue(true);
});
it("should create a Thematic Break with correct border properties", () => {
const newJson = Utility.jsonify(thematicBreak);
const attributes = {
color: "auto",
space: "1",
val: "single",
sz: "6",
};
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
});
});
});

View File

@ -1,4 +1,5 @@
import { Attributes, XmlComponent } from "../xml-components"; // http://officeopenxml.com/WPborders.php
import { Attributes, XmlComponent } from "../../xml-components";
class Border extends XmlComponent { class Border extends XmlComponent {

View File

@ -0,0 +1,28 @@
// http://officeopenxml.com/WPindentation.php
import { XmlAttributeComponent, XmlComponent } from "../../xml-components";
interface IIndentAttributesProperties {
left?: number;
hanging?: number;
firstLine?: number;
start?: number;
end?: number;
}
class IndentAttributes extends XmlAttributeComponent<IIndentAttributesProperties> {
protected xmlKeys = {
left: "w:left",
hanging: "w:hanging",
firstLine: "w:firstLine",
start: "w:start",
end: "w:end",
};
}
export class Indent extends XmlComponent {
constructor(attrs: object) {
super("w:ind");
this.root.push(new IndentAttributes(attrs));
}
}

View File

@ -0,0 +1,9 @@
export * from "./alignment";
export * from "./border";
export * from "./indent";
export * from "./keep";
export * from "./page-break";
export * from "./spacing";
export * from "./style";
export * from "./tab-stop";
export * from "./unordered-list";

View File

@ -0,0 +1,13 @@
import { XmlComponent } from "../../xml-components";
export class KeepLines extends XmlComponent {
constructor() {
super("w:keepLines");
}
}
export class KeepNext extends XmlComponent {
constructor() {
super("w:keepNext");
}
}

View File

@ -1,10 +1,7 @@
import { PageBreak } from "../../../docx/paragraph/page-break";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { PageBreak } from "./page-break";
return JSON.parse(stringifiedJson);
}
describe("PageBreak", () => { describe("PageBreak", () => {
let pageBreak: PageBreak; let pageBreak: PageBreak;
@ -15,21 +12,21 @@ describe("PageBreak", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Page Break with correct attributes", () => { it("should create a Page Break with correct attributes", () => {
let newJson = jsonify(pageBreak); const newJson = Utility.jsonify(pageBreak);
let attributes = { const attributes = {
type: "page" type: "page",
}; };
assert.equal(JSON.stringify(newJson.root[1].root[0].root), JSON.stringify(attributes)); assert.equal(JSON.stringify(newJson.root[1].root[0].root), JSON.stringify(attributes));
}); });
it("should create a Page Break with w:r", () => { it("should create a Page Break with w:r", () => {
let newJson = jsonify(pageBreak); const newJson = Utility.jsonify(pageBreak);
assert.equal(newJson.rootKey, "w:r"); assert.equal(newJson.rootKey, "w:r");
}); });
it("should create a Page Break with a Break inside", () => { it("should create a Page Break with a Break inside", () => {
let newJson = jsonify(pageBreak); const newJson = Utility.jsonify(pageBreak);
assert.equal(newJson.root[1].rootKey, "w:br"); assert.equal(newJson.root[1].rootKey, "w:br");
}); });
}); });
}); });

View File

@ -1,5 +1,6 @@
import { Run } from "../run"; // http://officeopenxml.com/WPtextSpecialContent-break.php
import { Attributes, XmlComponent } from "../xml-components"; import { Run } from "../../run";
import { Attributes, XmlComponent } from "../../xml-components";
class Break extends XmlComponent { class Break extends XmlComponent {

View File

@ -0,0 +1,24 @@
import { expect } from "chai";
import { Formatter } from "../../../export/formatter";
import { Spacing } from "./spacing";
describe("Spacing", () => {
describe("#constructor", () => {
it("should set the properties given", () => {
const spacing = new Spacing({before: 100, after: 120, line: 150});
const tree = new Formatter().format(spacing);
expect(tree).to.deep.equal({
"w:spacing": [{_attr: {"w:after": 120, "w:before": 100, "w:line": 150}}],
});
});
it("should only set the given properties", () => {
const spacing = new Spacing({before: 100});
const tree = new Formatter().format(spacing);
expect(tree).to.deep.equal({
"w:spacing": [{_attr: {"w:before": 100}}],
});
});
});
});

View File

@ -0,0 +1,23 @@
// http://officeopenxml.com/WPspacing.php
import { XmlAttributeComponent, XmlComponent } from "../../xml-components";
export interface ISpacingProperties {
after?: number;
before?: number;
line?: number;
}
class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
protected xmlKeys = {
after: "w:after",
before: "w:before",
line: "w:line",
};
}
export class Spacing extends XmlComponent {
constructor(opts: ISpacingProperties) {
super("w:spacing");
this.root.push(new SpacingAttributes(opts));
}
}

View File

@ -1,10 +1,7 @@
import { Style } from "../../../docx/paragraph/style";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { Style } from "./style";
return JSON.parse(stringifiedJson);
}
describe("ParagraphStyle", () => { describe("ParagraphStyle", () => {
let style: Style; let style: Style;
@ -12,15 +9,14 @@ describe("ParagraphStyle", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a style with given value", () => { it("should create a style with given value", () => {
style = new Style("test"); style = new Style("test");
let newJson = jsonify(style); const newJson = Utility.jsonify(style);
assert.equal(newJson.root[0].root.val, "test"); assert.equal(newJson.root[0].root.val, "test");
}); });
it("should create a style with blank val", () => { it("should create a style with blank val", () => {
style = new Style(""); style = new Style("");
let newJson = jsonify(style); const newJson = Utility.jsonify(style);
assert.equal(newJson.root[0].root.val, ""); assert.equal(newJson.root[0].root.val, "");
}); });
}); });
});
});

View File

@ -1,4 +1,4 @@
import { Attributes, XmlComponent } from "../xml-components"; import { Attributes, XmlComponent } from "../../xml-components";
export class Style extends XmlComponent { export class Style extends XmlComponent {

View File

@ -1,10 +1,7 @@
import { LeftTabStop, MaxRightTabStop } from "../../../docx/paragraph/tab-stop";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { LeftTabStop, MaxRightTabStop } from "./tab-stop";
return JSON.parse(stringifiedJson);
}
describe("LeftTabStop", () => { describe("LeftTabStop", () => {
let tabStop: LeftTabStop; let tabStop: LeftTabStop;
@ -15,23 +12,23 @@ describe("LeftTabStop", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Tab Stop with correct attributes", () => { it("should create a Tab Stop with correct attributes", () => {
let newJson = jsonify(tabStop); const newJson = Utility.jsonify(tabStop);
let attributes = { const attributes = {
val: "left", val: "left",
pos: 100 pos: 100,
}; };
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes)); assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
}); });
it("should create a Tab Stop with w:tab", () => { it("should create a Tab Stop with w:tab", () => {
let newJson = jsonify(tabStop); const newJson = Utility.jsonify(tabStop);
assert.equal(newJson.root[0].rootKey, "w:tab"); assert.equal(newJson.root[0].rootKey, "w:tab");
}); });
}); });
}); });
describe("RightTabStop", () => { describe("RightTabStop", () => {
// TODO
}); });
describe("MaxRightTabStop", () => { describe("MaxRightTabStop", () => {
@ -43,18 +40,18 @@ describe("MaxRightTabStop", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Tab Stop with correct attributes", () => { it("should create a Tab Stop with correct attributes", () => {
let newJson = jsonify(tabStop); const newJson = Utility.jsonify(tabStop);
let attributes = { const attributes = {
val: "right", val: "right",
pos: 9026 pos: 9026,
}; };
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes)); assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
}); });
it("should create a Tab Stop with w:tab", () => { it("should create a Tab Stop with w:tab", () => {
let newJson = jsonify(tabStop); const newJson = Utility.jsonify(tabStop);
assert.equal(newJson.root[0].rootKey, "w:tab"); assert.equal(newJson.root[0].rootKey, "w:tab");
}); });
}); });
}); });

View File

@ -0,0 +1,51 @@
// http://officeopenxml.com/WPtab.php
import { XmlAttributeComponent, XmlComponent } from "../../xml-components";
export class TabStop extends XmlComponent {
constructor(tab: Tab) {
super("w:tabs");
this.root.push(tab);
}
}
export type TabValue = "left" | "right" | "center" | "bar" | "clear" | "decimal" | "end" | "num" | "start";
export class TabAttributes extends XmlAttributeComponent<{val: TabValue, pos: string | number}> {
protected xmlKeys = {val: "w:val", pos: "w:pos"};
}
export class Tab extends XmlComponent {
constructor(value: TabValue, position: string | number) {
super("w:tab");
this.root.push(new TabAttributes({
val: value,
pos: position,
}));
}
}
export class MaxRightTabStop extends TabStop {
constructor() {
super(new Tab("right", 9026));
}
}
export class LeftTabStop extends TabStop {
constructor(position: number) {
super(new Tab("left", position));
}
}
export class RightTabStop extends TabStop {
constructor(position: number) {
super(new Tab("right", position));
}
}
export class CenterTabStop extends TabStop {
constructor(position: number) {
super(new Tab("center", position));
}
}

View File

@ -1,10 +1,7 @@
import { NumberProperties } from "../../../docx/paragraph/unordered-list";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { NumberProperties } from "./unordered-list";
return JSON.parse(stringifiedJson);
}
describe("NumberProperties", () => { describe("NumberProperties", () => {
let numberProperties: NumberProperties; let numberProperties: NumberProperties;
@ -15,20 +12,20 @@ describe("NumberProperties", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Number Properties with correct root key", () => { it("should create a Number Properties with correct root key", () => {
let newJson = jsonify(numberProperties); const newJson = Utility.jsonify(numberProperties);
assert.equal(newJson.rootKey, "w:numPr"); assert.equal(newJson.rootKey, "w:numPr");
}); });
it("should create a Page Break with a Indent Level inside", () => { it("should create a Page Break with a Indent Level inside", () => {
let newJson = jsonify(numberProperties); const newJson = Utility.jsonify(numberProperties);
assert.equal(newJson.root[0].rootKey, "w:ilvl"); assert.equal(newJson.root[0].rootKey, "w:ilvl");
assert.equal(newJson.root[0].root[0].root.val, 10); assert.equal(newJson.root[0].root[0].root.val, 10);
}); });
it("should create a Page Break with a Number Id inside", () => { it("should create a Page Break with a Number Id inside", () => {
let newJson = jsonify(numberProperties); const newJson = Utility.jsonify(numberProperties);
assert.equal(newJson.root[1].rootKey, "w:numId"); assert.equal(newJson.root[1].rootKey, "w:numId");
assert.equal(newJson.root[1].root[0].root.val, 5); assert.equal(newJson.root[1].root[0].root.val, 5);
}); });
}); });
}); });

View File

@ -1,5 +1,4 @@
import { Attributes, XmlComponent } from "../xml-components"; import { Attributes, XmlComponent } from "../../xml-components";
import { Style } from "./style";
export class NumberProperties extends XmlComponent { export class NumberProperties extends XmlComponent {

View File

@ -1,119 +1,3 @@
import { Num } from "../../numbering/num"; export * from "./formatting";
import { TextRun } from "../run/text-run"; export * from "./paragraph";
import { Attributes, XmlComponent } from "../xml-components"; export * from "./properties";
import { ThematicBreak } from "./border";
import { PageBreak } from "./page-break";
import { ParagraphProperties } from "./properties";
import { Style } from "./style";
import { LeftTabStop, MaxRightTabStop } from "./tab-stop";
import { NumberProperties } from "./unordered-list";
class Alignment extends XmlComponent {
constructor(type: string) {
super("w:jc");
this.root.push(new Attributes({
val: type,
}));
}
}
export class Paragraph extends XmlComponent {
private properties: ParagraphProperties;
constructor(text?: string) {
super("w:p");
this.properties = new ParagraphProperties();
this.root.push(this.properties);
if (text !== undefined) {
this.root.push(new TextRun(text));
}
}
public addText(run: TextRun): Paragraph {
this.root.push(run);
return this;
}
public heading1(): Paragraph {
this.properties.push(new Style("Heading1"));
return this;
}
public heading2(): Paragraph {
this.properties.push(new Style("Heading2"));
return this;
}
public heading3(): Paragraph {
this.properties.push(new Style("Heading3"));
return this;
}
public heading4(): Paragraph {
this.properties.push(new Style("Heading4"));
return this;
}
public heading5(): Paragraph {
this.properties.push(new Style("Heading5"));
return this;
}
public title(): Paragraph {
this.properties.push(new Style("Title"));
return this;
}
public center(): Paragraph {
this.properties.push(new Alignment("center"));
return this;
}
public left(): Paragraph {
this.properties.push(new Alignment("left"));
return this;
}
public right(): Paragraph {
this.properties.push(new Alignment("right"));
return this;
}
public justified(): Paragraph {
this.properties.push(new Alignment("both"));
return this;
}
public thematicBreak(): Paragraph {
this.properties.push(new ThematicBreak());
return this;
}
public pageBreak(): Paragraph {
this.properties.push(new PageBreak());
return this;
}
public maxRightTabStop(): Paragraph {
this.properties.push(new MaxRightTabStop());
return this;
}
public leftTabStop(position: number): Paragraph {
this.properties.push(new LeftTabStop(position));
return this;
}
public bullet(): Paragraph {
this.properties.push(new Style("ListParagraph"));
this.properties.push(new NumberProperties(1, 0));
return this;
}
public setNumbering(numbering: Num, indentLevel: number): Paragraph {
this.properties.push(new Style("ListParagraph"));
this.properties.push(new NumberProperties(numbering.id, indentLevel));
return this;
}
}

View File

@ -0,0 +1,293 @@
import { assert, expect } from "chai";
import * as docx from "../../docx";
import { Formatter } from "../../export/formatter";
import { Numbering } from "../../numbering";
describe("Paragraph", () => {
let paragraph: docx.Paragraph;
beforeEach(() => {
paragraph = new docx.Paragraph();
});
describe("#constructor()", () => {
it("should create valid JSON", () => {
const stringifiedJson = JSON.stringify(paragraph);
let newJson;
try {
newJson = JSON.parse(stringifiedJson);
} catch (e) {
assert.isTrue(false);
}
assert.isTrue(true);
});
it("should create have valid properties", () => {
const stringifiedJson = JSON.stringify(paragraph);
const newJson = JSON.parse(stringifiedJson);
assert.equal(newJson.root[0].rootKey, "w:pPr");
});
});
describe("#createTextRun", () => {
it("should add a new run to the paragraph and return it", () => {
const run = paragraph.createTextRun("this is a test run");
expect(run).to.be.instanceof(docx.TextRun);
const tree = new Formatter().format(paragraph)["w:p"];
expect(tree).to.be.an("array").which.includes({
"w:r": [
{"w:rPr": []},
{"w:t": [{_attr: {"xml:space": "preserve"}}, "this is a test run"]},
],
});
});
});
describe("#heading1()", () => {
it("should add heading style to JSON", () => {
paragraph.heading1();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Heading1"}}]}],
},
],
});
});
});
describe("#heading2()", () => {
it("should add heading style to JSON", () => {
paragraph.heading2();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Heading2"}}]}],
},
],
});
});
});
describe("#heading3()", () => {
it("should add heading style to JSON", () => {
paragraph.heading3();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Heading3"}}]}],
},
],
});
});
});
describe("#title()", () => {
it("should add title style to JSON", () => {
paragraph.title();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [{"w:pStyle": [{_attr: {"w:val": "Title"}}]}],
},
],
});
});
});
describe("#center()", () => {
it("should add center alignment to JSON", () => {
paragraph.center();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [{"w:jc": [{_attr: {"w:val": "center"}}]}],
},
],
});
});
});
describe("#thematicBreak()", () => {
it("should add thematic break to JSON", () => {
paragraph.thematicBreak();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [{
"w:pPr": [{
"w:pBdr": [{
"w:bottom": [{
_attr: {
"w:val": "single",
"w:color": "auto",
"w:space": "1",
"w:sz": "6",
},
}],
}],
}],
}],
});
});
});
describe("#pageBreak()", () => {
it("should add page break to JSON", () => {
paragraph.pageBreak();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [{
"w:pPr": [],
}, {
"w:r": [
{"w:rPr": []},
{"w:br": [{_attr: {"w:type": "page"}}]},
],
}],
});
});
});
describe("#bullet()", () => {
it("should add list paragraph style to JSON", () => {
paragraph.bullet();
const tree = new Formatter().format(paragraph);
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]["w:pPr"][0]).to.deep.equal({
"w:pStyle": [{_attr: {"w:val": "ListParagraph"}}],
});
});
it("it should add numbered properties", () => {
paragraph.bullet();
const tree = new Formatter().format(paragraph);
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(2);
expect(tree["w:p"][0]["w:pPr"][1]).to.deep.equal({
"w:numPr": [
{"w:ilvl": [{_attr: {"w:val": 0}}]},
{"w:numId": [{_attr: {"w:val": 1}}]},
],
});
});
});
describe("#setNumbering", () => {
it("should add list paragraph style to JSON", () => {
const numbering = new Numbering();
const numberedAbstract = numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
paragraph.setNumbering(letterNumbering, 0);
const tree = new Formatter().format(paragraph);
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]["w:pPr"][0]).to.deep.equal({
"w:pStyle": [{_attr: {"w:val": "ListParagraph"}}],
});
});
it("it should add numbered properties", () => {
const numbering = new Numbering();
const numberedAbstract = numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
paragraph.setNumbering(letterNumbering, 0);
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [
{"w:pStyle": [{_attr: {"w:val": "ListParagraph"}}]},
{
"w:numPr": [
{"w:ilvl": [{_attr: {"w:val": 0}}]},
{"w:numId": [{_attr: {"w:val": letterNumbering.id}}]},
],
},
],
},
],
});
});
});
describe("#style", () => {
it("should set the paragraph style to the given styleId", () => {
paragraph.style("myFancyStyle");
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [
{"w:pStyle": [{_attr: {"w:val": "myFancyStyle"}}]},
],
},
],
});
});
});
describe("#indent", () => {
it("should set the paragraph indent to the given values", () => {
paragraph.indent({ left: 720 });
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [
{"w:ind": [{_attr: {"w:left": 720}}]},
],
},
],
});
});
});
describe("#spacing", () => {
it("should set the paragraph spacing to the given values", () => {
paragraph.spacing({before: 90, line: 50});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:pPr": [
{"w:spacing": [{_attr: {"w:before": 90, "w:line": 50}}]},
],
},
],
});
});
});
describe("#keepLines", () => {
it("should set the paragraph keepLines sub-component", () => {
paragraph.keepLines();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [{"w:pPr": [{"w:keepLines": []}]}],
});
});
});
describe("#keepNext", () => {
it("should set the paragraph keepNext sub-component", () => {
paragraph.keepNext();
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [{"w:pPr": [{"w:keepNext": []}]}],
});
});
});
});

View File

@ -0,0 +1,165 @@
// http://officeopenxml.com/WPparagraph.php
import { IData } from "../../media/data";
import { Num } from "../../numbering/num";
import { Run } from "../run";
import { PictureRun } from "../run/picture-run";
import { TextRun } from "../run/text-run";
import { XmlComponent } from "../xml-components";
import { Alignment } from "./formatting/alignment";
import { ThematicBreak } from "./formatting/border";
import { Indent } from "./formatting/indent";
import { KeepLines, KeepNext } from "./formatting/keep";
import { PageBreak } from "./formatting/page-break";
import { ISpacingProperties, Spacing } from "./formatting/spacing";
import { Style } from "./formatting/style";
import { CenterTabStop, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop";
import { NumberProperties } from "./formatting/unordered-list";
import { ParagraphProperties } from "./properties";
export class Paragraph extends XmlComponent {
private properties: ParagraphProperties;
constructor(text?: string) {
super("w:p");
this.properties = new ParagraphProperties();
this.root.push(this.properties);
if (text !== undefined) {
this.root.push(new TextRun(text));
}
}
public addRun(run: Run): Paragraph {
this.root.push(run);
return this;
}
public createTextRun(text: string): TextRun {
const run = new TextRun(text);
this.addRun(run);
return run;
}
public createPictureRun(imageData: IData): PictureRun {
const run = new PictureRun(imageData);
this.addRun(run);
return run;
}
public heading1(): Paragraph {
this.properties.push(new Style("Heading1"));
return this;
}
public heading2(): Paragraph {
this.properties.push(new Style("Heading2"));
return this;
}
public heading3(): Paragraph {
this.properties.push(new Style("Heading3"));
return this;
}
public heading4(): Paragraph {
this.properties.push(new Style("Heading4"));
return this;
}
public heading5(): Paragraph {
this.properties.push(new Style("Heading5"));
return this;
}
public title(): Paragraph {
this.properties.push(new Style("Title"));
return this;
}
public center(): Paragraph {
this.properties.push(new Alignment("center"));
return this;
}
public left(): Paragraph {
this.properties.push(new Alignment("left"));
return this;
}
public right(): Paragraph {
this.properties.push(new Alignment("right"));
return this;
}
public justified(): Paragraph {
this.properties.push(new Alignment("both"));
return this;
}
public thematicBreak(): Paragraph {
this.properties.push(new ThematicBreak());
return this;
}
public pageBreak(): Paragraph {
this.root.push(new PageBreak());
return this;
}
public maxRightTabStop(): Paragraph {
this.properties.push(new MaxRightTabStop());
return this;
}
public leftTabStop(position: number): Paragraph {
this.properties.push(new LeftTabStop(position));
return this;
}
public rightTabStop(position: number): Paragraph {
this.properties.push(new RightTabStop(position));
return this;
}
public centerTabStop(position: number): Paragraph {
this.properties.push(new CenterTabStop(position));
return this;
}
public bullet(): Paragraph {
this.properties.push(new Style("ListParagraph"));
this.properties.push(new NumberProperties(1, 0));
return this;
}
public setNumbering(numbering: Num, indentLevel: number): Paragraph {
this.properties.push(new Style("ListParagraph"));
this.properties.push(new NumberProperties(numbering.id, indentLevel));
return this;
}
public style(styleId: string): Paragraph {
this.properties.push(new Style(styleId));
return this;
}
public indent(attrs: object): Paragraph {
this.properties.push(new Indent(attrs));
return this;
}
public spacing(params: ISpacingProperties): Paragraph {
this.properties.push(new Spacing(params));
return this;
}
public keepNext(): Paragraph {
this.properties.push(new KeepNext());
return this;
}
public keepLines(): Paragraph {
this.properties.push(new KeepLines());
return this;
}
}

View File

@ -1,10 +1,10 @@
import { Attributes, XmlComponent } from "../xml-components"; // http://officeopenxml.com/WPparagraphProperties.php
import { XmlComponent } from "../xml-components";
export class ParagraphProperties extends XmlComponent { export class ParagraphProperties extends XmlComponent {
constructor() { constructor() {
super("w:pPr"); super("w:pPr");
this.root.push(new Attributes());
} }
public push(item: XmlComponent): void { public push(item: XmlComponent): void {

View File

@ -1,32 +0,0 @@
import { Attributes, XmlComponent } from "../xml-components";
class TabStop extends XmlComponent {
constructor(tab: Tab) {
super("w:tabs");
this.root.push(tab);
}
}
class Tab extends XmlComponent {
constructor(value: string, position: any) {
super("w:tab");
this.root.push(new Attributes({
val: value,
pos: position,
}));
}
}
export class MaxRightTabStop extends TabStop {
constructor() {
super(new Tab("right", 9026));
}
}
export class LeftTabStop extends TabStop {
constructor(position: number) {
super(new Tab("left", position));
}
}

View File

@ -1,3 +0,0 @@
export class Row {
}

View File

@ -1,10 +1,7 @@
import { Break } from "../../../docx/run/break";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { Break } from "./break";
return JSON.parse(stringifiedJson);
}
describe("Break", () => { describe("Break", () => {
let currentBreak: Break; let currentBreak: Break;
@ -15,8 +12,8 @@ describe("Break", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Break with correct root key", () => { it("should create a Break with correct root key", () => {
let newJson = jsonify(currentBreak); const newJson = Utility.jsonify(currentBreak);
assert.equal(newJson.rootKey, "w:br"); assert.equal(newJson.rootKey, "w:br");
}); });
}); });
}); });

View File

@ -1,3 +1,4 @@
// http://officeopenxml.com/WPtextSpecialContent-break.php
import { XmlComponent } from "../xml-components"; import { XmlComponent } from "../xml-components";
export class Break extends XmlComponent { export class Break extends XmlComponent {

View File

@ -1,4 +1,7 @@
import { Attributes, XmlComponent } from "../xml-components"; import { Attributes, XmlComponent } from "../xml-components";
export { Underline } from "./underline";
export { SubScript, SuperScript } from "./script";
export { RunFonts } from "./run-fonts";
export class Bold extends XmlComponent { export class Bold extends XmlComponent {

View File

@ -1,81 +1,3 @@
import { Break } from "./break"; export * from "./run";
import { Caps, SmallCaps } from "./caps"; export * from "./text-run";
import { Bold, Italics } from "./formatting"; export * from "./picture-run";
import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
import { DoubleStrike, Strike } from "./strike";
import { Tab } from "./tab";
import { Underline } from "./underline";
import { Attributes, XmlComponent } from "../xml-components";
export class Run extends XmlComponent {
private properties: RunProperties;
constructor() {
super("w:r");
this.properties = new RunProperties();
this.root.push(this.properties);
}
public bold(): Run {
this.properties.push(new Bold());
return this;
}
public italic(): Run {
this.properties.push(new Italics());
return this;
}
public underline(): Run {
this.properties.push(new Underline());
return this;
}
public break(): Run {
this.root.splice(1, 0, new Break());
return this;
}
public tab(): Run {
this.root.splice(1, 0, new Tab());
return this;
}
public smallCaps(): Run {
this.properties.push(new SmallCaps());
return this;
}
public allCaps(): Run {
this.properties.push(new Caps());
return this;
}
public strike(): Run {
this.properties.push(new Strike());
return this;
}
public doubleStrike(): Run {
this.properties.push(new DoubleStrike());
return this;
}
public subScript(): Run {
this.properties.push(new SubScript());
return this;
}
public superScript(): Run {
this.properties.push(new SuperScript());
return this;
}
public font(fontName: string): Run {
this.properties.push(new RunFonts(fontName));
return this;
}
}

View File

@ -0,0 +1,16 @@
import { IData } from "../../media/data";
import { Run } from "../run";
import { Drawing } from "./run-components/drawing";
export class PictureRun extends Run {
constructor(imageData: IData) {
super();
if (imageData === undefined) {
throw new Error("imageData cannot be undefined");
}
this.root.push(new Drawing(imageData));
}
}

View File

@ -0,0 +1,27 @@
import { assert } from "chai";
import * as fs from "fs";
import { Utility } from "../../../../tests/utility";
import { Drawing } from "./";
describe("Drawing", () => {
let currentBreak: Drawing;
beforeEach(() => {
const path = "./demo/penguins.jpg";
currentBreak = new Drawing({
fileName: "test.jpg",
referenceId: 1,
stream: fs.createReadStream(path),
path: path,
});
});
describe("#constructor()", () => {
it("should create a Drawing with correct root key", () => {
const newJson = Utility.jsonify(currentBreak);
assert.equal(newJson.rootKey, "w:drawing");
// console.log(JSON.stringify(newJson, null, 2));
});
});
});

View File

@ -0,0 +1,16 @@
import { IData } from "../../../../media/data";
import { XmlComponent } from "../../../xml-components";
import { Inline } from "./inline";
export class Drawing extends XmlComponent {
constructor(imageData: IData) {
super("w:drawing");
if (imageData === undefined) {
throw new Error("imageData cannot be undefined");
}
this.root.push(new Inline(imageData.referenceId));
}
}

View File

@ -0,0 +1,10 @@
import { XmlComponent } from "../../../../../../xml-components";
import { Pic } from "./pic";
export class GraphicData extends XmlComponent {
constructor(referenceId: number) {
super("a:graphicData");
this.root.push(new Pic(referenceId));
}
}

View File

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

View File

@ -0,0 +1,21 @@
import { XmlAttributeComponent, XmlComponent } from "../../../../../../../../xml-components";
interface IBlipProperties {
embed: string;
}
class BlipAttributes extends XmlAttributeComponent<IBlipProperties> {
protected xmlKeys = {
embed: "r:embed",
};
}
export class Blip extends XmlComponent {
constructor(referenceId: number) {
super("a:blip");
this.root.push(new BlipAttributes({
embed: `rId${referenceId}`,
}));
}
}

View File

@ -0,0 +1,8 @@
import { XmlComponent } from "../../../../../../../../xml-components";
export class SourceRectangle extends XmlComponent {
constructor() {
super("a:srcRect");
}
}

View File

@ -0,0 +1,16 @@
import { XmlComponent } from "../../../../../../../../xml-components";
class FillRectangle extends XmlComponent {
constructor() {
super("a:fillRect");
}
}
export class Stretch extends XmlComponent {
constructor() {
super("a:stretch");
this.root.push(new FillRectangle());
}
}

View File

@ -0,0 +1,10 @@
import { XmlComponent } from "../../../../../../../xml-components";
import { BlipFill } from "./blip/blip-fill";
export class Pic extends XmlComponent {
constructor(referenceId: number) {
super("pic:pic");
this.root.push(new BlipFill(referenceId));
}
}

View File

@ -0,0 +1,23 @@
import { XmlAttributeComponent, XmlComponent } from "../../../../../xml-components";
import { GraphicData } from "./graphic-data";
interface IGraphicProperties {
a: string;
}
class GraphicAttributes extends XmlAttributeComponent<IGraphicProperties> {
protected xmlKeys = {
a: "xmlns:a",
};
}
export class Graphic extends XmlComponent {
constructor(referenceId: number) {
super("a:graphic");
this.root.push(new GraphicAttributes({
a: "http://schemas.openxmlformats.org/drawingml/2006/main",
}));
this.root.push(new GraphicData(referenceId));
}
}

View File

@ -0,0 +1,10 @@
import { XmlComponent } from "../../../../xml-components";
import { Graphic } from "./graphic";
export class Inline extends XmlComponent {
constructor(referenceId: number) {
super("wp:inline");
this.root.push(new Graphic(referenceId));
}
}

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import { Formatter } from "../../../export/formatter";
import { Text } from "./text";
describe("Text", () => {
describe("#constructor", () => {
it("creates an empty text run if no text is given", () => {
const t = new Text("");
const f = new Formatter().format(t);
expect(f).to.deep.equal({"w:t": [{_attr: {"xml:space": "preserve"}}]});
});
it("adds the passed in text to the component", () => {
const t = new Text(" this is\n text");
const f = new Formatter().format(t);
expect(f).to.deep.equal({"w:t": [
{_attr: {"xml:space": "preserve"}},
" this is\n text",
]});
});
});
});

View File

@ -0,0 +1,15 @@
import { XmlAttributeComponent, XmlComponent } from "../../xml-components";
class TextAttributes extends XmlAttributeComponent<{space: "default" | "preserve"}> {
protected xmlKeys = {space: "xml:space"};
}
export class Text extends XmlComponent {
constructor(text: string) {
super("w:t");
this.root.push(new TextAttributes({space: "preserve"}));
if (text) {
this.root.push(text);
}
}
}

View File

@ -1,6 +1,7 @@
import { expect } from "chai"; import { expect } from "chai";
import { RunFonts } from "../../../docx/run/run-fonts";
import { Formatter } from "../../../export/formatter"; import { Formatter } from "../../export/formatter";
import { RunFonts } from "./run-fonts";
describe("RunFonts", () => { describe("RunFonts", () => {

View File

@ -6,15 +6,12 @@ interface IRunFontAttributesProperties {
hint?: string; hint?: string;
} }
class RunFontAttributes extends XmlAttributeComponent { class RunFontAttributes extends XmlAttributeComponent<IRunFontAttributesProperties> {
protected xmlKeys = {
constructor(properties: IRunFontAttributesProperties) { ascii: "w:ascii",
super({ hAnsi: "w:hAnsi",
ascii: "w:ascii", hint: "w:hint",
hAnsi: "w:hAnsi", };
hint: "w:hint",
}, properties);
}
} }
export class RunFonts extends XmlComponent { export class RunFonts extends XmlComponent {

View File

@ -1,11 +1,8 @@
import { assert, expect } from "chai"; import { assert, expect } from "chai";
import { Run } from "../../../docx/run";
import { TextRun } from "../../../docx/run/text-run";
import { Formatter } from "../../../export/formatter";
function jsonify(obj: object) { import { Formatter } from "../../export/formatter";
return JSON.parse(JSON.stringify(obj)); import { Utility } from "../../tests/utility";
} import { Run } from "./";
describe("Run", () => { describe("Run", () => {
let run: Run; let run: Run;
@ -17,7 +14,7 @@ describe("Run", () => {
describe("#bold()", () => { describe("#bold()", () => {
it("it should add bold to the properties", () => { it("it should add bold to the properties", () => {
run.bold(); run.bold();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:b"); assert.equal(newJson.root[0].root[0].rootKey, "w:b");
}); });
}); });
@ -25,7 +22,7 @@ describe("Run", () => {
describe("#italic()", () => { describe("#italic()", () => {
it("it should add italics to the properties", () => { it("it should add italics to the properties", () => {
run.italic(); run.italic();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:i"); assert.equal(newJson.root[0].root[0].rootKey, "w:i");
}); });
}); });
@ -33,15 +30,35 @@ describe("Run", () => {
describe("#underline()", () => { describe("#underline()", () => {
it("it should add underline to the properties", () => { it("it should add underline to the properties", () => {
run.underline(); run.underline();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:u"); assert.equal(newJson.root[0].root[0].rootKey, "w:u");
}); });
it("should default to 'single' and no color", () => {
run.underline();
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{"w:rPr": [{"w:u": [{_attr: {"w:val": "single"}}]}]},
],
});
});
it("should set the style type and color if given", () => {
run.underline("double", "990011");
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{"w:rPr": [{"w:u": [{_attr: {"w:val": "double", "w:color": "990011"}}]}]},
],
});
});
}); });
describe("#smallCaps()", () => { describe("#smallCaps()", () => {
it("it should add smallCaps to the properties", () => { it("it should add smallCaps to the properties", () => {
run.smallCaps(); run.smallCaps();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:smallCaps"); assert.equal(newJson.root[0].root[0].rootKey, "w:smallCaps");
}); });
}); });
@ -49,7 +66,7 @@ describe("Run", () => {
describe("#caps()", () => { describe("#caps()", () => {
it("it should add caps to the properties", () => { it("it should add caps to the properties", () => {
run.allCaps(); run.allCaps();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:caps"); assert.equal(newJson.root[0].root[0].rootKey, "w:caps");
}); });
}); });
@ -57,7 +74,7 @@ describe("Run", () => {
describe("#strike()", () => { describe("#strike()", () => {
it("it should add strike to the properties", () => { it("it should add strike to the properties", () => {
run.strike(); run.strike();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:strike"); assert.equal(newJson.root[0].root[0].rootKey, "w:strike");
}); });
}); });
@ -65,7 +82,7 @@ describe("Run", () => {
describe("#doubleStrike()", () => { describe("#doubleStrike()", () => {
it("it should add caps to the properties", () => { it("it should add caps to the properties", () => {
run.doubleStrike(); run.doubleStrike();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[0].root[0].rootKey, "w:dstrike"); assert.equal(newJson.root[0].root[0].rootKey, "w:dstrike");
}); });
}); });
@ -73,7 +90,7 @@ describe("Run", () => {
describe("#break()", () => { describe("#break()", () => {
it("it should add break to the run", () => { it("it should add break to the run", () => {
run.break(); run.break();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[1].rootKey, "w:br"); assert.equal(newJson.root[1].rootKey, "w:br");
}); });
}); });
@ -81,7 +98,7 @@ describe("Run", () => {
describe("#tab()", () => { describe("#tab()", () => {
it("it should add break to the run", () => { it("it should add break to the run", () => {
run.tab(); run.tab();
const newJson = jsonify(run); const newJson = Utility.jsonify(run);
assert.equal(newJson.root[1].rootKey, "w:tab"); assert.equal(newJson.root[1].rootKey, "w:tab");
}); });
}); });
@ -101,4 +118,40 @@ describe("Run", () => {
}); });
}); });
}); });
describe("#color", () => {
it("should set the run to the color given", () => {
run.color("001122");
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{"w:rPr": [{"w:color": [{_attr: {"w:val": "001122"}}]}]},
],
});
});
});
describe("#size", () => {
it("should set the run to the given size", () => {
run.size(24);
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{"w:rPr": [{"w:sz": [{_attr: {"w:val": 24}}]}]},
],
});
});
});
describe("#style", () => {
it("should set the style to the given styleId", () => {
run.style("myRunStyle");
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{"w:rPr": [{"w:rStyle": [{_attr: {"w:val": "myRunStyle"}}]}]},
],
});
});
});
}); });

97
ts/docx/run/run.ts Normal file
View File

@ -0,0 +1,97 @@
// http://officeopenxml.com/WPtext.php
import { Break } from "./break";
import { Caps, SmallCaps } from "./caps";
import { Bold, Color, DoubleStrike, Italics, Size, Strike } from "./formatting";
import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
import { Style } from "./style";
import { Tab } from "./tab";
import { Underline } from "./underline";
import { XmlComponent } from "../xml-components";
export class Run extends XmlComponent {
private properties: RunProperties;
constructor() {
super("w:r");
this.properties = new RunProperties();
this.root.push(this.properties);
}
public bold(): Run {
this.properties.push(new Bold());
return this;
}
public italic(): Run {
this.properties.push(new Italics());
return this;
}
public underline(underlineType?: string, color?: string): Run {
this.properties.push(new Underline(underlineType, color));
return this;
}
public color(color: string): Run {
this.properties.push(new Color(color));
return this;
}
public size(size: number): Run {
this.properties.push(new Size(size));
return this;
}
public break(): Run {
this.root.splice(1, 0, new Break());
return this;
}
public tab(): Run {
this.root.splice(1, 0, new Tab());
return this;
}
public smallCaps(): Run {
this.properties.push(new SmallCaps());
return this;
}
public allCaps(): Run {
this.properties.push(new Caps());
return this;
}
public strike(): Run {
this.properties.push(new Strike());
return this;
}
public doubleStrike(): Run {
this.properties.push(new DoubleStrike());
return this;
}
public subScript(): Run {
this.properties.push(new SubScript());
return this;
}
public superScript(): Run {
this.properties.push(new SuperScript());
return this;
}
public font(fontName: string): Run {
this.properties.push(new RunFonts(fontName));
return this;
}
public style(styleId: string): Run {
this.properties.push(new Style(styleId));
return this;
}
}

View File

@ -1,10 +1,7 @@
import { SubScript, SuperScript } from "../../../docx/run/script";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { SubScript, SuperScript } from "./script";
return JSON.parse(stringifiedJson);
}
describe("SubScript", () => { describe("SubScript", () => {
let subScript: SubScript; let subScript: SubScript;
@ -15,15 +12,15 @@ describe("SubScript", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Sub Script with correct attributes", () => { it("should create a Sub Script with correct attributes", () => {
let newJson = jsonify(subScript); const newJson = Utility.jsonify(subScript);
let attributes = { const attributes = {
val: "subscript" val: "subscript",
}; };
assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes)); assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes));
}); });
it("should create a Sub Script with correct root key", () => { it("should create a Sub Script with correct root key", () => {
let newJson = jsonify(subScript); const newJson = Utility.jsonify(subScript);
assert.equal(newJson.rootKey, "w:vertAlign"); assert.equal(newJson.rootKey, "w:vertAlign");
}); });
}); });
@ -38,16 +35,16 @@ describe("SuperScript", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Super Script with correct attributes", () => { it("should create a Super Script with correct attributes", () => {
let newJson = jsonify(superScript); const newJson = Utility.jsonify(superScript);
let attributes = { const attributes = {
val: "superscript" val: "superscript",
}; };
assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes)); assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes));
}); });
it("should create a Super Script with correct root key", () => { it("should create a Super Script with correct root key", () => {
let newJson = jsonify(superScript); const newJson = Utility.jsonify(superScript);
assert.equal(newJson.rootKey, "w:vertAlign"); assert.equal(newJson.rootKey, "w:vertAlign");
}); });
}); });
}); });

View File

@ -1,6 +1,6 @@
import { Attributes, XmlComponent } from "../xml-components"; import { Attributes, XmlComponent } from "../xml-components";
abstract class VerticalAlign extends XmlComponent { export abstract class VerticalAlign extends XmlComponent {
constructor(type: string) { constructor(type: string) {
super("w:vertAlign"); super("w:vertAlign");

View File

@ -1,10 +1,7 @@
import { Strike, DoubleStrike } from "../../../docx/run/strike";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { DoubleStrike, Strike } from "./formatting";
return JSON.parse(stringifiedJson);
}
describe("Strike", () => { describe("Strike", () => {
let strike: Strike; let strike: Strike;
@ -15,7 +12,7 @@ describe("Strike", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Strike with correct root key", () => { it("should create a Strike with correct root key", () => {
let newJson = jsonify(strike); const newJson = Utility.jsonify(strike);
assert.equal(newJson.rootKey, "w:strike"); assert.equal(newJson.rootKey, "w:strike");
}); });
}); });
@ -30,8 +27,8 @@ describe("DoubleStrike", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Double Strike with correct root key", () => { it("should create a Double Strike with correct root key", () => {
let newJson = jsonify(strike); const newJson = Utility.jsonify(strike);
assert.equal(newJson.rootKey, "w:dstrike"); assert.equal(newJson.rootKey, "w:dstrike");
}); });
}); });
}); });

View File

@ -1,15 +0,0 @@
import { XmlComponent } from "../xml-components";
export class Strike extends XmlComponent {
constructor() {
super("w:strike");
}
}
export class DoubleStrike extends XmlComponent {
constructor() {
super("w:dstrike");
}
}

13
ts/docx/run/style.ts Normal file
View File

@ -0,0 +1,13 @@
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
class StyleAttributes extends XmlAttributeComponent<{val: string}> {
protected xmlKeys = {val: "w:val"};
}
export class Style extends XmlComponent {
constructor(styleId: string) {
super("w:rStyle");
this.root.push(new StyleAttributes({val: styleId}));
}
}

View File

@ -1,10 +1,7 @@
import { Tab } from "../../../docx/run/tab";
import { assert } from "chai"; import { assert } from "chai";
function jsonify(obj: Object) { import { Utility } from "../../tests/utility";
let stringifiedJson = JSON.stringify(obj); import { Tab } from "./tab";
return JSON.parse(stringifiedJson);
}
describe("Tab", () => { describe("Tab", () => {
let tab: Tab; let tab: Tab;
@ -15,8 +12,8 @@ describe("Tab", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Tab with correct root key", () => { it("should create a Tab with correct root key", () => {
let newJson = jsonify(tab); const newJson = Utility.jsonify(tab);
assert.equal(newJson.rootKey, "w:tab"); assert.equal(newJson.rootKey, "w:tab");
}); });
}); });
}); });

View File

@ -0,0 +1,20 @@
import { expect } from "chai";
import { Formatter } from "../../export/formatter";
import { TextRun } from "./text-run";
describe("TextRun", () => {
let run: TextRun;
describe("#constructor()", () => {
it("should add text into run", () => {
run = new TextRun("test");
const f = new Formatter().format(run);
expect(f).to.deep.equal({"w:r": [
{"w:rPr": []},
{"w:t": [{_attr: {"xml:space": "preserve"}}, "test"]},
]});
});
});
});

View File

@ -1,5 +1,5 @@
import { Run } from "../run"; import { Run } from "../run";
import { Text } from "./text"; import { Text } from "./run-components/text";
export class TextRun extends Run { export class TextRun extends Run {

View File

@ -1,9 +0,0 @@
import { XmlUnitComponent } from "../xml-components";
export class Text extends XmlUnitComponent {
constructor(text: string) {
super("w:t");
this.root = text;
}
}

View File

@ -1,21 +1,34 @@
import * as u from "../../../docx/run/underline"; import { assert, expect } from "chai";
import { TextRun } from "../../../docx/run/text-run";
import { assert } from "chai";
function jsonify(obj: Object) { import { Formatter } from "../../export/formatter";
let stringifiedJson = JSON.stringify(obj); import { Utility } from "../../tests/utility";
return JSON.parse(stringifiedJson); import * as u from "./underline";
}
describe("Underline", () => { describe("Underline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a new Underline object with u:u as the rootKey", () => { it("should create a new Underline object with u:u as the rootKey", () => {
let underline = new u.Underline(); const underline = new u.Underline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.rootKey, "w:u"); assert.equal(newJson.rootKey, "w:u");
}); });
it("should default to 'single' and no color", () => {
const underline = new u.Underline();
const tree = new Formatter().format(underline);
expect(tree).to.deep.equal({
"w:u": [{_attr: {"w:val": "single"}}],
});
});
it("should use the given style type and color", () => {
const underline = new u.Underline("double", "FF00CC");
const tree = new Formatter().format(underline);
expect(tree).to.deep.equal({
"w:u": [{_attr: {"w:val": "double", "w:color": "FF00CC"}}],
});
});
}); });
}); });
@ -23,14 +36,14 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should have u:u as the rootKey", () => { it("should have u:u as the rootKey", () => {
let underline = new u.DashDotDotHeavyUnderline(); const underline = new u.DashDotDotHeavyUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.rootKey, "w:u"); assert.equal(newJson.rootKey, "w:u");
}); });
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DashDotDotHeavyUnderline(); const underline = new u.DashDotDotHeavyUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dashDotDotHeavy"); assert.equal(newJson.root[0].root.val, "dashDotDotHeavy");
}); });
}); });
@ -40,8 +53,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DashDotHeavyUnderline(); const underline = new u.DashDotHeavyUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dashDotHeavy"); assert.equal(newJson.root[0].root.val, "dashDotHeavy");
}); });
}); });
@ -51,8 +64,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DashLongHeavyUnderline(); const underline = new u.DashLongHeavyUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dashLongHeavy"); assert.equal(newJson.root[0].root.val, "dashLongHeavy");
}); });
}); });
@ -62,8 +75,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DashLongUnderline(); const underline = new u.DashLongUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dashLong"); assert.equal(newJson.root[0].root.val, "dashLong");
}); });
}); });
@ -73,8 +86,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DashUnderline(); const underline = new u.DashUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dash"); assert.equal(newJson.root[0].root.val, "dash");
}); });
}); });
@ -84,8 +97,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DotDashUnderline(); const underline = new u.DotDashUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dotDash"); assert.equal(newJson.root[0].root.val, "dotDash");
}); });
}); });
@ -95,8 +108,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DotDotDashUnderline(); const underline = new u.DotDotDashUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dotDotDash"); assert.equal(newJson.root[0].root.val, "dotDotDash");
}); });
}); });
@ -106,8 +119,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DottedHeavyUnderline(); const underline = new u.DottedHeavyUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dottedHeavy"); assert.equal(newJson.root[0].root.val, "dottedHeavy");
}); });
}); });
@ -117,8 +130,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DottedUnderline(); const underline = new u.DottedUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "dotted"); assert.equal(newJson.root[0].root.val, "dotted");
}); });
}); });
@ -128,8 +141,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.DoubleUnderline(); const underline = new u.DoubleUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "double"); assert.equal(newJson.root[0].root.val, "double");
}); });
}); });
@ -139,8 +152,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.SingleUnderline(); const underline = new u.SingleUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "single"); assert.equal(newJson.root[0].root.val, "single");
}); });
}); });
@ -150,8 +163,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.ThickUnderline(); const underline = new u.ThickUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "thick"); assert.equal(newJson.root[0].root.val, "thick");
}); });
}); });
@ -161,8 +174,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.WaveUnderline(); const underline = new u.WaveUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "wave"); assert.equal(newJson.root[0].root.val, "wave");
}); });
}); });
@ -172,8 +185,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.WavyDoubleUnderline(); const underline = new u.WavyDoubleUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "wavyDouble"); assert.equal(newJson.root[0].root.val, "wavyDouble");
}); });
}); });
@ -183,8 +196,8 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.WavyHeavyUnderline(); const underline = new u.WavyHeavyUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "wavyHeavy"); assert.equal(newJson.root[0].root.val, "wavyHeavy");
}); });
}); });
@ -194,9 +207,9 @@ describe("DashDotDotHeavyUnderline", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should put value in attribute", () => { it("should put value in attribute", () => {
let underline = new u.WordsUnderline(); const underline = new u.WordsUnderline();
let newJson = jsonify(underline); const newJson = Utility.jsonify(underline);
assert.equal(newJson.root[0].root.val, "words"); assert.equal(newJson.root[0].root.val, "words");
}); });
}); });
}); });

View File

@ -1,6 +1,6 @@
import { Attributes, XmlComponent } from "../xml-components"; import { Attributes, XmlComponent } from "../xml-components";
abstract class BaseUnderline extends XmlComponent { export abstract class BaseUnderline extends XmlComponent {
constructor(underlineType: string, color?: string) { constructor(underlineType: string, color?: string) {
super("w:u"); super("w:u");
@ -13,8 +13,8 @@ abstract class BaseUnderline extends XmlComponent {
export class Underline extends BaseUnderline { export class Underline extends BaseUnderline {
constructor() { constructor(underlineType: string = "single", color?: string) {
super(""); super(underlineType, color);
} }
} }

View File

@ -1,3 +0,0 @@
export class Table {
}

View File

@ -0,0 +1,38 @@
import { expect } from "chai";
import { Formatter } from "../../export/formatter";
import { GridCol, TableGrid } from "./grid";
describe("GridCol", () => {
describe("#constructor", () => {
it("sets the width attribute to the value given", () => {
const grid = new GridCol(1234);
const tree = new Formatter().format(grid);
expect(tree).to.deep.equal({
"w:gridCol": [{_attr: {"w:w": 1234}}],
});
});
it("does not set a width attribute if not given", () => {
const grid = new GridCol();
const tree = new Formatter().format(grid);
expect(tree).to.deep.equal({"w:gridCol": []});
});
});
});
describe("TableGrid", () => {
describe("#constructor", () => {
it("creates a column for each width given", () => {
const grid = new TableGrid([1234, 321, 123]);
const tree = new Formatter().format(grid);
expect(tree).to.deep.equal({
"w:tblGrid": [
{"w:gridCol": [{_attr: {"w:w": 1234}}]},
{"w:gridCol": [{_attr: {"w:w": 321}}]},
{"w:gridCol": [{_attr: {"w:w": 123}}]},
],
});
});
});
});

21
ts/docx/table/grid.ts Normal file
View File

@ -0,0 +1,21 @@
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
export class TableGrid extends XmlComponent {
constructor(cols: number[]) {
super("w:tblGrid");
cols.forEach((col) => this.root.push(new GridCol(col)));
}
}
class GridColAttributes extends XmlAttributeComponent<{w: number}> {
protected xmlKeys = {w: "w:w"};
}
export class GridCol extends XmlComponent {
constructor(width?: number) {
super("w:gridCol");
if (width !== undefined) {
this.root.push(new GridColAttributes({w: width}));
}
}
}

1
ts/docx/table/index.ts Normal file
View File

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

View File

@ -0,0 +1,38 @@
import { expect } from "chai";
import { Formatter } from "../../export/formatter";
import { TableProperties } from "./properties";
describe("TableProperties", () => {
describe("#constructor", () => {
it("creates an initially empty property object", () => {
const tp = new TableProperties();
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({"w:tblPr": []});
});
});
describe("#setWidth", () => {
it("adds a table width property", () => {
const tp = new TableProperties().setWidth("dxa", 1234);
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [
{"w:tblW": [{_attr: {"w:type": "dxa", "w:w": 1234}}]},
],
});
});
});
describe("#fixedWidthLayout", () => {
it("sets the table to fixed width layout", () => {
const tp = new TableProperties().fixedWidthLayout();
const tree = new Formatter().format(tp);
expect(tree).to.deep.equal({
"w:tblPr": [
{"w:tblLayout": [{_attr: {"w:type": "fixed"}}]},
],
});
});
});
});

View File

@ -0,0 +1,48 @@
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
export type WidthTypes = "dxa" | "pct" | "nil" | "auto";
export class TableProperties extends XmlComponent {
constructor() {
super("w:tblPr");
}
public setWidth(type: WidthTypes, w: number | string): TableProperties {
this.root.push(new PreferredTableWidth(type, w));
return this;
}
public fixedWidthLayout(): TableProperties {
this.root.push(new TableLayout("fixed"));
return this;
}
}
interface ITableWidth {
type: WidthTypes;
w: number | string;
}
class TableWidthAttributes extends XmlAttributeComponent<ITableWidth> {
protected xmlKeys = {type: "w:type", w: "w:w"};
}
class PreferredTableWidth extends XmlComponent {
constructor(type: WidthTypes, w: number | string) {
super("w:tblW");
this.root.push(new TableWidthAttributes({type, w}));
}
}
type TableLayoutOptions = "autofit" | "fixed";
class TableLayoutAttributes extends XmlAttributeComponent<{type: TableLayoutOptions}> {
protected xmlKeys = {type: "w:type"};
}
class TableLayout extends XmlComponent {
constructor(type: TableLayoutOptions) {
super("w:tblLayout");
this.root.push(new TableLayoutAttributes({type}));
}
}

190
ts/docx/table/table.spec.ts Normal file
View File

@ -0,0 +1,190 @@
/* tslint:disable:no-unused-expression */
import { expect } from "chai";
import { Formatter } from "../../export/formatter";
import { Paragraph } from "../paragraph";
import { Table } from "./";
describe("Table", () => {
describe("#constructor", () => {
it("creates a table with the correct number of rows and columns", () => {
const table = new Table(3, 2);
const tree = new Formatter().format(table);
const cell = {"w:tc": [{"w:tcPr": []}, {"w:p": [{"w:pPr": []}]}]};
expect(tree).to.deep.equal({
"w:tbl": [
{"w:tblPr": []},
{"w:tblGrid": [
{"w:gridCol": [{_attr: {"w:w": 1}}]},
{"w:gridCol": [{_attr: {"w:w": 1}}]},
]},
{"w:tr": [{"w:trPr": []}, cell, cell]},
{"w:tr": [{"w:trPr": []}, cell, cell]},
{"w:tr": [{"w:trPr": []}, cell, cell]},
],
});
});
});
describe("#getRow and Row#getCell", () => {
it("returns the correct row", () => {
const table = new Table(2, 2);
table.getRow(0).getCell(0).addContent(new Paragraph("A1"));
table.getRow(0).getCell(1).addContent(new Paragraph("B1"));
table.getRow(1).getCell(0).addContent(new Paragraph("A2"));
table.getRow(1).getCell(1).addContent(new Paragraph("B2"));
const tree = new Formatter().format(table);
const cell = (c) => ({"w:tc": [
{"w:tcPr": []},
{"w:p": [
{"w:pPr": []},
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, c]}]},
]},
]});
expect(tree).to.deep.equal({
"w:tbl": [
{"w:tblPr": []},
{"w:tblGrid": [
{"w:gridCol": [{_attr: {"w:w": 1}}]},
{"w:gridCol": [{_attr: {"w:w": 1}}]},
]},
{"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]},
{"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]},
],
});
});
});
describe("#getCell", () => {
it("returns the correct cell", () => {
const table = new Table(2, 2);
table.getCell(0, 0).addContent(new Paragraph("A1"));
table.getCell(0, 1).addContent(new Paragraph("B1"));
table.getCell(1, 0).addContent(new Paragraph("A2"));
table.getCell(1, 1).addContent(new Paragraph("B2"));
const tree = new Formatter().format(table);
const cell = (c) => ({"w:tc": [
{"w:tcPr": []},
{"w:p": [
{"w:pPr": []},
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, c]}]},
]},
]});
expect(tree).to.deep.equal({
"w:tbl": [
{"w:tblPr": []},
{"w:tblGrid": [
{"w:gridCol": [{_attr: {"w:w": 1}}]},
{"w:gridCol": [{_attr: {"w:w": 1}}]},
]},
{"w:tr": [{"w:trPr": []}, cell("A1"), cell("B1")]},
{"w:tr": [{"w:trPr": []}, cell("A2"), cell("B2")]},
],
});
});
});
describe("#setWidth", () => {
it("sets the preferred width on the table", () => {
const table = new Table(2, 2).setWidth("pct", 1000);
const tree = new Formatter().format(table);
expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1);
expect(tree["w:tbl"][0]).to.deep.equal({
"w:tblPr": [
{"w:tblW": [{_attr: {"w:type": "pct", "w:w": 1000}}]},
],
});
});
});
describe("#fixedWidthLayout", () => {
it("sets the table to fixed width layout", () => {
const table = new Table(2, 2).fixedWidthLayout();
const tree = new Formatter().format(table);
expect(tree).to.have.property("w:tbl").which.is.an("array").with.has.length.at.least(1);
expect(tree["w:tbl"][0]).to.deep.equal({
"w:tblPr": [
{"w:tblLayout": [{_attr: {"w:type": "fixed"}}]},
],
});
});
});
describe("Cell", () => {
describe("#prepForXml", () => {
it("inserts a paragraph at the end of the cell if it is empty", () => {
const table = new Table(1, 1);
const tree = new Formatter().format(table);
expect(tree).to.have.property("w:tbl").which.is.an("array");
const row = tree["w:tbl"].find((x) => x["w:tr"]);
expect(row).not.to.be.undefined;
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
"w:tc": [
{"w:tcPr": []},
{"w:p": [{"w:pPr": []}]},
],
});
});
it("inserts a paragraph at the end of the cell even if it has a child table", () => {
const parentTable = new Table(1, 1);
parentTable.getCell(0, 0).addContent(new Table(1, 1));
const tree = new Formatter().format(parentTable);
expect(tree).to.have.property("w:tbl").which.is.an("array");
const row = tree["w:tbl"].find((x) => x["w:tr"]);
expect(row).not.to.be.undefined;
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
const cell = row["w:tr"].find((x) => x["w:tc"]);
expect(cell).not.to.be.undefined;
expect(cell["w:tc"][cell["w:tc"].length - 1]).to.deep.equal({
"w:p": [{"w:pPr": []}],
});
});
it("does not insert a paragraph if it already ends with one", () => {
const parentTable = new Table(1, 1);
parentTable.getCell(0, 0).addContent(new Paragraph("Hello"));
const tree = new Formatter().format(parentTable);
expect(tree).to.have.property("w:tbl").which.is.an("array");
const row = tree["w:tbl"].find((x) => x["w:tr"]);
expect(row).not.to.be.undefined;
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
"w:tc": [
{"w:tcPr": []},
{"w:p": [
{"w:pPr": []},
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, "Hello"]}]},
]},
],
});
});
});
describe("#createParagraph", () => {
it("inserts a new paragraph in the cell", () => {
const table = new Table(1, 1);
const para = table.getCell(0, 0).createParagraph("Test paragraph");
expect(para).to.be.an.instanceof(Paragraph);
const tree = new Formatter().format(table);
expect(tree).to.have.property("w:tbl").which.is.an("array");
const row = tree["w:tbl"].find((x) => x["w:tr"]);
expect(row).not.to.be.undefined;
expect(row["w:tr"]).to.be.an("array").which.has.length.at.least(1);
expect(row["w:tr"].find((x) => x["w:tc"])).to.deep.equal({
"w:tc": [
{"w:tcPr": []},
{"w:p": [
{"w:pPr": []},
{"w:r": [
{"w:rPr": []},
{"w:t": [{_attr: {"xml:space": "preserve"}}, "Test paragraph"]},
]},
]},
],
});
});
});
});
});

123
ts/docx/table/table.ts Normal file
View File

@ -0,0 +1,123 @@
import { Paragraph } from "../paragraph";
import { XmlComponent } from "../xml-components";
import { IXmlableObject } from "../xml-components/xmlable-object";
import { TableGrid } from "./grid";
import { TableProperties, WidthTypes } from "./properties";
export class Table extends XmlComponent {
private properties: TableProperties;
private rows: TableRow[];
private grid: TableGrid;
constructor(rows: number, cols: number) {
super("w:tbl");
this.properties = new TableProperties();
this.root.push(this.properties);
const gridCols: number[] = [];
for (let i = 0; i < cols; i++) {
/*
0-width columns don't get rendered correctly, so we need
to give them some value. A reasonable default would be
~6in / numCols, but if we do that it becomes very hard
to resize the table using setWidth, unless the layout
algorithm is set to 'fixed'. Instead, the approach here
means even in 'auto' layout, setting a width on the
table will make it look reasonable, as the layout
algorithm will expand columns to fit its content
*/
gridCols.push(1);
}
this.grid = new TableGrid(gridCols);
this.root.push(this.grid);
this.rows = [];
for (let i = 0; i < rows; i++) {
const cells: TableCell[] = [];
for (let j = 0; j < cols; j++) {
cells.push(new TableCell());
}
const row = new TableRow(cells);
this.rows.push(row);
this.root.push(row);
}
}
public getRow(ix: number): TableRow {
return this.rows[ix];
}
public getCell(row: number, col: number): TableCell {
return this.getRow(row).getCell(col);
}
public setWidth(type: WidthTypes, width: number | string): Table {
this.properties.setWidth(type, width);
return this;
}
public fixedWidthLayout(): Table {
this.properties.fixedWidthLayout();
return this;
}
}
export class TableRow extends XmlComponent {
private properties: TableRowProperties;
private cells: TableCell[];
constructor(cells: TableCell[]) {
super("w:tr");
this.properties = new TableRowProperties();
this.root.push(this.properties);
this.cells = cells;
cells.forEach((c) => this.root.push(c));
}
public getCell(ix: number): TableCell {
return this.cells[ix];
}
}
export class TableRowProperties extends XmlComponent {
constructor() {
super("w:trPr");
}
}
export class TableCell extends XmlComponent {
private properties: TableCellProperties;
constructor() {
super("w:tc");
this.properties = new TableCellProperties();
this.root.push(this.properties);
}
public addContent(content: Paragraph | Table): TableCell {
this.root.push(content);
return this;
}
public prepForXml(): IXmlableObject {
// Cells must end with a paragraph
const retval = super.prepForXml();
const content = retval["w:tc"];
if (!content[content.length - 1]["w:p"]) {
content.push(new Paragraph().prepForXml());
}
return retval;
}
public createParagraph(text?: string): Paragraph {
const para = new Paragraph(text);
this.addContent(para);
return para;
}
}
export class TableCellProperties extends XmlComponent {
constructor() {
super("w:tcPr");
}
}

View File

@ -0,0 +1,26 @@
import { assert } from "chai";
import { Attributes } from "./";
describe("Attribute", () => {
describe("#constructor()", () => {
it("should have val as defined with populated constructor", () => {
const newAttrs = new Attributes({
val: "test",
});
const stringifiedJson = JSON.stringify(newAttrs);
const newJson = JSON.parse(stringifiedJson);
assert.equal(newJson.root.val, "test");
});
it("should have space value as defined with populated constructor", () => {
const newAttrs = new Attributes({
space: "spaceTest",
});
const stringifiedJson = JSON.stringify(newAttrs);
const newJson = JSON.parse(stringifiedJson);
assert.equal(newJson.root.space, "spaceTest");
});
});
});

View File

@ -1,6 +1,6 @@
import { XmlAttributeComponent } from "./default-attributes"; import { XmlAttributeComponent } from "./default-attributes";
interface IAttributesProperties { export interface IAttributesProperties {
val?: string | number | boolean; val?: string | number | boolean;
color?: string; color?: string;
space?: string; space?: string;
@ -19,32 +19,29 @@ interface IAttributesProperties {
footer?: string; footer?: string;
gutter?: string; gutter?: string;
linePitch?: string; linePitch?: string;
pos?: string; pos?: string | number; // Little strange. Perhaps it is normal. Need to clarify in the spec.
} }
export class Attributes extends XmlAttributeComponent { export class Attributes extends XmlAttributeComponent<IAttributesProperties> {
protected xmlKeys = {
constructor(properties?: IAttributesProperties) { val: "w:val",
super({ color: "w:color",
val: "w:val", space: "w:space",
color: "w:color", sz: "w:sz",
space: "w:space", type: "w:type",
sz: "w:sz", rsidR: "w:rsidR",
type: "w:type", rsidRPr: "w:rsidRPr",
rsidR: "w:rsidR", rsidSect: "w:rsidSect",
rsidRPr: "w:rsidRPr", w: "w:w",
rsidSect: "w:rsidSect", h: "w:h",
w: "w:w", top: "w:top",
h: "w:h", right: "w:right",
top: "w:top", bottom: "w:bottom",
right: "w:right", left: "w:left",
bottom: "w:bottom", header: "w:header",
left: "w:left", footer: "w:footer",
header: "w:header", gutter: "w:gutter",
footer: "w:footer", linePitch: "w:linePitch",
gutter: "w:gutter", pos: "w:pos",
linePitch: "w:linePitch", };
pos: "w:pos",
}, properties);
}
} }

View File

@ -1,3 +1,5 @@
import { IXmlableObject } from "./xmlable-object";
export abstract class BaseXmlComponent { export abstract class BaseXmlComponent {
protected rootKey: string; protected rootKey: string;
@ -5,9 +7,5 @@ export abstract class BaseXmlComponent {
this.rootKey = rootKey; this.rootKey = rootKey;
} }
public abstract replaceKey(): void; public abstract prepForXml(): IXmlableObject;
public clearVariables(): void {
// Do Nothing
}
} }

View File

@ -1,30 +1,26 @@
import * as _ from "lodash";
import { BaseXmlComponent } from "./base"; import { BaseXmlComponent } from "./base";
import { IXmlableObject } from "./xmlable-object";
export abstract class XmlAttributeComponent extends BaseXmlComponent { export type AttributeMap<T> = {[P in keyof T]: string};
protected root: Object;
private xmlKeys: Object;
constructor(xmlKeys: Object, properties: Object) { export abstract class XmlAttributeComponent<T> extends BaseXmlComponent {
protected root: T;
protected xmlKeys: AttributeMap<T>;
constructor(properties: T) {
super("_attr"); super("_attr");
this.xmlKeys = xmlKeys;
this.root = properties; this.root = properties;
if (!properties) {
this.root = {};
}
} }
public replaceKey(): void { public prepForXml(): IXmlableObject {
if (this.root !== undefined) { const attrs = {};
_.forOwn(this.root, (value, key) => { Object.keys(this.root).forEach((key) => {
const value = this.root[key];
if (value !== undefined) {
const newKey = this.xmlKeys[key]; const newKey = this.xmlKeys[key];
this.root[newKey] = value; attrs[newKey] = value;
delete this.root[key]; }
}); });
this[this.rootKey] = this.root; return {_attr: attrs};
delete this.root;
}
} }
} }

View File

@ -1,30 +1,3 @@
import * as _ from "lodash"; export * from "./xml-component";
import { BaseXmlComponent } from "./base"; export * from "./attributes";
export abstract class XmlComponent extends BaseXmlComponent {
protected root: BaseXmlComponent[];
constructor(rootKey: string) {
super(rootKey);
this.root = new Array<BaseXmlComponent>();
}
public replaceKey(): void {
// console.log(this.rootKey);
// console.log(this.root);
if (this.root !== undefined) {
this.root.forEach((root) => {
if (root && root instanceof BaseXmlComponent) {
root.replaceKey();
}
});
this[this.rootKey] = this.root;
delete this.root;
}
}
}
export * from "./attributes"
export * from "./default-attributes"; export * from "./default-attributes";
export * from "./unit";
export * from "./property";

View File

@ -1,57 +0,0 @@
import { ParagraphProperties } from "../paragraph/properties";
import { RunProperties } from "../run/properties";
import { XmlComponent } from "./";
export class ParagraphPropertyXmlComponent extends XmlComponent {
private paragraphProperties: ParagraphProperties;
constructor(rootKey: string) {
super(rootKey);
this.paragraphProperties = new ParagraphProperties();
this.root.push(this.paragraphProperties);
}
public clearVariables(): void {
this.paragraphProperties.clearVariables();
delete this.paragraphProperties;
}
}
export class RunPropertyXmlComponent extends XmlComponent {
private runProperties: RunProperties;
constructor(rootKey: string) {
super(rootKey);
this.runProperties = new RunProperties();
this.root.push(this.runProperties);
}
public clearVariables(): void {
this.runProperties.clearVariables();
delete this.runProperties;
}
}
export class MultiPropertyXmlComponent extends XmlComponent {
private runProperties: RunProperties;
private paragraphProperties: ParagraphProperties;
constructor(rootKey: string) {
super(rootKey);
this.runProperties = new RunProperties();
this.root.push(this.runProperties);
this.paragraphProperties = new ParagraphProperties();
this.root.push(this.paragraphProperties);
}
public clearVariables(): void {
this.runProperties.clearVariables();
this.paragraphProperties.clearVariables();
delete this.runProperties;
delete this.paragraphProperties;
}
}

View File

@ -1,16 +0,0 @@
import {BaseXmlComponent} from "./base";
export abstract class XmlUnitComponent extends BaseXmlComponent {
protected root: string;
constructor(rootKey: string) {
super(rootKey);
}
public replaceKey(): void {
if (this.root !== undefined) {
this[this.rootKey] = this.root;
delete this.root;
}
}
}

View File

@ -0,0 +1,24 @@
import { assert } from "chai";
import { Utility } from "../../tests/utility";
import { XmlComponent } from "./";
class TestComponent extends XmlComponent {
}
describe("XmlComponent", () => {
let xmlComponent: TestComponent;
beforeEach(() => {
xmlComponent = new TestComponent("w:test");
});
describe("#constructor()", () => {
it("should create an Xml Component which has the correct rootKey", () => {
const newJson = Utility.jsonify(xmlComponent);
assert.equal(newJson.rootKey, "w:test");
});
});
});

View File

@ -0,0 +1,24 @@
import { BaseXmlComponent } from "./base";
import { IXmlableObject } from "./xmlable-object";
export { BaseXmlComponent };
export abstract class XmlComponent extends BaseXmlComponent {
protected root: Array<BaseXmlComponent | string>;
constructor(rootKey: string) {
super(rootKey);
this.root = new Array<BaseXmlComponent>();
}
public prepForXml(): IXmlableObject {
const children = this.root.map((comp) => {
if (comp instanceof BaseXmlComponent) {
return comp.prepForXml();
}
return comp;
}).filter((comp) => comp); // Exclude null, undefined, and empty strings
return {
[this.rootKey]: children,
};
}
}

View File

@ -0,0 +1,3 @@
export interface IXmlableObject extends Object {
_attr?: { [key: string]: (string | number | boolean) };
}

View File

@ -0,0 +1,78 @@
import { assert } from "chai";
import * as docx from "../docx";
import { Attributes } from "../docx/xml-components";
import { Formatter } from "../export/formatter";
import { Properties } from "../properties";
import { Utility } from "../tests/utility";
describe("Formatter", () => {
let formatter: Formatter;
beforeEach(() => {
formatter = new Formatter();
});
describe("#format()", () => {
it("should format simple paragraph", () => {
const paragraph = new docx.Paragraph();
const newJson = formatter.format(paragraph);
assert.isDefined(newJson["w:p"]);
});
it("should remove xmlKeys", () => {
const paragraph = new docx.Paragraph();
const newJson = formatter.format(paragraph);
const stringifiedJson = JSON.stringify(newJson);
assert(stringifiedJson.indexOf("xmlKeys") < 0);
});
it("should format simple paragraph with bold text", () => {
const paragraph = new docx.Paragraph();
paragraph.addRun(new docx.TextRun("test").bold());
const newJson = formatter.format(paragraph);
assert.isDefined(newJson["w:p"][1]["w:r"][0]["w:rPr"][0]["w:b"][0]._attr["w:val"]);
});
it("should format attributes (rsidSect)", () => {
const attributes = new Attributes({
rsidSect: "test2",
});
let newJson = formatter.format(attributes);
newJson = Utility.jsonify(newJson);
if (newJson._attr === undefined) {
assert.fail();
return;
}
assert.isDefined(newJson._attr["w:rsidSect"]);
});
it("should format attributes (val)", () => {
const attributes = new Attributes({
val: "test",
});
let newJson = formatter.format(attributes);
newJson = Utility.jsonify(newJson);
if (newJson._attr === undefined) {
assert.fail();
return;
}
assert.isDefined(newJson._attr["w:val"]);
});
it("should should change 'p' tag into 'w:p' tag", () => {
const paragraph = new docx.Paragraph();
const newJson = formatter.format(paragraph);
assert.isDefined(newJson["w:p"]);
});
it("should format Properties object correctly", () => {
const properties = new Properties({
title: "test document",
creator: "Dolan",
});
const newJson = formatter.format(properties);
assert.isDefined(newJson["cp:coreProperties"]);
});
});
});

View File

@ -1,52 +1,8 @@
import * as _ from "lodash"; import { BaseXmlComponent } from "../docx/xml-components";
import {XmlComponent} from "../docx/xml-components"; import { IXmlableObject } from "../docx/xml-components/xmlable-object";
export class Formatter { export class Formatter {
public format(input: BaseXmlComponent): IXmlableObject {
public format(input: any): Object { return input.prepForXml();
input.clearVariables();
this.replaceKeys(input);
const newJson = this.clense(input);
// console.log(JSON.stringify(newJson, null, " "));
return newJson;
} }
}
private replaceKeys(input: XmlComponent): Object {
input.replaceKey();
return input;
}
private clense(input: any): Object {
const newJson = this.jsonify(input);
this.deepTraverseJson(newJson, (parent, value, key) => {
if (key === "properties") {
delete parent[key];
}
if (key === "xmlKeys") {
delete parent[key];
}
if (key === "rootKey") {
delete parent[key];
}
});
return newJson;
}
private jsonify(obj: Object): Object {
let stringifiedJson = JSON.stringify(obj);
return JSON.parse(stringifiedJson);
}
private deepTraverseJson(json: Object, lambda: (json: any, value: any, key: any) => void): void {
_.forOwn(json, (value, key) => {
if (_.isObject(value) && key !== "xmlKeys" && key !== "rootKey") {
this.deepTraverseJson(value, lambda);
}
lambda(json, value, key);
});
}
}

View File

@ -1,2 +1,3 @@
export { LocalPacker } from "./packer/local"; export * from "./packer/local";
export { ExpressPacker } from "./packer/express"; export * from "./packer/express";
export * from "./packer/packer";

View File

@ -0,0 +1,99 @@
import * as archiver from "archiver";
import * as express from "express";
import * as fs from "fs";
import * as path from "path";
import * as xml from "xml";
import { Document } from "../../docx";
import { Media } from "../../media";
import { Numbering } from "../../numbering";
import { Properties } from "../../properties";
import { Styles } from "../../styles";
import { DefaultStylesFactory } from "../../styles/factory";
import { Formatter } from "../formatter";
const TEMPLATE_PATH = path.resolve(__dirname, "../../../template");
export class Compiler {
protected archive: archiver.Archiver;
private formatter: Formatter;
private style: Styles;
constructor(
protected document: Document,
style?: Styles,
private properties: Properties = new Properties({
creator: "Un-named",
revision: "1",
lastModifiedBy: "Un-named",
}),
private numbering: Numbering = new Numbering(),
private media: Media = new Media(),
) {
this.formatter = new Formatter();
this.archive = archiver.create("zip", {});
if (style) {
this.style = style;
} else {
const stylesFactory = new DefaultStylesFactory();
this.style = stylesFactory.newInstance();
}
this.archive.on("error", (err) => {
throw err;
});
}
public async compile(output: fs.WriteStream | express.Response): Promise<void> {
this.archive.pipe(output);
this.archive.glob("**", {
cwd: TEMPLATE_PATH,
});
this.archive.glob("**/.rels", {
cwd: TEMPLATE_PATH,
});
const xmlDocument = xml(this.formatter.format(this.document));
const xmlStyles = xml(this.formatter.format(this.style));
const xmlProperties = xml(this.formatter.format(this.properties), {
declaration: {
standalone: "yes",
encoding: "UTF-8",
},
});
const xmlNumbering = xml(this.formatter.format(this.numbering));
this.archive.append(xmlDocument, {
name: "word/document.xml",
});
this.archive.append(xmlStyles, {
name: "word/styles.xml",
});
this.archive.append(xmlProperties, {
name: "docProps/core.xml",
});
this.archive.append(xmlNumbering, {
name: "word/numbering.xml",
});
for (const data of this.media.array) {
this.archive.append(data.stream, {
name: `media/${data.fileName}`,
});
}
this.archive.finalize();
return new Promise<void>((resolve) => {
output.on("close", () => {
resolve();
});
});
}
}

View File

@ -1,16 +1,20 @@
import * as express from "express"; import * as express from "express";
import * as fs from "fs";
import { Document } from "../../docx/document"; import { Document } from "../../docx/document";
import { Media } from "../../media";
import { Numbering } from "../../numbering"; import { Numbering } from "../../numbering";
import { Properties } from "../../properties"; import { Properties } from "../../properties";
import { Packer } from "./packer"; import { Styles } from "../../styles";
import { Compiler } from "./compiler";
import { IPacker } from "./packer";
export class ExpressPacker implements IPacker {
export class ExpressPacker extends Packer {
private res: express.Response; private res: express.Response;
private packer: Compiler;
constructor(document: Document, res: express.Response, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) {
this.packer = new Compiler(document, styles, properties, numbering, media);
constructor(document: Document, res: express.Response, styles?: any, properties?: Properties, numbering?: Numbering) {
super(document, styles, properties, numbering);
this.res = res; this.res = res;
this.res.on("close", () => { this.res.on("close", () => {
@ -18,8 +22,10 @@ export class ExpressPacker extends Packer {
}); });
} }
public pack(name: string): void { public async pack(name: string): Promise<void> {
this.res.attachment(name + ".docx"); name = name.replace(/.docx$/, "");
super.pack(this.res);
this.res.attachment(`${name}.docx`);
await this.packer.compile(this.res);
} }
} }

View File

@ -0,0 +1,47 @@
/* tslint:disable:typedef space-before-function-paren */
import * as fs from "fs";
import { Document } from "../../docx/document";
import { Paragraph } from "../../docx/paragraph";
import { LocalPacker } from "../../export/packer/local";
import { Properties } from "../../properties";
import { DefaultStylesFactory } from "../../styles/factory";
describe("LocalPacker", () => {
let packer: LocalPacker;
let stylesFactory: DefaultStylesFactory;
beforeEach(() => {
const document = new Document();
const paragraph = new Paragraph("test text");
const heading = new Paragraph("Hello world").heading1();
document.addParagraph(new Paragraph("title").title());
document.addParagraph(heading);
document.addParagraph(new Paragraph("heading 2").heading2());
document.addParagraph(paragraph);
const properties = new Properties({
creator: "Dolan Miu",
revision: "1",
lastModifiedBy: "Dolan Miu",
});
stylesFactory = new DefaultStylesFactory();
packer = new LocalPacker(document, stylesFactory.newInstance(), properties);
});
describe("#pack()", () => {
it("should create a standard docx file", async function () {
this.timeout(99999999);
await packer.pack("build-tests/tests/test");
fs.statSync("build-tests/tests/test.docx");
});
});
describe("#packPdf", () => {
it("should create a standard PDF file", async function () {
this.timeout(99999999);
await packer.packPdf("build-tests/tests/pdf-test");
fs.statSync("build-tests/tests/pdf-test.pdf");
});
});
});

View File

@ -1,19 +1,52 @@
import * as fs from "fs"; import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { Document } from "../../docx/document"; import { Document } from "../../docx/document";
import { Media } from "../../media";
import { Numbering } from "../../numbering"; import { Numbering } from "../../numbering";
import { Properties } from "../../properties"; import { Properties } from "../../properties";
import { Packer } from "./packer"; import { Styles } from "../../styles";
import { Compiler } from "./compiler";
import { IPacker } from "./packer";
import { PdfConvertWrapper } from "./pdf-convert-wrapper";
export class LocalPacker implements IPacker {
export class LocalPacker extends Packer {
private stream: fs.WriteStream; private stream: fs.WriteStream;
private pdfConverter: PdfConvertWrapper;
private packer: Compiler;
constructor(document: Document, styles?: any, properties?: Properties, numbering?: Numbering) { constructor(document: Document, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) {
super(document, styles, properties, numbering); this.pdfConverter = new PdfConvertWrapper();
this.packer = new Compiler(document, styles, properties, numbering, media);
} }
public pack(path: string): void { public async pack(filePath: string): Promise<void> {
this.stream = fs.createWriteStream(path); filePath = filePath.replace(/.docx$/, "");
super.pack(this.stream);
this.stream = fs.createWriteStream(`${filePath}.docx`);
await this.packer.compile(this.stream);
}
public async packPdf(filePath: string): Promise<void> {
filePath = filePath.replace(/.pdf$/, "");
const fileName = path.basename(filePath, path.extname(filePath));
const tempPath = path.join(os.tmpdir(), `${fileName}.docx`);
this.stream = fs.createWriteStream(tempPath);
await this.packer.compile(this.stream);
const text = await this.pdfConverter.convert(tempPath);
// const writeFile = util.promisify(fs.writeFile); --use this in future, in 3 years time. Only in node 8
// return writeFile(`${filePath}.pdf`, text);
return new Promise<void>((resolve, reject) => {
fs.writeFile(`${filePath}.pdf`, text, (err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
} }
} }

View File

@ -1,96 +1,3 @@
import * as archiver from "archiver"; export interface IPacker {
import * as fs from "fs"; pack(path: string): void;
import * as xml from "xml";
import { Document } from "../../docx";
import { Numbering } from "../../numbering";
import { Properties } from "../../properties";
import { Styles } from "../../styles";
import { DefaultStylesFactory } from "../../styles/factory";
import { Formatter } from "../formatter";
const appRoot = require("app-root-path");
export abstract class Packer {
protected archive: any;
protected document: Document;
private formatter: Formatter;
private style: Styles;
private properties: Properties;
private numbering: Numbering;
constructor(document: Document, style?: any, properties?: Properties, numbering?: Numbering) {
this.formatter = new Formatter();
this.document = document;
this.style = style;
this.properties = properties;
this.numbering = numbering;
this.archive = archiver.create("zip", {});
if (!style) {
const stylesFactory = new DefaultStylesFactory();
this.style = stylesFactory.newInstance();
}
if (!properties) {
this.properties = new Properties({
creator: "Un-named",
revision: "1",
lastModifiedBy: "Un-named",
});
}
if (!numbering) {
this.numbering = new Numbering();
}
this.archive.on("error", (err) => {
throw err;
});
}
public pack(output: any): void {
this.archive.pipe(output);
console.log(appRoot.path + "/template");
this.archive.glob("**", {
expand: true,
cwd: appRoot.path + "/template",
});
this.archive.glob("**/.rels", {
expand: true,
cwd: appRoot.path + "/template",
});
// this.archive.file(appRoot.path + "/template/[Content_Types].xml", { name: "[Content_Types].xml" });
// console.log(__dirname + "/packer.js");
// this.archive.file(__dirname + "/packer.js", { name: "/[Content_Types].xml" });
/*this.archive.directory(appRoot.path + "/template", {
name: "/root/g.txt",
prefix: "root"
});*/
const xmlDocument = xml(this.formatter.format(this.document));
const xmlStyles = xml(this.formatter.format(this.style));
const xmlProperties = xml(this.formatter.format(this.properties), { declaration: { standalone: "yes", encoding: "UTF-8" } });
const xmlNumbering = xml(this.formatter.format(this.numbering));
// console.log(JSON.stringify(this.numbering, null, " "));
console.log(xmlNumbering);
this.archive.append(xmlDocument, {
name: "word/document.xml",
});
this.archive.append(xmlStyles, {
name: "word/styles.xml",
});
this.archive.append(xmlProperties, {
name: "docProps/core.xml",
});
this.archive.append(xmlNumbering, {
name: "word/numbering.xml",
});
this.archive.finalize();
}
} }

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