Compare commits
131 Commits
Author | SHA1 | Date | |
---|---|---|---|
b8232f7a02 | |||
49eadb0efc | |||
40dc90e585 | |||
0de302d192 | |||
80bab95f6c | |||
ba3d551c9f | |||
d14fe31f97 | |||
057f41e355 | |||
8c9b61b37a | |||
11e54b3e2c | |||
fa7cb0bef1 | |||
3977c8ab3b | |||
e8f92efe05 | |||
994df8531b | |||
3cdf96ee0c | |||
e2f55d52e9 | |||
d6fa33035a | |||
f11bca728f | |||
596761d78d | |||
8a3c8d4664 | |||
fdfce79e87 | |||
88340aa336 | |||
d0f53fdd4b | |||
d657f61e11 | |||
20e6b98770 | |||
5ae824358c | |||
3ea106bd22 | |||
538264dec5 | |||
6bcd1c2c24 | |||
120c3a7bbe | |||
2654799822 | |||
6245635b86 | |||
b4f1c4dd6a | |||
25a7ce3742 | |||
08bc069cbf | |||
0ebdcc30ed | |||
3eca81d3f5 | |||
faefbae3a1 | |||
370fb098ac | |||
c73019d84c | |||
250a1de71e | |||
012963e90a | |||
632f3cd19b | |||
1c8cd325d7 | |||
7bcdaab2f2 | |||
5b58e520f9 | |||
78f6ea6c44 | |||
b5172e73f9 | |||
5cf534ad26 | |||
d53cdb0558 | |||
212adbbffb | |||
99ab2f0ef5 | |||
a8201b2658 | |||
f13e676c3b | |||
61b01836bc | |||
2ee918b845 | |||
e9579d75c4 | |||
97824f1bb5 | |||
884c134b25 | |||
4f3cb49076 | |||
152285ed5a | |||
49b7ac212d | |||
83450e6277 | |||
6d6155c742 | |||
f8da2c311b | |||
f6bcaef5b5 | |||
1a9e71bfa1 | |||
3591e11637 | |||
47533cf4e2 | |||
de03f19b46 | |||
d1472368f6 | |||
b83d2c388f | |||
ee5425bef7 | |||
3fdbca939e | |||
c68dc8c52a | |||
96471ecb45 | |||
a13f898ad3 | |||
b4cce534a5 | |||
e0698554ad | |||
1649d2a0fa | |||
9683e89159 | |||
2bece0bb61 | |||
deb6c42d86 | |||
2b0953bb19 | |||
d25d22508c | |||
6db37eb4fb | |||
af461f8418 | |||
8bdbd1de39 | |||
1bdc93ef59 | |||
c12101fbc6 | |||
d5ba6578b3 | |||
b37aefdc8d | |||
0be7c26798 | |||
bf1d10e893 | |||
7dfb016faa | |||
b2aeb2d83c | |||
8f4c78e2a8 | |||
bd1f5898a8 | |||
05a4ef1702 | |||
da9e6d6664 | |||
e9d3853d93 | |||
5a9d6120a5 | |||
e8410ff692 | |||
3427d220c7 | |||
643e3c2f84 | |||
9b40b5e55e | |||
a622c210ef | |||
9495f30e2d | |||
617af87065 | |||
c1dd421b27 | |||
8eff6bd0cf | |||
8566c64eab | |||
afd468277e | |||
ca9c992237 | |||
c93b74661b | |||
08ff092638 | |||
2276572902 | |||
43ddeec6e1 | |||
9eaf04f4c7 | |||
c3c29bb119 | |||
ddb34e6a46 | |||
ef3055430b | |||
75cdae1473 | |||
860afe8f28 | |||
f685dbe0d1 | |||
50911fff57 | |||
a7064f4728 | |||
564e9600ea | |||
fcc4202598 | |||
bfbe59cb84 | |||
06abde2425 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: dolanmiu
|
||||
patreon: dolanmiu
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -52,7 +52,6 @@ docs/.nojekyll
|
||||
.idea
|
||||
|
||||
# Lock files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Documents
|
||||
|
8
.nycrc
8
.nycrc
@ -1,9 +1,9 @@
|
||||
{
|
||||
"check-coverage": true,
|
||||
"lines": 92.35,
|
||||
"functions": 88.28,
|
||||
"branches": 84.64,
|
||||
"statements": 92.16,
|
||||
"lines": 93.53,
|
||||
"functions": 89.63,
|
||||
"branches": 88.57,
|
||||
"statements": 93.34,
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
|
@ -10,7 +10,7 @@ script:
|
||||
- npm run style
|
||||
- npm run build
|
||||
- npm run ts-node -- ./demo/1-basic.ts
|
||||
- npm run e2e "My Document.docx"
|
||||
# - npm run e2e "My Document.docx"
|
||||
- npm run ts-node -- ./demo/2-declaritive-styles.ts
|
||||
- npm run ts-node -- ./demo/3-numbering-and-bullet-points.ts
|
||||
- npm run ts-node -- ./demo/4-basic-table.ts
|
||||
@ -20,7 +20,7 @@ script:
|
||||
- npm run ts-node -- ./demo/8-header-footer.ts
|
||||
- npm run ts-node -- ./demo/9-images-in-header-and-footer.ts
|
||||
- npm run ts-node -- ./demo/10-my-cv.ts
|
||||
- npm run e2e "My Document.docx"
|
||||
# - npm run e2e "My Document.docx"
|
||||
- npm run ts-node -- ./demo/11-declaritive-styles-2.ts
|
||||
- npm run ts-node -- ./demo/12-scaling-images.ts
|
||||
- npm run ts-node -- ./demo/13-xml-styles.ts
|
||||
|
10
README.md
10
README.md
@ -18,7 +18,7 @@
|
||||
[![codecov][codecov-image]][codecov-url]
|
||||
|
||||
<p align="center">
|
||||
<img src="https://i.imgur.com/H5FA1Qy.gif" alt="drawing" width="800"/>
|
||||
<img src="https://i.imgur.com/TCH0YzD.png" alt="drawing" width="800"/>
|
||||
</p>
|
||||
|
||||
# Demo
|
||||
@ -27,8 +27,8 @@
|
||||
|
||||
Here are examples of `docx` being used with basic `HTML/JS` in a browser environment:
|
||||
|
||||
* https://codepen.io/anon/pen/dqoVgQ
|
||||
* https://jsfiddle.net/3xhezb5w/2
|
||||
* https://codepen.io/dolanmiu/pen/RwNeObg
|
||||
* https://jsfiddle.net/dolanmiu/kqxrj35u/1/
|
||||
|
||||
Here is an example of `docx` working in `Angular`:
|
||||
|
||||
@ -73,6 +73,10 @@ Read the contribution guidelines [here](https://docx.js.org/#/contribution-guide
|
||||
[<img src="https://i.imgur.com/suiH2zc.png" alt="drawing" height="50"/>](https://www.dabblewriter.com/)
|
||||
[<img src="https://i.imgur.com/1LjuK2M.png" alt="drawing" height="50"/>](https://turbopatent.com/)
|
||||
[<img src="https://i.imgur.com/dHMg0wF.gif" alt="drawing" height="50"/>](http://www.madisoncres.com/)
|
||||
[<img src="https://i.imgur.com/QEZXU5b.png" alt="drawing" height="50"/>](https://www.beekast.com/)
|
||||
[<img src="https://imgur.com/XVU6aoi.png" alt="drawing" height="50"/>](https://herraizsoto.com/)
|
||||
[<img src="https://i.imgur.com/fn1xccG.png" alt="drawing" height="50"/>](http://www.ativer.com.br/)
|
||||
|
||||
|
||||
...and many more!
|
||||
|
||||
|
@ -16,9 +16,9 @@ doc.addSection({
|
||||
bold: true,
|
||||
}),
|
||||
new TextRun({
|
||||
text: "Github is the best",
|
||||
text: "\tGithub is the best",
|
||||
bold: true,
|
||||
}).tab(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
@ -238,9 +238,9 @@ class DocumentCreator {
|
||||
bold: true,
|
||||
}),
|
||||
new TextRun({
|
||||
text: dateText,
|
||||
text: `\t${dateText}`,
|
||||
bold: true,
|
||||
}).tab(),
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Page numbers
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { AlignmentType, Document, Header, Packer, PageBreak, Paragraph, TextRun } from "../build";
|
||||
import { AlignmentType, Document, Header, Packer, PageBreak, PageNumber, Paragraph, TextRun } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
@ -11,7 +11,12 @@ doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
children: [new TextRun("My Title "), new TextRun("Page ").pageNumber()],
|
||||
children: [
|
||||
new TextRun("My Title "),
|
||||
new TextRun({
|
||||
children: ["Page ", PageNumber.CURRENT],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@ -19,7 +24,12 @@ doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
children: [new TextRun("First Page Header "), new TextRun("Page ").pageNumber()],
|
||||
children: [
|
||||
new TextRun("First Page Header "),
|
||||
new TextRun({
|
||||
children: ["Page ", PageNumber.CURRENT],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Multiple sections and headers
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, Footer, Header, Packer, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build";
|
||||
import { Document, Footer, Header, Packer, PageNumber, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
@ -53,7 +53,11 @@ doc.addSection({
|
||||
default: new Header({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [new TextRun("Page number: ").pageNumber()],
|
||||
children: [
|
||||
new TextRun({
|
||||
children: ["Page number: ", PageNumber.CURRENT],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@ -69,7 +73,11 @@ doc.addSection({
|
||||
default: new Header({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [new TextRun("Page number: ").pageNumber()],
|
||||
children: [
|
||||
new TextRun({
|
||||
children: ["Page number: ", PageNumber.CURRENT],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@ -90,7 +98,11 @@ doc.addSection({
|
||||
default: new Header({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [new TextRun("Page number: ").pageNumber()],
|
||||
children: [
|
||||
new TextRun({
|
||||
children: ["Page number: ", PageNumber.CURRENT],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
|
@ -1,16 +1,54 @@
|
||||
// Footnotes
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, Packer, Paragraph } from "../build";
|
||||
import { Document, FootnoteReferenceRun, Packer, Paragraph, TextRun } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
doc.addSection({
|
||||
children: [new Paragraph("Hello World").referenceFootnote(1), new Paragraph("Hello World").referenceFootnote(2)],
|
||||
const doc = new Document({
|
||||
footnotes: [
|
||||
new Paragraph("Foo"),
|
||||
new Paragraph("Test"),
|
||||
new Paragraph("My amazing reference"),
|
||||
new Paragraph("Foo1"),
|
||||
new Paragraph("Test1"),
|
||||
new Paragraph("My amazing reference1"),
|
||||
],
|
||||
});
|
||||
|
||||
doc.createFootnote(new Paragraph("Test"));
|
||||
doc.createFootnote(new Paragraph("My amazing reference"));
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
children: ["Hello", new FootnoteReferenceRun(1)],
|
||||
}),
|
||||
new TextRun({
|
||||
children: [" World!", new FootnoteReferenceRun(2)],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Hello World"), new FootnoteReferenceRun(3)],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
children: ["Hello", new FootnoteReferenceRun(4)],
|
||||
}),
|
||||
new TextRun({
|
||||
children: [" World!", new FootnoteReferenceRun(5)],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Hello World"), new FootnoteReferenceRun(6)],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Packer.toBuffer(doc).then((buffer) => {
|
||||
fs.writeFileSync("My Document.docx", buffer);
|
||||
|
@ -15,9 +15,9 @@ doc.addSection({
|
||||
bold: true,
|
||||
}),
|
||||
new TextRun({
|
||||
text: "Bar",
|
||||
text: "\tBar",
|
||||
bold: true,
|
||||
}).tab(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Example on how to customise the look at feel using Styles
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build";
|
||||
import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build";
|
||||
|
||||
const doc = new Document({
|
||||
creator: "Clippy",
|
||||
@ -19,6 +19,7 @@ const doc = new Document({
|
||||
size: 28,
|
||||
bold: true,
|
||||
italics: true,
|
||||
color: "red",
|
||||
},
|
||||
paragraph: {
|
||||
spacing: {
|
||||
@ -82,15 +83,23 @@ const doc = new Document({
|
||||
},
|
||||
],
|
||||
},
|
||||
numbering: {
|
||||
config: [
|
||||
{
|
||||
reference: "my-crazy-numbering",
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerLetter",
|
||||
text: "%1)",
|
||||
alignment: AlignmentType.LEFT,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const numberedAbstract = doc.Numbering.createAbstractNumbering();
|
||||
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left");
|
||||
|
||||
const letterNumbering = doc.Numbering.createConcreteNumbering(numberedAbstract);
|
||||
const letterNumbering5 = doc.Numbering.createConcreteNumbering(numberedAbstract);
|
||||
letterNumbering5.overrideLevel(0, 5);
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
@ -105,21 +114,21 @@ doc.addSection({
|
||||
new Paragraph({
|
||||
text: "Option1",
|
||||
numbering: {
|
||||
num: letterNumbering,
|
||||
reference: "my-crazy-numbering",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "Option5 -- override 2 to 5",
|
||||
numbering: {
|
||||
num: letterNumbering,
|
||||
reference: "my-crazy-numbering",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "Option3",
|
||||
numbering: {
|
||||
num: letterNumbering,
|
||||
reference: "my-crazy-numbering",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
@ -152,6 +161,10 @@ doc.addSection({
|
||||
text: "and then underlined ",
|
||||
underline: {},
|
||||
}),
|
||||
new TextRun({
|
||||
text: "and then emphasis-mark ",
|
||||
emphasisMark: {},
|
||||
}),
|
||||
new TextRun({
|
||||
text: "and back to normal.",
|
||||
}),
|
||||
|
@ -1,7 +1,7 @@
|
||||
// This demo shows how to create bookmarks then link to them with internal hyperlinks
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, HeadingLevel, Packer, PageBreak, Paragraph } from "../build";
|
||||
import { Bookmark, Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph } from "../build";
|
||||
|
||||
const LOREM_IPSUM =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante.";
|
||||
@ -10,19 +10,19 @@ const doc = new Document({
|
||||
creator: "Clippy",
|
||||
title: "Sample Document",
|
||||
description: "A brief example of using docx with bookmarks and internal hyperlinks",
|
||||
hyperlinks: {
|
||||
myAnchorId: {
|
||||
text: "Hyperlink",
|
||||
type: HyperlinkType.INTERNAL,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const anchorId = "anchorID";
|
||||
|
||||
// First create the bookmark
|
||||
const bookmark = doc.createBookmark(anchorId, "Lorem Ipsum");
|
||||
const hyperlink = doc.createInternalHyperLink(anchorId, `Click me!`);
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
children: [bookmark],
|
||||
children: [new Bookmark("myAnchorId", "Lorem Ipsum")],
|
||||
}),
|
||||
new Paragraph("\n"),
|
||||
new Paragraph(LOREM_IPSUM),
|
||||
@ -30,7 +30,7 @@ doc.addSection({
|
||||
children: [new PageBreak()],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [hyperlink],
|
||||
children: [new HyperlinkRef("myAnchorId")],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -1,23 +1,53 @@
|
||||
// Numbered lists
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, Numbering, Packer, Paragraph } from "../build";
|
||||
import { AlignmentType, Document, Packer, Paragraph } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
const numbering = new Numbering();
|
||||
|
||||
const abstractNum = numbering.createAbstractNumbering();
|
||||
abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 });
|
||||
|
||||
const concrete = numbering.createConcreteNumbering(abstractNum);
|
||||
const doc = new Document({
|
||||
numbering: {
|
||||
config: [
|
||||
{
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: "upperRoman",
|
||||
text: "%1",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 720, hanging: 260 },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
reference: "my-crazy-reference",
|
||||
},
|
||||
{
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: "decimal",
|
||||
text: "%1",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 720, hanging: 260 },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
reference: "my-number-numbering-reference",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "line with contextual spacing",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-reference",
|
||||
level: 0,
|
||||
},
|
||||
contextualSpacing: true,
|
||||
@ -28,7 +58,7 @@ doc.addSection({
|
||||
new Paragraph({
|
||||
text: "line with contextual spacing",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-reference",
|
||||
level: 0,
|
||||
},
|
||||
contextualSpacing: true,
|
||||
@ -39,7 +69,7 @@ doc.addSection({
|
||||
new Paragraph({
|
||||
text: "line without contextual spacing",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-reference",
|
||||
level: 0,
|
||||
},
|
||||
contextualSpacing: false,
|
||||
@ -50,7 +80,7 @@ doc.addSection({
|
||||
new Paragraph({
|
||||
text: "line without contextual spacing",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-reference",
|
||||
level: 0,
|
||||
},
|
||||
contextualSpacing: false,
|
||||
@ -58,6 +88,27 @@ doc.addSection({
|
||||
before: 200,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "Step 1 - Add sugar",
|
||||
numbering: {
|
||||
reference: "my-number-numbering-reference",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "Step 2 - Add wheat",
|
||||
numbering: {
|
||||
reference: "my-number-numbering-reference",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "Step 3 - Put in oven",
|
||||
numbering: {
|
||||
reference: "my-number-numbering-reference",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -1,46 +1,91 @@
|
||||
// Numbering and bullet points example
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, Numbering, Packer, Paragraph } from "../build";
|
||||
import { AlignmentType, Document, Packer, Paragraph } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
const numbering = new Numbering();
|
||||
|
||||
const abstractNum = numbering.createAbstractNumbering();
|
||||
abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 });
|
||||
abstractNum.createLevel(1, "decimal", "%2.", "start").indent({ left: 1440, hanging: 980 });
|
||||
abstractNum.createLevel(2, "lowerLetter", "%3)", "start").indent({ left: 2160, hanging: 1700 });
|
||||
|
||||
const concrete = numbering.createConcreteNumbering(abstractNum);
|
||||
const doc = new Document({
|
||||
numbering: {
|
||||
config: [
|
||||
{
|
||||
reference: "my-crazy-numbering",
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: "upperRoman",
|
||||
text: "%1",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 720, hanging: 260 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 1,
|
||||
format: "decimal",
|
||||
text: "%2.",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 1440, hanging: 980 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
format: "lowerLetter",
|
||||
text: "%3)",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 2160, hanging: 1700 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 3,
|
||||
format: "upperLetter",
|
||||
text: "%4)",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 2880, hanging: 2420 },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "Hey you",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-numbering",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "What's up fam",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-numbering",
|
||||
level: 1,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "Hello World 2",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-numbering",
|
||||
level: 1,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "Yeah boi",
|
||||
numbering: {
|
||||
num: concrete,
|
||||
reference: "my-crazy-numbering",
|
||||
level: 2,
|
||||
},
|
||||
}),
|
||||
@ -68,6 +113,27 @@ doc.addSection({
|
||||
level: 3,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "101 MSXFM",
|
||||
numbering: {
|
||||
reference: "my-crazy-numbering",
|
||||
level: 3,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "back to level 1",
|
||||
numbering: {
|
||||
reference: "my-crazy-numbering",
|
||||
level: 1,
|
||||
},
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "back to level 0",
|
||||
numbering: {
|
||||
reference: "my-crazy-numbering",
|
||||
level: 0,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Example of how you would create a table and add data to it
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, HeadingLevel, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign } from "../build";
|
||||
import { Document, HeadingLevel, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign, TextDirection } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
@ -17,6 +17,14 @@ const table = new Table({
|
||||
children: [new Paragraph({}), new Paragraph({})],
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: "bottom to top" }), new Paragraph({})],
|
||||
textDirection: TextDirection.BOTTOM_TO_TOP_LEFT_TO_RIGHT,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph({ text: "top to bottom" }), new Paragraph({})],
|
||||
textDirection: TextDirection.TOP_TO_BOTTOM_RIGHT_TO_LEFT,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
@ -38,6 +46,22 @@ const table = new Table({
|
||||
],
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "Text above should be vertical from bottom to top",
|
||||
}),
|
||||
],
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "Text above should be vertical from top to bottom",
|
||||
}),
|
||||
],
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
@ -1,7 +1,8 @@
|
||||
// Example of how you would merge cells together (Rows and Columns) and apply shading
|
||||
// Also includes an example on how to center tables
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
|
||||
import { AlignmentType, BorderStyle, Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
@ -29,6 +30,7 @@ const table = new Table({
|
||||
});
|
||||
|
||||
const table2 = new Table({
|
||||
alignment: AlignmentType.CENTER,
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
@ -66,6 +68,7 @@ const table2 = new Table({
|
||||
});
|
||||
|
||||
const table3 = new Table({
|
||||
alignment: AlignmentType.CENTER,
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
@ -181,7 +184,7 @@ const table5 = new Table({
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [],
|
||||
children: [new Paragraph("1,0")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("1,2")],
|
||||
@ -192,10 +195,10 @@ const table5 = new Table({
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [],
|
||||
children: [new Paragraph("2,0")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [],
|
||||
children: [new Paragraph("2,1")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@ -206,6 +209,163 @@ const table5 = new Table({
|
||||
},
|
||||
});
|
||||
|
||||
const borders = {
|
||||
top: {
|
||||
style: BorderStyle.DASH_SMALL_GAP,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
bottom: {
|
||||
style: BorderStyle.DASH_SMALL_GAP,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
left: {
|
||||
style: BorderStyle.DASH_SMALL_GAP,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
right: {
|
||||
style: BorderStyle.DASH_SMALL_GAP,
|
||||
size: 1,
|
||||
color: "red",
|
||||
},
|
||||
};
|
||||
|
||||
const table6 = new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
borders,
|
||||
children: [new Paragraph("0,0")],
|
||||
rowSpan: 2,
|
||||
}),
|
||||
new TableCell({
|
||||
borders,
|
||||
children: [new Paragraph("0,1")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
borders,
|
||||
children: [new Paragraph("1,1")],
|
||||
rowSpan: 2,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
borders,
|
||||
children: [new Paragraph("2,0")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
width: {
|
||||
size: 100,
|
||||
type: WidthType.PERCENTAGE,
|
||||
},
|
||||
});
|
||||
|
||||
const table7 = new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("0,0")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("0,1")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("0,2")],
|
||||
rowSpan: 2,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("0,3")],
|
||||
rowSpan: 3,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("1,0")],
|
||||
columnSpan: 2,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("2,0")],
|
||||
columnSpan: 2,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("2,2")],
|
||||
rowSpan: 2,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("3,0")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("3,1")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("3,3")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
width: {
|
||||
size: 100,
|
||||
type: WidthType.PERCENTAGE,
|
||||
},
|
||||
});
|
||||
|
||||
const table8 = new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({ children: [new Paragraph("1,1")] }),
|
||||
new TableCell({ children: [new Paragraph("1,2")] }),
|
||||
new TableCell({ children: [new Paragraph("1,3")] }),
|
||||
new TableCell({ children: [new Paragraph("1,4")], rowSpan: 4, borders }),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({ children: [new Paragraph("2,1")] }),
|
||||
new TableCell({ children: [new Paragraph("2,2")] }),
|
||||
new TableCell({ children: [new Paragraph("2,3")], rowSpan: 3 }),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({ children: [new Paragraph("3,1")] }),
|
||||
new TableCell({ children: [new Paragraph("3,2")], rowSpan: 2 }),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({ children: [new Paragraph("4,1")] }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
width: {
|
||||
size: 100,
|
||||
type: WidthType.PERCENTAGE,
|
||||
},
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
table,
|
||||
@ -219,10 +379,16 @@ doc.addSection({
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
}),
|
||||
table3,
|
||||
new Paragraph("Merging columns"),
|
||||
new Paragraph("Merging columns 1"),
|
||||
table4,
|
||||
new Paragraph("More Merging columns"),
|
||||
new Paragraph("Merging columns 2"),
|
||||
table5,
|
||||
new Paragraph("Merging columns 3"),
|
||||
table6,
|
||||
new Paragraph("Merging columns 4"),
|
||||
table7,
|
||||
new Paragraph("Merging columns 5"),
|
||||
table8,
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
Document,
|
||||
OverlapType,
|
||||
Packer,
|
||||
Paragraph,
|
||||
RelativeHorizontalPosition,
|
||||
@ -43,6 +44,7 @@ const table = new Table({
|
||||
verticalAnchor: TableAnchorType.MARGIN,
|
||||
relativeHorizontalPosition: RelativeHorizontalPosition.RIGHT,
|
||||
relativeVerticalPosition: RelativeVerticalPosition.BOTTOM,
|
||||
overlap: OverlapType.NEVER,
|
||||
},
|
||||
width: {
|
||||
size: 4535,
|
||||
|
@ -1,15 +1,32 @@
|
||||
// Example on how to add hyperlinks to websites
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, Packer, Paragraph } from "../build";
|
||||
import { Document, HyperlinkRef, HyperlinkType, Packer, Paragraph, Media } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
const link = doc.createHyperlink("http://www.example.com", "Hyperlink");
|
||||
const doc = new Document({
|
||||
hyperlinks: {
|
||||
myCoolLink: {
|
||||
link: "http://www.example.com",
|
||||
text: "Hyperlink",
|
||||
type: HyperlinkType.EXTERNAL,
|
||||
},
|
||||
myOtherLink: {
|
||||
link: "http://www.google.com",
|
||||
text: "Google Link",
|
||||
type: HyperlinkType.EXTERNAL,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [link],
|
||||
children: [new HyperlinkRef("myCoolLink")],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [image1, new HyperlinkRef("myOtherLink")],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Example how to display page numbers
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { AlignmentType, Document, Footer, Header, Packer, PageNumberFormat, Paragraph, TextRun } from "../build";
|
||||
import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, PageNumberFormat, Paragraph, TextRun } from "../build";
|
||||
|
||||
const doc = new Document({});
|
||||
|
||||
@ -9,9 +9,17 @@ doc.addSection({
|
||||
headers: {
|
||||
default: new Header({
|
||||
children: [
|
||||
new Paragraph("Foo Bar corp. ")
|
||||
.addRun(new TextRun("Page Number ").pageNumber())
|
||||
.addRun(new TextRun(" to ").numberOfTotalPages()),
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun("Foo Bar corp. "),
|
||||
new TextRun({
|
||||
children: ["Page Number ", PageNumber.CURRENT],
|
||||
}),
|
||||
new TextRun({
|
||||
children: [" to ", PageNumber.TOTAL_PAGES],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
@ -19,11 +27,17 @@ doc.addSection({
|
||||
default: new Footer({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "Foo Bar corp. ",
|
||||
alignment: AlignmentType.CENTER,
|
||||
})
|
||||
.addRun(new TextRun("Page Number: ").pageNumber())
|
||||
.addRun(new TextRun(" to ").numberOfTotalPages()),
|
||||
children: [
|
||||
new TextRun("Foo Bar corp. "),
|
||||
new TextRun({
|
||||
children: ["Page Number: ", PageNumber.CURRENT],
|
||||
}),
|
||||
new TextRun({
|
||||
children: [" to ", PageNumber.TOTAL_PAGES],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
@ -32,11 +46,21 @@ doc.addSection({
|
||||
pageNumberFormatType: PageNumberFormat.DECIMAL,
|
||||
},
|
||||
children: [
|
||||
new Paragraph("Hello World 1").pageBreak(),
|
||||
new Paragraph("Hello World 2").pageBreak(),
|
||||
new Paragraph("Hello World 3").pageBreak(),
|
||||
new Paragraph("Hello World 4").pageBreak(),
|
||||
new Paragraph("Hello World 5").pageBreak(),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Hello World 1"), new PageBreak()],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Hello World 2"), new PageBreak()],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Hello World 3"), new PageBreak()],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Hello World 4"), new PageBreak()],
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Hello World 5"), new PageBreak()],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Multiple sections with total number of pages in each section
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { AlignmentType, Document, Packer, PageNumberFormat, TextRun, Header, Paragraph, Footer, PageBreak } from "../build";
|
||||
import { AlignmentType, Document, Footer, Header, Packer, PageBreak, PageNumber, PageNumberFormat, Paragraph, TextRun } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
@ -10,8 +10,12 @@ const header = new Header({
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun("Header on another page"),
|
||||
new TextRun("Page Number: ").pageNumber(),
|
||||
new TextRun(" to ").numberOfTotalPagesSection(),
|
||||
new TextRun({
|
||||
children: ["Page number: ", PageNumber.CURRENT],
|
||||
}),
|
||||
new TextRun({
|
||||
children: [" to ", PageNumber.TOTAL_PAGES_IN_SECTION],
|
||||
}),
|
||||
],
|
||||
alignment: AlignmentType.CENTER,
|
||||
}),
|
||||
|
31
demo/48-vertical-align.ts
Normal file
31
demo/48-vertical-align.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// Example of making content of section vertically aligned
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, Packer, Paragraph, SectionVerticalAlignValue, TextRun } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
doc.addSection({
|
||||
properties: {
|
||||
verticalAlign: SectionVerticalAlignValue.CENTER,
|
||||
},
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun("Hello World"),
|
||||
new TextRun({
|
||||
text: "Foo Bar",
|
||||
bold: true,
|
||||
}),
|
||||
new TextRun({
|
||||
text: "\tGithub is the best",
|
||||
bold: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Packer.toBuffer(doc).then((buffer) => {
|
||||
fs.writeFileSync("My Document.docx", buffer);
|
||||
});
|
37
demo/49-table-borders.ts
Normal file
37
demo/49-table-borders.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// Add custom borders to the table itself
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { BorderStyle, Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
const table = new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph("Hello")],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph("World")],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
doc.addSection({ children: [table] });
|
||||
|
||||
Packer.toBuffer(doc).then((buffer) => {
|
||||
fs.writeFileSync("My Document.docx", buffer);
|
||||
});
|
61
demo/50-readme-demo.ts
Normal file
61
demo/50-readme-demo.ts
Normal file
@ -0,0 +1,61 @@
|
||||
// Simple example to add text to a document
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, HeadingLevel, Media, Packer, Paragraph, Table, TableCell, TableRow, VerticalAlign } from "../build";
|
||||
|
||||
const doc = new Document();
|
||||
|
||||
const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
|
||||
const image2 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
|
||||
|
||||
const table = new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [new Paragraph(image1)],
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
}),
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "Hello",
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
}),
|
||||
],
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "World",
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new TableCell({
|
||||
children: [new Paragraph(image1)],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "Hello World",
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
}),
|
||||
table,
|
||||
new Paragraph(image2),
|
||||
],
|
||||
});
|
||||
|
||||
Packer.toBuffer(doc).then((buffer) => {
|
||||
fs.writeFileSync("My Document.docx", buffer);
|
||||
});
|
37
demo/51-character-styles.ts
Normal file
37
demo/51-character-styles.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// Custom character styles using JavaScript configuration
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, Packer, Paragraph, TextRun } from "../build";
|
||||
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
characterStyles: [
|
||||
{
|
||||
id: "myRedStyle",
|
||||
name: "My Wonky Style",
|
||||
basedOn: "Normal",
|
||||
run: {
|
||||
color: "990000",
|
||||
italics: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "Foo bar",
|
||||
style: "myRedStyle",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Packer.toBuffer(doc).then((buffer) => {
|
||||
fs.writeFileSync("My Document.docx", buffer);
|
||||
});
|
37
demo/52-japanese.ts
Normal file
37
demo/52-japanese.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// Japanese text - Need to use a Japanese font
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, HeadingLevel, Packer, Paragraph } from "../build";
|
||||
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
paragraphStyles: [
|
||||
{
|
||||
id: "Normal",
|
||||
name: "Normal",
|
||||
basedOn: "Normal",
|
||||
next: "Normal",
|
||||
quickFormat: true,
|
||||
run: {
|
||||
font: "MS Gothic",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "KFCを食べるのが好き",
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "こんにちは",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Packer.toBuffer(doc).then((buffer) => {
|
||||
fs.writeFileSync("My Document.docx", buffer);
|
||||
});
|
55
demo/53-chinese.ts
Normal file
55
demo/53-chinese.ts
Normal file
@ -0,0 +1,55 @@
|
||||
// Chinese text - Chinese text need to use a Chinese font. And ascii text need to use a ascii font.
|
||||
// Different from the `52-japanese.ts`.
|
||||
// `52-japanese.ts` will set all characters to use Japanese font.
|
||||
// `53-chinese.ts` will set Chinese characters to use Chinese font, and set ascii characters to use ascii font.
|
||||
|
||||
// Note that if the OS have not install `KaiTi` font, this demo doesn't work.
|
||||
|
||||
// Import from 'docx' rather than '../build' if you install from npm
|
||||
import * as fs from "fs";
|
||||
import { Document, HeadingLevel, Packer, Paragraph, TextRun } from "../build";
|
||||
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
paragraphStyles: [
|
||||
{
|
||||
id: "Normal",
|
||||
name: "Normal",
|
||||
basedOn: "Normal",
|
||||
next: "Normal",
|
||||
quickFormat: true,
|
||||
run: {
|
||||
font: {
|
||||
ascii: "Times",
|
||||
eastAsia: "KaiTi",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: "中文和英文 Chinese and English",
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
}),
|
||||
new Paragraph({
|
||||
text: "中文和英文 Chinese and English",
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: "中文和英文 Chinese and English",
|
||||
font: { eastAsia: "SimSun" }, // set eastAsia to "SimSun".
|
||||
// The ascii characters will use the default font ("Times") specified in paragraphStyles
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
Packer.toBuffer(doc).then((buffer) => {
|
||||
fs.writeFileSync("My Document.docx", buffer);
|
||||
});
|
@ -21,9 +21,9 @@ doc.addSection({
|
||||
bold: true,
|
||||
}),
|
||||
new TextRun({
|
||||
text: "Github is the best",
|
||||
text: "\tGithub is the best",
|
||||
bold: true,
|
||||
}).tab(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new Paragraph({
|
||||
|
@ -12,21 +12,21 @@
|
||||
|
||||
<script>
|
||||
function generate() {
|
||||
const doc = new Document();
|
||||
const doc = new docx.Document();
|
||||
|
||||
doc.addSection({
|
||||
children: [
|
||||
new Paragraph({
|
||||
new docx.Paragraph({
|
||||
children: [
|
||||
new TextRun("Hello World"),
|
||||
new TextRun({
|
||||
new docx.TextRun("Hello World"),
|
||||
new docx.TextRun({
|
||||
text: "Foo Bar",
|
||||
bold: true,
|
||||
}),
|
||||
new TextRun({
|
||||
text: "Github is the best",
|
||||
new docx.TextRun({
|
||||
text: "\tGithub is the best",
|
||||
bold: true,
|
||||
}).tab(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
|
||||
|
||||
Packer.toBlob(doc).then((blob) => {
|
||||
docx.Packer.toBlob(doc).then((blob) => {
|
||||
console.log(blob);
|
||||
saveAs(blob, "example.docx");
|
||||
console.log("Document created successfully");
|
||||
|
@ -50,9 +50,9 @@ doc.addSection({
|
||||
bold: true,
|
||||
}),
|
||||
new TextRun({
|
||||
text: "Github is the best",
|
||||
text: "\tGithub is the best",
|
||||
bold: true,
|
||||
}).tab(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
@ -7,7 +7,12 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="Generate .docx documents with JavaScript">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/style.min.css"
|
||||
title="docsify-darklight-theme"
|
||||
type="text/css"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -26,6 +31,10 @@
|
||||
<script src="https://unpkg.com/docsify-copy-code@2"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
||||
<script src="//unpkg.com/prismjs/components/prism-typescript.min.js"></script>
|
||||
<script
|
||||
src="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/index.min.js"
|
||||
type="text/javascript">
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -20,11 +20,7 @@ This method is useful for adding different [text](text.md) with different styles
|
||||
|
||||
```ts
|
||||
const paragraph = new Paragraph({
|
||||
children: [
|
||||
new TextRun("Lorem Ipsum Foo Bar"),
|
||||
new TextRun("Hello World"),
|
||||
new SymbolRun("F071"),
|
||||
],
|
||||
children: [new TextRun("Lorem Ipsum Foo Bar"), new TextRun("Hello World"), new SymbolRun("F071")],
|
||||
});
|
||||
```
|
||||
|
||||
@ -61,7 +57,7 @@ doc.addSection({
|
||||
This is the list of options for a paragraph. A detailed explanation is below:
|
||||
|
||||
| Property | Type | Mandatory? | Possible Values |
|
||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| [text](#text) | `string` | Optional | |
|
||||
| [heading](#heading) | `HeadingLevel` | Optional | `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`, `TITLE` |
|
||||
| [border](#border) | `IBorderOptions` | Optional | `top`, `bottom`, `left`, `right`. Each of these are of type IBorderPropertyOptions. Click here for Example |
|
||||
@ -80,7 +76,7 @@ This is the list of options for a paragraph. A detailed explanation is below:
|
||||
| style | `string` | Optional | |
|
||||
| tabStop | `{ left?: ITabStopOptions; right?: ITabStopOptions; maxRight?: { leader: LeaderType; }; center?: ITabStopOptions }` | Optional | |
|
||||
| bullet | `{ level: number }` | Optional | |
|
||||
| numbering | `{ num: Num; level: number; custom?: boolean }` | Optional | |
|
||||
| numbering | `{ num: ConcreteNumbering; level: number; custom?: boolean }` | Optional | |
|
||||
|
||||
## Text
|
||||
|
||||
@ -252,10 +248,7 @@ To move to a new page (insert a page break):
|
||||
|
||||
```ts
|
||||
const paragraph = new docx.Paragraph({
|
||||
children: [
|
||||
new TextRun("Amazing Heading"),
|
||||
new PageBreak(),
|
||||
]
|
||||
children: [new TextRun("Amazing Heading"), new PageBreak()],
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -22,10 +22,11 @@ const name = new TextRun({
|
||||
### Run formatting
|
||||
|
||||
- `bold`, `italics`, `smallCaps`, `allCaps`, `strike`, `doubleStrike`, `subScript`, `superScript`: Set the formatting property to true
|
||||
- `underline(style="single", color=null)`: Set the underline style and color
|
||||
- `underline({type="single", color=null})`: Set the underline style and color
|
||||
- `emphasisMark({type="dot"})`: Set the emphasis mark style
|
||||
- `color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`)
|
||||
- `size(halfPts)`: Set the font size, measured in half-points
|
||||
- `font(name)`: Set the run's font
|
||||
- `font(name)` or `font({ascii, cs, eastAsia, hAnsi, hint})`: Set the run's font
|
||||
- `style(name)`: Apply a named run style
|
||||
- `characterSpacing(value)`: Set the character spacing adjustment (in TWIPs)
|
||||
|
||||
|
@ -12,7 +12,7 @@ Simply call the relevant methods on the paragraph listed below. Then just add a
|
||||
|
||||
```ts
|
||||
const paragraph = new Paragraph({
|
||||
children: [new TextRun("Hey everyone").bold(), new TextRun("11th November 1999").tab()],
|
||||
children: [new TextRun("Hey everyone").bold(), new TextRun(\t"11th November 1999")],
|
||||
tabStops: [
|
||||
{
|
||||
type: TabStopType.RIGHT,
|
||||
@ -26,7 +26,7 @@ The example above will create a left aligned text, and a right aligned text on t
|
||||
|
||||
```ts
|
||||
const paragraph = new Paragraph({
|
||||
children: [new TextRun("Second tab stop here I come!").tab().tab()],
|
||||
children: [new TextRun("Second tab stop here I come!")],
|
||||
tabStops: [
|
||||
{
|
||||
type: TabStopType.RIGHT,
|
||||
@ -46,7 +46,7 @@ You can add multiple tab stops of the same `type` too.
|
||||
|
||||
```ts
|
||||
const paragraph = new Paragraph({
|
||||
children: [new TextRun("Multiple tab stops!").tab().tab()],
|
||||
children: [new TextRun("Multiple tab stops!")],
|
||||
tabStops: [
|
||||
{
|
||||
type: TabStopType.RIGHT,
|
||||
|
@ -68,6 +68,15 @@ const text = new TextRun({
|
||||
});
|
||||
```
|
||||
|
||||
### Emphasis Mark
|
||||
|
||||
```ts
|
||||
const text = new TextRun({
|
||||
text: "and then emphasis mark",
|
||||
emphasisMark: {},
|
||||
});
|
||||
```
|
||||
|
||||
### Strike through
|
||||
|
||||
```ts
|
||||
|
8453
package-lock.json
generated
Normal file
8453
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "docx",
|
||||
"version": "5.0.0-rc7",
|
||||
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
|
||||
"version": "5.2.2",
|
||||
"description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"pretest": "rimraf ./build",
|
||||
@ -50,7 +50,9 @@
|
||||
"types": "./build/index.d.ts",
|
||||
"dependencies": {
|
||||
"@types/jszip": "^3.1.4",
|
||||
"@types/node": "^14.0.5",
|
||||
"jszip": "^3.1.5",
|
||||
"shortid": "^2.2.15",
|
||||
"xml": "^1.0.1",
|
||||
"xml-js": "^1.6.8"
|
||||
},
|
||||
@ -64,6 +66,7 @@
|
||||
"@types/chai": "^3.4.35",
|
||||
"@types/mocha": "^2.2.39",
|
||||
"@types/request-promise": "^4.1.42",
|
||||
"@types/shortid": "0.0.29",
|
||||
"@types/sinon": "^4.3.1",
|
||||
"@types/webpack": "^4.4.24",
|
||||
"awesome-typescript-loader": "^3.4.1",
|
||||
@ -74,7 +77,7 @@
|
||||
"jszip": "^3.1.5",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-webpack": "^1.0.1",
|
||||
"nyc": "^13.1.0",
|
||||
"nyc": "^14.1.1",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^1.15.2",
|
||||
"prompt": "^1.0.0",
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BaseXmlComponent, IXmlableObject } from "file/xml-components";
|
||||
import { File } from "../file";
|
||||
|
||||
export class Formatter {
|
||||
public format(input: BaseXmlComponent): IXmlableObject {
|
||||
const output = input.prepForXml();
|
||||
public format(input: BaseXmlComponent, file?: File): IXmlableObject {
|
||||
const output = input.prepForXml(file);
|
||||
|
||||
if (output) {
|
||||
return output;
|
||||
|
@ -5,7 +5,7 @@ export class ImageReplacer {
|
||||
let currentXmlData = xmlData;
|
||||
|
||||
mediaData.forEach((image, i) => {
|
||||
currentXmlData = currentXmlData.replace(`{${image.fileName}}`, (offset + i).toString());
|
||||
currentXmlData = currentXmlData.replace(new RegExp(`{${image.fileName}}`, "g"), (offset + i).toString());
|
||||
});
|
||||
|
||||
return currentXmlData;
|
||||
|
@ -4,6 +4,7 @@ import * as xml from "xml";
|
||||
import { File } from "file";
|
||||
import { Formatter } from "../formatter";
|
||||
import { ImageReplacer } from "./image-replacer";
|
||||
import { NumberingReplacer } from "./numbering-replacer";
|
||||
|
||||
interface IXmlifyedFile {
|
||||
readonly data: string;
|
||||
@ -30,10 +31,12 @@ interface IXmlifyedFileMapping {
|
||||
export class Compiler {
|
||||
private readonly formatter: Formatter;
|
||||
private readonly imageReplacer: ImageReplacer;
|
||||
private readonly numberingReplacer: NumberingReplacer;
|
||||
|
||||
constructor() {
|
||||
this.formatter = new Formatter();
|
||||
this.imageReplacer = new ImageReplacer();
|
||||
this.numberingReplacer = new NumberingReplacer();
|
||||
}
|
||||
|
||||
public compile(file: File, prettifyXml?: boolean): JSZip {
|
||||
@ -68,7 +71,7 @@ export class Compiler {
|
||||
file.verifyUpdateFields();
|
||||
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
|
||||
|
||||
const documentXmlData = xml(this.formatter.format(file.Document), prettify);
|
||||
const documentXmlData = xml(this.formatter.format(file.Document, file), prettify);
|
||||
const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media);
|
||||
|
||||
return {
|
||||
@ -82,24 +85,25 @@ export class Compiler {
|
||||
);
|
||||
});
|
||||
|
||||
return xml(this.formatter.format(file.DocumentRelationships), prettify);
|
||||
return xml(this.formatter.format(file.DocumentRelationships, file), prettify);
|
||||
})(),
|
||||
path: "word/_rels/document.xml.rels",
|
||||
},
|
||||
Document: {
|
||||
data: (() => {
|
||||
const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount);
|
||||
const referenedXmlData = this.numberingReplacer.replace(xmlData, file.Numbering.ConcreteNumbering);
|
||||
|
||||
return xmlData;
|
||||
return referenedXmlData;
|
||||
})(),
|
||||
path: "word/document.xml",
|
||||
},
|
||||
Styles: {
|
||||
data: xml(this.formatter.format(file.Styles), prettify),
|
||||
data: xml(this.formatter.format(file.Styles, file), prettify),
|
||||
path: "word/styles.xml",
|
||||
},
|
||||
Properties: {
|
||||
data: xml(this.formatter.format(file.CoreProperties), {
|
||||
data: xml(this.formatter.format(file.CoreProperties, file), {
|
||||
declaration: {
|
||||
standalone: "yes",
|
||||
encoding: "UTF-8",
|
||||
@ -108,15 +112,15 @@ export class Compiler {
|
||||
path: "docProps/core.xml",
|
||||
},
|
||||
Numbering: {
|
||||
data: xml(this.formatter.format(file.Numbering), prettify),
|
||||
data: xml(this.formatter.format(file.Numbering, file), prettify),
|
||||
path: "word/numbering.xml",
|
||||
},
|
||||
FileRelationships: {
|
||||
data: xml(this.formatter.format(file.FileRelationships), prettify),
|
||||
data: xml(this.formatter.format(file.FileRelationships, file), prettify),
|
||||
path: "_rels/.rels",
|
||||
},
|
||||
HeaderRelationships: file.Headers.map((headerWrapper, index) => {
|
||||
const xmlData = xml(this.formatter.format(headerWrapper.Header), prettify);
|
||||
const xmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify);
|
||||
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
|
||||
|
||||
mediaDatas.forEach((mediaData, i) => {
|
||||
@ -128,12 +132,12 @@ export class Compiler {
|
||||
});
|
||||
|
||||
return {
|
||||
data: xml(this.formatter.format(headerWrapper.Relationships), prettify),
|
||||
data: xml(this.formatter.format(headerWrapper.Relationships, file), prettify),
|
||||
path: `word/_rels/header${index + 1}.xml.rels`,
|
||||
};
|
||||
}),
|
||||
FooterRelationships: file.Footers.map((footerWrapper, index) => {
|
||||
const xmlData = xml(this.formatter.format(footerWrapper.Footer), prettify);
|
||||
const xmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify);
|
||||
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
|
||||
|
||||
mediaDatas.forEach((mediaData, i) => {
|
||||
@ -145,12 +149,12 @@ export class Compiler {
|
||||
});
|
||||
|
||||
return {
|
||||
data: xml(this.formatter.format(footerWrapper.Relationships), prettify),
|
||||
data: xml(this.formatter.format(footerWrapper.Relationships, file), prettify),
|
||||
path: `word/_rels/footer${index + 1}.xml.rels`,
|
||||
};
|
||||
}),
|
||||
Headers: file.Headers.map((headerWrapper, index) => {
|
||||
const tempXmlData = xml(this.formatter.format(headerWrapper.Header), prettify);
|
||||
const tempXmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify);
|
||||
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
|
||||
// TODO: 0 needs to be changed when headers get relationships of their own
|
||||
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
|
||||
@ -161,7 +165,7 @@ export class Compiler {
|
||||
};
|
||||
}),
|
||||
Footers: file.Footers.map((footerWrapper, index) => {
|
||||
const tempXmlData = xml(this.formatter.format(footerWrapper.Footer), prettify);
|
||||
const tempXmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify);
|
||||
const mediaDatas = this.imageReplacer.getMediaData(tempXmlData, file.Media);
|
||||
// TODO: 0 needs to be changed when headers get relationships of their own
|
||||
const xmlData = this.imageReplacer.replace(tempXmlData, mediaDatas, 0);
|
||||
@ -172,19 +176,19 @@ export class Compiler {
|
||||
};
|
||||
}),
|
||||
ContentTypes: {
|
||||
data: xml(this.formatter.format(file.ContentTypes), prettify),
|
||||
data: xml(this.formatter.format(file.ContentTypes, file), prettify),
|
||||
path: "[Content_Types].xml",
|
||||
},
|
||||
AppProperties: {
|
||||
data: xml(this.formatter.format(file.AppProperties), prettify),
|
||||
data: xml(this.formatter.format(file.AppProperties, file), prettify),
|
||||
path: "docProps/app.xml",
|
||||
},
|
||||
FootNotes: {
|
||||
data: xml(this.formatter.format(file.FootNotes), prettify),
|
||||
data: xml(this.formatter.format(file.FootNotes, file), prettify),
|
||||
path: "word/footnotes.xml",
|
||||
},
|
||||
Settings: {
|
||||
data: xml(this.formatter.format(file.Settings), prettify),
|
||||
data: xml(this.formatter.format(file.Settings, file), prettify),
|
||||
path: "word/settings.xml",
|
||||
},
|
||||
};
|
||||
|
17
src/export/packer/numbering-replacer.ts
Normal file
17
src/export/packer/numbering-replacer.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { ConcreteNumbering } from "file";
|
||||
|
||||
export class NumberingReplacer {
|
||||
public replace(xmlData: string, concreteNumberings: ConcreteNumbering[]): string {
|
||||
let currentXmlData = xmlData;
|
||||
|
||||
for (const concreteNumbering of concreteNumberings) {
|
||||
if (!concreteNumbering.reference) {
|
||||
continue;
|
||||
}
|
||||
|
||||
currentXmlData = currentXmlData.replace(new RegExp(`{${concreteNumbering.reference}}`, "g"), concreteNumbering.id.toString());
|
||||
}
|
||||
|
||||
return currentXmlData;
|
||||
}
|
||||
}
|
@ -1,9 +1,22 @@
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { DocumentAttributes } from "../document/document-attributes";
|
||||
import { INumberingOptions } from "../numbering";
|
||||
import { HyperlinkType, Paragraph } from "../paragraph";
|
||||
import { IStylesOptions } from "../styles";
|
||||
import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components";
|
||||
|
||||
export interface IInternalHyperlinkDefinition {
|
||||
readonly text: string;
|
||||
readonly type: HyperlinkType.INTERNAL;
|
||||
}
|
||||
|
||||
export interface IExternalHyperlinkDefinition {
|
||||
readonly link: string;
|
||||
readonly text: string;
|
||||
readonly type: HyperlinkType.EXTERNAL;
|
||||
}
|
||||
|
||||
export interface IPropertiesOptions {
|
||||
readonly title?: string;
|
||||
readonly subject?: string;
|
||||
@ -14,6 +27,11 @@ export interface IPropertiesOptions {
|
||||
readonly revision?: string;
|
||||
readonly externalStyles?: string;
|
||||
readonly styles?: IStylesOptions;
|
||||
readonly numbering?: INumberingOptions;
|
||||
readonly footnotes?: Paragraph[];
|
||||
readonly hyperlinks?: {
|
||||
readonly [key: string]: IInternalHyperlinkDefinition | IExternalHyperlinkDefinition;
|
||||
};
|
||||
}
|
||||
|
||||
export class CoreProperties extends XmlComponent {
|
||||
|
@ -22,9 +22,6 @@ describe("Body", () => {
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:body": [
|
||||
{
|
||||
"w:p": {},
|
||||
},
|
||||
{
|
||||
"w:sectPr": [
|
||||
{ "w:pgSz": { _attr: { "w:w": 10000, "w:h": 10000, "w:orient": "portrait" } } },
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
import { Paragraph, ParagraphProperties, TableOfContents } from "../..";
|
||||
import { File } from "../../../file";
|
||||
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
|
||||
|
||||
export class Body extends XmlComponent {
|
||||
@ -24,12 +25,13 @@ export class Body extends XmlComponent {
|
||||
this.sections.push(new SectionProperties(options));
|
||||
}
|
||||
|
||||
public prepForXml(): IXmlableObject | undefined {
|
||||
public prepForXml(file?: File): IXmlableObject | undefined {
|
||||
if (this.sections.length === 1) {
|
||||
this.root.splice(0, 1);
|
||||
this.root.push(this.sections.pop() as SectionProperties);
|
||||
}
|
||||
|
||||
return super.prepForXml();
|
||||
return super.prepForXml(file);
|
||||
}
|
||||
|
||||
public push(component: XmlComponent): void {
|
||||
|
@ -5,3 +5,4 @@ export * from "./page-size";
|
||||
export * from "./page-number";
|
||||
export * from "./page-border";
|
||||
export * from "./line-number";
|
||||
export * from "./vertical-align";
|
||||
|
@ -8,6 +8,7 @@ import { Media } from "file/media";
|
||||
import { PageBorderOffsetFrom } from "./page-border";
|
||||
import { PageNumberFormat } from "./page-number";
|
||||
import { SectionProperties } from "./section-properties";
|
||||
import { SectionVerticalAlignValue } from "./vertical-align";
|
||||
|
||||
describe("SectionProperties", () => {
|
||||
describe("#constructor()", () => {
|
||||
@ -39,6 +40,7 @@ describe("SectionProperties", () => {
|
||||
pageNumberStart: 10,
|
||||
pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT,
|
||||
titlePage: true,
|
||||
verticalAlign: SectionVerticalAlignValue.TOP,
|
||||
});
|
||||
const tree = new Formatter().format(properties);
|
||||
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);
|
||||
|
@ -18,6 +18,7 @@ import { IPageNumberTypeAttributes, PageNumberType } from "./page-number";
|
||||
import { PageSize } from "./page-size/page-size";
|
||||
import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes";
|
||||
import { TitlePage } from "./title-page/title-page";
|
||||
import { ISectionVerticalAlignAttributes, SectionVerticalAlign } from "./vertical-align";
|
||||
|
||||
export interface IHeaderFooterGroup<T> {
|
||||
readonly default?: T;
|
||||
@ -45,7 +46,8 @@ export type SectionPropertiesOptions = IPageSizeAttributes &
|
||||
IPageNumberTypeAttributes &
|
||||
ILineNumberAttributes &
|
||||
IPageBordersOptions &
|
||||
ITitlePageOptions & {
|
||||
ITitlePageOptions &
|
||||
ISectionVerticalAlignAttributes & {
|
||||
readonly column?: {
|
||||
readonly space?: number;
|
||||
readonly count?: number;
|
||||
@ -87,6 +89,7 @@ export class SectionProperties extends XmlComponent {
|
||||
pageBorderBottom,
|
||||
pageBorderLeft,
|
||||
titlePage = false,
|
||||
verticalAlign,
|
||||
} = options;
|
||||
|
||||
this.options = options;
|
||||
@ -121,6 +124,10 @@ export class SectionProperties extends XmlComponent {
|
||||
if (titlePage) {
|
||||
this.root.push(new TitlePage());
|
||||
}
|
||||
|
||||
if (verticalAlign) {
|
||||
this.root.push(new SectionVerticalAlign(verticalAlign));
|
||||
}
|
||||
}
|
||||
|
||||
private addHeaders(headers?: IHeaderFooterGroup<HeaderWrapper>): void {
|
||||
|
@ -0,0 +1,2 @@
|
||||
export * from "./vertical-align";
|
||||
export * from "./vertical-align-attributes";
|
@ -0,0 +1,12 @@
|
||||
import { XmlAttributeComponent } from "file/xml-components";
|
||||
import { SectionVerticalAlignValue } from "./vertical-align";
|
||||
|
||||
export interface ISectionVerticalAlignAttributes {
|
||||
readonly verticalAlign?: SectionVerticalAlignValue;
|
||||
}
|
||||
|
||||
export class SectionVerticalAlignAttributes extends XmlAttributeComponent<ISectionVerticalAlignAttributes> {
|
||||
protected readonly xmlKeys = {
|
||||
verticalAlign: "w:val",
|
||||
};
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// http://officeopenxml.com/WPsection.php
|
||||
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
import { SectionVerticalAlignAttributes } from "./vertical-align-attributes";
|
||||
|
||||
export enum SectionVerticalAlignValue {
|
||||
BOTH = "both",
|
||||
BOTTOM = "bottom",
|
||||
CENTER = "center",
|
||||
TOP = "top",
|
||||
}
|
||||
|
||||
export class SectionVerticalAlign extends XmlComponent {
|
||||
constructor(value: SectionVerticalAlignValue) {
|
||||
super("w:vAlign");
|
||||
this.root.push(new SectionVerticalAlignAttributes({ verticalAlign: value }));
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// http://officeopenxml.com/WPdocument.php
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
import { Paragraph } from "../paragraph";
|
||||
import { Hyperlink, Paragraph } from "../paragraph";
|
||||
import { Table } from "../table";
|
||||
import { TableOfContents } from "../table-of-contents";
|
||||
import { Body } from "./body";
|
||||
@ -36,7 +36,7 @@ export class Document extends XmlComponent {
|
||||
this.root.push(this.body);
|
||||
}
|
||||
|
||||
public add(item: Paragraph | Table | TableOfContents): Document {
|
||||
public add(item: Paragraph | Table | TableOfContents | Hyperlink): Document {
|
||||
this.body.push(item);
|
||||
return this;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { Formatter } from "export/formatter";
|
||||
|
||||
import { File } from "./file";
|
||||
import { Footer, Header } from "./header";
|
||||
import { Paragraph } from "./paragraph";
|
||||
import { HyperlinkRef, Paragraph } from "./paragraph";
|
||||
import { Table, TableCell, TableRow } from "./table";
|
||||
import { TableOfContents } from "./table-of-contents";
|
||||
|
||||
@ -20,8 +20,8 @@ describe("File", () => {
|
||||
|
||||
const tree = new Formatter().format(doc.Document.Body);
|
||||
|
||||
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
|
||||
});
|
||||
|
||||
it("should create with correct headers and footers", () => {
|
||||
@ -39,8 +39,8 @@ describe("File", () => {
|
||||
|
||||
const tree = new Formatter().format(doc.Document.Body);
|
||||
|
||||
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
|
||||
});
|
||||
|
||||
it("should create with first headers and footers", () => {
|
||||
@ -58,8 +58,8 @@ describe("File", () => {
|
||||
|
||||
const tree = new Formatter().format(doc.Document.Body);
|
||||
|
||||
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
|
||||
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first");
|
||||
expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
|
||||
expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first");
|
||||
});
|
||||
|
||||
it("should create with correct headers", () => {
|
||||
@ -81,13 +81,98 @@ describe("File", () => {
|
||||
|
||||
const tree = new Formatter().format(doc.Document.Body);
|
||||
|
||||
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
|
||||
expect(tree["w:body"][1]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even");
|
||||
expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
|
||||
expect(tree["w:body"][0]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even");
|
||||
|
||||
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][1]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first");
|
||||
expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even");
|
||||
expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default");
|
||||
expect(tree["w:body"][0]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first");
|
||||
expect(tree["w:body"][0]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even");
|
||||
});
|
||||
|
||||
it("should add child", () => {
|
||||
const doc = new File(undefined, undefined, [
|
||||
{
|
||||
children: [new Paragraph("test")],
|
||||
},
|
||||
]);
|
||||
|
||||
const tree = new Formatter().format(doc.Document.Body);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:body": [
|
||||
{
|
||||
"w:p": [
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:t": [
|
||||
{
|
||||
_attr: {
|
||||
"xml:space": "preserve",
|
||||
},
|
||||
},
|
||||
"test",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:sectPr": [
|
||||
{
|
||||
"w:pgSz": {
|
||||
_attr: {
|
||||
"w:h": 16838,
|
||||
"w:orient": "portrait",
|
||||
"w:w": 11906,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:pgMar": {
|
||||
_attr: {
|
||||
"w:bottom": 1440,
|
||||
"w:footer": 708,
|
||||
"w:gutter": 0,
|
||||
"w:header": 708,
|
||||
"w:left": 1440,
|
||||
"w:mirrorMargins": false,
|
||||
"w:right": 1440,
|
||||
"w:top": 1440,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:cols": {
|
||||
_attr: {
|
||||
"w:num": 1,
|
||||
"w:space": 708,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:docGrid": {
|
||||
_attr: {
|
||||
"w:linePitch": 360,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should add hyperlink child", () => {
|
||||
const doc = new File(undefined, undefined, [
|
||||
{
|
||||
children: [new HyperlinkRef("test")],
|
||||
},
|
||||
]);
|
||||
|
||||
expect(doc.HyperlinkCache).to.deep.equal({});
|
||||
});
|
||||
});
|
||||
|
||||
@ -102,6 +187,16 @@ describe("File", () => {
|
||||
expect(spy.called).to.equal(true);
|
||||
});
|
||||
|
||||
it("should add hyperlink child", () => {
|
||||
const doc = new File();
|
||||
|
||||
doc.addSection({
|
||||
children: [new HyperlinkRef("test")],
|
||||
});
|
||||
|
||||
expect(doc.HyperlinkCache).to.deep.equal({});
|
||||
});
|
||||
|
||||
it("should call the underlying document's add when adding a Table", () => {
|
||||
const file = new File();
|
||||
const spy = sinon.spy(file.Document, "add");
|
||||
@ -148,13 +243,196 @@ describe("File", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createFootnote", () => {
|
||||
it("should call the underlying document's createFootnote", () => {
|
||||
const wrapper = new File();
|
||||
const spy = sinon.spy(wrapper.FootNotes, "createFootNote");
|
||||
wrapper.createFootnote(new Paragraph(""));
|
||||
describe("#HyperlinkCache", () => {
|
||||
it("should initially have empty hyperlink cache", () => {
|
||||
const file = new File();
|
||||
|
||||
expect(spy.called).to.equal(true);
|
||||
expect(file.HyperlinkCache).to.deep.equal({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createFootnote", () => {
|
||||
it("should create footnote", () => {
|
||||
const wrapper = new File({
|
||||
footnotes: [new Paragraph("hello")],
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(wrapper.FootNotes);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:footnotes": [
|
||||
{
|
||||
_attr: {
|
||||
"mc:Ignorable": "w14 w15 wp14",
|
||||
"xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
|
||||
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
||||
"xmlns:o": "urn:schemas-microsoft-com:office:office",
|
||||
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
||||
"xmlns:v": "urn:schemas-microsoft-com:vml",
|
||||
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
||||
"xmlns:w10": "urn:schemas-microsoft-com:office:word",
|
||||
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
|
||||
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
|
||||
"xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml",
|
||||
"xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
||||
"xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
|
||||
"xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
|
||||
"xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
|
||||
"xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
|
||||
"xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:footnote": [
|
||||
{
|
||||
_attr: {
|
||||
"w:id": -1,
|
||||
"w:type": "separator",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:spacing": {
|
||||
_attr: {
|
||||
"w:after": 0,
|
||||
"w:line": 240,
|
||||
"w:lineRule": "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rStyle": {
|
||||
_attr: {
|
||||
"w:val": "FootnoteReference",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:footnoteRef": {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:separator": {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:footnote": [
|
||||
{
|
||||
_attr: {
|
||||
"w:id": 0,
|
||||
"w:type": "continuationSeparator",
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:p": [
|
||||
{
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:spacing": {
|
||||
_attr: {
|
||||
"w:after": 0,
|
||||
"w:line": 240,
|
||||
"w:lineRule": "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rStyle": {
|
||||
_attr: {
|
||||
"w:val": "FootnoteReference",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:footnoteRef": {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:continuationSeparator": {},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:footnote": [
|
||||
{
|
||||
_attr: {
|
||||
"w:id": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:p": [
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rStyle": {
|
||||
_attr: {
|
||||
"w:val": "FootnoteReference",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:footnoteRef": {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:t": [
|
||||
{
|
||||
_attr: {
|
||||
"xml:space": "preserve",
|
||||
},
|
||||
},
|
||||
"hello",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as shortid from "shortid";
|
||||
import { AppProperties } from "./app-properties/app-properties";
|
||||
import { ContentTypes } from "./content-types/content-types";
|
||||
import { CoreProperties, IPropertiesOptions } from "./core-properties";
|
||||
@ -16,7 +17,7 @@ import { Footer, Header } from "./header";
|
||||
import { HeaderWrapper, IDocumentHeader } from "./header-wrapper";
|
||||
import { Media } from "./media";
|
||||
import { Numbering } from "./numbering";
|
||||
import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
|
||||
import { Hyperlink, HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph";
|
||||
import { Relationships } from "./relationships";
|
||||
import { TargetModeType } from "./relationships/relationship/relationship";
|
||||
import { Settings } from "./settings";
|
||||
@ -40,7 +41,7 @@ export interface ISectionOptions {
|
||||
readonly size?: IPageSizeAttributes;
|
||||
readonly margins?: IPageMarginAttributes;
|
||||
readonly properties?: SectionPropertiesOptions;
|
||||
readonly children: Array<Paragraph | Table | TableOfContents>;
|
||||
readonly children: Array<Paragraph | Table | TableOfContents | HyperlinkRef>;
|
||||
}
|
||||
|
||||
export class File {
|
||||
@ -60,6 +61,7 @@ export class File {
|
||||
private readonly contentTypes: ContentTypes;
|
||||
private readonly appProperties: AppProperties;
|
||||
private readonly styles: Styles;
|
||||
private readonly hyperlinkCache: { readonly [key: string]: Hyperlink } = {};
|
||||
|
||||
constructor(
|
||||
options: IPropertiesOptions = {
|
||||
@ -71,7 +73,13 @@ export class File {
|
||||
sections: ISectionOptions[] = [],
|
||||
) {
|
||||
this.coreProperties = new CoreProperties(options);
|
||||
this.numbering = new Numbering();
|
||||
this.numbering = new Numbering(
|
||||
options.numbering
|
||||
? options.numbering
|
||||
: {
|
||||
config: [],
|
||||
},
|
||||
);
|
||||
this.docRelationships = new Relationships();
|
||||
this.fileRelationships = new Relationships();
|
||||
this.appProperties = new AppProperties();
|
||||
@ -126,33 +134,42 @@ export class File {
|
||||
this.document.Body.addSection(section.properties ? section.properties : {});
|
||||
|
||||
for (const child of section.children) {
|
||||
if (child instanceof HyperlinkRef) {
|
||||
const hyperlink = this.hyperlinkCache[child.id];
|
||||
this.document.add(hyperlink);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.document.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.footnotes) {
|
||||
for (const paragraph of options.footnotes) {
|
||||
this.footNotes.createFootNote(paragraph);
|
||||
}
|
||||
}
|
||||
|
||||
public createHyperlink(link: string, text?: string): Hyperlink {
|
||||
const newText = text === undefined ? link : text;
|
||||
const hyperlink = new Hyperlink(newText, this.docRelationships.RelationshipCount);
|
||||
this.docRelationships.createRelationship(
|
||||
hyperlink.linkId,
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||
link,
|
||||
TargetModeType.EXTERNAL,
|
||||
);
|
||||
return hyperlink;
|
||||
if (options.hyperlinks) {
|
||||
const cache = {};
|
||||
|
||||
for (const key in options.hyperlinks) {
|
||||
if (!options.hyperlinks[key]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
public createInternalHyperLink(anchor: string, text?: string): Hyperlink {
|
||||
const newText = text === undefined ? anchor : text;
|
||||
const hyperlink = new Hyperlink(newText, this.docRelationships.RelationshipCount, anchor);
|
||||
// NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark
|
||||
// we don't need to create a new relationship.
|
||||
return hyperlink;
|
||||
const hyperlinkRef = options.hyperlinks[key];
|
||||
|
||||
const hyperlink =
|
||||
hyperlinkRef.type === HyperlinkType.EXTERNAL
|
||||
? this.createHyperlink(hyperlinkRef.link, hyperlinkRef.text)
|
||||
: this.createInternalHyperLink(key, hyperlinkRef.text);
|
||||
|
||||
cache[key] = hyperlink;
|
||||
}
|
||||
|
||||
public createBookmark(name: string, text: string = name): Bookmark {
|
||||
return new Bookmark(name, text, this.docRelationships.RelationshipCount);
|
||||
this.hyperlinkCache = cache;
|
||||
}
|
||||
}
|
||||
|
||||
public addSection({
|
||||
@ -180,12 +197,14 @@ export class File {
|
||||
});
|
||||
|
||||
for (const child of children) {
|
||||
this.document.add(child);
|
||||
}
|
||||
if (child instanceof HyperlinkRef) {
|
||||
const hyperlink = this.hyperlinkCache[child.id];
|
||||
this.document.add(hyperlink);
|
||||
continue;
|
||||
}
|
||||
|
||||
public createFootnote(paragraph: Paragraph): void {
|
||||
this.footNotes.createFootNote(paragraph);
|
||||
this.document.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
public verifyUpdateFields(): void {
|
||||
@ -194,6 +213,24 @@ export class File {
|
||||
}
|
||||
}
|
||||
|
||||
private createHyperlink(link: string, text: string = link): Hyperlink {
|
||||
const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase());
|
||||
this.docRelationships.createRelationship(
|
||||
hyperlink.linkId,
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||
link,
|
||||
TargetModeType.EXTERNAL,
|
||||
);
|
||||
return hyperlink;
|
||||
}
|
||||
|
||||
private createInternalHyperLink(anchor: string, text: string = anchor): Hyperlink {
|
||||
const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase(), anchor);
|
||||
// NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark
|
||||
// we don't need to create a new relationship.
|
||||
return hyperlink;
|
||||
}
|
||||
|
||||
private createHeader(header: Header): HeaderWrapper {
|
||||
const wrapper = new HeaderWrapper(this.media, this.currentRelationshipId++);
|
||||
|
||||
@ -326,4 +363,8 @@ export class File {
|
||||
public get Settings(): Settings {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
public get HyperlinkCache(): { readonly [key: string]: Hyperlink } {
|
||||
return this.hyperlinkCache;
|
||||
}
|
||||
}
|
||||
|
1
src/file/footnotes/footnote/index.ts
Normal file
1
src/file/footnotes/footnote/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./run";
|
1
src/file/footnotes/footnote/run/index.ts
Normal file
1
src/file/footnotes/footnote/run/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./reference-run";
|
@ -1 +1,2 @@
|
||||
export * from "./footnotes";
|
||||
export * from "./footnote";
|
||||
|
@ -12,3 +12,4 @@ export * from "./xml-components";
|
||||
export * from "./header-wrapper";
|
||||
export * from "./footer-wrapper";
|
||||
export * from "./header";
|
||||
export * from "./footnotes";
|
||||
|
687
src/file/numbering/abstract-numbering.spec.ts
Normal file
687
src/file/numbering/abstract-numbering.spec.ts
Normal file
@ -0,0 +1,687 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
import { EMPTY_OBJECT } from "file/xml-components";
|
||||
|
||||
import { AlignmentType, EmphasisMarkType, TabStopPosition } from "../paragraph";
|
||||
import { UnderlineType } from "../paragraph/run/underline";
|
||||
import { ShadingType } from "../table";
|
||||
import { AbstractNumbering } from "./abstract-numbering";
|
||||
|
||||
describe("AbstractNumbering", () => {
|
||||
it("stores its ID at its .id property", () => {
|
||||
const abstractNumbering = new AbstractNumbering(5, []);
|
||||
expect(abstractNumbering.id).to.equal(5);
|
||||
});
|
||||
|
||||
describe("#createLevel", () => {
|
||||
it("creates a level with the given characteristics", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 3,
|
||||
format: "lowerLetter",
|
||||
text: "%1)",
|
||||
alignment: AlignmentType.END,
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
|
||||
});
|
||||
|
||||
it("uses 'start' as the default alignment", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 3,
|
||||
format: "lowerLetter",
|
||||
text: "%1)",
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
|
||||
});
|
||||
|
||||
describe("formatting methods: paragraph properties", () => {
|
||||
it("#indent", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 720 },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#spacing", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
spacing: { before: 50, after: 150 },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#center", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
alignment: AlignmentType.CENTER,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#left", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
alignment: AlignmentType.LEFT,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#right", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
alignment: AlignmentType.RIGHT,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#justified", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
alignment: AlignmentType.JUSTIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#thematicBreak", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
thematicBreak: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:pBdr": [
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 1,
|
||||
"w:val": "single",
|
||||
"w:sz": 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#leftTabStop", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
leftTabStop: 1200,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#maxRightTabStop", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
rightTabStop: TabStopPosition.MAX,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepLines", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
keepLines: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:keepLines": EMPTY_OBJECT }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepNext", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
paragraph: {
|
||||
keepNext: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:keepNext": EMPTY_OBJECT }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatting methods: run properties", () => {
|
||||
it("#size", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
size: 24,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#smallCaps", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
smallCaps: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#allCaps", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
allCaps: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#strike", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
strike: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#doubleStrike", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
doubleStrike: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#subScript", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
subScript: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#superScript", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
superScript: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#font by name", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
font: "Times",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rFonts": {
|
||||
_attr: {
|
||||
"w:ascii": "Times",
|
||||
"w:cs": "Times",
|
||||
"w:eastAsia": "Times",
|
||||
"w:hAnsi": "Times",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#font for ascii and eastAsia", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
font: {
|
||||
ascii: "Times",
|
||||
eastAsia: "KaiTi",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rFonts": {
|
||||
_attr: {
|
||||
"w:ascii": "Times",
|
||||
"w:eastAsia": "KaiTi",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#bold", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
bold: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:b": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#italics", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
italics: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:i": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#highlight", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
highlight: "005599",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#shadow", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
shadow: {
|
||||
type: ShadingType.PERCENT_10,
|
||||
fill: "00FFFF",
|
||||
color: "FF0000",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
describe("#underline", () => {
|
||||
it("should set underline to 'single' if no arguments are given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
underline: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style if given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
underline: {
|
||||
type: UnderlineType.DOUBLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style and color if given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
underline: {
|
||||
type: UnderlineType.DOUBLE,
|
||||
color: "005599",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#emphasisMark", () => {
|
||||
it("should set emphasisMark to 'dot' if no arguments are given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
emphasisMark: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style if given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
emphasisMark: {
|
||||
type: EmphasisMarkType.DOT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("#color", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1, [
|
||||
{
|
||||
level: 0,
|
||||
format: "lowerRoman",
|
||||
text: "%0.",
|
||||
style: {
|
||||
run: {
|
||||
color: "123456",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = new Formatter().format(abstractNumbering);
|
||||
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
import { Level } from "./level";
|
||||
|
||||
import { ILevelsOptions, Level } from "./level";
|
||||
import { MultiLevelType } from "./multi-level-type";
|
||||
|
||||
interface IAbstractNumberingAttributesProperties {
|
||||
@ -17,7 +18,7 @@ class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberi
|
||||
export class AbstractNumbering extends XmlComponent {
|
||||
public readonly id: number;
|
||||
|
||||
constructor(id: number) {
|
||||
constructor(id: number, levelOptions: ILevelsOptions[]) {
|
||||
super("w:abstractNum");
|
||||
this.root.push(
|
||||
new AbstractNumberingAttributes({
|
||||
@ -27,15 +28,9 @@ export class AbstractNumbering extends XmlComponent {
|
||||
);
|
||||
this.root.push(new MultiLevelType("hybridMultilevel"));
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public addLevel(level: Level): void {
|
||||
this.root.push(level);
|
||||
}
|
||||
|
||||
public createLevel(num: number, format: string, text: string, align: string = "start"): Level {
|
||||
const level = new Level(num, format, text, align);
|
||||
this.addLevel(level);
|
||||
return level;
|
||||
for (const option of levelOptions) {
|
||||
this.root.push(new Level(option));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
79
src/file/numbering/concrete-numbering.spec.ts
Normal file
79
src/file/numbering/concrete-numbering.spec.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { LevelForOverride } from "./level";
|
||||
import { ConcreteNumbering } from "./num";
|
||||
|
||||
describe("ConcreteNumbering", () => {
|
||||
describe("#overrideLevel", () => {
|
||||
let concreteNumbering: ConcreteNumbering;
|
||||
beforeEach(() => {
|
||||
concreteNumbering = new ConcreteNumbering(0, 1);
|
||||
});
|
||||
|
||||
it("sets a new override level for the given level number", () => {
|
||||
concreteNumbering.overrideLevel(3);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{ _attr: { "w:ilvl": 3 } },
|
||||
{
|
||||
"w:lvl": [
|
||||
{ _attr: { "w:ilvl": 3, "w15:tentative": 1 } },
|
||||
{ "w:start": { _attr: { "w:val": 1 } } },
|
||||
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the startOverride element if start is given", () => {
|
||||
concreteNumbering.overrideLevel(1, 9);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{
|
||||
_attr: {
|
||||
"w:ilvl": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:startOverride": {
|
||||
_attr: {
|
||||
"w:val": 9,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:lvl": [
|
||||
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
|
||||
{ "w:start": { _attr: { "w:val": 1 } } },
|
||||
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the lvl element if overrideLevel.Level is accessed", () => {
|
||||
const ol = concreteNumbering.overrideLevel(1);
|
||||
expect(ol.Level).to.be.instanceof(LevelForOverride);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{ _attr: { "w:ilvl": 1 } },
|
||||
{
|
||||
"w:lvl": [
|
||||
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
|
||||
{ "w:start": { _attr: { "w:val": 1 } } },
|
||||
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -2,9 +2,7 @@ import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-compon
|
||||
import {
|
||||
Alignment,
|
||||
AlignmentType,
|
||||
IIndentAttributesProperties,
|
||||
Indent,
|
||||
ISpacingProperties,
|
||||
KeepLines,
|
||||
KeepNext,
|
||||
Spacing,
|
||||
@ -15,7 +13,7 @@ import {
|
||||
import { ParagraphProperties } from "../paragraph/properties";
|
||||
import * as formatting from "../paragraph/run/formatting";
|
||||
import { RunProperties } from "../paragraph/run/properties";
|
||||
import { UnderlineType } from "../paragraph/run/underline";
|
||||
import { IParagraphStyleOptions2, IRunStyleOptions } from "../styles/style-options";
|
||||
|
||||
interface ILevelAttributesProperties {
|
||||
readonly ilvl?: number;
|
||||
@ -63,7 +61,7 @@ class LevelText extends XmlComponent {
|
||||
}
|
||||
|
||||
class LevelJc extends XmlComponent {
|
||||
constructor(value: string) {
|
||||
constructor(value: AlignmentType) {
|
||||
super("w:lvlJc");
|
||||
this.root.push(
|
||||
new Attributes({
|
||||
@ -79,6 +77,19 @@ export enum LevelSuffix {
|
||||
TAB = "tab",
|
||||
}
|
||||
|
||||
export interface ILevelsOptions {
|
||||
readonly level: number;
|
||||
readonly format?: string;
|
||||
readonly text?: string;
|
||||
readonly alignment?: AlignmentType;
|
||||
readonly start?: number;
|
||||
readonly suffix?: LevelSuffix;
|
||||
readonly style?: {
|
||||
readonly run?: IRunStyleOptions;
|
||||
readonly paragraph?: IParagraphStyleOptions2;
|
||||
};
|
||||
}
|
||||
|
||||
class Suffix extends XmlComponent {
|
||||
constructor(value: LevelSuffix) {
|
||||
super("w:suff");
|
||||
@ -94,7 +105,7 @@ export class LevelBase extends XmlComponent {
|
||||
private readonly paragraphProperties: ParagraphProperties;
|
||||
private readonly runProperties: RunProperties;
|
||||
|
||||
constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) {
|
||||
constructor({ level, format, text, alignment = AlignmentType.START, start = 1, style, suffix }: ILevelsOptions) {
|
||||
super("w:lvl");
|
||||
this.root.push(
|
||||
new LevelAttributes({
|
||||
@ -103,17 +114,15 @@ export class LevelBase extends XmlComponent {
|
||||
}),
|
||||
);
|
||||
|
||||
if (start !== undefined) {
|
||||
this.root.push(new Start(start));
|
||||
this.root.push(new LevelJc(alignment));
|
||||
|
||||
if (format) {
|
||||
this.root.push(new NumberFormat(format));
|
||||
}
|
||||
if (numberFormat !== undefined) {
|
||||
this.root.push(new NumberFormat(numberFormat));
|
||||
}
|
||||
if (levelText !== undefined) {
|
||||
this.root.push(new LevelText(levelText));
|
||||
}
|
||||
if (lvlJc !== undefined) {
|
||||
this.root.push(new LevelJc(lvlJc));
|
||||
|
||||
if (text) {
|
||||
this.root.push(new LevelText(text));
|
||||
}
|
||||
|
||||
this.paragraphProperties = new ParagraphProperties({});
|
||||
@ -121,156 +130,116 @@ export class LevelBase extends XmlComponent {
|
||||
|
||||
this.root.push(this.paragraphProperties);
|
||||
this.root.push(this.runProperties);
|
||||
|
||||
if (suffix) {
|
||||
this.root.push(new Suffix(suffix));
|
||||
}
|
||||
|
||||
public setSuffix(value: LevelSuffix): LevelBase {
|
||||
this.root.push(new Suffix(value));
|
||||
return this;
|
||||
if (style) {
|
||||
if (style.run) {
|
||||
if (style.run.size) {
|
||||
this.runProperties.push(new formatting.Size(style.run.size));
|
||||
}
|
||||
|
||||
public addParagraphProperty(property: XmlComponent): Level {
|
||||
this.paragraphProperties.push(property);
|
||||
return this;
|
||||
if (style.run.bold) {
|
||||
this.runProperties.push(new formatting.Bold());
|
||||
}
|
||||
|
||||
public addRunProperty(property: XmlComponent): Level {
|
||||
this.runProperties.push(property);
|
||||
return this;
|
||||
if (style.run.italics) {
|
||||
this.runProperties.push(new formatting.Italics());
|
||||
}
|
||||
|
||||
// ---------- Run formatting ---------------------- //
|
||||
|
||||
public size(twips: number): Level {
|
||||
this.addRunProperty(new formatting.Size(twips));
|
||||
return this;
|
||||
if (style.run.smallCaps) {
|
||||
this.runProperties.push(new formatting.SmallCaps());
|
||||
}
|
||||
|
||||
public bold(): Level {
|
||||
this.addRunProperty(new formatting.Bold());
|
||||
return this;
|
||||
if (style.run.allCaps) {
|
||||
this.runProperties.push(new formatting.Caps());
|
||||
}
|
||||
|
||||
public italics(): Level {
|
||||
this.addRunProperty(new formatting.Italics());
|
||||
return this;
|
||||
if (style.run.strike) {
|
||||
this.runProperties.push(new formatting.Strike());
|
||||
}
|
||||
|
||||
public smallCaps(): Level {
|
||||
this.addRunProperty(new formatting.SmallCaps());
|
||||
return this;
|
||||
if (style.run.doubleStrike) {
|
||||
this.runProperties.push(new formatting.DoubleStrike());
|
||||
}
|
||||
|
||||
public allCaps(): Level {
|
||||
this.addRunProperty(new formatting.Caps());
|
||||
return this;
|
||||
if (style.run.subScript) {
|
||||
this.runProperties.push(new formatting.SubScript());
|
||||
}
|
||||
|
||||
public strike(): Level {
|
||||
this.addRunProperty(new formatting.Strike());
|
||||
return this;
|
||||
if (style.run.superScript) {
|
||||
this.runProperties.push(new formatting.SuperScript());
|
||||
}
|
||||
|
||||
public doubleStrike(): Level {
|
||||
this.addRunProperty(new formatting.DoubleStrike());
|
||||
return this;
|
||||
if (style.run.underline) {
|
||||
this.runProperties.push(new formatting.Underline(style.run.underline.type, style.run.underline.color));
|
||||
}
|
||||
|
||||
public subScript(): Level {
|
||||
this.addRunProperty(new formatting.SubScript());
|
||||
return this;
|
||||
if (style.run.emphasisMark) {
|
||||
this.runProperties.push(new formatting.EmphasisMark(style.run.emphasisMark.type));
|
||||
}
|
||||
|
||||
public superScript(): Level {
|
||||
this.addRunProperty(new formatting.SuperScript());
|
||||
return this;
|
||||
if (style.run.color) {
|
||||
this.runProperties.push(new formatting.Color(style.run.color));
|
||||
}
|
||||
|
||||
public underline(underlineType?: UnderlineType, color?: string): Level {
|
||||
this.addRunProperty(new formatting.Underline(underlineType, color));
|
||||
return this;
|
||||
if (style.run.font) {
|
||||
this.runProperties.push(new formatting.RunFonts(style.run.font));
|
||||
}
|
||||
|
||||
public color(color: string): Level {
|
||||
this.addRunProperty(new formatting.Color(color));
|
||||
return this;
|
||||
if (style.run.highlight) {
|
||||
this.runProperties.push(new formatting.Highlight(style.run.highlight));
|
||||
}
|
||||
|
||||
public font(fontName: string): Level {
|
||||
this.addRunProperty(new formatting.RunFonts(fontName));
|
||||
return this;
|
||||
if (style.run.shadow) {
|
||||
this.runProperties.push(new formatting.Shading(style.run.shadow.type, style.run.shadow.fill, style.run.shadow.color));
|
||||
}
|
||||
}
|
||||
|
||||
public highlight(color: string): Level {
|
||||
this.addRunProperty(new formatting.Highlight(color));
|
||||
return this;
|
||||
if (style.paragraph) {
|
||||
if (style.paragraph.alignment) {
|
||||
this.paragraphProperties.push(new Alignment(style.paragraph.alignment));
|
||||
}
|
||||
|
||||
public shadow(value: string, fill: string, color: string): Level {
|
||||
this.addRunProperty(new formatting.Shading(value, fill, color));
|
||||
return this;
|
||||
}
|
||||
// --------------------- Paragraph formatting ------------------------ //
|
||||
|
||||
public center(): Level {
|
||||
this.addParagraphProperty(new Alignment(AlignmentType.CENTER));
|
||||
return this;
|
||||
if (style.paragraph.thematicBreak) {
|
||||
this.paragraphProperties.push(new ThematicBreak());
|
||||
}
|
||||
|
||||
public left(): Level {
|
||||
this.addParagraphProperty(new Alignment(AlignmentType.LEFT));
|
||||
return this;
|
||||
if (style.paragraph.rightTabStop) {
|
||||
this.paragraphProperties.push(new TabStop(TabStopType.RIGHT, style.paragraph.rightTabStop));
|
||||
}
|
||||
|
||||
public right(): Level {
|
||||
this.addParagraphProperty(new Alignment(AlignmentType.RIGHT));
|
||||
return this;
|
||||
if (style.paragraph.leftTabStop) {
|
||||
this.paragraphProperties.push(new TabStop(TabStopType.LEFT, style.paragraph.leftTabStop));
|
||||
}
|
||||
|
||||
public justified(): Level {
|
||||
this.addParagraphProperty(new Alignment(AlignmentType.BOTH));
|
||||
return this;
|
||||
if (style.paragraph.indent) {
|
||||
this.paragraphProperties.push(new Indent(style.paragraph.indent));
|
||||
}
|
||||
|
||||
public thematicBreak(): Level {
|
||||
this.addParagraphProperty(new ThematicBreak());
|
||||
return this;
|
||||
if (style.paragraph.spacing) {
|
||||
this.paragraphProperties.push(new Spacing(style.paragraph.spacing));
|
||||
}
|
||||
|
||||
public rightTabStop(position: number): Level {
|
||||
return this.addParagraphProperty(new TabStop(TabStopType.RIGHT, position));
|
||||
if (style.paragraph.keepNext) {
|
||||
this.paragraphProperties.push(new KeepNext());
|
||||
}
|
||||
|
||||
public leftTabStop(position: number): Level {
|
||||
this.addParagraphProperty(new TabStop(TabStopType.LEFT, position));
|
||||
return this;
|
||||
if (style.paragraph.keepLines) {
|
||||
this.paragraphProperties.push(new KeepLines());
|
||||
}
|
||||
|
||||
public indent(attrs: IIndentAttributesProperties): Level {
|
||||
this.addParagraphProperty(new Indent(attrs));
|
||||
return this;
|
||||
}
|
||||
|
||||
public spacing(params: ISpacingProperties): Level {
|
||||
this.addParagraphProperty(new Spacing(params));
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepNext(): Level {
|
||||
this.addParagraphProperty(new KeepNext());
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepLines(): Level {
|
||||
this.addParagraphProperty(new KeepLines());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class Level extends LevelBase {
|
||||
// This is the level that sits under abstractNum. We make a
|
||||
// handful of properties required
|
||||
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) {
|
||||
super(level, 1, numberFormat, levelText, lvlJc);
|
||||
constructor(options: ILevelsOptions) {
|
||||
super(options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,10 @@ class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
|
||||
protected readonly xmlKeys = { numId: "w:numId" };
|
||||
}
|
||||
|
||||
export class Num extends XmlComponent {
|
||||
export class ConcreteNumbering extends XmlComponent {
|
||||
public readonly id: number;
|
||||
|
||||
constructor(numId: number, abstractNumId: number) {
|
||||
constructor(numId: number, abstractNumId: number, public readonly reference?: string) {
|
||||
super("w:num");
|
||||
this.root.push(
|
||||
new NumAttributes({
|
||||
@ -55,7 +55,9 @@ export class LevelOverride extends XmlComponent {
|
||||
this.root.push(new StartOverride(start));
|
||||
}
|
||||
|
||||
this.lvl = new LevelForOverride(this.levelNum);
|
||||
this.lvl = new LevelForOverride({
|
||||
level: this.levelNum,
|
||||
});
|
||||
this.root.push(this.lvl);
|
||||
}
|
||||
|
||||
|
@ -2,24 +2,15 @@ import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { AbstractNumbering } from "./abstract-numbering";
|
||||
import { LevelForOverride } from "./level";
|
||||
import { Num } from "./num";
|
||||
import { Numbering } from "./numbering";
|
||||
|
||||
import { EMPTY_OBJECT } from "file/xml-components";
|
||||
import { TabStopPosition } from "../paragraph";
|
||||
import { UnderlineType } from "../paragraph/run/underline";
|
||||
|
||||
describe("Numbering", () => {
|
||||
let numbering: Numbering;
|
||||
|
||||
beforeEach(() => {
|
||||
numbering = new Numbering();
|
||||
});
|
||||
|
||||
describe("#constructor", () => {
|
||||
it("creates a default numbering with one abstract and one concrete instance", () => {
|
||||
const numbering = new Numbering({
|
||||
config: [],
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(numbering);
|
||||
expect(Object.keys(tree)).to.deep.equal(["w:numbering"]);
|
||||
const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]);
|
||||
@ -48,418 +39,4 @@ describe("Numbering", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createAbstractNumbering", () => {
|
||||
it("returns a new AbstractNumbering instance", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
expect(a2).to.be.instanceof(AbstractNumbering);
|
||||
});
|
||||
|
||||
it("assigns a unique ID to each abstract numbering it creates", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
const a3 = numbering.createAbstractNumbering();
|
||||
expect(a2.id).not.to.equal(a3.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createConcreteNumbering", () => {
|
||||
it("returns a new Num instance with its abstract ID set to the AbstractNumbering's ID", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
const n = numbering.createConcreteNumbering(a2);
|
||||
expect(n).to.be.instanceof(Num);
|
||||
const tree = new Formatter().format(numbering);
|
||||
const serializedN = tree["w:numbering"].find((obj) => obj["w:num"] && obj["w:num"][0]._attr["w:numId"] === n.id);
|
||||
expect(serializedN["w:num"][1]["w:abstractNumId"]._attr["w:val"]).to.equal(a2.id);
|
||||
});
|
||||
|
||||
it("assigns a unique ID to each concrete numbering it creates", () => {
|
||||
const a2 = numbering.createAbstractNumbering();
|
||||
const n = numbering.createConcreteNumbering(a2);
|
||||
const n2 = numbering.createConcreteNumbering(a2);
|
||||
expect(n.id).not.to.equal(n2.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("AbstractNumbering", () => {
|
||||
it("stores its ID at its .id property", () => {
|
||||
const abstractNumbering = new AbstractNumbering(5);
|
||||
expect(abstractNumbering.id).to.equal(5);
|
||||
});
|
||||
|
||||
describe("#createLevel", () => {
|
||||
it("creates a level with the given characteristics", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)", "end");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
|
||||
});
|
||||
|
||||
it("uses 'start' as the default alignment", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
|
||||
expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
|
||||
});
|
||||
|
||||
describe("formatting methods: paragraph properties", () => {
|
||||
it("#indent", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").indent({ left: 720 });
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#spacing", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").spacing({ before: 50, after: 150 });
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#center", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").center();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#left", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.", "left").left();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#right", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").right();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#justified", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").justified();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#thematicBreak", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").thematicBreak();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:pBdr": [
|
||||
{
|
||||
"w:bottom": {
|
||||
_attr: {
|
||||
"w:color": "auto",
|
||||
"w:space": 1,
|
||||
"w:val": "single",
|
||||
"w:sz": 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#leftTabStop", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").leftTabStop(1200);
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#maxRightTabStop", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").rightTabStop(TabStopPosition.MAX);
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepLines", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepLines();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:keepLines": EMPTY_OBJECT }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepNext", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepNext();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{ "w:keepNext": EMPTY_OBJECT }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatting methods: run properties", () => {
|
||||
it("#size", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").size(24);
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#smallCaps", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").smallCaps();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#allCaps", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").allCaps();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#strike", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").strike();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#doubleStrike", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").doubleStrike();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#subScript", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").subScript();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#superScript", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").superScript();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#font", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").font("Times");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [
|
||||
{ "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#bold", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").bold();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:b": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#italics", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").italics();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:i": { _attr: { "w:val": true } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#highlight", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").highlight("005599");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("#shadow", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").shadow("pct10", "00FFFF", "FF0000");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
describe("#underline", () => {
|
||||
it("should set underline to 'single' if no arguments are given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style if given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE);
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style and color if given", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE, "005599");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("#color", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").color("123456");
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("concrete numbering", () => {
|
||||
describe("#overrideLevel", () => {
|
||||
let numbering;
|
||||
let abstractNumbering;
|
||||
let concreteNumbering;
|
||||
beforeEach(() => {
|
||||
numbering = new Numbering();
|
||||
abstractNumbering = numbering.createAbstractNumbering();
|
||||
concreteNumbering = numbering.createConcreteNumbering(abstractNumbering);
|
||||
});
|
||||
|
||||
it("sets a new override level for the given level number", () => {
|
||||
concreteNumbering.overrideLevel(3);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{
|
||||
_attr: {
|
||||
"w:ilvl": 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:lvl": {
|
||||
_attr: {
|
||||
"w:ilvl": 3,
|
||||
"w15:tentative": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the startOverride element if start is given", () => {
|
||||
concreteNumbering.overrideLevel(1, 9);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{
|
||||
_attr: {
|
||||
"w:ilvl": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:startOverride": {
|
||||
_attr: {
|
||||
"w:val": 9,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:lvl": {
|
||||
_attr: {
|
||||
"w:ilvl": 1,
|
||||
"w15:tentative": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the lvl element if overrideLevel.Level is accessed", () => {
|
||||
const ol = concreteNumbering.overrideLevel(1);
|
||||
expect(ol.Level).to.be.instanceof(LevelForOverride);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{ _attr: { "w:ilvl": 1 } },
|
||||
{
|
||||
"w:lvl": { _attr: { "w15:tentative": 1, "w:ilvl": 1 } },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,17 +1,27 @@
|
||||
import { Indent } from "file/paragraph";
|
||||
// http://officeopenxml.com/WPnumbering.php
|
||||
import { AlignmentType } from "file/paragraph";
|
||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { DocumentAttributes } from "../document/document-attributes";
|
||||
import { AbstractNumbering } from "./abstract-numbering";
|
||||
import { Num } from "./num";
|
||||
import { ILevelsOptions } from "./level";
|
||||
import { ConcreteNumbering } from "./num";
|
||||
|
||||
export interface INumberingOptions {
|
||||
readonly config: Array<{
|
||||
readonly levels: ILevelsOptions[];
|
||||
readonly reference: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class Numbering extends XmlComponent {
|
||||
// tslint:disable-next-line:readonly-keyword
|
||||
private nextId: number;
|
||||
|
||||
private readonly abstractNumbering: XmlComponent[] = [];
|
||||
private readonly concreteNumbering: XmlComponent[] = [];
|
||||
private readonly abstractNumbering: AbstractNumbering[] = [];
|
||||
private readonly concreteNumbering: ConcreteNumbering[] = [];
|
||||
|
||||
constructor() {
|
||||
constructor(options: INumberingOptions) {
|
||||
super("w:numbering");
|
||||
this.root.push(
|
||||
new DocumentAttributes({
|
||||
@ -37,39 +47,114 @@ export class Numbering extends XmlComponent {
|
||||
|
||||
this.nextId = 0;
|
||||
|
||||
const abstractNumbering = this.createAbstractNumbering();
|
||||
|
||||
abstractNumbering.createLevel(0, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 720, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(1, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 1440, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(2, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 2160, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(3, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 2880, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(4, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 3600, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(5, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 4320, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(6, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 5040, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(7, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 5760, hanging: 360 }));
|
||||
|
||||
abstractNumbering.createLevel(8, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 6480, hanging: 360 }));
|
||||
const abstractNumbering = this.createAbstractNumbering([
|
||||
{
|
||||
level: 0,
|
||||
format: "bullet",
|
||||
text: "\u25CF",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 720, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 1,
|
||||
format: "bullet",
|
||||
text: "\u25CB",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 1440, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 2,
|
||||
format: "bullet",
|
||||
text: "\u25A0",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 2160, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 3,
|
||||
format: "bullet",
|
||||
text: "\u25CF",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 2880, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 4,
|
||||
format: "bullet",
|
||||
text: "\u25CB",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 3600, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 5,
|
||||
format: "bullet",
|
||||
text: "\u25A0",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 4320, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 6,
|
||||
format: "bullet",
|
||||
text: "\u25CF",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 5040, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 7,
|
||||
format: "bullet",
|
||||
text: "\u25CF",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 5760, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 8,
|
||||
format: "bullet",
|
||||
text: "\u25CF",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 6480, hanging: 360 },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
this.createConcreteNumbering(abstractNumbering);
|
||||
}
|
||||
|
||||
public createAbstractNumbering(): AbstractNumbering {
|
||||
const num = new AbstractNumbering(this.nextId++);
|
||||
this.abstractNumbering.push(num);
|
||||
return num;
|
||||
for (const con of options.config) {
|
||||
const currentAbstractNumbering = this.createAbstractNumbering(con.levels);
|
||||
this.createConcreteNumbering(currentAbstractNumbering, con.reference);
|
||||
}
|
||||
|
||||
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
|
||||
const num = new Num(this.nextId++, abstractNumbering.id);
|
||||
this.concreteNumbering.push(num);
|
||||
return num;
|
||||
}
|
||||
|
||||
public prepForXml(): IXmlableObject | undefined {
|
||||
@ -77,4 +162,20 @@ export class Numbering extends XmlComponent {
|
||||
this.concreteNumbering.forEach((x) => this.root.push(x));
|
||||
return super.prepForXml();
|
||||
}
|
||||
|
||||
private createConcreteNumbering(abstractNumbering: AbstractNumbering, reference?: string): ConcreteNumbering {
|
||||
const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id, reference);
|
||||
this.concreteNumbering.push(num);
|
||||
return num;
|
||||
}
|
||||
|
||||
private createAbstractNumbering(options: ILevelsOptions[]): AbstractNumbering {
|
||||
const num = new AbstractNumbering(this.nextId++, options);
|
||||
this.abstractNumbering.push(num);
|
||||
return num;
|
||||
}
|
||||
|
||||
public get ConcreteNumbering(): ConcreteNumbering[] {
|
||||
return this.concreteNumbering;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
// http://officeopenxml.com/WPalignment.php
|
||||
// http://officeopenxml.com/WPtableAlignment.php
|
||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
|
||||
export enum AlignmentType {
|
||||
|
@ -7,6 +7,7 @@ export interface IIndentAttributesProperties {
|
||||
readonly firstLine?: number;
|
||||
readonly start?: number;
|
||||
readonly end?: number;
|
||||
readonly right?: number;
|
||||
}
|
||||
|
||||
class IndentAttributes extends XmlAttributeComponent<IIndentAttributesProperties> {
|
||||
@ -16,6 +17,7 @@ class IndentAttributes extends XmlAttributeComponent<IIndentAttributesProperties
|
||||
firstLine: "w:firstLine",
|
||||
start: "w:start",
|
||||
end: "w:end",
|
||||
right: "w:end", // alias
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,8 @@ export enum HeadingLevel {
|
||||
}
|
||||
|
||||
export class Style extends XmlComponent {
|
||||
public readonly styleId: string;
|
||||
|
||||
constructor(styleId: string) {
|
||||
super("w:pStyle");
|
||||
this.styleId = styleId;
|
||||
this.root.push(
|
||||
new Attributes({
|
||||
val: styleId,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Attributes, XmlComponent } from "file/xml-components";
|
||||
|
||||
export class NumberProperties extends XmlComponent {
|
||||
constructor(numberId: number, indentLevel: number) {
|
||||
constructor(numberId: number | string, indentLevel: number) {
|
||||
super("w:numPr");
|
||||
this.root.push(new IndentLevel(indentLevel));
|
||||
this.root.push(new NumberId(numberId));
|
||||
@ -20,11 +20,11 @@ class IndentLevel extends XmlComponent {
|
||||
}
|
||||
|
||||
class NumberId extends XmlComponent {
|
||||
constructor(id: number) {
|
||||
constructor(id: number | string) {
|
||||
super("w:numId");
|
||||
this.root.push(
|
||||
new Attributes({
|
||||
val: id,
|
||||
val: typeof id === "string" ? `{${id}}` : id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { assert } from "chai";
|
||||
import { assert, expect } from "chai";
|
||||
|
||||
import { Utility } from "tests/utility";
|
||||
|
||||
@ -8,7 +8,7 @@ describe("Bookmark", () => {
|
||||
let bookmark: Bookmark;
|
||||
|
||||
beforeEach(() => {
|
||||
bookmark = new Bookmark("anchor", "Internal Link", 0);
|
||||
bookmark = new Bookmark("anchor", "Internal Link");
|
||||
});
|
||||
|
||||
it("should create a bookmark with three root elements", () => {
|
||||
@ -21,11 +21,8 @@ describe("Bookmark", () => {
|
||||
|
||||
it("should create a bookmark with the correct attributes on the bookmark start element", () => {
|
||||
const newJson = Utility.jsonify(bookmark);
|
||||
const attributes = {
|
||||
name: "anchor",
|
||||
id: "1",
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.start.root[0].root), JSON.stringify(attributes));
|
||||
|
||||
assert.equal(newJson.start.root[0].root.name, "anchor");
|
||||
});
|
||||
|
||||
it("should create a bookmark with the correct attributes on the text element", () => {
|
||||
@ -35,9 +32,6 @@ describe("Bookmark", () => {
|
||||
|
||||
it("should create a bookmark with the correct attributes on the bookmark end element", () => {
|
||||
const newJson = Utility.jsonify(bookmark);
|
||||
const attributes = {
|
||||
id: "1",
|
||||
};
|
||||
assert.equal(JSON.stringify(newJson.end.root[0].root), JSON.stringify(attributes));
|
||||
expect(newJson.end.root[0].root.id).to.be.a("string");
|
||||
});
|
||||
});
|
||||
|
@ -1,49 +1,41 @@
|
||||
// http://officeopenxml.com/WPbookmark.php
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
import * as shortid from "shortid";
|
||||
import { TextRun } from "../run";
|
||||
import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes";
|
||||
|
||||
export class Bookmark {
|
||||
public readonly linkId: number;
|
||||
public readonly start: BookmarkStart;
|
||||
public readonly text: TextRun;
|
||||
public readonly end: BookmarkEnd;
|
||||
|
||||
constructor(name: string, text: string, relationshipsCount: number) {
|
||||
this.linkId = relationshipsCount + 1;
|
||||
constructor(name: string, text: string) {
|
||||
const linkId = shortid.generate().toLowerCase();
|
||||
|
||||
this.start = new BookmarkStart(name, this.linkId);
|
||||
this.start = new BookmarkStart(name, linkId);
|
||||
this.text = new TextRun(text);
|
||||
this.end = new BookmarkEnd(this.linkId);
|
||||
this.end = new BookmarkEnd(linkId);
|
||||
}
|
||||
}
|
||||
|
||||
export class BookmarkStart extends XmlComponent {
|
||||
public readonly linkId: number;
|
||||
|
||||
constructor(name: string, relationshipsCount: number) {
|
||||
constructor(name: string, linkId: string) {
|
||||
super("w:bookmarkStart");
|
||||
|
||||
this.linkId = relationshipsCount;
|
||||
const id = `${this.linkId}`;
|
||||
const attributes = new BookmarkStartAttributes({
|
||||
name,
|
||||
id,
|
||||
id: linkId,
|
||||
});
|
||||
this.root.push(attributes);
|
||||
}
|
||||
}
|
||||
|
||||
export class BookmarkEnd extends XmlComponent {
|
||||
public readonly linkId: number;
|
||||
|
||||
constructor(relationshipsCount: number) {
|
||||
constructor(linkId: string) {
|
||||
super("w:bookmarkEnd");
|
||||
|
||||
this.linkId = relationshipsCount;
|
||||
const id = `${this.linkId}`;
|
||||
const attributes = new BookmarkEndAttributes({
|
||||
id,
|
||||
id: linkId,
|
||||
});
|
||||
this.root.push(attributes);
|
||||
}
|
||||
|
@ -3,12 +3,13 @@ import { expect } from "chai";
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { Hyperlink } from "./";
|
||||
import { HyperlinkRef } from "./hyperlink";
|
||||
|
||||
describe("Hyperlink", () => {
|
||||
let hyperlink: Hyperlink;
|
||||
|
||||
beforeEach(() => {
|
||||
hyperlink = new Hyperlink("https://example.com", 0);
|
||||
hyperlink = new Hyperlink("https://example.com", "superid");
|
||||
});
|
||||
|
||||
describe("#constructor()", () => {
|
||||
@ -19,7 +20,7 @@ describe("Hyperlink", () => {
|
||||
{
|
||||
_attr: {
|
||||
"w:history": 1,
|
||||
"r:id": "rId1",
|
||||
"r:id": "rIdsuperid",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -34,7 +35,7 @@ describe("Hyperlink", () => {
|
||||
|
||||
describe("with optional anchor parameter", () => {
|
||||
beforeEach(() => {
|
||||
hyperlink = new Hyperlink("Anchor Text", 0, "anchor");
|
||||
hyperlink = new Hyperlink("Anchor Text", "superid2", "anchor");
|
||||
});
|
||||
|
||||
it("should create an internal link with anchor tag", () => {
|
||||
@ -59,3 +60,11 @@ describe("Hyperlink", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("HyperlinkRef", () => {
|
||||
describe("#constructor()", () => {
|
||||
const hyperlinkRef = new HyperlinkRef("test-id");
|
||||
|
||||
expect(hyperlinkRef.id).to.equal("test-id");
|
||||
});
|
||||
});
|
||||
|
@ -3,14 +3,23 @@ import { XmlComponent } from "file/xml-components";
|
||||
import { TextRun } from "../run";
|
||||
import { HyperlinkAttributes, IHyperlinkAttributesProperties } from "./hyperlink-attributes";
|
||||
|
||||
export enum HyperlinkType {
|
||||
INTERNAL = "INTERNAL",
|
||||
EXTERNAL = "EXTERNAL",
|
||||
}
|
||||
|
||||
export class HyperlinkRef {
|
||||
constructor(public readonly id: string) {}
|
||||
}
|
||||
|
||||
export class Hyperlink extends XmlComponent {
|
||||
public readonly linkId: number;
|
||||
public readonly linkId: string;
|
||||
private readonly textRun: TextRun;
|
||||
|
||||
constructor(text: string, relationshipsCount: number, anchor?: string) {
|
||||
constructor(text: string, relationshipId: string, anchor?: string) {
|
||||
super("w:hyperlink");
|
||||
|
||||
this.linkId = relationshipsCount + 1;
|
||||
this.linkId = relationshipId;
|
||||
|
||||
const props: IHyperlinkAttributesProperties = {
|
||||
history: 1,
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { assert, expect } from "chai";
|
||||
import * as shortid from "shortid";
|
||||
import { stub } from "sinon";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
import { EMPTY_OBJECT } from "file/xml-components";
|
||||
|
||||
import { Numbering } from "../numbering";
|
||||
import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting";
|
||||
import { Bookmark } from "./links";
|
||||
import { Paragraph } from "./paragraph";
|
||||
|
||||
describe("Paragraph", () => {
|
||||
@ -596,14 +598,9 @@ describe("Paragraph", () => {
|
||||
|
||||
describe("#setNumbering", () => {
|
||||
it("should add list paragraph style to JSON", () => {
|
||||
const numbering = new Numbering();
|
||||
const numberedAbstract = numbering.createAbstractNumbering();
|
||||
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
|
||||
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
|
||||
|
||||
const paragraph = new Paragraph({
|
||||
numbering: {
|
||||
num: letterNumbering,
|
||||
reference: "test id",
|
||||
level: 0,
|
||||
},
|
||||
});
|
||||
@ -622,14 +619,9 @@ describe("Paragraph", () => {
|
||||
});
|
||||
|
||||
it("it should add numbered properties", () => {
|
||||
const numbering = new Numbering();
|
||||
const numberedAbstract = numbering.createAbstractNumbering();
|
||||
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
|
||||
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
|
||||
|
||||
const paragraph = new Paragraph({
|
||||
numbering: {
|
||||
num: letterNumbering,
|
||||
reference: "test id",
|
||||
level: 0,
|
||||
},
|
||||
});
|
||||
@ -640,10 +632,7 @@ describe("Paragraph", () => {
|
||||
"w:pPr": [
|
||||
{ "w:pStyle": { _attr: { "w:val": "ListParagraph" } } },
|
||||
{
|
||||
"w:numPr": [
|
||||
{ "w:ilvl": { _attr: { "w:val": 0 } } },
|
||||
{ "w:numId": { _attr: { "w:val": letterNumbering.id } } },
|
||||
],
|
||||
"w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -652,6 +641,49 @@ describe("Paragraph", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("it should add bookmark", () => {
|
||||
stub(shortid, "generate").callsFake(() => {
|
||||
return "test-unique-id";
|
||||
});
|
||||
const paragraph = new Paragraph({
|
||||
children: [new Bookmark("test-id", "test")],
|
||||
});
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [
|
||||
{
|
||||
"w:bookmarkStart": {
|
||||
_attr: {
|
||||
"w:id": "test-unique-id",
|
||||
"w:name": "test-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:r": [
|
||||
{
|
||||
"w:t": [
|
||||
{
|
||||
_attr: {
|
||||
"xml:space": "preserve",
|
||||
},
|
||||
},
|
||||
"test",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:bookmarkEnd": {
|
||||
_attr: {
|
||||
"w:id": "test-unique-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
describe("#style", () => {
|
||||
it("should set the paragraph style to the given styleId", () => {
|
||||
const paragraph = new Paragraph({
|
||||
|
@ -1,8 +1,8 @@
|
||||
// http://officeopenxml.com/WPparagraph.php
|
||||
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
|
||||
import { Num } from "file/numbering/num";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { File } from "../file";
|
||||
import { Alignment, AlignmentType } from "./formatting/alignment";
|
||||
import { Bidirectional } from "./formatting/bidirectional";
|
||||
import { IBorderOptions, ThematicBreak } from "./formatting/border";
|
||||
@ -13,7 +13,7 @@ import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spa
|
||||
import { HeadingLevel, Style } from "./formatting/style";
|
||||
import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop";
|
||||
import { NumberProperties } from "./formatting/unordered-list";
|
||||
import { Bookmark, Hyperlink, OutlineLevel } from "./links";
|
||||
import { Bookmark, HyperlinkRef, OutlineLevel } from "./links";
|
||||
import { ParagraphProperties } from "./properties";
|
||||
import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run";
|
||||
|
||||
@ -41,11 +41,13 @@ export interface IParagraphOptions {
|
||||
readonly level: number;
|
||||
};
|
||||
readonly numbering?: {
|
||||
readonly num: Num;
|
||||
readonly reference: string;
|
||||
readonly level: number;
|
||||
readonly custom?: boolean;
|
||||
};
|
||||
readonly children?: Array<TextRun | PictureRun | Hyperlink | SymbolRun | Bookmark | PageBreak | SequentialIdentifier>;
|
||||
readonly children?: Array<
|
||||
TextRun | PictureRun | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | FootnoteReferenceRun | HyperlinkRef
|
||||
>;
|
||||
}
|
||||
|
||||
export class Paragraph extends XmlComponent {
|
||||
@ -141,7 +143,7 @@ export class Paragraph extends XmlComponent {
|
||||
if (!options.numbering.custom) {
|
||||
this.properties.push(new Style("ListParagraph"));
|
||||
}
|
||||
this.properties.push(new NumberProperties(options.numbering.num.id, options.numbering.level));
|
||||
this.properties.push(new NumberProperties(options.numbering.reference, options.numbering.level));
|
||||
}
|
||||
|
||||
if (options.children) {
|
||||
@ -158,9 +160,15 @@ export class Paragraph extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public referenceFootnote(id: number): Paragraph {
|
||||
this.root.push(new FootnoteReferenceRun(id));
|
||||
return this;
|
||||
public prepForXml(file: File): IXmlableObject | undefined {
|
||||
for (const element of this.root) {
|
||||
if (element instanceof HyperlinkRef) {
|
||||
const index = this.root.indexOf(element);
|
||||
this.root[index] = file.HyperlinkCache[element.id];
|
||||
}
|
||||
}
|
||||
|
||||
return super.prepForXml();
|
||||
}
|
||||
|
||||
public addRunToFront(run: Run): Paragraph {
|
||||
|
29
src/file/paragraph/run/emphasis-mark.spec.ts
Normal file
29
src/file/paragraph/run/emphasis-mark.spec.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import * as em from "./emphasis-mark";
|
||||
|
||||
describe("EmphasisMark", () => {
|
||||
describe("#constructor()", () => {
|
||||
it("should create a new EmphasisMark object with w:em as the rootKey", () => {
|
||||
const emphasisMark = new em.EmphasisMark();
|
||||
const tree = new Formatter().format(emphasisMark);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:em": { _attr: { "w:val": "dot" } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("DotEmphasisMark", () => {
|
||||
describe("#constructor()", () => {
|
||||
it("should put value in attribute", () => {
|
||||
const emphasisMark = new em.DotEmphasisMark();
|
||||
const tree = new Formatter().format(emphasisMark);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:em": { _attr: { "w:val": "dot" } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
28
src/file/paragraph/run/emphasis-mark.ts
Normal file
28
src/file/paragraph/run/emphasis-mark.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Attributes, XmlComponent } from "file/xml-components";
|
||||
|
||||
export enum EmphasisMarkType {
|
||||
DOT = "dot",
|
||||
}
|
||||
|
||||
export abstract class BaseEmphasisMark extends XmlComponent {
|
||||
protected constructor(emphasisMarkType: EmphasisMarkType) {
|
||||
super("w:em");
|
||||
this.root.push(
|
||||
new Attributes({
|
||||
val: emphasisMarkType,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class EmphasisMark extends BaseEmphasisMark {
|
||||
constructor(emphasisMarkType: EmphasisMarkType = EmphasisMarkType.DOT) {
|
||||
super(emphasisMarkType);
|
||||
}
|
||||
}
|
||||
|
||||
export class DotEmphasisMark extends BaseEmphasisMark {
|
||||
constructor() {
|
||||
super(EmphasisMarkType.DOT);
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { Attributes, XmlComponent } from "file/xml-components";
|
||||
|
||||
export { Underline } from "./underline";
|
||||
export { EmphasisMark } from "./emphasis-mark";
|
||||
export { SubScript, SuperScript } from "./script";
|
||||
export { RunFonts } from "./run-fonts";
|
||||
export { RunFonts, IFontAttributesProperties } from "./run-fonts";
|
||||
|
||||
export class Bold extends XmlComponent {
|
||||
constructor() {
|
||||
|
@ -5,3 +5,5 @@ export * from "./picture-run";
|
||||
export * from "./run-fonts";
|
||||
export * from "./sequential-identifier";
|
||||
export * from "./underline";
|
||||
export * from "./emphasis-mark";
|
||||
export * from "./tab";
|
||||
|
@ -7,10 +7,6 @@ export class PictureRun extends Run {
|
||||
constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) {
|
||||
super({});
|
||||
|
||||
if (imageData === undefined) {
|
||||
throw new Error("imageData cannot be undefined");
|
||||
}
|
||||
|
||||
const drawing = new Drawing(imageData, drawingOptions);
|
||||
|
||||
this.root.push(drawing);
|
||||
|
@ -6,12 +6,6 @@ import { Text } from "./text";
|
||||
|
||||
describe("Text", () => {
|
||||
describe("#constructor", () => {
|
||||
it("creates an empty text run if no text is given", () => {
|
||||
const t = new Text("");
|
||||
const f = new Formatter().format(t);
|
||||
expect(f).to.deep.equal({ "w:t": { _attr: { "xml:space": "preserve" } } });
|
||||
});
|
||||
|
||||
it("adds the passed in text to the component", () => {
|
||||
const t = new Text(" this is\n text");
|
||||
const f = new Formatter().format(t);
|
||||
|
@ -9,8 +9,7 @@ export class Text extends XmlComponent {
|
||||
constructor(text: string) {
|
||||
super("w:t");
|
||||
this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));
|
||||
if (text) {
|
||||
|
||||
this.root.push(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,5 +21,12 @@ describe("RunFonts", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the font attrs for ascii and eastAsia", () => {
|
||||
const tree = new Formatter().format(new RunFonts({ ascii: "Times", eastAsia: "KaiTi" }));
|
||||
expect(tree).to.deep.equal({
|
||||
"w:rFonts": { _attr: { "w:ascii": "Times", "w:eastAsia": "KaiTi" } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
|
||||
interface IRunFontAttributesProperties {
|
||||
readonly ascii: string;
|
||||
readonly cs: string;
|
||||
readonly eastAsia: string;
|
||||
readonly hAnsi: string;
|
||||
export interface IFontAttributesProperties {
|
||||
readonly ascii?: string;
|
||||
readonly cs?: string;
|
||||
readonly eastAsia?: string;
|
||||
readonly hAnsi?: string;
|
||||
readonly hint?: string;
|
||||
}
|
||||
|
||||
class RunFontAttributes extends XmlAttributeComponent<IRunFontAttributesProperties> {
|
||||
class RunFontAttributes extends XmlAttributeComponent<IFontAttributesProperties> {
|
||||
protected readonly xmlKeys = {
|
||||
ascii: "w:ascii",
|
||||
cs: "w:cs",
|
||||
@ -19,16 +19,26 @@ class RunFontAttributes extends XmlAttributeComponent<IRunFontAttributesProperti
|
||||
}
|
||||
|
||||
export class RunFonts extends XmlComponent {
|
||||
constructor(ascii: string, hint?: string) {
|
||||
constructor(name: string, hint?: string);
|
||||
constructor(attrs: string | IFontAttributesProperties);
|
||||
constructor(nameOrAttrs: string | IFontAttributesProperties, hint?: string) {
|
||||
super("w:rFonts");
|
||||
if (typeof nameOrAttrs === "string") {
|
||||
// use constructor(name: string, hint?: string);
|
||||
const name = nameOrAttrs;
|
||||
this.root.push(
|
||||
new RunFontAttributes({
|
||||
ascii: ascii,
|
||||
cs: ascii,
|
||||
eastAsia: ascii,
|
||||
hAnsi: ascii,
|
||||
ascii: name,
|
||||
cs: name,
|
||||
eastAsia: name,
|
||||
hAnsi: name,
|
||||
hint: hint,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// use constructor(attrs: IRunFontAttributesProperties);
|
||||
const attrs = nameOrAttrs;
|
||||
this.root.push(new RunFontAttributes(attrs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
// import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
|
||||
import { ShadingType } from "file/table";
|
||||
|
||||
import { Run } from "./";
|
||||
import { EmphasisMarkType } from "./emphasis-mark";
|
||||
import { PageNumber } from "./run";
|
||||
import { UnderlineType } from "./underline";
|
||||
|
||||
describe("Run", () => {
|
||||
@ -82,6 +85,30 @@ describe("Run", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#emphasisMark()", () => {
|
||||
it("should default to 'dot'", () => {
|
||||
const run = new Run({
|
||||
emphasisMark: {},
|
||||
});
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [{ "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }] }],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style type if given", () => {
|
||||
const run = new Run({
|
||||
emphasisMark: {
|
||||
type: EmphasisMarkType.DOT,
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [{ "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#smallCaps()", () => {
|
||||
it("it should add smallCaps to the properties", () => {
|
||||
const run = new Run({
|
||||
@ -130,6 +157,30 @@ describe("Run", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#subScript()", () => {
|
||||
it("it should add subScript to the properties", () => {
|
||||
const run = new Run({
|
||||
subScript: true,
|
||||
});
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [{ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#superScript()", () => {
|
||||
it("it should add superScript to the properties", () => {
|
||||
const run = new Run({
|
||||
superScript: true,
|
||||
});
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [{ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }] }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#highlight()", () => {
|
||||
it("it should add highlight to the properties", () => {
|
||||
const run = new Run({
|
||||
@ -197,17 +248,6 @@ describe("Run", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#tab()", () => {
|
||||
it("it should add break to the run", () => {
|
||||
const run = new Run({});
|
||||
run.tab();
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [{ "w:tab": {} }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#font()", () => {
|
||||
it("should set the font as named", () => {
|
||||
const run = new Run({
|
||||
@ -220,7 +260,42 @@ describe("Run", () => {
|
||||
"w:r": [
|
||||
{
|
||||
"w:rPr": [
|
||||
{ "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } },
|
||||
{
|
||||
"w:rFonts": {
|
||||
_attr: {
|
||||
"w:ascii": "Times",
|
||||
"w:cs": "Times",
|
||||
"w:eastAsia": "Times",
|
||||
"w:hAnsi": "Times",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the font for ascii and eastAsia", () => {
|
||||
const run = new Run({
|
||||
font: {
|
||||
ascii: "Times",
|
||||
eastAsia: "KaiTi",
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rFonts": {
|
||||
_attr: {
|
||||
"w:ascii": "Times",
|
||||
"w:eastAsia": "KaiTi",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -270,8 +345,10 @@ describe("Run", () => {
|
||||
|
||||
describe("#numberOfTotalPages", () => {
|
||||
it("should set the run to the RTL mode", () => {
|
||||
const run = new Run({});
|
||||
run.numberOfTotalPages();
|
||||
const run = new Run({
|
||||
children: [PageNumber.TOTAL_PAGES],
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
@ -286,8 +363,10 @@ describe("Run", () => {
|
||||
|
||||
describe("#numberOfTotalPagesSection", () => {
|
||||
it("should set the run to the RTL mode", () => {
|
||||
const run = new Run({});
|
||||
run.numberOfTotalPagesSection();
|
||||
const run = new Run({
|
||||
children: [PageNumber.TOTAL_PAGES_IN_SECTION],
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
@ -302,8 +381,9 @@ describe("Run", () => {
|
||||
|
||||
describe("#pageNumber", () => {
|
||||
it("should set the run to the RTL mode", () => {
|
||||
const run = new Run({});
|
||||
run.pageNumber();
|
||||
const run = new Run({
|
||||
children: [PageNumber.CURRENT],
|
||||
});
|
||||
const tree = new Formatter().format(run);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
|
@ -2,9 +2,11 @@
|
||||
import { ShadingType } from "file/table";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
|
||||
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
|
||||
import { FieldInstruction } from "file/table-of-contents/field-instruction";
|
||||
import { Break } from "./break";
|
||||
import { Caps, SmallCaps } from "./caps";
|
||||
import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark";
|
||||
import { Begin, End, Separate } from "./field";
|
||||
import {
|
||||
Bold,
|
||||
@ -24,12 +26,17 @@ import {
|
||||
} from "./formatting";
|
||||
import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
|
||||
import { RunProperties } from "./properties";
|
||||
import { RunFonts } from "./run-fonts";
|
||||
import { Text } from "./run-components/text";
|
||||
import { IFontAttributesProperties, RunFonts } from "./run-fonts";
|
||||
import { SubScript, SuperScript } from "./script";
|
||||
import { Style } from "./style";
|
||||
import { Tab } from "./tab";
|
||||
import { Underline, UnderlineType } from "./underline";
|
||||
|
||||
interface IFontOptions {
|
||||
readonly name: string;
|
||||
readonly hint?: string;
|
||||
}
|
||||
|
||||
export interface IRunOptions {
|
||||
readonly bold?: true;
|
||||
readonly italics?: true;
|
||||
@ -37,6 +44,9 @@ export interface IRunOptions {
|
||||
readonly color?: string;
|
||||
readonly type?: UnderlineType;
|
||||
};
|
||||
readonly emphasisMark?: {
|
||||
readonly type?: EmphasisMarkType;
|
||||
};
|
||||
readonly color?: string;
|
||||
readonly size?: number;
|
||||
readonly rightToLeft?: boolean;
|
||||
@ -47,17 +57,21 @@ export interface IRunOptions {
|
||||
readonly subScript?: boolean;
|
||||
readonly superScript?: boolean;
|
||||
readonly style?: string;
|
||||
readonly font?: {
|
||||
readonly name: string;
|
||||
readonly hint?: string;
|
||||
};
|
||||
readonly font?: IFontOptions | IFontAttributesProperties;
|
||||
readonly highlight?: string;
|
||||
readonly shading?: {
|
||||
readonly type: ShadingType;
|
||||
readonly fill: string;
|
||||
readonly color: string;
|
||||
};
|
||||
readonly children?: Array<Begin | FieldInstruction | Separate | End>;
|
||||
readonly children?: Array<Begin | FieldInstruction | Separate | End | PageNumber | FootnoteReferenceRun | string>;
|
||||
readonly text?: string;
|
||||
}
|
||||
|
||||
export enum PageNumber {
|
||||
CURRENT = "CURRENT",
|
||||
TOTAL_PAGES = "TOTAL_PAGES",
|
||||
TOTAL_PAGES_IN_SECTION = "TOTAL_PAGES_IN_SECTION",
|
||||
}
|
||||
|
||||
export class Run extends XmlComponent {
|
||||
@ -82,6 +96,10 @@ export class Run extends XmlComponent {
|
||||
this.properties.push(new Underline(options.underline.type, options.underline.color));
|
||||
}
|
||||
|
||||
if (options.emphasisMark) {
|
||||
this.properties.push(new EmphasisMark(options.emphasisMark.type));
|
||||
}
|
||||
|
||||
if (options.color) {
|
||||
this.properties.push(new Color(options.color));
|
||||
}
|
||||
@ -124,7 +142,11 @@ export class Run extends XmlComponent {
|
||||
}
|
||||
|
||||
if (options.font) {
|
||||
if ("name" in options.font) {
|
||||
this.properties.push(new RunFonts(options.font.name, options.font.hint));
|
||||
} else {
|
||||
this.properties.push(new RunFonts(options.font));
|
||||
}
|
||||
}
|
||||
|
||||
if (options.highlight) {
|
||||
@ -139,8 +161,37 @@ export class Run extends XmlComponent {
|
||||
|
||||
if (options.children) {
|
||||
for (const child of options.children) {
|
||||
if (typeof child === "string") {
|
||||
switch (child) {
|
||||
case PageNumber.CURRENT:
|
||||
this.root.push(new Begin());
|
||||
this.root.push(new Page());
|
||||
this.root.push(new Separate());
|
||||
this.root.push(new End());
|
||||
break;
|
||||
case PageNumber.TOTAL_PAGES:
|
||||
this.root.push(new Begin());
|
||||
this.root.push(new NumberOfPages());
|
||||
this.root.push(new Separate());
|
||||
this.root.push(new End());
|
||||
break;
|
||||
case PageNumber.TOTAL_PAGES_IN_SECTION:
|
||||
this.root.push(new Begin());
|
||||
this.root.push(new NumberOfPagesSection());
|
||||
this.root.push(new Separate());
|
||||
this.root.push(new End());
|
||||
break;
|
||||
default:
|
||||
this.root.push(new Text(child));
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
this.root.push(child);
|
||||
}
|
||||
} else if (options.text) {
|
||||
this.root.push(new Text(options.text));
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,33 +199,4 @@ export class Run extends XmlComponent {
|
||||
this.root.splice(1, 0, new Break());
|
||||
return this;
|
||||
}
|
||||
|
||||
public tab(): Run {
|
||||
this.root.splice(1, 0, new Tab());
|
||||
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 numberOfTotalPages(): Run {
|
||||
this.root.push(new Begin());
|
||||
this.root.push(new NumberOfPages());
|
||||
this.root.push(new Separate());
|
||||
this.root.push(new End());
|
||||
return this;
|
||||
}
|
||||
|
||||
public numberOfTotalPagesSection(): Run {
|
||||
this.root.push(new Begin());
|
||||
this.root.push(new NumberOfPagesSection());
|
||||
this.root.push(new Separate());
|
||||
this.root.push(new End());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { EmphasisMarkType } from "./emphasis-mark";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { UnderlineType } from "./underline";
|
||||
@ -44,6 +46,9 @@ describe("SymbolRun", () => {
|
||||
color: "red",
|
||||
type: UnderlineType.DOUBLE,
|
||||
},
|
||||
emphasisMark: {
|
||||
type: EmphasisMarkType.DOT,
|
||||
},
|
||||
color: "green",
|
||||
size: 40,
|
||||
highlight: "yellow",
|
||||
@ -59,6 +64,7 @@ describe("SymbolRun", () => {
|
||||
{ "w:i": { _attr: { "w:val": true } } },
|
||||
{ "w:iCs": { _attr: { "w:val": true } } },
|
||||
{ "w:u": { _attr: { "w:val": "double", "w:color": "red" } } },
|
||||
{ "w:em": { _attr: { "w:val": "dot" } } },
|
||||
{ "w:color": { _attr: { "w:val": "green" } } },
|
||||
{ "w:sz": { _attr: { "w:val": 40 } } },
|
||||
{ "w:szCs": { _attr: { "w:val": 40 } } },
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
|
||||
|
||||
import { TextRun } from "./text-run";
|
||||
|
||||
@ -16,4 +17,25 @@ describe("TextRun", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#referenceFootnote()", () => {
|
||||
it("should add a valid footnote reference", () => {
|
||||
run = new TextRun({
|
||||
children: ["test", new FootnoteReferenceRun(1)],
|
||||
});
|
||||
const tree = new Formatter().format(run);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:r": [
|
||||
{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "test"] },
|
||||
{
|
||||
"w:r": [
|
||||
{ "w:rPr": [{ "w:rStyle": { _attr: { "w:val": "FootnoteReference" } } }] },
|
||||
{ "w:footnoteReference": { _attr: { "w:id": 1 } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { IRunOptions, Run } from "./run";
|
||||
import { Text } from "./run-components/text";
|
||||
|
||||
export interface ITextRunOptions extends IRunOptions {
|
||||
readonly text: string;
|
||||
}
|
||||
|
||||
export class TextRun extends Run {
|
||||
constructor(options: ITextRunOptions | string) {
|
||||
constructor(options: IRunOptions | string) {
|
||||
if (typeof options === "string") {
|
||||
super({});
|
||||
this.root.push(new Text(options));
|
||||
@ -14,6 +10,5 @@ export class TextRun extends Run {
|
||||
}
|
||||
|
||||
super(options);
|
||||
this.root.push(new Text(options.text));
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ export class Relationships extends XmlComponent {
|
||||
this.root.push(relationship);
|
||||
}
|
||||
|
||||
public createRelationship(id: number, type: RelationshipType, target: string, targetMode?: TargetModeType): Relationship {
|
||||
public createRelationship(id: number | string, type: RelationshipType, target: string, targetMode?: TargetModeType): Relationship {
|
||||
const relationship = new Relationship(`rId${id}`, type, target, targetMode);
|
||||
this.addRelationship(relationship);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Size, SizeComplexScript } from "file/paragraph/run/formatting";
|
||||
import { RunProperties } from "file/paragraph/run/properties";
|
||||
import { RunFonts } from "file/paragraph/run/run-fonts";
|
||||
import { IFontAttributesProperties, RunFonts } from "file/paragraph/run/run-fonts";
|
||||
import { XmlComponent } from "file/xml-components";
|
||||
|
||||
export class RunPropertiesDefaults extends XmlComponent {
|
||||
@ -18,8 +18,8 @@ export class RunPropertiesDefaults extends XmlComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public font(fontName: string): RunPropertiesDefaults {
|
||||
this.properties.push(new RunFonts(fontName));
|
||||
public font(font: string | IFontAttributesProperties): RunPropertiesDefaults {
|
||||
this.properties.push(new RunFonts(font));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./styles";
|
||||
export * from "./style/character-style";
|
||||
export * from "./style/paragraph-style";
|
||||
export * from "./style-options";
|
||||
|
56
src/file/styles/style-options.ts
Normal file
56
src/file/styles/style-options.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
AlignmentType,
|
||||
EmphasisMarkType,
|
||||
IFontAttributesProperties,
|
||||
IIndentAttributesProperties,
|
||||
ISpacingProperties,
|
||||
UnderlineType,
|
||||
} from "../paragraph";
|
||||
import { ShadingType } from "../table";
|
||||
|
||||
export interface IRunStyleOptions {
|
||||
readonly size?: number;
|
||||
readonly bold?: boolean;
|
||||
readonly italics?: boolean;
|
||||
readonly smallCaps?: boolean;
|
||||
readonly allCaps?: boolean;
|
||||
readonly strike?: boolean;
|
||||
readonly doubleStrike?: boolean;
|
||||
readonly subScript?: boolean;
|
||||
readonly superScript?: boolean;
|
||||
readonly underline?: {
|
||||
readonly type?: UnderlineType;
|
||||
readonly color?: string;
|
||||
};
|
||||
readonly emphasisMark?: {
|
||||
readonly type?: EmphasisMarkType;
|
||||
};
|
||||
readonly color?: string;
|
||||
readonly font?: string | IFontAttributesProperties;
|
||||
readonly characterSpacing?: number;
|
||||
readonly highlight?: string;
|
||||
readonly shadow?: {
|
||||
readonly type: ShadingType;
|
||||
readonly fill: string;
|
||||
readonly color: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IParagraphStyleOptions2 {
|
||||
readonly alignment?: AlignmentType;
|
||||
readonly thematicBreak?: boolean;
|
||||
readonly contextualSpacing?: boolean;
|
||||
readonly rightTabStop?: number;
|
||||
readonly leftTabStop?: number;
|
||||
readonly indent?: IIndentAttributesProperties;
|
||||
readonly spacing?: ISpacingProperties;
|
||||
readonly keepNext?: boolean;
|
||||
readonly keepLines?: boolean;
|
||||
readonly outlineLevel?: number;
|
||||
}
|
||||
|
||||
// Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export const WORKAROUND4 = "";
|
@ -1,6 +1,7 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
import { EmphasisMarkType } from "file/paragraph/run/emphasis-mark";
|
||||
import { UnderlineType } from "file/paragraph/run/underline";
|
||||
import { ShadingType } from "file/table";
|
||||
import { EMPTY_OBJECT } from "file/xml-components";
|
||||
@ -201,7 +202,7 @@ describe("CharacterStyle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should add font", () => {
|
||||
it("should add font by name", () => {
|
||||
const style = new CharacterStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
@ -240,6 +241,46 @@ describe("CharacterStyle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should add font for ascii and eastAsia", () => {
|
||||
const style = new CharacterStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
font: {
|
||||
ascii: "test font ascii",
|
||||
eastAsia: "test font eastAsia",
|
||||
},
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rFonts": {
|
||||
_attr: {
|
||||
"w:ascii": "test font ascii",
|
||||
"w:eastAsia": "test font eastAsia",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:uiPriority": {
|
||||
_attr: {
|
||||
"w:val": 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:unhideWhenUsed": EMPTY_OBJECT,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should add character spacing", () => {
|
||||
const style = new CharacterStyle({
|
||||
id: "myStyleId",
|
||||
@ -412,6 +453,66 @@ describe("CharacterStyle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#emphasisMark", () => {
|
||||
it("should set emphasisMark to 'dot' if no arguments are given", () => {
|
||||
const style = new CharacterStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
emphasisMark: {},
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
|
||||
},
|
||||
{
|
||||
"w:uiPriority": {
|
||||
_attr: {
|
||||
"w:val": 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:unhideWhenUsed": EMPTY_OBJECT,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style if given", () => {
|
||||
const style = new CharacterStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
emphasisMark: {
|
||||
type: EmphasisMarkType.DOT,
|
||||
},
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
|
||||
},
|
||||
{
|
||||
"w:uiPriority": {
|
||||
_attr: {
|
||||
"w:val": 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:unhideWhenUsed": EMPTY_OBJECT,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("#superScript", () => {
|
||||
const style = new CharacterStyle({
|
||||
id: "myStyleId",
|
||||
@ -616,7 +717,17 @@ describe("CharacterStyle", () => {
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "character", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:shd": {
|
||||
_attr: {
|
||||
"w:val": "pct10",
|
||||
"w:fill": "00FFFF",
|
||||
"w:color": "FF0000",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:uiPriority": {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { EmphasisMarkType } from "file/paragraph/run/emphasis-mark";
|
||||
import * as formatting from "file/paragraph/run/formatting";
|
||||
import { RunProperties } from "file/paragraph/run/properties";
|
||||
import { IFontAttributesProperties } from "file/paragraph/run/run-fonts";
|
||||
import { UnderlineType } from "file/paragraph/run/underline";
|
||||
|
||||
import { BasedOn, Link, SemiHidden, UiPriority, UnhideWhenUsed } from "./components";
|
||||
@ -23,8 +25,11 @@ export interface IBaseCharacterStyleOptions {
|
||||
readonly type?: UnderlineType;
|
||||
readonly color?: string;
|
||||
};
|
||||
readonly emphasisMark?: {
|
||||
readonly type?: EmphasisMarkType;
|
||||
};
|
||||
readonly color?: string;
|
||||
readonly font?: string;
|
||||
readonly font?: string | IFontAttributesProperties;
|
||||
readonly characterSpacing?: number;
|
||||
readonly highlight?: string;
|
||||
readonly shadow?: {
|
||||
@ -104,6 +109,10 @@ export class CharacterStyle extends Style {
|
||||
this.runProperties.push(new formatting.Underline(options.run.underline.type, options.run.underline.color));
|
||||
}
|
||||
|
||||
if (options.run.emphasisMark) {
|
||||
this.runProperties.push(new formatting.EmphasisMark(options.run.emphasisMark.type));
|
||||
}
|
||||
|
||||
if (options.run.color) {
|
||||
this.runProperties.push(new formatting.Color(options.run.color));
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
import { AlignmentType, TabStopPosition } from "file/paragraph";
|
||||
import { AlignmentType, EmphasisMarkType, TabStopPosition } from "file/paragraph";
|
||||
import { UnderlineType } from "file/paragraph/run/underline";
|
||||
import { ShadingType } from "file/table";
|
||||
import { EMPTY_OBJECT } from "file/xml-components";
|
||||
@ -49,7 +49,15 @@ describe("ParagraphStyle", () => {
|
||||
const style = new ParagraphStyle({ id: "myStyleId", quickFormat: true });
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:qFormat": EMPTY_OBJECT }],
|
||||
"w:style": [
|
||||
{
|
||||
_attr: {
|
||||
"w:type": "paragraph",
|
||||
"w:styleId": "myStyleId",
|
||||
},
|
||||
},
|
||||
{ "w:qFormat": EMPTY_OBJECT },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@ -220,6 +228,32 @@ describe("ParagraphStyle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("#contextualSpacing", () => {
|
||||
const style = new ParagraphStyle({
|
||||
id: "myStyleId",
|
||||
paragraph: {
|
||||
contextualSpacing: true,
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:pPr": [
|
||||
{
|
||||
"w:contextualSpacing": {
|
||||
_attr: {
|
||||
"w:val": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#leftTabStop", () => {
|
||||
const style = new ParagraphStyle({
|
||||
id: "myStyleId",
|
||||
@ -273,7 +307,15 @@ describe("ParagraphStyle", () => {
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }] }],
|
||||
"w:style": [
|
||||
{
|
||||
_attr: {
|
||||
"w:type": "paragraph",
|
||||
"w:styleId": "myStyleId",
|
||||
},
|
||||
},
|
||||
{ "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }] },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@ -286,7 +328,15 @@ describe("ParagraphStyle", () => {
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }] }],
|
||||
"w:style": [
|
||||
{
|
||||
_attr: {
|
||||
"w:type": "paragraph",
|
||||
"w:styleId": "myStyleId",
|
||||
},
|
||||
},
|
||||
{ "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }] },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@ -434,7 +484,7 @@ describe("ParagraphStyle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("#font", () => {
|
||||
it("#font by name", () => {
|
||||
const style = new ParagraphStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
@ -447,7 +497,46 @@ describe("ParagraphStyle", () => {
|
||||
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [
|
||||
{ "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } },
|
||||
{
|
||||
"w:rFonts": {
|
||||
_attr: {
|
||||
"w:ascii": "Times",
|
||||
"w:cs": "Times",
|
||||
"w:eastAsia": "Times",
|
||||
"w:hAnsi": "Times",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#font for ascii and eastAsia", () => {
|
||||
const style = new ParagraphStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
font: {
|
||||
ascii: "Times",
|
||||
eastAsia: "KaiTi",
|
||||
},
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:rFonts": {
|
||||
_attr: {
|
||||
"w:ascii": "Times",
|
||||
"w:eastAsia": "KaiTi",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -524,7 +613,17 @@ describe("ParagraphStyle", () => {
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
|
||||
"w:rPr": [
|
||||
{
|
||||
"w:shd": {
|
||||
_attr: {
|
||||
"w:val": "pct10",
|
||||
"w:fill": "00FFFF",
|
||||
"w:color": "FF0000",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -591,6 +690,46 @@ describe("ParagraphStyle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#emphasisMark", () => {
|
||||
it("should set emphasisMark to 'dot' if no arguments are given", () => {
|
||||
const style = new ParagraphStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
emphasisMark: {},
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the style if given", () => {
|
||||
const style = new ParagraphStyle({
|
||||
id: "myStyleId",
|
||||
run: {
|
||||
emphasisMark: {
|
||||
type: EmphasisMarkType.DOT,
|
||||
},
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } },
|
||||
{
|
||||
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("#color", () => {
|
||||
const style = new ParagraphStyle({
|
||||
id: "myStyleId",
|
||||
@ -613,7 +752,15 @@ describe("ParagraphStyle", () => {
|
||||
const style = new ParagraphStyle({ id: "myStyleId", link: "MyLink" });
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:link": { _attr: { "w:val": "MyLink" } } }],
|
||||
"w:style": [
|
||||
{
|
||||
_attr: {
|
||||
"w:type": "paragraph",
|
||||
"w:styleId": "myStyleId",
|
||||
},
|
||||
},
|
||||
{ "w:link": { _attr: { "w:val": "MyLink" } } },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@ -621,7 +768,15 @@ describe("ParagraphStyle", () => {
|
||||
const style = new ParagraphStyle({ id: "myStyleId", semiHidden: true });
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:semiHidden": EMPTY_OBJECT }],
|
||||
"w:style": [
|
||||
{
|
||||
_attr: {
|
||||
"w:type": "paragraph",
|
||||
"w:styleId": "myStyleId",
|
||||
},
|
||||
},
|
||||
{ "w:semiHidden": EMPTY_OBJECT },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
@ -646,7 +801,15 @@ describe("ParagraphStyle", () => {
|
||||
const style = new ParagraphStyle({ id: "myStyleId", unhideWhenUsed: true });
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:unhideWhenUsed": EMPTY_OBJECT }],
|
||||
"w:style": [
|
||||
{
|
||||
_attr: {
|
||||
"w:type": "paragraph",
|
||||
"w:styleId": "myStyleId",
|
||||
},
|
||||
},
|
||||
{ "w:unhideWhenUsed": EMPTY_OBJECT },
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
import {
|
||||
Alignment,
|
||||
AlignmentType,
|
||||
ContextualSpacing,
|
||||
Indent,
|
||||
ISpacingProperties,
|
||||
KeepLines,
|
||||
KeepNext,
|
||||
OutlineLevel,
|
||||
@ -10,12 +9,11 @@ import {
|
||||
Spacing,
|
||||
ThematicBreak,
|
||||
} from "file/paragraph";
|
||||
import { IIndentAttributesProperties, TabStop, TabStopType } from "file/paragraph/formatting";
|
||||
import { TabStop, TabStopType } from "file/paragraph/formatting";
|
||||
import * as formatting from "file/paragraph/run/formatting";
|
||||
import { RunProperties } from "file/paragraph/run/properties";
|
||||
import { UnderlineType } from "file/paragraph/run/underline";
|
||||
import { ShadingType } from "file/table";
|
||||
|
||||
import { IParagraphStyleOptions2, IRunStyleOptions } from "../style-options";
|
||||
import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components";
|
||||
import { Style } from "./style";
|
||||
|
||||
@ -27,41 +25,8 @@ export interface IBaseParagraphStyleOptions {
|
||||
readonly semiHidden?: boolean;
|
||||
readonly uiPriority?: number;
|
||||
readonly unhideWhenUsed?: boolean;
|
||||
readonly run?: {
|
||||
readonly size?: number;
|
||||
readonly bold?: boolean;
|
||||
readonly italics?: boolean;
|
||||
readonly smallCaps?: boolean;
|
||||
readonly allCaps?: boolean;
|
||||
readonly strike?: boolean;
|
||||
readonly doubleStrike?: boolean;
|
||||
readonly subScript?: boolean;
|
||||
readonly superScript?: boolean;
|
||||
readonly underline?: {
|
||||
readonly type?: UnderlineType;
|
||||
readonly color?: string;
|
||||
};
|
||||
readonly color?: string;
|
||||
readonly font?: string;
|
||||
readonly characterSpacing?: number;
|
||||
readonly highlight?: string;
|
||||
readonly shadow?: {
|
||||
readonly type: ShadingType;
|
||||
readonly fill: string;
|
||||
readonly color: string;
|
||||
};
|
||||
};
|
||||
readonly paragraph?: {
|
||||
readonly alignment?: AlignmentType;
|
||||
readonly thematicBreak?: boolean;
|
||||
readonly rightTabStop?: number;
|
||||
readonly leftTabStop?: number;
|
||||
readonly indent?: IIndentAttributesProperties;
|
||||
readonly spacing?: ISpacingProperties;
|
||||
readonly keepNext?: boolean;
|
||||
readonly keepLines?: boolean;
|
||||
readonly outlineLevel?: number;
|
||||
};
|
||||
readonly run?: IRunStyleOptions;
|
||||
readonly paragraph?: IParagraphStyleOptions2;
|
||||
}
|
||||
|
||||
export interface IParagraphStyleOptions extends IBaseParagraphStyleOptions {
|
||||
@ -149,6 +114,10 @@ export class ParagraphStyle extends Style {
|
||||
this.runProperties.push(new formatting.Underline(options.run.underline.type, options.run.underline.color));
|
||||
}
|
||||
|
||||
if (options.run.emphasisMark) {
|
||||
this.runProperties.push(new formatting.EmphasisMark(options.run.emphasisMark.type));
|
||||
}
|
||||
|
||||
if (options.run.color) {
|
||||
this.runProperties.push(new formatting.Color(options.run.color));
|
||||
}
|
||||
@ -179,6 +148,10 @@ export class ParagraphStyle extends Style {
|
||||
this.paragraphProperties.push(new ThematicBreak());
|
||||
}
|
||||
|
||||
if (options.paragraph.contextualSpacing) {
|
||||
this.paragraphProperties.push(new ContextualSpacing(options.paragraph.contextualSpacing));
|
||||
}
|
||||
|
||||
if (options.paragraph.rightTabStop) {
|
||||
this.paragraphProperties.push(new TabStop(TabStopType.RIGHT, options.paragraph.rightTabStop));
|
||||
}
|
||||
|
@ -158,6 +158,31 @@ export class VAlign extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export enum TextDirection {
|
||||
BOTTOM_TO_TOP_LEFT_TO_RIGHT = "btLr",
|
||||
LEFT_TO_RIGHT_TOP_TO_BOTTOM = "lrTb",
|
||||
TOP_TO_BOTTOM_RIGHT_TO_LEFT = "tbRl",
|
||||
}
|
||||
|
||||
class TDirectionAttributes extends XmlAttributeComponent<{ readonly val: TextDirection }> {
|
||||
protected readonly xmlKeys = { val: "w:val" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Text Direction within a table cell
|
||||
*/
|
||||
export class TDirection extends XmlComponent {
|
||||
constructor(value: TextDirection) {
|
||||
super("w:textDirection");
|
||||
|
||||
this.root.push(
|
||||
new TDirectionAttributes({
|
||||
val: value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export enum WidthType {
|
||||
/** Auto. */
|
||||
AUTO = "auto",
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
GridSpan,
|
||||
TableCellBorders,
|
||||
TableCellWidth,
|
||||
TDirection,
|
||||
TextDirection,
|
||||
VAlign,
|
||||
VerticalAlign,
|
||||
VerticalMerge,
|
||||
@ -61,4 +63,10 @@ export class TableCellProperties extends IgnoreIfEmptyXmlComponent {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setTextDirection(type: TextDirection): TableCellProperties {
|
||||
this.root.push(new TDirection(type));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { BorderStyle } from "file/styles";
|
||||
|
||||
import { ShadingType } from "../shading";
|
||||
import { TableCell } from "./table-cell";
|
||||
import { TableCellBorders, TableCellWidth, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components";
|
||||
import { TableCellBorders, TableCellWidth, TextDirection, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components";
|
||||
|
||||
describe("TableCellBorders", () => {
|
||||
describe("#prepForXml", () => {
|
||||
@ -271,6 +271,34 @@ describe("TableCell", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with text direction", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
textDirection: TextDirection.BOTTOM_TO_TOP_LEFT_TO_RIGHT,
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(cell);
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tc": [
|
||||
{
|
||||
"w:tcPr": [
|
||||
{
|
||||
"w:textDirection": {
|
||||
_attr: {
|
||||
"w:val": "btLr",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:p": {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with vertical merge", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
@ -395,6 +423,33 @@ describe("TableCell", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with width", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
width: { size: 100, type: WidthType.DXA },
|
||||
});
|
||||
const tree = new Formatter().format(cell);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:tc": [
|
||||
{
|
||||
"w:tcPr": [
|
||||
{
|
||||
"w:tcW": {
|
||||
_attr: {
|
||||
"w:type": "dxa",
|
||||
"w:w": 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"w:p": {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should create with column span", () => {
|
||||
const cell = new TableCell({
|
||||
children: [],
|
||||
|
@ -3,17 +3,23 @@ import { Paragraph } from "file/paragraph";
|
||||
import { BorderStyle } from "file/styles";
|
||||
import { IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
|
||||
import { File } from "../../file";
|
||||
import { ITableShadingAttributesProperties } from "../shading";
|
||||
import { Table } from "../table";
|
||||
import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins";
|
||||
import { VerticalAlign, VerticalMergeType } from "./table-cell-components";
|
||||
import { TextDirection, VerticalAlign, VerticalMergeType, WidthType } from "./table-cell-components";
|
||||
import { TableCellProperties } from "./table-cell-properties";
|
||||
|
||||
export interface ITableCellOptions {
|
||||
readonly shading?: ITableShadingAttributesProperties;
|
||||
readonly margins?: ITableCellMarginOptions;
|
||||
readonly verticalAlign?: VerticalAlign;
|
||||
readonly textDirection?: TextDirection;
|
||||
readonly verticalMerge?: VerticalMergeType;
|
||||
readonly width?: {
|
||||
readonly size: number | string;
|
||||
readonly type?: WidthType;
|
||||
};
|
||||
readonly columnSpan?: number;
|
||||
readonly rowSpan?: number;
|
||||
readonly borders?: {
|
||||
@ -58,8 +64,15 @@ export class TableCell extends XmlComponent {
|
||||
this.properties.setVerticalAlign(options.verticalAlign);
|
||||
}
|
||||
|
||||
if (options.textDirection) {
|
||||
this.properties.setTextDirection(options.textDirection);
|
||||
}
|
||||
|
||||
if (options.verticalMerge) {
|
||||
this.properties.addVerticalMerge(options.verticalMerge);
|
||||
} else if (options.rowSpan && options.rowSpan > 1) {
|
||||
// if cell already have a `verticalMerge`, don't handle `rowSpan`
|
||||
this.properties.addVerticalMerge(VerticalMergeType.RESTART);
|
||||
}
|
||||
|
||||
if (options.margins) {
|
||||
@ -74,8 +87,8 @@ export class TableCell extends XmlComponent {
|
||||
this.properties.addGridSpan(options.columnSpan);
|
||||
}
|
||||
|
||||
if (options.rowSpan && options.rowSpan > 1) {
|
||||
this.properties.addVerticalMerge(VerticalMergeType.RESTART);
|
||||
if (options.width) {
|
||||
this.properties.setWidth(options.width.size, options.width.type);
|
||||
}
|
||||
|
||||
if (options.borders) {
|
||||
@ -102,11 +115,11 @@ export class TableCell extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public prepForXml(): IXmlableObject | undefined {
|
||||
public prepForXml(file?: File): IXmlableObject | undefined {
|
||||
// Cells must end with a paragraph
|
||||
if (!(this.root[this.root.length - 1] instanceof Paragraph)) {
|
||||
this.root.push(new Paragraph({}));
|
||||
}
|
||||
return super.prepForXml();
|
||||
return super.prepForXml(file);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user