Compare commits

...

100 Commits
3.2.0 ... 3.6.0

Author SHA1 Message Date
ba0ca78696 Version bump 2018-05-22 02:35:03 +01:00
22ba53ad4c Merge pull request #79 from formatically/master
Removed unnecessary header2 references
2018-05-20 20:28:23 +01:00
81304f50d1 Removed unnecessary header2 references 2018-05-19 20:52:45 -06:00
3c22372f49 Merge pull request #76 from formatically/master
Added DifferentFirstPageHeader and Page Number features
2018-05-19 12:30:39 +01:00
7584671312 Fixed TSLint Errors 2018-05-18 09:21:27 -06:00
7296c9e744 Removed extra empty lines 2018-05-17 19:51:01 -06:00
c63a8982e4 Added demo14 to showcase differentFirstPageHeader and pagenumbers 2018-05-17 19:44:37 -06:00
8d35dc1bb0 Added tests, renamed pagenumber to page-number 2018-05-17 13:32:33 -06:00
0fedfcf709 Renamed titlepage to title-page 2018-05-17 11:54:13 -06:00
6c2eb882bb Added differentFirstPageHeader section property 2018-05-17 11:45:06 -06:00
59560d96ba Add downloads badge and remove existing node badge 2018-05-16 20:10:12 +01:00
864c9afd93 Remove Gemnasium because they no longer exist :( 2018-05-16 20:00:01 +01:00
b4f07f51ae Merge pull request #70 from dolanmiu/feat/h4buli-update
H4 update
2018-05-16 19:51:40 +01:00
e9b153095c Add demo for custom xml styles 2018-05-16 19:34:25 +01:00
7968c1efcf Removed header-wrapper-2 2018-05-12 22:22:54 -04:00
87648a742c Initial Commit 2018-05-12 20:04:54 -04:00
4d7bdc2ed9 Version bump 2018-05-08 22:19:01 +01:00
d10c707f12 Add tests for catching errors for exporter packer.pack 2018-05-08 20:40:04 +01:00
ac512b2eab Merge branch 'feat/h4buli-update' of https://github.com/dolanmiu/docx into feat/h4buli-update 2018-05-08 02:12:20 +01:00
fdf6a59c4c Add back xml-component export 2018-05-08 01:26:13 +01:00
4b9a6a6735 Merge branch 'master' into feat/h4buli-update 2018-05-08 01:23:56 +01:00
49cc8a267c Merge pull request #71 from ivanryuu/hyperlink_support
External hyperlink support
2018-05-08 01:16:28 +01:00
68cb57aea6 fix style issues 2018-05-06 23:18:00 -05:00
9d7fd55e4c add hyperlink to paragraph and doc 2018-05-06 22:24:16 -05:00
195c62f80b modify relationships to support external links 2018-05-06 22:23:35 -05:00
1fd222abea create hyperlink style 2018-05-06 22:23:04 -05:00
ac40e13e33 added support for hyperlinks 2018-05-06 22:20:56 -05:00
53ab822dbc Fix style 2018-05-06 03:38:08 +01:00
0c9c292291 Fix tests. _attr is needed 2018-05-06 03:27:58 +01:00
573dd753a7 Fix styling and linting 2018-05-06 03:19:36 +01:00
79b5b3a1f6 Merge branch 'master' into feat/h4buli-update 2018-05-06 03:03:45 +01:00
52e8fe576e Bump prettier version 2018-05-06 03:03:35 +01:00
0d34d2d92e Merge branch 'master' into feat/h4buli-update 2018-05-06 03:02:26 +01:00
b389ac6347 Add style fix command 2018-05-06 03:02:06 +01:00
534c601068 Linting fixes 2018-05-06 02:58:16 +01:00
424436579b Remove xml component from main export 2018-05-06 02:58:08 +01:00
a716360faa Restore numbering 2018-05-06 02:57:40 +01:00
af485c678d Merge branch 'master' of https://github.com/h4buli/docx into feat/h4buli-update
# Conflicts:
#	package.json
#	src/file/numbering/numbering.ts
2018-05-06 02:57:15 +01:00
84e298e7ee Version bump 2018-05-04 15:59:46 +02:00
753287d9d1 extend table and table cell support for cell merge, span, borders ... (#6)
* extend table and table cell support for cell merge, span, borders ...

* added tests for table cell borders and table cell width, renamed some variables, added jsdoc
2018-05-04 15:56:28 +02:00
21bb8f9016 Version bump 2018-04-26 14:18:22 +02:00
dc136daeab tables: add option to pass column size when creating a table
- add optionto the XmlComponent to `delete`/skip elements when exporting to xml
2018-04-26 14:16:02 +02:00
e67f5f80e1 Version bump 2018-04-23 11:50:40 +02:00
3691d79a4a add method to add child to an element 2018-04-23 11:49:57 +02:00
8108eca2fa Update README.md 2018-04-21 00:04:14 +01:00
4f48c8fb80 Merge branch 'master' of github.com:h4buli/docx 2018-04-20 16:01:09 +02:00
20ba081308 Version bump 2018-04-20 16:00:44 +02:00
2119ae769b Images: Extend API for working with images (#5)
* extend creating image using buffer and dimensions from outside

* remove empty space
2018-04-20 15:59:06 +02:00
c618ca7539 Version bump 2018-04-17 16:33:08 +02:00
8b11140be2 export AbstractNumbering class 2018-04-17 16:31:36 +02:00
bebfec7755 version bump 2018-04-17 15:39:57 +02:00
124aac4888 fixed failing tests - removed test for the ctor for Numbering class 2018-04-17 15:38:51 +02:00
b3bfd063d8 Merge branch 'master' of github.com:h4buli/docx 2018-04-17 15:34:46 +02:00
c92cab5e5b Fix Numbering (#4)
* Version bump

* (fix): fixed issue with numbering in document
2018-04-17 15:33:53 +02:00
8c613195f3 Version bump 2018-04-10 21:55:02 +01:00
41f941728e Clean up and tests 2018-04-10 21:54:52 +01:00
ecf1542d95 Add instructions image 2018-04-10 21:42:21 +01:00
06b2bbba25 Add image demo 2018-04-10 21:36:11 +01:00
0494fdeabd Add other demo scripts as part of build 2018-04-10 21:08:40 +01:00
226206b100 Fix build 2018-04-10 21:06:02 +01:00
02f80bc616 Merge pull request #68 from citizenos/master
Updated bullet list creating
2018-04-10 21:04:27 +01:00
3189c9251a modified style script 2018-04-10 11:00:25 +03:00
ae43137906 revert demo10.js, fix paragraph tests 2018-04-10 10:25:12 +03:00
50bee30799 reverted gitignore 2018-04-09 23:21:51 +03:00
38484a3063 removed build folder 2018-04-09 23:18:55 +03:00
919327ed08 bullet points update 2018-04-05 16:50:25 +03:00
c00c5fa02d Merge pull request #67 from citizenos/master
image scaling add rounding
2018-04-03 22:51:15 +01:00
80f09ac10b modified git ignore 2018-04-02 14:37:54 +03:00
ee721ffbec image scaling add rounding 2018-04-02 12:55:43 +03:00
323f91dd68 Version bump 2018-03-28 16:31:44 +02:00
810ccb40d7 Merge pull request #3 from h4buli/feature/import-styles
styles: support for external styles. parsing external styles.
2018-03-28 16:02:11 +02:00
5242f7d55c styles: support for external styles. parsing external styles. 2018-03-28 14:54:07 +02:00
d293ae516c Merge pull request #2 from h4buli/revert-1-feature/external-styles-support
Revert "(styles): add support to provide external styles (as complete file co…"
2018-03-26 16:29:16 +02:00
3fb563f9c8 Revert "(styles): add support to provide external styles (as complete file co…" 2018-03-26 16:28:40 +02:00
1dd1c65341 Version bump 2018-03-24 19:24:24 +00:00
e593327fea Merge pull request #66 from dolanmiu/feat/resizable-pictures
Feat/resizable pictures
2018-03-24 19:23:22 +00:00
64e0aeeb18 Fix demo 2018-03-24 19:11:25 +00:00
05816abc12 Add scalable image feature 2018-03-24 19:06:10 +00:00
ce306aef07 Version bump 2018-03-23 12:25:30 +01:00
373c850f35 Merge pull request #1 from h4buli/feature/external-styles-support
(styles): add support to provide external styles (as complete file co…
2018-03-23 12:21:00 +01:00
a0e00b8eff (styles): add support to provide external styles (as complete file content) 2018-03-23 12:18:31 +01:00
a954c69458 Fix tests 2018-03-22 23:04:46 +00:00
a0e034bd55 Version bump 2018-03-22 22:56:05 +00:00
1a1c1f26d9 Border for tables by default 2018-03-22 22:55:33 +00:00
114c429aed pdf-export: remove option to export to pdf 2018-03-21 10:53:07 +01:00
40251a76f6 setup: rename npm package 2018-03-21 10:46:22 +01:00
a102b479e6 Add new demo 2018-03-10 23:04:45 +00:00
5b5f5ea203 Merge pull request #63 from felipeochoa/stream-packer
Added generic stream packer
2018-02-25 15:40:46 +00:00
40a8e581f1 Added generic stream packer 2018-02-23 19:21:00 -05:00
1b988e7135 Remove old deployment scripts 2018-02-21 22:09:55 +00:00
9433c7aedc Update documentation links 2018-02-21 22:09:42 +00:00
479dfed987 Clean up and nojekyll 2018-02-21 21:51:17 +00:00
1866033128 Remove spec files from docs 2018-02-21 21:46:02 +00:00
844b9ab2ec Update travis for docs 2018-02-21 21:34:43 +00:00
e6713d6ce2 Ignore My Document generated file 2018-02-13 23:21:20 +00:00
069ce73c07 Add My CV Demo 2018-02-13 23:20:14 +00:00
6dda6a5513 Formatting 2018-02-13 02:10:42 +00:00
329def42ac Prettier formatting 2018-02-13 02:08:28 +00:00
c5137b5fad Add RunKit examples 2018-02-13 02:07:56 +00:00
ab05ccb846 Simplify prettier command 2018-02-10 17:56:52 +00:00
76 changed files with 2221 additions and 246 deletions

3
.gitignore vendored
View File

@ -46,3 +46,6 @@ build-tests
# Lock files
package-lock.json
# Documents
My Document.docx

5
.prettierrc.yml Normal file
View File

@ -0,0 +1,5 @@
trailingComma: all
printWidth: 140
tabWidth: 4
arrowParens: always
bracketSpacing: true

View File

@ -15,11 +15,21 @@ script:
- node ./demo/demo5.js
- node ./demo/demo6.js
- node ./demo/demo7.js
- node ./demo/demo8.js
- node ./demo/demo9.js
- node ./demo/demo10.js
- node ./demo/demo11.js
- node ./demo/demo12.js
after_failure:
- "cat /home/travis/builds/dolanmiu/docx/npm-debug.log"
after_success:
- bash ./deploy-docs.sh
env:
global:
- ENCRYPTION_LABEL: "ad385fa3b525"
- npm run typedoc
- echo "janchi.co.uk" > docs/.nojekyll
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
keep-history: true
local-dir: docs
on:
branch: master

View File

@ -6,16 +6,16 @@
Generate .docx files with JS/TS very easily, written in TS.
</p>
-----
---
[![NPM version][npm-image]][npm-url]
[![Downloads per month][downloads-image]][downloads-url]
[![Build Status][travis-image]][travis-url]
[![Dependency Status][gemnasium-image]][gemnasium-url]
[![Dependency Status][daviddm-image]][daviddm-url]
[![Known Vulnerabilities][snky-image]][snky-url]
[![Chat on Gitter][gitter-image]][gitter-url]
[![code style: prettier][prettier-image]][prettier-url]
[![NPM](https://nodei.co/npm/docx.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/docx/)
[![PRs Welcome][pr-image]][pr-url]
# docx
@ -27,23 +27,39 @@ $ npm install --save docx
## Demo
Press `endpoint` on the `RunKit` website:
![RunKit Instructions](https://user-images.githubusercontent.com/2917613/38582539-f84311b6-3d07-11e8-90db-5885ae02c3c4.png)
* https://runkit.com/dolanmiu/docx-demo1 - Simple paragraph and text
* https://runkit.com/dolanmiu/docx-demo2 - Advanced Paragraphs and text
* https://runkit.com/dolanmiu/docx-demo3 - Bullet points
* https://runkit.com/dolanmiu/docx-demo4 - Simple table
* https://runkit.com/dolanmiu/docx-demo5 - Images
* https://runkit.com/dolanmiu/docx-demo6 - Margins
* https://runkit.com/dolanmiu/docx-demo7 - Landscape
* https://runkit.com/dolanmiu/docx-demo8/1.0.1 - Header and Footer
* https://runkit.com/dolanmiu/docx-demo10 - **My CV generated with docx**
#### Run demos locally:
```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.
This command 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)
Full documentation can be found here: [http://dolanmiu.github.io/docx/index.html](http://dolanmiu.github.io/docx/index.html)
## Simple Usage
```js
// Used to create docx files
var docx = require('docx');
var docx = require("docx");
// Create document
var doc = new docx.Document();
@ -51,7 +67,7 @@ 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'));
paragraph.addRun(new docx.TextRun("Lorem Ipsum Foo Bar"));
doc.addParagraph(paragraph);
// Used to export the file into a .docx file
@ -61,9 +77,9 @@ var exporter = new docx.LocalPacker(doc);
// res is express' Response object
var exporter = new docx.ExpressPacker(doc, res);
exporter.pack('My First Document');
exporter.pack("My First Document");
// If you want to export it as a .pdf file instead
exporter.packPdf('My First Document');
exporter.packPdf("My First Document");
// done! A file called 'My First Document.docx'
// will be in your file system if you used LocalPacker
@ -71,13 +87,14 @@ exporter.packPdf('My First Document');
```
## Examples
Check [the Wiki](https://github.com/dolanmiu/docx/wiki/Examples) for examples.
# Contributing
Read the contribution guidelines [here](https://github.com/dolanmiu/docx/wiki/Contributing-Guidelines).
-----
---
Made with 💖
@ -85,6 +102,8 @@ Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contri
[npm-image]: https://badge.fury.io/js/docx.svg
[npm-url]: https://npmjs.org/package/docx
[downloads-image]: https://img.shields.io/npm/dm/docx.svg
[downloads-url]: https://npmjs.org/package/docx
[travis-image]: https://travis-ci.org/dolanmiu/docx.svg?branch=master
[travis-url]: https://travis-ci.org/dolanmiu/docx
[daviddm-image]: https://david-dm.org/dolanmiu/docx.svg?theme=shields.io
@ -93,7 +112,7 @@ Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contri
[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
[prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg
[prettier-url]: https://github.com/prettier/prettier
[pr-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
[pr-url]: http://makeapullrequest.com

File diff suppressed because one or more lines are too long

314
demo/demo10.js Normal file
View File

@ -0,0 +1,314 @@
const docx = require("../build");
const PHONE_NUMBER = "07534563401";
const PROFILE_URL = "https://www.linkedin.com/in/dolan1";
const EMAIL = "docx@docx.com";
const experiences = [
{
isCurrent: true,
summary: "Full-stack developer working with Angular and Java. Working for the iShares platform",
title: "Associate Software Developer",
startDate: {
month: 11,
year: 2017,
},
company: {
name: "BlackRock",
},
},
{
isCurrent: false,
summary:
"Full-stack developer working with Angular, Node and TypeScript. Working for the iShares platform. Emphasis on Dev-ops and developing the continous integration pipeline.",
title: "Software Developer",
endDate: {
month: 11,
year: 2017,
},
startDate: {
month: 10,
year: 2016,
},
company: {
name: "Torch Markets",
},
},
{
isCurrent: false,
summary:
"Used ASP.NET MVC 5 to produce a diversity data collection tool for the future of British television.\n\nUsed AngularJS and C# best practices. Technologies used include JavaScript, ASP.NET MVC 5, SQL, Oracle, SASS, Bootstrap, Grunt.",
title: "Software Developer",
endDate: {
month: 10,
year: 2016,
},
startDate: {
month: 3,
year: 2015,
},
company: {
name: "Soundmouse",
},
},
{
isCurrent: false,
summary:
"Develop web commerce platforms for various high profile clients.\n\nCreated a log analysis web application with the Play Framework in Java, incorporating Test Driven Development. It asynchronously uploads and processes large (2 GB) log files, and outputs meaningful results in context with the problem. \n\nAnalysis and development of the payment system infrastructure and user accounts section to be used by several clients of the company such as Waitrose, Tally Weijl, DJ Sports, Debenhams, Ann Summers, John Lewis and others.\n\nTechnologies used include WebSphere Commerce, Java, JavaScript and JSP.",
title: "Java Developer",
endDate: {
month: 10,
year: 2014,
},
startDate: {
month: 3,
year: 2013,
},
company: {
name: "Soundmouse",
},
},
];
const education = [
{
degree: "Master of Science (MSc)",
fieldOfStudy: "Computer Science",
notes:
"Exam Results: 1st Class with Distinction, Dissertation: 1st Class with Distinction\n\nRelevant Courses: Java and C# Programming, Software Engineering, Artificial Intelligence, \nComputational Photography, Algorithmics, Architecture and Hardware.\n\nCreated a Windows 8 game in JavaScript for the dissertation. \n\nCreated an award-winning 3D stereoscopic game in C# using XNA.",
schoolName: "University College London",
startDate: {
year: 2012,
},
endDate: {
year: 2013,
},
},
{
degree: "Bachelor of Engineering (BEng)",
fieldOfStudy: "Material Science and Engineering",
notes:
"Exam Results: 2:1, Dissertation: 1st Class with Distinction\n\nRelevant courses: C Programming, Mathematics and Business for Engineers.",
schoolName: "Imperial College London",
startDate: {
year: 2009,
},
endDate: {
year: 2012,
},
},
];
const skills = [
{
name: "Angular",
},
{
name: "TypeScript",
},
{
name: "JavaScript",
},
{
name: "NodeJS",
},
];
const achievements = [
{
issuer: "Oracle",
name: "Oracle Certified Expert",
},
];
class DocumentCreator {
create(data) {
const experiences = data[0];
const educations = data[1];
const skills = data[2];
const achivements = data[3];
const document = new docx.Document();
document.addParagraph(new docx.Paragraph("Dolan Miu").title());
document.addParagraph(this.createContactInfo(PHONE_NUMBER, PROFILE_URL, EMAIL));
document.addParagraph(this.createHeading("Education"));
for (const education of educations) {
document.addParagraph(
this.createInstitutionHeader(education.schoolName, `${education.startDate.year} - ${education.endDate.year}`),
);
document.addParagraph(this.createRoleText(`${education.fieldOfStudy} - ${education.degree}`));
const bulletPoints = this.splitParagraphIntoBullets(education.notes);
bulletPoints.forEach((bulletPoint) => {
document.addParagraph(this.createBullet(bulletPoint));
});
}
document.addParagraph(this.createHeading("Experience"));
for (const position of experiences) {
document.addParagraph(
this.createInstitutionHeader(
position.company.name,
this.createPositionDateText(position.startDate, position.endDate, position.isCurrent),
),
);
document.addParagraph(this.createRoleText(position.title));
const bulletPoints = this.splitParagraphIntoBullets(position.summary);
bulletPoints.forEach((bulletPoint) => {
document.addParagraph(this.createBullet(bulletPoint));
});
}
document.addParagraph(this.createHeading("Skills, Achievements and Interests"));
document.addParagraph(this.createSubHeading("Skills"));
document.addParagraph(this.createSkillList(skills));
document.addParagraph(this.createSubHeading("Achievements"));
for (const achievementParagraph of this.createAchivementsList(achivements)) {
document.addParagraph(achievementParagraph);
}
document.addParagraph(this.createSubHeading("Interests"));
document.addParagraph(this.createInterests("Programming, Technology, Music Production, Web Design, 3D Modelling, Dancing."));
document.addParagraph(this.createHeading("References"));
document.addParagraph(
new docx.Paragraph(
"Dr. Dean Mohamedally Director of Postgraduate Studies Department of Computer Science, University College London Malet Place, Bloomsbury, London WC1E d.mohamedally@ucl.ac.uk",
),
);
document.addParagraph(new docx.Paragraph("More references upon request"));
document.addParagraph(
new docx.Paragraph(
"This CV was generated in real-time based on my Linked-In profile from my personal website www.dolan.bio.",
).center(),
);
return document;
}
createContactInfo(phoneNumber, profileUrl, email) {
const paragraph = new docx.Paragraph().center();
const contactInfo = new docx.TextRun(`Mobile: ${phoneNumber} | LinkedIn: ${profileUrl} | Email: ${email}`);
const address = new docx.TextRun("Address: 58 Elm Avenue, Kent ME4 6ER, UK").break();
paragraph.addRun(contactInfo);
paragraph.addRun(address);
return paragraph;
}
createHeading(text) {
return new docx.Paragraph(text).heading1().thematicBreak();
}
createSubHeading(text) {
return new docx.Paragraph(text).heading2();
}
createInstitutionHeader(institutionName, dateText) {
const paragraph = new docx.Paragraph().maxRightTabStop();
const institution = new docx.TextRun(institutionName).bold();
const date = new docx.TextRun(dateText).tab().bold();
paragraph.addRun(institution);
paragraph.addRun(date);
return paragraph;
}
createRoleText(roleText) {
const paragraph = new docx.Paragraph();
const role = new docx.TextRun(roleText).italic();
paragraph.addRun(role);
return paragraph;
}
createBullet(text) {
return new docx.Paragraph(text).bullet();
}
createSkillList(skills) {
const paragraph = new docx.Paragraph();
const skillConcat = skills.map((skill) => skill.name).join(", ") + ".";
paragraph.addRun(new docx.TextRun(skillConcat));
return paragraph;
}
createAchivementsList(achivements) {
const arr = [];
for (const achievement of achivements) {
arr.push(new docx.Paragraph(achievement.name).bullet());
}
return arr;
}
createInterests(interests) {
const paragraph = new docx.Paragraph();
paragraph.addRun(new docx.TextRun(interests));
return paragraph;
}
splitParagraphIntoBullets(text) {
return text.split("\n\n");
}
createPositionDateText(startDate, endDate, isCurrent) {
const startDateText = this.getMonthFromInt(startDate.month) + ". " + startDate.year;
const endDateText = isCurrent ? "Present" : `${this.getMonthFromInt(endDate.month)}. ${endDate.year}`;
return `${startDateText} - ${endDateText}`;
}
getMonthFromInt(value) {
switch (value) {
case 1:
return "Jan";
case 2:
return "Feb";
case 3:
return "Mar";
case 4:
return "Apr";
case 5:
return "May";
case 6:
return "Jun";
case 7:
return "Jul";
case 8:
return "Aug";
case 9:
return "Sept";
case 10:
return "Oct";
case 11:
return "Nov";
case 12:
return "Dec";
}
}
}
const documentCreator = new DocumentCreator();
const doc = documentCreator.create([experiences, education, skills, achievements]);
var exporter = new docx.LocalPacker(doc);
exporter.pack("Dolan Miu CV");
console.log("Document created successfully at project root!");

132
demo/demo11.js Normal file
View File

@ -0,0 +1,132 @@
const docx = require("../build");
const doc = new docx.Document(undefined, {
top: 700,
right: 700,
bottom: 700,
left: 700,
});
doc.Styles.createParagraphStyle("Heading1", "Heading 1")
.basedOn("Normal")
.next("Normal")
.quickFormat()
.font("Calibri")
.size(52)
.center()
.bold()
.color(000000)
.spacing({ line: 340 })
.underline("single", "000000");
doc.Styles.createParagraphStyle("Heading2", "Heading 2")
.basedOn("Normal")
.next("Normal")
.font("Calibri")
.quickFormat()
.size(26)
.bold()
.spacing({ line: 340 });
doc.Styles.createParagraphStyle("Heading3", "Heading 3")
.basedOn("Normal")
.next("Normal")
.font("Calibri")
.quickFormat()
.size(26)
.bold()
.spacing({ line: 276 });
doc.Styles.createParagraphStyle("Heading4", "Heading 4")
.basedOn("Normal")
.next("Normal")
.justified()
.font("Calibri")
.size(26)
.bold();
doc.Styles.createParagraphStyle("normalPara", "Normal Para")
.basedOn("Normal")
.next("Normal")
.font("Calibri")
.quickFormat()
.leftTabStop(453.543307087)
.maxRightTabStop(453.543307087)
.size(26)
.spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 });
doc.Styles.createParagraphStyle("normalPara2", "Normal Para2")
.basedOn("Normal")
.next("Normal")
.quickFormat()
.font("Calibri")
.size(26)
.justified()
.spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 });
doc.Styles.createParagraphStyle("aside", "Aside")
.basedOn("Normal")
.next("Normal")
.color("999999")
.italics()
.indent(720)
.spacing({ line: 276 });
doc.Styles.createParagraphStyle("wellSpaced", "Well Spaced")
.basedOn("Normal")
.spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 });
doc.Styles.createParagraphStyle("ListParagraph", "List Paragraph")
.quickFormat()
.basedOn("Normal");
doc.createImage("./demo/images/pizza.gif");
doc
.createParagraph("HEADING")
.heading1()
.center();
doc.Footer.createParagraph("1")
.style("normalPara")
.right();
doc.createParagraph("Ref. :").style("normalPara");
doc.createParagraph("Date :").style("normalPara");
doc.createParagraph("To,").style("normalPara");
doc.createParagraph("The Superindenting Engineer,(O &M)").style("normalPara");
doc.createParagraph("Sub : ").style("normalPara");
doc.createParagraph("Ref. : ").style("normalPara");
doc.createParagraph("Sir,").style("normalPara");
doc.createParagraph("BRIEF DESCRIPTION").style("normalPara");
var table = new docx.Table(4, 4);
var contentParagraph = table
.getRow(0)
.getCell(0)
.addContent(new docx.Paragraph("Pole No."));
table.properties.width = 10000;
doc.addTable(table);
var arrboth = [{
image: "./demo/images/pizza.gif",
comment: "Test"
}, {
image: "./demo/images/pizza.gif",
comment: "Test 2"
}];
arrboth.forEach(function(item) {
const para = doc.createParagraph();
para.createTextRun(doc.createImage(item.image));
para.properties.width = 60;
para.properties.height = 90;
doc.createParagraph(item.comment).style("normalPara2");
});
var exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");

21
demo/demo12.js Normal file
View File

@ -0,0 +1,21 @@
const docx = require("../build");
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
doc.addParagraph(paragraph);
const image = doc.createImage("./demo/images/pizza.gif");
const image2 = doc.createImage("./demo/images/pizza.gif");
const image3 = doc.createImage("./demo/images/pizza.gif");
const image4 = doc.createImage("./demo/images/pizza.gif");
image.scale(0.5);
image2.scale(1)
image3.scale(2.5);
image4.scale(4);
var exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");
console.log("Document created successfully at project root!");

26
demo/demo13.js Normal file
View File

@ -0,0 +1,26 @@
// This example shows 3 styles
const fs = require('fs');
const docx = require('../build');
const styles = fs.readFileSync('./demo/assets/custom-styles.xml', 'utf-8');
const doc = new docx.Document({
title: 'Title',
externalStyles: styles
});
doc.createParagraph('Cool Heading Text').heading1();
let paragraph = new docx.Paragraph('This is a custom named style from the template "MyFancyStyle"');
paragraph.style('MyFancyStyle');
doc.addParagraph(paragraph);
doc.createParagraph('Some normal text')
doc.createParagraph('MyFancyStyle again').style('MyFancyStyle');
paragraph.style('MyFancyStyle');
doc.addParagraph(paragraph);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

23
demo/demo14.js Normal file
View File

@ -0,0 +1,23 @@
const docx = require('../build');
var doc = new docx.Document(undefined,{differentFirstPageHeader:true});
doc.createParagraph("First Page").pageBreak()
doc.createParagraph("Second Page");
var pageNumber = new docx.TextRun().pageNumber()
var pageoneheader = new docx.Paragraph("First Page Header ").right();
pageoneheader.addRun(pageNumber);
doc.firstPageHeader.addParagraph(pageoneheader);
var pagetwoheader = new docx.Paragraph("My Title ").right();
pagetwoheader.addRun(pageNumber)
doc.Header.addParagraph(pagetwoheader)
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

View File

@ -29,6 +29,16 @@ doc.addParagraph(subP);
doc.addParagraph(secondSubP);
doc.addParagraph(subSubP);
var bullet1 = new docx.Paragraph("Hey you").bullet();
var bullet2 = new docx.Paragraph("What's up fam").bullet(1);
var bullet3 = new docx.Paragraph("Hello World 2").bullet(2);
var bullet4 = new docx.Paragraph("Yeah boi").bullet(3);
doc.addParagraph(bullet1);
doc.addParagraph(bullet2);
doc.addParagraph(bullet3);
doc.addParagraph(bullet4);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');

View File

@ -1,68 +0,0 @@
#!/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

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "docx",
"version": "3.2.0",
"version": "3.6.0",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
"main": "build/index.js",
"scripts": {
@ -13,8 +13,9 @@
"tsc": "rimraf ./build && tsc -p .",
"webpack": "rimraf ./build && webpack",
"demo": "npm run build && node ./demo",
"typedoc": "npm run build && typedoc --out docs/ src/ --module commonjs --target ES6 --disableOutputCheck",
"style": "prettier -l --trailing-comma all --print-width 140 --arrow-parens always \"src/**/*.ts\"",
"typedoc": "typedoc --out docs/ src/ --module commonjs --target ES6 --disableOutputCheck --excludePrivate --externalPattern \"**/*.spec.ts\"",
"style": "prettier -l \"src/**/*.ts\"",
"style.fix": "prettier \"src/**/*.ts\" --write",
"fix-types": "node types-absolute-fixer.js"
},
"files": [
@ -45,6 +46,7 @@
"@types/image-size": "0.0.29",
"@types/request-promise": "^4.1.41",
"archiver": "^2.1.1",
"fast-xml-parser": "^3.3.6",
"image-size": "^0.6.2",
"request": "^2.83.0",
"request-promise": "^4.2.2",
@ -59,16 +61,18 @@
"devDependencies": {
"@types/chai": "^3.4.35",
"@types/mocha": "^2.2.39",
"@types/sinon": "^4.3.1",
"awesome-typescript-loader": "^3.4.1",
"chai": "^3.5.0",
"glob": "^7.1.2",
"mocha": "^3.2.0",
"mocha-webpack": "^1.0.1",
"prettier": "^1.10.2",
"prettier": "^1.12.1",
"prompt": "^1.0.0",
"replace-in-file": "^3.1.0",
"rimraf": "^2.5.2",
"shelljs": "^0.7.7",
"sinon": "^5.0.7",
"tslint": "^5.1.0",
"typedoc": "^0.9.0",
"typescript": "2.6.2",

View File

@ -1,3 +1,4 @@
export * from "./packer/local";
export * from "./packer/express";
export * from "./packer/packer";
export * from "./packer/stream";

View File

@ -1,6 +1,6 @@
import * as archiver from "archiver";
import * as express from "express";
import * as fs from "fs";
import { Writable } from "stream";
import * as xml from "xml";
import { File } from "file";
@ -19,7 +19,7 @@ export class Compiler {
});
}
public async compile(output: fs.WriteStream | express.Response): Promise<void> {
public async compile(output: Writable | express.Response): Promise<void> {
this.archive.pipe(output);
const xmlDocument = xml(this.formatter.format(this.file.Document), true);
@ -34,6 +34,7 @@ export class Compiler {
const xmlRelationships = xml(this.formatter.format(this.file.DocumentRelationships));
const xmlFileRelationships = xml(this.formatter.format(this.file.FileRelationships));
const xmlHeader = xml(this.formatter.format(this.file.Header.Header));
const xmlHeader2 = xml(this.formatter.format(this.file.firstPageHeader.Header));
const xmlFooter = xml(this.formatter.format(this.file.Footer.Footer));
const xmlHeaderRelationships = xml(this.formatter.format(this.file.Header.Relationships));
const xmlFooterRelationships = xml(this.formatter.format(this.file.Footer.Relationships));
@ -64,6 +65,10 @@ export class Compiler {
name: "word/header1.xml",
});
this.archive.append(xmlHeader2, {
name: "word/header2.xml",
});
this.archive.append(xmlFooter, {
name: "word/footer1.xml",
});

View File

@ -0,0 +1,43 @@
// tslint:disable:typedef space-before-function-paren
// tslint:disable:no-empty
// tslint:disable:no-any
import { assert } from "chai";
import { stub } from "sinon";
import { ExpressPacker } from "../../export/packer/express";
import { File, Paragraph } from "../../file";
describe("LocalPacker", () => {
let packer: ExpressPacker;
beforeEach(() => {
const file = new File({
creator: "Dolan Miu",
revision: "1",
lastModifiedBy: "Dolan Miu",
});
const paragraph = new Paragraph("test text");
const heading = new Paragraph("Hello world").heading1();
file.addParagraph(new Paragraph("title").title());
file.addParagraph(heading);
file.addParagraph(new Paragraph("heading 2").heading2());
file.addParagraph(paragraph);
const expressResMock = {
on: () => {},
attachment: () => {},
};
packer = new ExpressPacker(file, expressResMock as any);
});
describe("#pack()", () => {
it("should handle exception if it throws any", () => {
const compiler = stub((packer as any).packer, "compile");
compiler.throwsException();
return packer.pack("build/tests/test").catch((error) => {
assert.isDefined(error);
});
});
});
});

View File

@ -1,5 +1,7 @@
/* tslint:disable:typedef space-before-function-paren */
import { assert } from "chai";
import * as fs from "fs";
import { stub } from "sinon";
import { LocalPacker } from "../../export/packer/local";
import { File, Paragraph } from "../../file";
@ -29,14 +31,36 @@ describe("LocalPacker", () => {
await packer.pack("build/tests/test");
fs.statSync("build/tests/test.docx");
});
it("should handle exception if it throws any", () => {
// tslint:disable-next-line:no-any
const compiler = stub((packer as any).packer, "compile");
compiler.throwsException();
return packer.pack("build/tests/test").catch((error) => {
assert.isDefined(error);
});
});
});
describe("#packPdf", () => {
it("should create a standard PDF file", async function() {
this.timeout(99999999);
// tslint:disable-next-line:no-any
const pdfConverterConvert = stub((packer as any).pdfConverter, "convert");
pdfConverterConvert.returns("Test PDF Contents");
await packer.packPdf("build/tests/pdf-test");
fs.statSync("build/tests/pdf-test.pdf");
});
it("should handle exception if it throws any", () => {
// tslint:disable-next-line:no-any
const compiler = stub((packer as any).packer, "compile");
compiler.throwsException();
return packer.packPdf("build/tests/pdf-test").catch((error) => {
assert.isDefined(error);
});
});
});
});

View File

@ -0,0 +1,25 @@
import { Readable, Transform } from "stream";
import { File } from "../../file";
import { Compiler } from "./compiler";
import { IPacker } from "./packer";
class Pipe extends Transform {
public _transform(chunk: Buffer | string, encoding: string, callback: () => void): void {
this.push(chunk, encoding);
callback();
}
}
export class StreamPacker implements IPacker {
private readonly compiler: Compiler;
constructor(file: File) {
this.compiler = new Compiler(file);
}
public pack(): Readable {
const pipe = new Pipe();
this.compiler.compile(pipe);
return pipe;
}
}

View File

@ -24,7 +24,9 @@ export class ContentTypes extends XmlComponent {
this.root.push(
new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", "/word/document.xml"),
);
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", "/word/header1.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", "/word/header2.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", "/word/footer1.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", "/word/styles.xml"));
this.root.push(new Override("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml"));

View File

@ -10,6 +10,7 @@ export interface IPropertiesOptions {
description?: string;
lastModifiedBy?: string;
revision?: string;
externalStyles?: string;
}
export class CoreProperties extends XmlComponent {

View File

@ -2,12 +2,12 @@ import { XmlComponent } from "file/xml-components";
import { HeaderReferenceAttributes } from "./header-reference-attributes";
export class HeaderReference extends XmlComponent {
constructor() {
constructor(order: string, refID: number) {
super("w:headerReference");
this.root.push(
new HeaderReferenceAttributes({
type: "default",
id: `rId${3}`,
type: order,
id: `rId${refID}`,
}),
);
}

View File

@ -10,6 +10,7 @@ import { PageMargin } from "./page-margin/page-margin";
import { IPageMarginAttributes } from "./page-margin/page-margin-attributes";
import { PageSize } from "./page-size/page-size";
import { IPageSizeAttributes } from "./page-size/page-size-attributes";
import { TitlePage } from "./title-page/title-page";
export type SectionPropertiesOptions = IPageSizeAttributes & IPageMarginAttributes & IColumnsAttributes & IDocGridAttributesProperties;
@ -30,6 +31,7 @@ export class SectionProperties extends XmlComponent {
space: 708,
linePitch: 360,
orientation: "portrait",
differentFirstPageHeader: false,
};
const mergedOptions = {
@ -51,7 +53,13 @@ export class SectionProperties extends XmlComponent {
);
this.root.push(new Columns(mergedOptions.space));
this.root.push(new DocumentGrid(mergedOptions.linePitch));
this.root.push(new HeaderReference());
this.root.push(new HeaderReference("default", 3));
if (mergedOptions.differentFirstPageHeader) {
this.root.push(new HeaderReference("first", 5));
this.root.push(new TitlePage());
}
this.root.push(new FooterReference());
}
}

View File

@ -0,0 +1,11 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface IHeaderReferenceAttributes {
value: string;
}
export class TitlePageAttributes extends XmlAttributeComponent<IHeaderReferenceAttributes> {
protected xmlKeys = {
value: "w:val",
};
}

View File

@ -0,0 +1,17 @@
import { expect } from "chai";
import { Formatter } from "../../../../../export/formatter";
import { TitlePage } from "./title-page";
describe("PageSize", () => {
describe("#constructor()", () => {
it("should create title page property for different first page header", () => {
const properties = new TitlePage();
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:titlePg"]);
expect(tree["w:titlePg"]).to.be.an.instanceof(Array);
expect(tree["w:titlePg"][0]).to.deep.equal({ _attr: { "w:val": "1" } });
});
});
});

View File

@ -0,0 +1,13 @@
import { XmlComponent } from "file/xml-components";
import { TitlePageAttributes } from "./title-page-attributes";
export class TitlePage extends XmlComponent {
constructor() {
super("w:titlePg");
this.root.push(
new TitlePageAttributes({
value: "1",
}),
);
}
}

View File

@ -57,17 +57,17 @@ export class Document extends XmlComponent {
return table;
}
public addDrawing(imageData: IMediaData): void {
public addDrawing(pictureParagraph: Paragraph): void {
this.body.push(pictureParagraph);
}
public createDrawing(imageData: IMediaData): PictureRun {
const paragraph = new Paragraph();
const run = new PictureRun(imageData);
paragraph.addRun(run);
this.addDrawing(paragraph);
this.body.push(paragraph);
}
public createDrawing(imageData: IMediaData): void {
this.addDrawing(imageData);
return;
return run;
}
}

View File

@ -3,6 +3,8 @@ import { XmlComponent } from "file/xml-components";
import { Inline } from "./inline";
export class Drawing extends XmlComponent {
private inline: Inline;
constructor(imageData: IMediaData) {
super("w:drawing");
@ -10,6 +12,12 @@ export class Drawing extends XmlComponent {
throw new Error("imageData cannot be undefined");
}
this.root.push(new Inline(imageData.referenceId, imageData.dimensions));
this.inline = new Inline(imageData.referenceId, imageData.dimensions);
this.root.push(this.inline);
}
public scale(factorX: number, factorY: number): void {
this.inline.scale(factorX, factorY);
}
}

View File

@ -2,14 +2,23 @@ import { XmlComponent } from "file/xml-components";
import { ExtentAttributes } from "./extent-attributes";
export class Extent extends XmlComponent {
private attributes: ExtentAttributes;
constructor(x: number, y: number) {
super("wp:extent");
this.root.push(
new ExtentAttributes({
this.attributes = new ExtentAttributes({
cx: x,
cy: y,
}),
);
});
this.root.push(this.attributes);
}
public setXY(x: number, y: number): void {
this.attributes.set({
cx: x,
cy: y,
});
}
}

View File

@ -3,6 +3,8 @@ import { GraphicDataAttributes } from "./graphic-data-attribute";
import { Pic } from "./pic";
export class GraphicData extends XmlComponent {
private pic: Pic;
constructor(referenceId: number, x: number, y: number) {
super("a:graphicData");
@ -12,6 +14,12 @@ export class GraphicData extends XmlComponent {
}),
);
this.root.push(new Pic(referenceId, x, y));
this.pic = new Pic(referenceId, x, y);
this.root.push(this.pic);
}
public setXY(x: number, y: number): void {
this.pic.setXY(x, y);
}
}

View File

@ -6,6 +6,8 @@ import { PicAttributes } from "./pic-attributes";
import { ShapeProperties } from "./shape-properties/shape-properties";
export class Pic extends XmlComponent {
private shapeProperties: ShapeProperties;
constructor(referenceId: number, x: number, y: number) {
super("pic:pic");
@ -14,8 +16,15 @@ export class Pic extends XmlComponent {
xmlns: "http://schemas.openxmlformats.org/drawingml/2006/picture",
}),
);
this.shapeProperties = new ShapeProperties(x, y);
this.root.push(new NonVisualPicProperties());
this.root.push(new BlipFill(referenceId));
this.root.push(new ShapeProperties(x, y));
}
public setXY(x: number, y: number): void {
this.shapeProperties.setXY(x, y);
}
}

View File

@ -3,14 +3,23 @@ import { XmlComponent } from "file/xml-components";
import { ExtentsAttributes } from "./extents-attributes";
export class Extents extends XmlComponent {
private attributes: ExtentsAttributes;
constructor(x: number, y: number) {
super("a:ext");
this.root.push(
new ExtentsAttributes({
this.attributes = new ExtentsAttributes({
cx: x,
cy: y,
}),
);
});
this.root.push(this.attributes);
}
public setXY(x: number, y: number): void {
this.attributes.set({
cx: x,
cy: y,
});
}
}

View File

@ -4,10 +4,18 @@ import { Extents } from "./extents/extents";
import { Offset } from "./offset/off";
export class Form extends XmlComponent {
private extents: Extents;
constructor(x: number, y: number) {
super("a:xfrm");
this.root.push(new Extents(x, y));
this.extents = new Extents(x, y);
this.root.push(this.extents);
this.root.push(new Offset());
}
public setXY(x: number, y: number): void {
this.extents.setXY(x, y);
}
}

View File

@ -7,6 +7,8 @@ import { PresetGeometry } from "./preset-geometry/preset-geometry";
import { ShapePropertiesAttributes } from "./shape-properties-attributes";
export class ShapeProperties extends XmlComponent {
private form: Form;
constructor(x: number, y: number) {
super("pic:spPr");
@ -16,9 +18,15 @@ export class ShapeProperties extends XmlComponent {
}),
);
this.root.push(new Form(x, y));
this.form = new Form(x, y);
this.root.push(this.form);
this.root.push(new PresetGeometry());
// this.root.push(new NoFill());
// this.root.push(new Outline());
}
public setXY(x: number, y: number): void {
this.form.setXY(x, y);
}
}

View File

@ -0,0 +1,33 @@
import { XmlAttributeComponent, XmlComponent } from "file/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 {
private data: GraphicData;
constructor(referenceId: number, x: number, y: number) {
super("a:graphic");
this.root.push(
new GraphicAttributes({
a: "http://schemas.openxmlformats.org/drawingml/2006/main",
}),
);
this.data = new GraphicData(referenceId, x, y);
this.root.push(this.data);
}
public setXY(x: number, y: number): void {
this.data.setXY(x, y);
}
}

View File

@ -1,24 +1 @@
import { XmlAttributeComponent, XmlComponent } from "file/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, x: number, y: number) {
super("a:graphic");
this.root.push(
new GraphicAttributes({
a: "http://schemas.openxmlformats.org/drawingml/2006/main",
}),
);
this.root.push(new GraphicData(referenceId, x, y));
}
}
export * from "./graphic";

View File

@ -9,7 +9,10 @@ import { GraphicFrameProperties } from "./graphic-frame/graphic-frame-properties
import { InlineAttributes } from "./inline-attributes";
export class Inline extends XmlComponent {
constructor(referenceId: number, dimensions: IMediaDataDimensions) {
private extent: Extent;
private graphic: Graphic;
constructor(referenceId: number, private dimensions: IMediaDataDimensions) {
super("wp:inline");
this.root.push(
@ -21,10 +24,21 @@ export class Inline extends XmlComponent {
}),
);
this.root.push(new Extent(dimensions.emus.x, dimensions.emus.y));
this.extent = new Extent(dimensions.emus.x, dimensions.emus.y);
this.graphic = new Graphic(referenceId, dimensions.emus.x, dimensions.emus.y);
this.root.push(this.extent);
this.root.push(new EffectExtent());
this.root.push(new DocProperties());
this.root.push(new GraphicFrameProperties());
this.root.push(new Graphic(referenceId, dimensions.emus.x, dimensions.emus.y));
this.root.push(this.graphic);
}
public scale(factorX: number, factorY: number): void {
const newX = Math.round(this.dimensions.emus.x * factorX);
const newY = Math.round(this.dimensions.emus.y * factorY);
this.extent.setXY(newX, newY);
this.graphic.setXY(newX, newY);
}
}

View File

@ -1,15 +1,17 @@
import { IMediaData } from "file/media";
import { AppProperties } from "./app-properties/app-properties";
import { ContentTypes } from "./content-types/content-types";
import { CoreProperties, IPropertiesOptions } from "./core-properties";
import { Document } from "./document";
import { SectionPropertiesOptions } from "./document/body/section-properties/section-properties";
import { FooterWrapper } from "./footer-wrapper";
import { HeaderWrapper } from "./header-wrapper";
import { FirstPageHeaderWrapper, HeaderWrapper } from "./header-wrapper";
import { Media } from "./media";
import { Numbering } from "./numbering";
import { Paragraph } from "./paragraph";
import { Hyperlink, Paragraph, PictureRun } from "./paragraph";
import { Relationships } from "./relationships";
import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory";
import { DefaultStylesFactory } from "./styles/factory";
import { Table } from "./table";
@ -22,14 +24,15 @@ export class File {
private readonly docRelationships: Relationships;
private readonly fileRelationships: Relationships;
private readonly headerWrapper: HeaderWrapper;
private readonly firstPageHeaderWrapper: FirstPageHeaderWrapper;
private readonly footerWrapper: FooterWrapper;
private readonly contentTypes: ContentTypes;
private readonly appProperties: AppProperties;
constructor(options?: IPropertiesOptions, sectionPropertiesOptions?: SectionPropertiesOptions) {
this.document = new Document(sectionPropertiesOptions);
const stylesFactory = new DefaultStylesFactory();
this.styles = stylesFactory.newInstance();
if (!options) {
options = {
@ -39,6 +42,14 @@ export class File {
};
}
if (options.externalStyles) {
const stylesFactory = new ExternalStylesFactory();
this.styles = stylesFactory.newInstance(options.externalStyles);
} else {
const stylesFactory = new DefaultStylesFactory();
this.styles = stylesFactory.newInstance();
}
this.coreProperties = new CoreProperties(options);
this.numbering = new Numbering();
this.docRelationships = new Relationships();
@ -57,13 +68,23 @@ export class File {
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",
"header1.xml",
);
this.docRelationships.createRelationship(
5,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",
"header2.xml",
);
this.docRelationships.createRelationship(
4,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer",
"footer1.xml",
);
this.media = new Media();
this.headerWrapper = new HeaderWrapper(this.media);
this.firstPageHeaderWrapper = new FirstPageHeaderWrapper(this.media);
this.footerWrapper = new FooterWrapper(this.media);
this.contentTypes = new ContentTypes();
this.fileRelationships = new Relationships();
@ -101,14 +122,36 @@ export class File {
return this.document.createTable(rows, cols);
}
public createImage(image: string): void {
public createImage(image: string): PictureRun {
const mediaData = this.media.addMedia(image, this.docRelationships.RelationshipCount);
this.docRelationships.createRelationship(
mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
this.document.createDrawing(mediaData);
return this.document.createDrawing(mediaData);
}
public createImageData(imageName: string, data: Buffer, width?: number, height?: number): IMediaData {
const mediaData = this.media.addMediaWithData(imageName, data, this.docRelationships.RelationshipCount, width, height);
this.docRelationships.createRelationship(
mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
return mediaData;
}
public createHyperlink(link: string, text?: string): Hyperlink {
text = text === undefined ? link : text;
const hyperlink = new Hyperlink(text, this.docRelationships.RelationshipCount);
this.docRelationships.createRelationship(
hyperlink.linkId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
link,
"External",
);
return hyperlink;
}
public get Document(): Document {
@ -143,6 +186,10 @@ export class File {
return this.headerWrapper;
}
public get firstPageHeader(): FirstPageHeaderWrapper {
return this.firstPageHeaderWrapper;
}
public get Footer(): FooterWrapper {
return this.footerWrapper;
}

View File

@ -4,6 +4,56 @@ import { Paragraph } from "./paragraph";
import { Relationships } from "./relationships";
import { Table } from "./table";
export class FirstPageHeaderWrapper {
private readonly header: Header;
private readonly relationships: Relationships;
constructor(private readonly media: Media) {
this.header = new Header();
this.relationships = new Relationships();
}
public addParagraph(paragraph: Paragraph): void {
this.header.addParagraph(paragraph);
}
public createParagraph(text?: string): Paragraph {
const para = new Paragraph(text);
this.addParagraph(para);
return para;
}
public addTable(table: Table): void {
this.header.addTable(table);
}
public createTable(rows: number, cols: number): Table {
return this.header.createTable(rows, cols);
}
public addDrawing(imageData: IMediaData): void {
this.header.addDrawing(imageData);
}
public createImage(image: string): void {
const mediaData = this.media.addMedia(image, this.relationships.RelationshipCount);
this.relationships.createRelationship(
mediaData.referenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
this.addDrawing(mediaData);
}
public get Header(): Header {
return this.header;
}
public get Relationships(): Relationships {
return this.relationships;
}
}
export class HeaderWrapper {
private readonly header: Header;
private readonly relationships: Relationships;

View File

@ -5,3 +5,4 @@ export * from "./numbering";
export * from "./media";
export * from "./drawing";
export * from "./styles";
export * from "./xml-components";

View File

@ -13,8 +13,8 @@ export interface IMediaDataDimensions {
export interface IMediaData {
referenceId: number;
stream: fs.ReadStream;
path: string;
stream: fs.ReadStream | Buffer;
path?: string;
fileName: string;
dimensions: IMediaDataDimensions;
}

View File

@ -24,10 +24,34 @@ export class Media {
public addMedia(filePath: string, relationshipsCount: number): IMediaData {
const key = path.basename(filePath);
const dimensions = sizeOf(filePath);
return this.createMedia(key, relationshipsCount, dimensions, fs.createReadStream(filePath), filePath);
}
public addMediaWithData(fileName: string, data: Buffer, relationshipsCount: number, width?: number, height?: number): IMediaData {
const key = fileName;
let dimensions;
if (width && height) {
dimensions = {
width: width,
height: height,
};
} else {
dimensions = sizeOf(data);
}
return this.createMedia(key, relationshipsCount, dimensions, data);
}
private createMedia(
key: string,
relationshipsCount: number,
dimensions: { width: number; height: number },
data: fs.ReadStream | Buffer,
filePath?: string,
): IMediaData {
const imageData = {
referenceId: this.map.size + relationshipsCount + 1,
stream: fs.createReadStream(filePath),
stream: data,
path: filePath,
fileName: key,
dimensions: {

View File

@ -1 +1,2 @@
export * from "./numbering";
export * from "./abstract-numbering";

View File

@ -1,13 +1,15 @@
import { XmlComponent } from "file/xml-components";
import { Indent } from "file/paragraph";
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes";
import { Indent } from "../paragraph/formatting";
import { RunFonts } from "../paragraph/run/run-fonts";
import { AbstractNumbering } from "./abstract-numbering";
import { Num } from "./num";
export class Numbering extends XmlComponent {
private nextId: number;
private abstractNumbering: XmlComponent[] = [];
private concreteNumbering: XmlComponent[] = [];
constructor() {
super("w:numbering");
this.root.push(
@ -36,63 +38,42 @@ export class Numbering extends XmlComponent {
const abstractNumbering = this.createAbstractNumbering();
abstractNumbering
.createLevel(0, "bullet", "•", "left")
.addParagraphProperty(new Indent({ left: 720, hanging: 360 }))
.addRunProperty(new RunFonts("Symbol", "default"));
abstractNumbering.createLevel(0, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 720, hanging: 360 }));
abstractNumbering
.createLevel(1, "bullet", "o", "left")
.addParagraphProperty(new Indent({ left: 1440, hanging: 360 }))
.addRunProperty(new RunFonts("Courier New", "default"));
abstractNumbering.createLevel(1, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 1440, hanging: 360 }));
abstractNumbering
.createLevel(2, "bullet", "•", "left")
.addParagraphProperty(new Indent({ left: 2160, hanging: 360 }))
.addRunProperty(new RunFonts("Wingdings", "default"));
abstractNumbering.createLevel(2, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 2160, hanging: 360 }));
abstractNumbering
.createLevel(3, "bullet", "•", "left")
.addParagraphProperty(new Indent({ left: 2880, hanging: 360 }))
.addRunProperty(new RunFonts("Symbol", "default"));
abstractNumbering.createLevel(3, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 2880, hanging: 360 }));
abstractNumbering
.createLevel(4, "bullet", "o", "left")
.addParagraphProperty(new Indent({ left: 3600, hanging: 360 }))
.addRunProperty(new RunFonts("Courier New", "default"));
abstractNumbering.createLevel(4, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 3600, hanging: 360 }));
abstractNumbering
.createLevel(5, "bullet", "•", "left")
.addParagraphProperty(new Indent({ left: 4320, hanging: 360 }))
.addRunProperty(new RunFonts("Wingdings", "default"));
abstractNumbering.createLevel(5, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 4320, hanging: 360 }));
abstractNumbering
.createLevel(6, "bullet", "•", "left")
.addParagraphProperty(new Indent({ left: 5040, hanging: 360 }))
.addRunProperty(new RunFonts("Symbol", "default"));
abstractNumbering.createLevel(6, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 5040, hanging: 360 }));
abstractNumbering
.createLevel(7, "bullet", "o", "left")
.addParagraphProperty(new Indent({ left: 5760, hanging: 360 }))
.addRunProperty(new RunFonts("Courier New", "default"));
abstractNumbering.createLevel(7, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 5760, hanging: 360 }));
abstractNumbering
.createLevel(8, "bullet", "•", "left")
.addParagraphProperty(new Indent({ left: 6480, hanging: 360 }))
.addRunProperty(new RunFonts("Wingdings", "default"));
abstractNumbering.createLevel(8, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 6480, hanging: 360 }));
this.createConcreteNumbering(abstractNumbering);
}
public createAbstractNumbering(): AbstractNumbering {
const num = new AbstractNumbering(this.nextId++);
this.root.push(num);
this.abstractNumbering.push(num);
return num;
}
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
const num = new Num(this.nextId++, abstractNumbering.id);
this.root.push(num);
this.concreteNumbering.push(num);
return num;
}
public prepForXml(): IXmlableObject {
this.abstractNumbering.forEach((x) => this.root.push(x));
this.concreteNumbering.forEach((x) => this.root.push(x));
return super.prepForXml();
}
}

View File

@ -2,3 +2,4 @@ export * from "./formatting";
export * from "./paragraph";
export * from "./properties";
export * from "./run";
export * from "./links";

View File

@ -0,0 +1,13 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface IHyperlinkAttributesProperties {
id?: string;
history: number;
}
export class HyperlinkAttributes extends XmlAttributeComponent<IHyperlinkAttributesProperties> {
protected xmlKeys = {
id: "r:id",
history: "w:history",
};
}

View File

@ -0,0 +1,40 @@
import { assert, expect } from "chai";
import { Formatter } from "../../../export/formatter";
import { Utility } from "../../../tests/utility";
import { Hyperlink } from "./";
describe("Hyperlink", () => {
let hyperlink: Hyperlink;
beforeEach(() => {
hyperlink = new Hyperlink("https://example.com", 0);
});
describe("#constructor()", () => {
it("should create a hyperlink with correct root key", () => {
const newJson = Utility.jsonify(hyperlink);
assert.equal(newJson.rootKey, "w:hyperlink");
});
it("should create a hyperlink with right attributes", () => {
const newJson = Utility.jsonify(hyperlink);
const attributes = {
id: "rId1",
history: 1,
};
assert.equal(JSON.stringify(newJson.root[0].root), JSON.stringify(attributes));
});
it("should create a hyperlink with a run component", () => {
const tree = new Formatter().format(hyperlink);
const runJson = {
"w:r": [
{ "w:rPr": [{ "w:rStyle": [{ _attr: { "w:val": "Hyperlink" } }] }] },
{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "https://example.com"] },
],
};
expect(tree["w:hyperlink"][1]).to.deep.equal(runJson);
});
});
});

View File

@ -0,0 +1,21 @@
// http://officeopenxml.com/WPhyperlink.php
import { XmlComponent } from "file/xml-components";
import { TextRun } from "../run";
import { HyperlinkAttributes } from "./hyperlink-attributes";
export class Hyperlink extends XmlComponent {
public linkId: number;
constructor(text: string, relationshipsCount: number) {
super("w:hyperlink");
this.linkId = relationshipsCount + 1;
const attributes = new HyperlinkAttributes({
id: `rId${this.linkId}`,
history: 1,
});
this.root.push(attributes);
this.root.push(new TextRun(text).style("Hyperlink"));
}
}

View File

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

View File

@ -162,7 +162,7 @@ describe("Paragraph", () => {
});
describe("#bullet()", () => {
it("should add list paragraph style to JSON", () => {
it("should default to 0 indent level if no bullet was specified", () => {
paragraph.bullet();
const tree = new Formatter().format(paragraph);
expect(tree)
@ -178,8 +178,24 @@ describe("Paragraph", () => {
});
});
it("should add list paragraph style to JSON", () => {
paragraph.bullet(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", () => {
paragraph.bullet();
paragraph.bullet(1);
const tree = new Formatter().format(paragraph);
expect(tree)
.to.have.property("w:p")
@ -190,7 +206,7 @@ describe("Paragraph", () => {
.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 } }] }],
"w:numPr": [{ "w:ilvl": [{ _attr: { "w:val": 1 } }] }, { "w:numId": [{ _attr: { "w:val": 1 } }] }],
});
});
});

View File

@ -13,6 +13,7 @@ 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 { Hyperlink } from "./links";
import { ParagraphProperties } from "./properties";
export class Paragraph extends XmlComponent {
@ -32,6 +33,11 @@ export class Paragraph extends XmlComponent {
return this;
}
public addHyperLink(hyperlink: Hyperlink): Paragraph {
this.root.push(hyperlink);
return this;
}
public createTextRun(text: string): TextRun {
const run = new TextRun(text);
this.addRun(run);
@ -69,6 +75,11 @@ export class Paragraph extends XmlComponent {
return this;
}
public heading6(): Paragraph {
this.properties.push(new Style("Heading6"));
return this;
}
public title(): Paragraph {
this.properties.push(new Style("Title"));
return this;
@ -124,9 +135,9 @@ export class Paragraph extends XmlComponent {
return this;
}
public bullet(): Paragraph {
public bullet(indentLevel: number = 0): Paragraph {
this.properties.push(new Style("ListParagraph"));
this.properties.push(new NumberProperties(1, 0));
this.properties.push(new NumberProperties(1, indentLevel));
return this;
}
@ -136,6 +147,11 @@ export class Paragraph extends XmlComponent {
return this;
}
public setCustomNumbering(numberId: number, indentLevel: number): Paragraph {
this.properties.push(new NumberProperties(numberId, indentLevel));
return this;
}
public style(styleId: string): Paragraph {
this.properties.push(new Style(styleId));
return this;

View File

@ -0,0 +1,38 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate" }> {
protected xmlKeys = { type: "w:fldCharType" };
}
class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> {
protected xmlKeys = { space: "xml:space" };
}
export class Begin extends XmlComponent {
constructor() {
super("w:fldChar");
this.root.push(new FidCharAttrs({ type: "begin" }));
}
}
export class Page extends XmlComponent {
constructor() {
super("w:instrText");
this.root.push(new TextAttributes({ space: "preserve" }));
this.root.push("PAGE");
}
}
export class Separate extends XmlComponent {
constructor() {
super("w:fldChar");
this.root.push(new FidCharAttrs({ type: "separate" }));
}
}
export class End extends XmlComponent {
constructor() {
super("w:fldChar");
this.root.push(new FidCharAttrs({ type: "end" }));
}
}

View File

@ -3,6 +3,8 @@ import { IMediaData } from "../../media/data";
import { Run } from "../run";
export class PictureRun extends Run {
private drawing: Drawing;
constructor(imageData: IMediaData) {
super();
@ -10,6 +12,20 @@ export class PictureRun extends Run {
throw new Error("imageData cannot be undefined");
}
this.root.push(new Drawing(imageData));
this.drawing = new Drawing(imageData);
this.root.push(this.drawing);
}
public scale(factorX: number, factorY?: number): void {
if (!factorX) {
factorX = 1;
}
if (!factorY) {
factorY = factorX;
}
this.drawing.scale(factorX, factorY);
}
}

View File

@ -2,6 +2,7 @@
import { Break } from "./break";
import { Caps, SmallCaps } from "./caps";
import { Bold, Color, DoubleStrike, Italics, Size, Strike } from "./formatting";
import { Begin, End, Page, Separate } from "./page-number";
import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
@ -55,6 +56,14 @@ export class Run extends XmlComponent {
return this;
}
public pageNumber(): Run {
this.root.push(new Begin());
this.root.push(new Page());
this.root.push(new Separate());
this.root.push(new End());
return this;
}
public smallCaps(): Run {
this.properties.push(new SmallCaps());
return this;

View File

@ -4,6 +4,7 @@ export interface IRelationshipAttributesProperties {
id: string;
type: string;
target: string;
targetMode?: string;
}
export class RelationshipAttributes extends XmlAttributeComponent<IRelationshipAttributesProperties> {
@ -11,5 +12,6 @@ export class RelationshipAttributes extends XmlAttributeComponent<IRelationshipA
id: "Id",
type: "Type",
target: "Target",
targetMode: "TargetMode",
};
}

View File

@ -13,10 +13,13 @@ export type RelationshipType =
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
| "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
export type TargetModeType = "External";
export class Relationship extends XmlComponent {
constructor(id: string, type: RelationshipType, target: string) {
constructor(id: string, type: RelationshipType, target: string, targetMode?: TargetModeType) {
super("Relationship");
this.root.push(
@ -24,6 +27,7 @@ export class Relationship extends XmlComponent {
id,
type,
target,
targetMode,
}),
);
}

View File

@ -1,6 +1,6 @@
import { XmlComponent } from "file/xml-components";
import { RelationshipsAttributes } from "./attributes";
import { Relationship, RelationshipType } from "./relationship/relationship";
import { Relationship, RelationshipType, TargetModeType } from "./relationship/relationship";
export class Relationships extends XmlComponent {
constructor() {
@ -16,8 +16,8 @@ export class Relationships extends XmlComponent {
this.root.push(relationship);
}
public createRelationship(id: number, type: RelationshipType, target: string): Relationship {
const relationship = new Relationship(`rId${id}`, type, target);
public createRelationship(id: number, type: RelationshipType, target: string, targetMode?: TargetModeType): Relationship {
const relationship = new Relationship(`rId${id}`, type, target, targetMode);
this.addRelationship(relationship);
return relationship;

View File

@ -0,0 +1,159 @@
import { expect } from "chai";
import { ExternalStylesFactory } from "./external-styles-factory";
describe("External styles factory", () => {
let externalStyles;
beforeEach(() => {
externalStyles = `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:mc="first" xmlns:r="second">
<w:docDefaults>
</w:docDefaults>
<w:latentStyles w:defLockedState="1" w:defUIPriority="99">
</w:latentStyles>
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
<w:name w:val="Normal"/>
<w:qFormat/>
</w:style>
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="heading 1"/>
<w:basedOn w:val="Normal"/>
<w:pPr>
<w:keepNext/>
<w:keepLines/>
<w:pBdr>
<w:bottom w:val="single" w:sz="4" w:space="1" w:color="auto"/>
</w:pBdr>
</w:pPr>
</w:style>
</w:styles>`;
});
describe("#parse", () => {
it("should parse w:styles attributes", () => {
const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any;
expect(importedStyle.rootKey).to.equal("w:styles");
expect(importedStyle.root[0]._attr).to.eql({
"xmlns:mc": "first",
"xmlns:r": "second",
});
});
it("should parse other child elements of w:styles", () => {
const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any;
expect(importedStyle.root.length).to.equal(5);
expect(importedStyle.root[1]).to.eql({
deleted: false,
root: [],
rootKey: "w:docDefaults",
});
expect(importedStyle.root[2]).to.eql({
_attr: {
"w:defLockedState": "1",
"w:defUIPriority": "99",
},
deleted: false,
root: [],
rootKey: "w:latentStyles",
});
});
it("should parse styles elements", () => {
const importedStyle = new ExternalStylesFactory().newInstance(externalStyles) as any;
expect(importedStyle.root.length).to.equal(5);
expect(importedStyle.root[3]).to.eql({
_attr: {
"w:default": "1",
"w:styleId": "Normal",
"w:type": "paragraph",
},
deleted: false,
root: [
{
_attr: {
"w:val": "Normal",
},
deleted: false,
root: [],
rootKey: "w:name",
},
{
deleted: false,
root: [],
rootKey: "w:qFormat",
},
],
rootKey: "w:style",
});
expect(importedStyle.root[4]).to.eql({
_attr: {
"w:styleId": "Heading1",
"w:type": "paragraph",
},
deleted: false,
root: [
{
_attr: {
"w:val": "heading 1",
},
deleted: false,
root: [],
rootKey: "w:name",
},
{
_attr: {
"w:val": "Normal",
},
deleted: false,
root: [],
rootKey: "w:basedOn",
},
{
deleted: false,
root: [
{
deleted: false,
root: [],
rootKey: "w:keepNext",
},
{
deleted: false,
root: [],
rootKey: "w:keepLines",
},
{
deleted: false,
root: [
{
_attr: {
"w:color": "auto",
"w:space": "1",
"w:sz": "4",
"w:val": "single",
},
deleted: false,
root: [],
rootKey: "w:bottom",
},
],
rootKey: "w:pBdr",
},
],
rootKey: "w:pPr",
},
],
rootKey: "w:style",
});
});
});
});

View File

@ -0,0 +1,64 @@
import * as fastXmlParser from "fast-xml-parser";
import { Styles } from "./";
import { ImportedRootElementAttributes, ImportedXmlComponent } from "./../../file/xml-components";
const parseOptions = {
ignoreAttributes: false,
attributeNamePrefix: "",
attrNodeName: "_attr",
};
export class ExternalStylesFactory {
/**
* Creates new Style based on the given styles.
* Parses the styles and convert them to XmlComponent.
* Example content from styles.xml:
* <?xml version="1.0">
* <w:styles xmlns:mc="some schema" ...>
*
* <w:style w:type="paragraph" w:styleId="Heading1">
* <w:name w:val="heading 1"/>
* .....
* </w:style>
*
* <w:style w:type="paragraph" w:styleId="Heading2">
* <w:name w:val="heading 2"/>
* .....
* </w:style>
*
* <w:docDefaults>Or any other element will be parsed to</w:docDefaults>
*
* </w:styles>
* @param externalStyles context from styles.xml
*/
public newInstance(externalStyles: string): Styles {
const xmlStyles = fastXmlParser.parse(externalStyles, parseOptions)["w:styles"];
// create styles with attributes from the parsed xml
const importedStyle = new Styles(new ImportedRootElementAttributes(xmlStyles._attr));
// convert other elements (not styles definitions, but default styles and so on ...)
Object.keys(xmlStyles)
.filter((element) => element !== "_attr" && element !== "w:style")
.forEach((element) => {
importedStyle.push(new ImportedXmlComponent(element, xmlStyles[element]._attr));
});
// convert the styles one by one
xmlStyles["w:style"].map((style) => this.convertElement("w:style", style)).forEach(importedStyle.push.bind(importedStyle));
return importedStyle;
}
// tslint:disable-next-line:no-any
public convertElement(elementName: string, element: any): ImportedXmlComponent {
const xmlElement = new ImportedXmlComponent(elementName, element._attr);
if (typeof element === "object") {
Object.keys(element)
.filter((key) => key !== "_attr")
.map((item) => this.convertElement(item, element[item]))
.forEach(xmlElement.push.bind(xmlElement));
}
return xmlElement;
}
}

View File

@ -1,7 +1,7 @@
import { DocumentAttributes } from "../document/document-attributes";
import { Color, Italics, Size } from "../paragraph/run/formatting";
import { Styles } from "./";
// import { DocumentDefaults } from "./defaults";
import {
Heading1Style,
Heading2Style,
@ -9,13 +9,22 @@ import {
Heading4Style,
Heading5Style,
Heading6Style,
HyperlinkStyle,
ListParagraph,
TitleStyle,
} from "./style";
export class DefaultStylesFactory {
public newInstance(): Styles {
const styles = new Styles();
const documentAttributes = new DocumentAttributes({
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
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",
Ignorable: "w14 w15",
});
const styles = new Styles(documentAttributes);
styles.createDocumentDefaults();
const titleStyle = new TitleStyle();
@ -54,6 +63,8 @@ export class DefaultStylesFactory {
// listParagraph.addParagraphProperty();
styles.push(listParagraph);
const hyperLinkStyle = new HyperlinkStyle();
styles.push(hyperLinkStyle);
return styles;
}
}

View File

@ -1,21 +1,13 @@
import { XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes";
import { BaseXmlComponent, XmlComponent } from "file/xml-components";
import { DocumentDefaults } from "./defaults";
import { ParagraphStyle } from "./style";
export class Styles extends XmlComponent {
constructor() {
constructor(initialStyles?: BaseXmlComponent) {
super("w:styles");
this.root.push(
new DocumentAttributes({
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
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",
Ignorable: "w14 w15",
}),
);
if (initialStyles) {
this.root.push(initialStyles);
}
}
public push(style: XmlComponent): Styles {

View File

@ -3,7 +3,7 @@ import * as paragraph from "../../paragraph";
import * as formatting from "../../paragraph/run/formatting";
import { RunProperties } from "../../paragraph/run/properties";
import { BasedOn, Name, Next, QuickFormat } from "./components";
import { BasedOn, Name, Next, QuickFormat, UiPriority, UnhideWhenUsed } from "./components";
export interface IStyleAttributes {
type?: string;
@ -249,3 +249,43 @@ export class ListParagraph extends ParagraphStyle {
this.root.push(new QuickFormat());
}
}
export class CharacterStyle extends Style {
private readonly runProperties: RunProperties;
constructor(styleId: string, name?: string) {
super({ type: "character", styleId: styleId }, name);
this.runProperties = new RunProperties();
this.root.push(this.runProperties);
this.root.push(new UiPriority("99"));
this.root.push(new UnhideWhenUsed(""));
}
public basedOn(parentId: string): CharacterStyle {
this.root.push(new BasedOn(parentId));
return this;
}
public addRunProperty(property: XmlComponent): void {
this.runProperties.push(property);
}
public color(color: string): CharacterStyle {
this.addRunProperty(new formatting.Color(color));
return this;
}
public underline(underlineType?: string, color?: string): CharacterStyle {
this.addRunProperty(new formatting.Underline(underlineType, color));
return this;
}
}
export class HyperlinkStyle extends CharacterStyle {
constructor() {
super("Hyperlink", "Hyperlink");
this.basedOn("DefaultParagraphFont")
.color("0563C1")
.underline("single");
}
}

View File

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

View File

@ -16,6 +16,11 @@ export class TableProperties extends XmlComponent {
this.root.push(new TableLayout("fixed"));
return this;
}
public setBorder(): TableProperties {
this.root.push(new TableBorders());
return this;
}
}
interface ITableWidth {
@ -46,3 +51,38 @@ class TableLayout extends XmlComponent {
this.root.push(new TableLayoutAttributes({ type }));
}
}
class TableBorders extends XmlComponent {
constructor() {
super("w:tblBorders");
this.root.push(new TableBordersElement("w:top", "single", 4, 0, "auto"));
this.root.push(new TableBordersElement("w:left", "single", 4, 0, "auto"));
this.root.push(new TableBordersElement("w:bottom", "single", 4, 0, "auto"));
this.root.push(new TableBordersElement("w:right", "single", 4, 0, "auto"));
this.root.push(new TableBordersElement("w:insideH", "single", 4, 0, "auto"));
this.root.push(new TableBordersElement("w:insideV", "single", 4, 0, "auto"));
}
}
class TableBordersElement extends XmlComponent {
constructor(elementName: string, value: string, size: number, space: number, color: string) {
super(elementName);
this.root.push(
new TableBordersAttributes({
value,
size,
space,
color,
}),
);
}
}
class TableBordersAttributes extends XmlAttributeComponent<{ value: string; size: number; space: number; color: string }> {
protected xmlKeys = {
value: "w:val",
size: "w:sz",
space: "w:space",
color: "w:color",
};
}

View File

@ -0,0 +1,181 @@
import { expect } from "chai";
import { TableCellBorders, BorderStyle, TableCellWidth, WidthType } from "./table-cell";
import { Formatter } from "../../export/formatter";
describe("TableCellBorders", () => {
describe("#prepForXml", () => {
it("should not add empty borders element if there are no borders defined", () => {
const tb = new TableCellBorders();
const tree = new Formatter().format(tb);
expect(tree).to.deep.equal("");
});
});
describe("#addingBorders", () => {
it("should add top border", () => {
const tb = new TableCellBorders();
tb.addTopBorder(BorderStyle.DOTTED, 1, "FF00FF");
const tree = new Formatter().format(tb);
expect(tree).to.deep.equal({
"w:tcBorders": [
{
"w:top": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 1,
"w:val": "dotted",
},
},
],
},
],
});
});
it("should add start(left) border", () => {
const tb = new TableCellBorders();
tb.addStartBorder(BorderStyle.SINGLE, 2, "FF00FF");
const tree = new Formatter().format(tb);
expect(tree).to.deep.equal({
"w:tcBorders": [
{
"w:start": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 2,
"w:val": "single",
},
},
],
},
],
});
});
it("should add bottom border", () => {
const tb = new TableCellBorders();
tb.addBottomBorder(BorderStyle.DOUBLE, 1, "FF00FF");
const tree = new Formatter().format(tb);
expect(tree).to.deep.equal({
"w:tcBorders": [
{
"w:bottom": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 1,
"w:val": "double",
},
},
],
},
],
});
});
it("should add end(right) border", () => {
const tb = new TableCellBorders();
tb.addEndBorder(BorderStyle.THICK, 3, "FF00FF");
const tree = new Formatter().format(tb);
expect(tree).to.deep.equal({
"w:tcBorders": [
{
"w:end": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 3,
"w:val": "thick",
},
},
],
},
],
});
});
it("should add multiple borders", () => {
const tb = new TableCellBorders();
tb.addTopBorder(BorderStyle.DOTTED, 1, "FF00FF");
tb.addEndBorder(BorderStyle.THICK, 3, "FF00FF");
tb.addBottomBorder(BorderStyle.DOUBLE, 1, "FF00FF");
tb.addStartBorder(BorderStyle.SINGLE, 2, "FF00FF");
const tree = new Formatter().format(tb);
expect(tree).to.deep.equal({
"w:tcBorders": [
{
"w:top": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 1,
"w:val": "dotted",
},
},
],
},
{
"w:end": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 3,
"w:val": "thick",
},
},
],
},
{
"w:bottom": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 1,
"w:val": "double",
},
},
],
},
{
"w:start": [
{
_attr: {
"w:color": "FF00FF",
"w:sz": 2,
"w:val": "single",
},
},
],
},
],
});
});
});
});
describe("TableCellWidth", () => {
describe("#constructor", () => {
it("should create object", () => {
const tcWidth = new TableCellWidth(100, WidthType.DXA);
const tree = new Formatter().format(tcWidth);
expect(tree).to.deep.equal({
"w:tcW": [
{
_attr: {
"w:type": "dxa",
"w:w": 100,
},
},
],
});
});
});
});

View File

@ -0,0 +1,207 @@
import { IXmlableObject, XmlAttributeComponent, XmlComponent } from "file/xml-components";
export enum BorderStyle {
SINGLE = "single",
DASH_DOT_STROKED = "dashDotStroked",
DASHED = "dashed",
DASH_SMALL_GAP = "dashSmallGap",
DOT_DASH = "dotDash",
DOT_DOT_DASH = "dotDotDash",
DOTTED = "dotted",
DOUBLE = "double",
DOUBLE_WAVE = "doubleWave",
INSET = "inset",
NIL = "nil",
NONE = "none",
OUTSET = "outset",
THICK = "thick",
THICK_THIN_LARGE_GAP = "thickThinLargeGap",
THICK_THIN_MEDIUM_GAP = "thickThinMediumGap",
THICK_THIN_SMALL_GAP = "thickThinSmallGap",
THIN_THICK_LARGE_GAP = "thinThickLargeGap",
THIN_THICK_MEDIUM_GAP = "thinThickMediumGap",
THIN_THICK_SMALL_GAP = "thinThickSmallGap",
THIN_THICK_THIN_LARGE_GAP = "thinThickThinLargeGap",
THIN_THICK_THIN_MEDIUM_GAP = "thinThickThinMediumGap",
THIN_THICK_THIN_SMALL_GAP = "thinThickThinSmallGap",
THREE_D_EMBOSS = "threeDEmboss",
THREE_D_ENGRAVE = "threeDEngrave",
TRIPLE = "triple",
WAVE = "wave",
}
interface ICellBorder {
style: BorderStyle;
size: number;
color: string;
}
class CellBorderAttributes extends XmlAttributeComponent<ICellBorder> {
protected xmlKeys = { style: "w:val", size: "w:sz", color: "w:color" };
}
class BaseTableCellBorder extends XmlComponent {
public setProperties(style: BorderStyle, size: number, color: string): BaseTableCellBorder {
const attrs = new CellBorderAttributes({
style: style,
size: size,
color: color,
});
this.root.push(attrs);
return this;
}
}
export class TableCellBorders extends XmlComponent {
constructor() {
super("w:tcBorders");
}
public prepForXml(): IXmlableObject {
return this.root.length > 0 ? super.prepForXml() : "";
}
public addTopBorder(style: BorderStyle, size: number, color: string): TableCellBorders {
const top = new BaseTableCellBorder("w:top");
top.setProperties(style, size, color);
this.root.push(top);
return this;
}
public addStartBorder(style: BorderStyle, size: number, color: string): TableCellBorders {
const start = new BaseTableCellBorder("w:start");
start.setProperties(style, size, color);
this.root.push(start);
return this;
}
public addBottomBorder(style: BorderStyle, size: number, color: string): TableCellBorders {
const bottom = new BaseTableCellBorder("w:bottom");
bottom.setProperties(style, size, color);
this.root.push(bottom);
return this;
}
public addEndBorder(style: BorderStyle, size: number, color: string): TableCellBorders {
const end = new BaseTableCellBorder("w:end");
end.setProperties(style, size, color);
this.root.push(end);
return this;
}
}
/**
* Attributes fot the GridSpan element.
*/
class GridSpanAttributes extends XmlAttributeComponent<{ val: number }> {
protected xmlKeys = { val: "w:val" };
}
/**
* GridSpan element. Should be used in a table cell. Pass the number of columns that this cell need to span.
*/
export class GridSpan extends XmlComponent {
constructor(value: number) {
super("w:gridSpan");
this.root.push(
new GridSpanAttributes({
val: value,
}),
);
}
}
/**
* Vertical merge types.
*/
export enum VMergeType {
/**
* Cell that is merged with upper one.
*/
CONTINUE = "continue",
/**
* Cell that is starting the vertical merge.
*/
RESTART = "restart",
}
class VMergeAttributes extends XmlAttributeComponent<{ val: VMergeType }> {
protected xmlKeys = { val: "w:val" };
}
/**
* Vertical merge element. Should be used in a table cell.
*/
export class VMerge extends XmlComponent {
constructor(value: VMergeType) {
super("w:vMerge");
this.root.push(
new VMergeAttributes({
val: value,
}),
);
}
}
export enum VerticalAlign {
BOTTOM = "bottom",
CENTER = "center",
TOP = "top",
}
class VAlignAttributes extends XmlAttributeComponent<{ val: VerticalAlign }> {
protected xmlKeys = { val: "w:val" };
}
/**
* Vertical align element.
*/
export class VAlign extends XmlComponent {
constructor(value: VerticalAlign) {
super("w:vAlign");
this.root.push(
new VAlignAttributes({
val: value,
}),
);
}
}
export enum WidthType {
/** Auto. */
AUTO = "auto",
/** Value is in twentieths of a point */
DXA = "dxa",
/** No (empty) value. */
NIL = "nil",
/** Value is in percentage. */
PERCENTAGE = "pct",
}
class TableCellWidthAttributes extends XmlAttributeComponent<{ type: WidthType; width: string | number }> {
protected xmlKeys = { width: "w:w", type: "w:type" };
}
/**
* Table cell width element.
*/
export class TableCellWidth extends XmlComponent {
constructor(value: string | number, type: WidthType) {
super("w:tcW");
this.root.push(
new TableCellWidthAttributes({
width: value,
type: type,
}),
);
}
}

View File

@ -5,6 +5,83 @@ import { Formatter } from "../../export/formatter";
import { Paragraph } from "../paragraph";
import { Table } from "./";
const DEFAULT_TABLE_PROPERTIES = {
"w:tblBorders": [
{
"w:top": [
{
_attr: {
"w:color": "auto",
"w:space": 0,
"w:sz": 4,
"w:val": "single",
},
},
],
},
{
"w:left": [
{
_attr: {
"w:color": "auto",
"w:space": 0,
"w:sz": 4,
"w:val": "single",
},
},
],
},
{
"w:bottom": [
{
_attr: {
"w:color": "auto",
"w:space": 0,
"w:sz": 4,
"w:val": "single",
},
},
],
},
{
"w:right": [
{
_attr: {
"w:color": "auto",
"w:space": 0,
"w:sz": 4,
"w:val": "single",
},
},
],
},
{
"w:insideH": [
{
_attr: {
"w:color": "auto",
"w:space": 0,
"w:sz": 4,
"w:val": "single",
},
},
],
},
{
"w:insideV": [
{
_attr: {
"w:color": "auto",
"w:space": 0,
"w:sz": 4,
"w:val": "single",
},
},
],
},
],
};
describe("Table", () => {
describe("#constructor", () => {
it("creates a table with the correct number of rows and columns", () => {
@ -13,7 +90,7 @@ describe("Table", () => {
const cell = { "w:tc": [{ "w:tcPr": [] }, { "w:p": [{ "w:pPr": [] }] }] };
expect(tree).to.deep.equal({
"w:tbl": [
{ "w:tblPr": [] },
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES] },
{
"w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 1 } }] }],
},
@ -55,7 +132,7 @@ describe("Table", () => {
});
expect(tree).to.deep.equal({
"w:tbl": [
{ "w:tblPr": [] },
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES] },
{
"w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 1 } }] }],
},
@ -84,7 +161,7 @@ describe("Table", () => {
});
expect(tree).to.deep.equal({
"w:tbl": [
{ "w:tblPr": [] },
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES] },
{
"w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 1 } }] }],
},
@ -104,7 +181,7 @@ describe("Table", () => {
.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 } }] }],
"w:tblPr": [DEFAULT_TABLE_PROPERTIES, { "w:tblW": [{ _attr: { "w:type": "pct", "w:w": 1000 } }] }],
});
});
});
@ -118,7 +195,7 @@ describe("Table", () => {
.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" } }] }],
"w:tblPr": [DEFAULT_TABLE_PROPERTIES, { "w:tblLayout": [{ _attr: { "w:type": "fixed" } }] }],
});
});
});

View File

@ -1,3 +1,4 @@
import { GridSpan, TableCellBorders, TableCellWidth, VAlign, VerticalAlign, VMerge, VMergeType, WidthType } from "file/table/table-cell";
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Paragraph } from "../paragraph";
import { TableGrid } from "./grid";
@ -8,11 +9,15 @@ export class Table extends XmlComponent {
private readonly rows: TableRow[];
private readonly grid: TableGrid;
constructor(rows: number, cols: number) {
constructor(rows: number, cols: number, colSizes?: number[]) {
super("w:tbl");
this.properties = new TableProperties();
this.root.push(this.properties);
this.properties.setBorder();
if (colSizes && colSizes.length > 0) {
this.grid = new TableGrid(colSizes);
} else {
const gridCols: number[] = [];
for (let i = 0; i < cols; i++) {
/*
@ -28,6 +33,8 @@ export class Table extends XmlComponent {
gridCols.push(1);
}
this.grid = new TableGrid(gridCols);
}
this.root.push(this.grid);
this.rows = [];
@ -111,10 +118,45 @@ export class TableCell extends XmlComponent {
this.addContent(para);
return para;
}
get cellProperties(): TableCellProperties {
return this.properties;
}
}
export class TableCellProperties extends XmlComponent {
private cellBorder: TableCellBorders;
constructor() {
super("w:tcPr");
this.cellBorder = new TableCellBorders();
this.root.push(this.cellBorder);
}
get borders(): TableCellBorders {
return this.cellBorder;
}
public addGridSpan(cellSpan: number): TableCellProperties {
this.root.push(new GridSpan(cellSpan));
return this;
}
public addVerticalMerge(type: VMergeType): TableCellProperties {
this.root.push(new VMerge(type));
return this;
}
public setVerticalAlign(vAlignType: VerticalAlign): TableCellProperties {
this.root.push(new VAlign(vAlignType));
return this;
}
public setWidth(width: string | number, type: WidthType): TableCellProperties {
this.root.push(new TableCellWidth(width, type));
return this;
}
}

View File

@ -2,10 +2,15 @@ import { IXmlableObject } from "./xmlable-object";
export abstract class BaseXmlComponent {
protected rootKey: string;
protected deleted: boolean = false;
constructor(rootKey: string) {
this.rootKey = rootKey;
}
public abstract prepForXml(): IXmlableObject;
public get isDeleted(): boolean {
return this.deleted;
}
}

View File

@ -23,4 +23,8 @@ export abstract class XmlAttributeComponent<T> extends BaseXmlComponent {
});
return { _attr: attrs };
}
public set(properties: T): void {
this.root = properties;
}
}

View File

@ -0,0 +1,34 @@
import { expect } from "chai";
import { ImportedXmlComponent } from "./";
describe("ImportedXmlComponent", () => {
let importedXmlComponent: ImportedXmlComponent;
beforeEach(() => {
const attributes = {
someAttr: "1",
otherAttr: "2",
};
importedXmlComponent = new ImportedXmlComponent("w:test", attributes);
importedXmlComponent.push(new ImportedXmlComponent("w:child"));
});
describe("#prepForXml()", () => {
it("should transform for xml", () => {
const converted = importedXmlComponent.prepForXml();
expect(converted).to.eql({
"w:test": [
{
_attr: {
someAttr: "1",
otherAttr: "2",
},
},
{
"w:child": [],
},
],
});
});
});
});

View File

@ -0,0 +1,74 @@
// tslint:disable:no-any
// tslint:disable:variable-name
import { IXmlableObject, XmlComponent } from "./";
/**
* Represents imported xml component from xml file.
*/
export class ImportedXmlComponent extends XmlComponent {
private _attr: any;
constructor(rootKey: string, attr?: any) {
super(rootKey);
if (attr) {
this._attr = attr;
}
}
/**
* Transforms the object so it can be converted to xml. Example:
* <w:someKey someAttr="1" otherAttr="11">
* <w:child childAttr="2">
* </w:child>
* </w:someKey>
* {
* 'w:someKey': [
* {
* _attr: {
* someAttr: "1",
* otherAttr: "11"
* }
* },
* {
* 'w:child': [
* {
* _attr: {
* childAttr: "2"
* }
* }
* ]
* }
* ]
* }
*/
public prepForXml(): IXmlableObject {
const result = super.prepForXml();
if (!!this._attr) {
if (!Array.isArray(result[this.rootKey])) {
result[this.rootKey] = [result[this.rootKey]];
}
result[this.rootKey].unshift({ _attr: this._attr });
}
return result;
}
public push(xmlComponent: XmlComponent): void {
this.root.push(xmlComponent);
}
}
/**
* Used for the attributes of root element that is being imported.
*/
export class ImportedRootElementAttributes extends XmlComponent {
constructor(private _attr: any) {
super("");
}
public prepForXml(): IXmlableObject {
return {
_attr: this._attr,
};
}
}

View File

@ -1,4 +1,5 @@
export * from "./xml-component";
export * from "./attributes";
export * from "./default-attributes";
export * from "./imported-xml-component";
export * from "./xmlable-object";

View File

@ -18,4 +18,15 @@ describe("XmlComponent", () => {
assert.equal(newJson.rootKey, "w:test");
});
});
describe("#prepForXml()", () => {
it("should skip deleted elements", () => {
const child = new TestComponent("w:test1");
child.delete();
xmlComponent.addChildElement(child);
const xml = xmlComponent.prepForXml();
assert.equal(xml["w:test"].length, 0);
});
});
});

View File

@ -12,6 +12,12 @@ export abstract class XmlComponent extends BaseXmlComponent {
public prepForXml(): IXmlableObject {
const children = this.root
.filter((c) => {
if (c instanceof BaseXmlComponent) {
return !c.isDeleted;
}
return true;
})
.map((comp) => {
if (comp instanceof BaseXmlComponent) {
return comp.prepForXml();
@ -23,4 +29,15 @@ export abstract class XmlComponent extends BaseXmlComponent {
[this.rootKey]: children,
};
}
// TODO: Unused method
public addChildElement(child: XmlComponent | string): XmlComponent {
this.root.push(child);
return this;
}
public delete(): void {
this.deleted = true;
}
}