Merge branch 'master' into feat/right-indent

# Conflicts:
#	demo/21-bookmarks.ts
#	package-lock.json
#	src/file/core-properties/properties.ts
#	src/file/file.spec.ts
#	src/file/file.ts
#	src/file/footnotes/footnote/footnote.spec.ts
#	src/file/footnotes/footnote/footnote.ts
#	src/file/footnotes/footnotes.ts
This commit is contained in:
Dolan
2021-03-08 03:40:11 +00:00
278 changed files with 11084 additions and 3779 deletions

View File

@ -7,6 +7,7 @@ indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.md]
max_line_length = off

104
.github/workflows/default.yml vendored Normal file
View File

@ -0,0 +1,104 @@
name: Default
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci
- name: Build
run: npm run build
- name: Archive Production Artifact
uses: actions/upload-artifact@master
with:
name: build
path: build
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci
- name: Test
run: npm run test.coverage
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci
- name: Lint
run: npm run lint
prettier:
name: Prettier
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci
- name: Prettier
run: npm run style
demos:
name: Run Demos
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@master
- name: Install Dependencies
run: npm ci
- name: Download Artifact
uses: actions/download-artifact@master
with:
name: build
path: build
- name: Run demos
run: |
npm run ts-node -- ./demo/1-basic.ts
npm run ts-node -- ./demo/2-declaritive-styles.ts
npm run ts-node -- ./demo/3-numbering-and-bullet-points.ts
npm run ts-node -- ./demo/4-basic-table.ts
npm run ts-node -- ./demo/5-images.ts
npm run ts-node -- ./demo/6-page-borders.ts
npm run ts-node -- ./demo/7-landscape.ts
npm run ts-node -- ./demo/8-header-footer.ts
npm run ts-node -- ./demo/9-images-in-header-and-footer.ts
npm run ts-node -- ./demo/10-my-cv.ts
npm run ts-node -- ./demo/11-declaritive-styles-2.ts
npm run ts-node -- ./demo/12-scaling-images.ts
npm run ts-node -- ./demo/13-xml-styles.ts
npm run ts-node -- ./demo/14-page-numbers.ts
npm run ts-node -- ./demo/15-page-break-before.ts
npm run ts-node -- ./demo/16-multiple-sections.ts
npm run ts-node -- ./demo/17-footnotes.ts
npm run ts-node -- ./demo/18-image-from-buffer.ts
npm run ts-node -- ./demo/19-export-to-base64.ts
npm run ts-node -- ./demo/20-table-cell-borders.ts
npm run ts-node -- ./demo/21-bookmarks.ts
npm run ts-node -- ./demo/22-right-to-left-text.ts
npm run ts-node -- ./demo/23-base64-images.ts
npm run ts-node -- ./demo/24-images-to-table-cell.ts
npm run ts-node -- ./demo/26-paragraph-borders.ts
npm run ts-node -- ./demo/27-declaritive-styles-3.ts
npm run ts-node -- ./demo/28-table-of-contents.ts
npm run ts-node -- ./demo/29-numbered-lists.ts
npm run ts-node -- ./demo/30-template-document.ts
npm run ts-node -- ./demo/31-tables.ts
npm run ts-node -- ./demo/32-merge-and-shade-table-cells.ts
npm run ts-node -- ./demo/33-sequential-captions.ts
npm run ts-node -- ./demo/34-floating-tables.ts

2
.nvmrc
View File

@ -1 +1 @@
v8
v10

11
.nycrc
View File

@ -1,14 +1,15 @@
{
"check-coverage": true,
"lines": 93.53,
"functions": 89.63,
"branches": 88.57,
"statements": 93.34,
"lines": 97.77,
"functions": 93.89,
"branches": 94.83,
"statements": 97.75,
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts"
"src/**/*.spec.ts",
"src/import-dotx/import-dotx.ts"
],
"reporter": [
"lcov",

View File

@ -1,6 +1,6 @@
language: node_js
node_js:
- 9
- 10
install:
- npm install
- npm install -g codecov
@ -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

View File

@ -11,6 +11,7 @@
[![NPM version][npm-image]][npm-url]
[![Downloads per month][downloads-image]][downloads-url]
[![Build Status][travis-image]][travis-url]
[![GitHub Action Workflow Status][github-actions-workflow-image]][github-actions-workflow-url]
[![Dependency Status][daviddm-image]][daviddm-url]
[![Known Vulnerabilities][snky-image]][snky-url]
[![Chat on Gitter][gitter-image]][gitter-url]
@ -27,13 +28,22 @@
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`:
* https://stackblitz.com/edit/angular-afvxtz
Here is an example of `docx` working in `React`:
* https://stackblitz.com/edit/react-ts-qq25sp
* https://stackblitz.com/edit/react-ts-qdqu7z (adding images to Word Document)
Here is an example of `docx` working in `Vue.js`:
* https://stackblitz.com/edit/vuejs-docx
## Node
Press `endpoint` on the `RunKit` website:
@ -50,7 +60,7 @@ Press `endpoint` on the `RunKit` website:
* https://runkit.com/dolanmiu/docx-demo8 - Header and Footer
* https://runkit.com/dolanmiu/docx-demo10 - **My CV generated with docx**
More [here](https://docx.js.org/#/examples) and [here](https://github.com/dolanmiu/docx/tree/master/demo)
More [here](https://github.com/dolanmiu/docx/tree/master/demo)
# How to use & Documentation
@ -58,7 +68,7 @@ Please refer to the [documentation at https://docx.js.org/](https://docx.js.org/
# Examples
Check the `examples` section in the [documentation](https://docx.js.org/#/examples) and the [demo folder](https://github.com/dolanmiu/docx/tree/master/demo) for examples.
Check the [demo folder](https://github.com/dolanmiu/docx/tree/master/demo) for examples.
# Contributing
@ -74,7 +84,11 @@ Read the contribution guidelines [here](https://docx.js.org/#/contribution-guide
[<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/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/)
[<img src="https://i.imgur.com/cmykN7c.png" alt="drawing" />](https://www.arity.co/)
[<img src="https://i.imgur.com/PXo25um.png" alt="drawing" height="50"/>](https://www.circadianrisk.com/)
...and many more!
@ -91,6 +105,8 @@ Made with 💖
[downloads-url]: https://npmjs.org/package/docx
[travis-image]: https://travis-ci.org/dolanmiu/docx.svg?branch=master
[travis-url]: https://travis-ci.org/dolanmiu/docx
[github-actions-workflow-image]: https://github.com/dolanmiu/docx/workflows/Default/badge.svg
[github-actions-workflow-url]: https://github.com/dolanmiu/docx/actions
[daviddm-image]: https://david-dm.org/dolanmiu/docx.svg?theme=shields.io
[daviddm-url]: https://david-dm.org/dolanmiu/docx
[snky-image]: https://snyk.io/test/github/dolanmiu/docx/badge.svg

View File

@ -204,7 +204,10 @@ class DocumentCreator {
alignment: AlignmentType.CENTER,
children: [
new TextRun(`Mobile: ${phoneNumber} | LinkedIn: ${profileUrl} | Email: ${email}`),
new TextRun("Address: 58 Elm Avenue, Kent ME4 6ER, UK").break(),
new TextRun({
text: "Address: 58 Elm Avenue, Kent ME4 6ER, UK",
break: 1,
}),
],
});
}

View File

@ -3,6 +3,7 @@
import * as fs from "fs";
import {
AlignmentType,
convertInchesToTwip,
Document,
Footer,
HeadingLevel,
@ -18,13 +19,8 @@ import {
const doc = new Document({
styles: {
paragraphStyles: [
{
id: "Heading1",
name: "Heading 1",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
default: {
heading1: {
run: {
font: "Calibri",
size: 52,
@ -40,12 +36,7 @@ const doc = new Document({
spacing: { line: 340 },
},
},
{
id: "Heading2",
name: "Heading 2",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
heading2: {
run: {
font: "Calibri",
size: 26,
@ -55,12 +46,7 @@ const doc = new Document({
spacing: { line: 340 },
},
},
{
id: "Heading3",
name: "Heading 3",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
heading3: {
run: {
font: "Calibri",
size: 26,
@ -70,12 +56,7 @@ const doc = new Document({
spacing: { line: 276 },
},
},
{
id: "Heading4",
name: "Heading 4",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
heading4: {
run: {
font: "Calibri",
size: 26,
@ -85,6 +66,8 @@ const doc = new Document({
alignment: AlignmentType.JUSTIFIED,
},
},
},
paragraphStyles: [
{
id: "normalPara",
name: "Normal Para",
@ -128,7 +111,7 @@ const doc = new Document({
},
paragraph: {
spacing: { line: 276 },
indent: { left: 720 },
indent: { left: convertInchesToTwip(0.5) },
},
},
{
@ -139,12 +122,6 @@ const doc = new Document({
spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 },
},
},
{
id: "ListParagraph",
name: "List Paragraph",
basedOn: "Normal",
quickFormat: true,
},
],
},
});

View File

@ -1,20 +1,25 @@
// 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 { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build";
import {
AlignmentType,
convertInchesToTwip,
Document,
HeadingLevel,
LevelFormat,
Packer,
Paragraph,
TextRun,
UnderlineType,
} from "../build";
const doc = new Document({
creator: "Clippy",
title: "Sample Document",
description: "A brief example of using docx",
styles: {
paragraphStyles: [
{
id: "Heading1",
name: "Heading 1",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
default: {
heading1: {
run: {
size: 28,
bold: true,
@ -27,12 +32,7 @@ const doc = new Document({
},
},
},
{
id: "Heading2",
name: "Heading 2",
basedOn: "Normal",
next: "Normal",
quickFormat: true,
heading2: {
run: {
size: 26,
bold: true,
@ -48,6 +48,13 @@ const doc = new Document({
},
},
},
listParagraph: {
run: {
color: "#FF0000",
},
},
},
paragraphStyles: [
{
id: "aside",
name: "Aside",
@ -59,7 +66,7 @@ const doc = new Document({
},
paragraph: {
indent: {
left: 720,
left: convertInchesToTwip(0.5),
},
spacing: {
line: 276,
@ -75,12 +82,6 @@ const doc = new Document({
spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 },
},
},
{
id: "ListParagraph",
name: "List Paragraph",
basedOn: "Normal",
quickFormat: true,
},
],
},
numbering: {
@ -90,7 +91,7 @@ const doc = new Document({
levels: [
{
level: 0,
format: "lowerLetter",
format: LevelFormat.LOWER_LETTER,
text: "%1)",
alignment: AlignmentType.LEFT,
},
@ -161,11 +162,26 @@ doc.addSection({
text: "and then underlined ",
underline: {},
}),
new TextRun({
text: "and then emphasis-mark ",
emphasisMark: {},
}),
new TextRun({
text: "and back to normal.",
}),
],
}),
new Paragraph({
style: "Strong",
children: [
new TextRun({
text: "Strong Style",
}),
new TextRun({
text: " - Very strong.",
}),
],
}),
],
});

View File

@ -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 { Bookmark, Document, HeadingLevel, HyperlinkRef, HyperlinkType, Packer, PageBreak, Paragraph, TextRun } from "../build";
import { Bookmark, Document, Footer, HeadingLevel, InternalHyperlink, Packer, PageBreak, Paragraph, TextRun } 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,15 +10,26 @@ 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,
},
},
});
doc.addSection({
footers: {
default: new Footer({
children: [
new Paragraph({
children: [
new InternalHyperlink({
child: new TextRun({
text: "Click here!",
style: "Hyperlink",
}),
anchor: "myAnchorId",
}),
],
}),
],
}),
},
children: [
new Paragraph({
heading: HeadingLevel.HEADING_1,
@ -35,7 +46,15 @@ doc.addSection({
children: [new PageBreak()],
}),
new Paragraph({
children: [new HyperlinkRef("myAnchorId")],
children: [
new InternalHyperlink({
child: new TextRun({
text: "Anchor Text",
style: "Hyperlink",
}),
anchor: "myAnchorId",
}),
],
}),
],
});

View File

@ -1,7 +1,7 @@
// This demo shows right to left for special languages
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, Paragraph, TextRun } from "../build";
import { Document, Packer, Paragraph, Table, TableCell, TableRow, TextRun } from "../build";
const doc = new Document();
@ -36,6 +36,31 @@ doc.addSection({
}),
],
}),
new Table({
visuallyRightToLeft: true,
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("שלום עולם")],
}),
new TableCell({
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [],
}),
new TableCell({
children: [new Paragraph("שלום עולם")],
}),
],
}),
],
}),
],
});

View File

@ -1,7 +1,7 @@
// Custom styles using JavaScript configuration
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph, UnderlineType } from "../build";
import { Document, convertInchesToTwip, HeadingLevel, Packer, Paragraph, UnderlineType } from "../build";
const doc = new Document({
styles: {
@ -17,7 +17,7 @@ const doc = new Document({
},
paragraph: {
indent: {
left: 720,
left: convertInchesToTwip(0.5),
},
spacing: {
line: 276,

View File

@ -1,7 +1,7 @@
// Numbered lists
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { AlignmentType, Document, Packer, Paragraph } from "../build";
import { AlignmentType, convertInchesToTwip, Document, LevelFormat, Packer, Paragraph } from "../build";
const doc = new Document({
numbering: {
@ -10,12 +10,12 @@ const doc = new Document({
levels: [
{
level: 0,
format: "upperRoman",
format: LevelFormat.UPPER_ROMAN,
text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.18) },
},
},
},
@ -26,18 +26,34 @@ const doc = new Document({
levels: [
{
level: 0,
format: "decimal",
format: LevelFormat.DECIMAL,
text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.18) },
},
},
},
],
reference: "my-number-numbering-reference",
},
{
levels: [
{
level: 0,
format: LevelFormat.DECIMAL_ZERO,
text: "[%1]",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.18) },
},
},
},
],
reference: "padded-numbering-reference",
},
],
},
});
@ -109,6 +125,139 @@ doc.addSection({
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "test",
numbering: {
reference: "padded-numbering-reference",
level: 0,
},
}),
],
});

View File

@ -1,7 +1,7 @@
// Numbering and bullet points example
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { AlignmentType, Document, Packer, Paragraph } from "../build";
import { AlignmentType, convertInchesToTwip, Document, LevelFormat, Packer, Paragraph } from "../build";
const doc = new Document({
numbering: {
@ -11,40 +11,40 @@ const doc = new Document({
levels: [
{
level: 0,
format: "upperRoman",
format: LevelFormat.UPPER_ROMAN,
text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.18) },
},
},
},
{
level: 1,
format: "decimal",
format: LevelFormat.DECIMAL,
text: "%2.",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 1440, hanging: 980 },
indent: { left: convertInchesToTwip(1), hanging: convertInchesToTwip(0.68) },
},
},
},
{
level: 2,
format: "lowerLetter",
format: LevelFormat.LOWER_LETTER,
text: "%3)",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 2160, hanging: 1700 },
indent: { left: convertInchesToTwip(1.5), hanging: convertInchesToTwip(1.18) },
},
},
},
{
level: 3,
format: "upperLetter",
format: LevelFormat.UPPER_LETTER,
text: "%4)",
alignment: AlignmentType.START,
style: {

View File

@ -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,
}),
],
}),
],

View File

@ -2,7 +2,20 @@
// Also includes an example on how to center tables
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
import {
AlignmentType,
BorderStyle,
convertInchesToTwip,
Document,
HeadingLevel,
Packer,
Paragraph,
ShadingType,
Table,
TableCell,
TableRow,
WidthType,
} from "../build";
const doc = new Document();
@ -37,10 +50,10 @@ const table2 = new Table({
new TableCell({
children: [new Paragraph("World")],
margins: {
top: 1000,
bottom: 1000,
left: 1000,
right: 1000,
top: convertInchesToTwip(0.69),
bottom: convertInchesToTwip(0.69),
left: convertInchesToTwip(0.69),
right: convertInchesToTwip(0.69),
},
columnSpan: 3,
}),
@ -64,7 +77,7 @@ const table2 = new Table({
size: 100,
type: WidthType.AUTO,
},
columnWidths: [1000, 1000, 1000],
columnWidths: [convertInchesToTwip(0.69), convertInchesToTwip(0.69), convertInchesToTwip(0.69)],
});
const table3 = new Table({
@ -119,14 +132,14 @@ const table3 = new Table({
}),
],
width: {
size: 7000,
size: convertInchesToTwip(4.86),
type: WidthType.DXA,
},
margins: {
top: 400,
bottom: 400,
right: 400,
left: 400,
top: convertInchesToTwip(0.27),
bottom: convertInchesToTwip(0.27),
right: convertInchesToTwip(0.27),
left: convertInchesToTwip(0.27),
},
});
@ -184,7 +197,7 @@ const table5 = new Table({
new TableRow({
children: [
new TableCell({
children: [],
children: [new Paragraph("1,0")],
}),
new TableCell({
children: [new Paragraph("1,2")],
@ -195,10 +208,10 @@ const table5 = new Table({
new TableRow({
children: [
new TableCell({
children: [],
children: [new Paragraph("2,0")],
}),
new TableCell({
children: [],
children: [new Paragraph("2,1")],
}),
],
}),
@ -209,6 +222,161 @@ 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,
@ -222,10 +390,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,
],
});

View File

@ -1,22 +1,91 @@
// 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, HyperlinkRef, HyperlinkType, Packer, Paragraph } from "../build";
import { Document, ExternalHyperlink, Footer, FootnoteReferenceRun, Media, Packer, Paragraph, TextRun } from "../build";
const doc = new Document({
hyperlinks: {
myCoolLink: {
footnotes: [
new Paragraph({
children: [
new TextRun("Click here for the "),
new ExternalHyperlink({
child: new TextRun({
text: "Footnotes external hyperlink",
style: "Hyperlink",
}),
link: "http://www.example.com",
text: "Hyperlink",
type: HyperlinkType.EXTERNAL,
},
},
}),
],
}),
],
});
const image1 = Media.addImage(doc, fs.readFileSync("./demo/images/image1.jpeg"));
doc.addSection({
footers: {
default: new Footer({
children: [
new Paragraph({
children: [new HyperlinkRef("myCoolLink")],
children: [
new TextRun("Click here for the "),
new ExternalHyperlink({
child: new TextRun({
text: "Footer external hyperlink",
style: "Hyperlink",
}),
link: "http://www.example.com",
}),
],
}),
],
}),
},
headers: {
default: new Footer({
children: [
new Paragraph({
children: [
new TextRun("Click here for the "),
new ExternalHyperlink({
child: new TextRun({
text: "Header external hyperlink",
style: "Hyperlink",
}),
link: "http://www.google.com",
}),
],
}),
],
}),
},
children: [
new Paragraph({
children: [
new ExternalHyperlink({
child: new TextRun({
text: "Anchor Text",
style: "Hyperlink",
}),
link: "http://www.example.com",
}),
new FootnoteReferenceRun(1)
],
}),
new Paragraph({
children: [
new ExternalHyperlink({
child: image1,
link: "http://www.google.com",
}),
new ExternalHyperlink({
child: new TextRun({
text: "BBC News Link",
style: "Hyperlink",
}),
link: "https://www.bbc.co.uk/news",
}),
],
}),
],
});

View File

@ -1,11 +1,95 @@
// 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, Packer, Paragraph, Table, TableCell, TableRow } from "../build";
import { Document, Packer, Paragraph, Table, TableCell, TableRow, WidthType } from "../build";
const doc = new Document();
const table = new Table({
columnWidths: [3505, 5505],
rows: [
new TableRow({
children: [
new TableCell({
width: {
size: 3505,
type: WidthType.DXA,
},
children: [new Paragraph("Hello")],
}),
new TableCell({
width: {
size: 5505,
type: WidthType.DXA,
},
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
width: {
size: 3505,
type: WidthType.DXA,
},
children: [],
}),
new TableCell({
width: {
size: 5505,
type: WidthType.DXA,
},
children: [new Paragraph("World")],
}),
],
}),
],
});
const table2 = new Table({
columnWidths: [4505, 4505],
rows: [
new TableRow({
children: [
new TableCell({
width: {
size: 4505,
type: WidthType.DXA,
},
children: [new Paragraph("Hello")],
}),
new TableCell({
width: {
size: 4505,
type: WidthType.DXA,
},
children: [],
}),
],
}),
new TableRow({
children: [
new TableCell({
width: {
size: 4505,
type: WidthType.DXA,
},
children: [],
}),
new TableCell({
width: {
size: 4505,
type: WidthType.DXA,
},
children: [new Paragraph("World")],
}),
],
}),
],
});
const table3 = new Table({
rows: [
new TableRow({
children: [
@ -31,7 +115,14 @@ const table = new Table({
});
doc.addSection({
children: [table],
children: [
new Paragraph({ text: "Table with skewed widths" }),
table,
new Paragraph({ text: "Table with equal widths" }),
table2,
new Paragraph({ text: "Table without setting widths" }),
table3,
],
});
Packer.toBuffer(doc).then((buffer) => {

View File

@ -35,6 +35,22 @@ doc.addSection({
],
});
doc.addSection({
properties: {
column: {
space: 708,
count: 2,
separate: true,
},
},
children: [
new Paragraph("This text will be split into 2 columns on a page."),
new Paragraph(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -28,6 +28,18 @@ doc.addSection({
}),
],
}),
new Paragraph({
shading: {
type: ShadingType.DIAGONAL_CROSS,
color: "00FFFF",
fill: "FF0000",
},
children: [
new TextRun({
text: "Hello World for entire paragraph",
}),
],
}),
],
}),
},

View File

@ -0,0 +1,50 @@
// 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, Packer, Paragraph, Table, TableCell, TableRow, WidthType } from "../build";
const styles = fs.readFileSync("./demo/assets/custom-styles.xml", "utf-8");
const doc = new Document({
title: "Title",
externalStyles: styles
});
// Create a table and pass the XML Style
const table = new Table({
style: 'MyCustomTableStyle',
width: {
size: 9070,
type: WidthType.DXA
},
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Header Colum 1")],
}),
new TableCell({
children: [new Paragraph("Header Colum 2")],
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Column Content 3")],
}),
new TableCell({
children: [new Paragraph("Column Content 2")],
}),
],
}),
],
});
doc.addSection({
children: [table],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -1,7 +1,19 @@
// Add custom borders to the table itself
// Add custom borders and no-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";
import {
BorderStyle,
Document,
HeadingLevel,
Packer,
Paragraph,
Table,
TableBorders,
TableCell,
TableRow,
TextDirection,
VerticalAlign,
} from "../build";
const doc = new Document();
@ -10,6 +22,28 @@ const table = new Table({
new TableRow({
children: [
new TableCell({
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",
},
},
children: [new Paragraph("Hello")],
}),
new TableCell({
@ -30,7 +64,103 @@ const table = new Table({
],
});
doc.addSection({ children: [table] });
// Using the no-border convenience object. It is the same as writing this manually:
// const borders = {
// top: {
// style: BorderStyle.NONE,
// size: 0,
// color: "auto",
// },
// bottom: {
// style: BorderStyle.NONE,
// size: 0,
// color: "auto",
// },
// left: {
// style: BorderStyle.NONE,
// size: 0,
// color: "auto",
// },
// right: {
// style: BorderStyle.NONE,
// size: 0,
// color: "auto",
// },
// insideHorizontal: {
// style: BorderStyle.NONE,
// size: 0,
// color: "auto",
// },
// insideVertical: {
// style: BorderStyle.NONE,
// size: 0,
// color: "auto",
// },
// };
const noBorderTable = new Table({
borders: TableBorders.NONE,
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph({}), new Paragraph({})],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [new Paragraph({}), new Paragraph({})],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [new Paragraph({ text: "bottom to top" }), new Paragraph({})],
textDirection: TextDirection.BOTTOM_TO_TOP_LEFT_TO_RIGHT,
}),
new TableCell({
children: [new Paragraph({ text: "top to bottom" }), new Paragraph({})],
textDirection: TextDirection.TOP_TO_BOTTOM_RIGHT_TO_LEFT,
}),
],
}),
new TableRow({
children: [
new TableCell({
children: [
new Paragraph({
text:
"Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah",
heading: HeadingLevel.HEADING_1,
}),
],
}),
new TableCell({
children: [
new Paragraph({
text: "This text should be in the middle of the cell",
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [
new Paragraph({
text: "Text above should be vertical from bottom to top",
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
new TableCell({
children: [
new Paragraph({
text: "Text above should be vertical from top to bottom",
}),
],
verticalAlign: VerticalAlign.CENTER,
}),
],
}),
],
});
doc.addSection({ children: [table, new Paragraph("Hello"), noBorderTable] });
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);

View File

@ -22,6 +22,7 @@ const image4 = Media.addImage(doc, fs.readFileSync("./demo/images/parrots.bmp"))
const image5 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
const image6 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
floating: {
zIndex: 10,
horizontalPosition: {
offset: 1014400,
},
@ -33,6 +34,7 @@ const image6 = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"), 2
const image7 = Media.addImage(doc, fs.readFileSync("./demo/images/cat.jpg"), 200, 200, {
floating: {
zIndex: 5,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.PAGE,
align: HorizontalPositionAlign.RIGHT,

View File

@ -0,0 +1,57 @@
// 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,
},
},
{
id: "strong",
name: "Strong",
basedOn: "Normal",
run: {
bold: true,
},
},
],
},
});
doc.addSection({
children: [
new Paragraph({
children: [
new TextRun({
text: "Foo bar",
style: "myRedStyle",
}),
],
}),
new Paragraph({
children: [
new TextRun({
text: "First Word",
style: "strong",
}),
new TextRun({
text:
" - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
}),
],
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

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

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

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

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

View File

@ -0,0 +1,30 @@
// Custom Properties
// Custom properties are incredibly useful if you want to be able to apply quick parts or custom cover pages
// to the document in Word after the document has been generated. Standard properties (such as creator, title
// and subject) cover typical use cases, but sometimes custom properties are required.
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer } from "../build";
const doc = new Document(
// Standard properties
{
creator: "Creator",
title: "Title",
subject: "Subject",
description: "Description",
customProperties: [
{ name: "Subtitle", value: "Subtitle" },
{ name: "Address", value: "Address" },
],
},
// No file properties
{},
// No sections
[],
);
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

146
demo/54-track-revisions.ts Normal file
View File

@ -0,0 +1,146 @@
// Track Revisions aka. "Track Changes"
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import {
AlignmentType,
DeletedTextRun,
Document,
Footer,
FootnoteReferenceRun,
InsertedTextRun,
Packer,
PageNumber,
Paragraph,
ShadingType,
TextRun,
} from "../build";
/*
For reference, see
- https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.insertedrun
- https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.deletedrun
The method `addTrackRevisions()` adds an element `<w:trackRevisions />` to the `settings.xml` file. This specifies that the application shall track *new* revisions made to the existing document.
See also https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.trackrevisions
Note that this setting enables to track *new changes* after teh file is generated, so this example will still show inserted and deleted text runs when you remove it.
*/
const doc = new Document({
footnotes: [
new Paragraph({
children: [
new TextRun("This is a footnote"),
new DeletedTextRun({
text: " with some extra text which was deleted",
id: 0,
author: "Firstname Lastname",
date: "2020-10-06T09:05:00Z",
}),
new InsertedTextRun({
text: " and new content",
id: 1,
author: "Firstname Lastname",
date: "2020-10-06T09:05:00Z",
}),
],
}),
],
features: {
trackRevisions: true,
},
});
const paragraph = new Paragraph({
children: [
new TextRun("This is a simple demo "),
new TextRun({
text: "on how to ",
}),
new InsertedTextRun({
text: "mark a text as an insertion ",
id: 0,
author: "Firstname Lastname",
date: "2020-10-06T09:00:00Z",
}),
new DeletedTextRun({
text: "or a deletion.",
id: 1,
author: "Firstname Lastname",
date: "2020-10-06T09:00:00Z",
}),
],
});
doc.addSection({
properties: {},
children: [
paragraph,
new Paragraph({
children: [
new TextRun("This is a demo "),
new DeletedTextRun({
break: 1,
text: "in order",
color: "red",
bold: true,
size: 24,
font: {
name: "Garamond",
},
shading: {
type: ShadingType.REVERSE_DIAGONAL_STRIPE,
color: "00FFFF",
fill: "FF0000",
},
id: 2,
author: "Firstname Lastname",
date: "2020-10-06T09:00:00Z",
}),
new InsertedTextRun({
text: "to show how to ",
bold: false,
id: 3,
author: "Firstname Lastname",
date: "2020-10-06T09:05:00Z",
}),
new TextRun({
bold: true,
children: ["\tuse Inserted and Deleted TextRuns.", new FootnoteReferenceRun(1)],
}),
],
}),
],
footers: {
default: new Footer({
children: [
new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun("Awesome LLC"),
new TextRun({
children: ["Page Number: ", PageNumber.CURRENT],
}),
new DeletedTextRun({
children: [" to ", PageNumber.TOTAL_PAGES],
id: 4,
author: "Firstname Lastname",
date: "2020-10-06T09:05:00Z",
}),
new InsertedTextRun({
children: [" from ", PageNumber.TOTAL_PAGES],
bold: true,
id: 5,
author: "Firstname Lastname",
date: "2020-10-06T09:05:00Z",
}),
],
}),
],
}),
},
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

294
demo/55-math.ts Normal file
View File

@ -0,0 +1,294 @@
// 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,
Math,
MathAngledBrackets,
MathCurlyBrackets,
MathFraction,
MathFunction,
MathPreSubSuperScript,
MathRadical,
MathRoundBrackets,
MathRun,
MathSquareBrackets,
MathSubScript,
MathSubSuperScript,
MathSum,
MathSuperScript,
Packer,
Paragraph,
TextRun,
} from "../build";
const doc = new Document();
doc.addSection({
properties: {},
children: [
new Paragraph({
children: [
new Math({
children: [
new MathRun("2+2"),
new MathFraction({
numerator: [new MathRun("hi")],
denominator: [new MathRun("2")],
}),
],
}),
new TextRun({
text: "Foo Bar",
bold: true,
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathFraction({
numerator: [
new MathRun("1"),
new MathRadical({
children: [new MathRun("2")],
}),
],
denominator: [new MathRun("2")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathSum({
children: [new MathRun("test")],
}),
new MathSum({
children: [
new MathSuperScript({
children: [new MathRun("e")],
superScript: [new MathRun("2")],
}),
],
subScript: [new MathRun("i")],
}),
new MathSum({
children: [
new MathRadical({
children: [new MathRun("i")],
}),
],
subScript: [new MathRun("i")],
superScript: [new MathRun("10")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathSuperScript({
children: [new MathRun("test")],
superScript: [new MathRun("hello")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathSubScript({
children: [new MathRun("test")],
subScript: [new MathRun("hello")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathSubScript({
children: [new MathRun("x")],
subScript: [
new MathSuperScript({
children: [new MathRun("y")],
superScript: [new MathRun("2")],
}),
],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathSubSuperScript({
children: [new MathRun("test")],
superScript: [new MathRun("hello")],
subScript: [new MathRun("world")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathPreSubSuperScript({
children: [new MathRun("test")],
superScript: [new MathRun("hello")],
subScript: [new MathRun("world")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathSubScript({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
subScript: [new MathRun("4")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathSubScript({
children: [
new MathRadical({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
degree: [new MathRun("4")],
}),
],
subScript: [new MathRun("x")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathRadical({
children: [new MathRun("4")],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathFunction({
name: [
new MathSuperScript({
children: [new MathRun("cos")],
superScript: [new MathRun("-1")],
}),
],
children: [new MathRun("100")],
}),
new MathRun("×"),
new MathFunction({
name: [new MathRun("sin")],
children: [new MathRun("360")],
}),
new MathRun("= x"),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathRoundBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
new MathSquareBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
new MathCurlyBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
new MathAngledBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
],
}),
],
}),
new Paragraph({
children: [
new Math({
children: [
new MathFraction({
numerator: [
new MathRadical({
children: [new MathRun("4")],
}),
],
denominator: [new MathRun("2a")],
}),
],
}),
],
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -0,0 +1,33 @@
// Change background colour of whole document
// 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({
background: {
color: "C45911",
},
});
doc.addSection({
properties: {},
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);
});

View File

@ -0,0 +1,88 @@
// Numbered lists - Add parent number in sub number
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { AlignmentType, convertInchesToTwip, Document, HeadingLevel, LevelFormat, Packer, Paragraph } from "../build";
const doc = new Document({
numbering: {
config: [
{
levels: [
{
level: 0,
format: LevelFormat.DECIMAL,
text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: convertInchesToTwip(0.5), hanging: 260 },
},
},
},
{
level: 1,
format: LevelFormat.DECIMAL,
text: "%1.%2",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 1.25 * convertInchesToTwip(0.5), hanging: 1.25 * 260 },
},
run: {
bold: true,
size: 18,
font: "Times New Roman",
},
},
},
],
reference: "my-number-numbering-reference",
},
],
},
});
doc.addSection({
children: [
new Paragraph({
text: "How to make cake",
heading: HeadingLevel.HEADING_1,
}),
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 2a - Stir the wheat in a circle",
numbering: {
reference: "my-number-numbering-reference",
level: 1,
},
}),
new Paragraph({
text: "Step 3 - Put in oven",
numbering: {
reference: "my-number-numbering-reference",
level: 0,
},
}),
new Paragraph({
text: "How to make cake",
heading: HeadingLevel.HEADING_1,
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

93
demo/58-section-types.ts Normal file
View File

@ -0,0 +1,93 @@
// Usage of different Section Types
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, Paragraph, TextRun, SectionType } from "../build";
const doc = new Document();
doc.addSection({
properties: {},
children: [
new Paragraph({
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
],
}),
],
});
doc.addSection({
properties: {
type: SectionType.CONTINUOUS,
},
children: [
new Paragraph({
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
],
}),
],
});
doc.addSection({
properties: {
type: SectionType.ODD_PAGE,
},
children: [
new Paragraph({
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
],
}),
],
});
doc.addSection({
properties: {
type: SectionType.EVEN_PAGE,
},
children: [
new Paragraph({
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
],
}),
],
});
doc.addSection({
properties: {
type: SectionType.NEXT_PAGE,
},
children: [
new Paragraph({
children: [
new TextRun("Hello World"),
new TextRun({
text: "Foo Bar",
bold: true,
}),
],
}),
],
});
Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,3 @@
<p align="center">
<img alt="clippy the assistant" src="https://i.imgur.com/37uBGhO.gif">
</p>
<p align="center">
Easily generate .docx files with JS/TS. Works for Node and on the Browser. :100:
</p>
---
# Welcome
## Installation
@ -63,17 +53,11 @@ Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});
// Done! A file called 'My First Document.docx' will be in your file system.
// Done! A file called 'My Document.docx' will be in your file system.
```
## Honoured Mentions
[@felipeochoa](https://github.com/felipeochoa)
[@h4buli](https://github.com/h4buli)
<p align="center">
<img alt="clippy the assistant" src="http://i60.tinypic.com/339pvtt.png">
<img alt="clippy the assistant" src="./clippy.png">
</p>
---

10
docs/_coverpage.md Normal file
View File

@ -0,0 +1,10 @@
<img src="https://i.imgur.com/37uBGhO.gif" alt="drawing" style="width:200px;"/>
> Easily generate .docx files with JS/TS. Works for Node and on the Browser. :100:
- Simple, declarative API
- 50+ usage examples
- Battle tested, mature, 97%+ coverage
[GitHub](https://github.com/dolanmiu/docx)
[Get Started](#Welcome)

View File

@ -1,6 +1,6 @@
* [Getting Started](/)
* [Examples](examples.md)
* [Examples](https://github.com/dolanmiu/docx/tree/master/demo)
* API
@ -20,12 +20,16 @@
* [Tab Stops](usage/tab-stops.md)
* [Table of Contents](usage/table-of-contents.md)
* [Page Numbers](usage/page-numbers.md)
* [Change Tracking](usage/change-tracking.md)
* [Math](usage/math.md)
* Styling
* [Styling with JS](usage/styling-with-js.md)
* [Styling with XML](usage/styling-with-xml.md)
* Exporting
* [Packers](usage/packers.md)
* Utility
* [Convenience functions](usage/convenience-functions.md)
* [Contribution Guidelines](contribution-guidelines.md)

BIN
docs/clippy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/clippy.psd Normal file

Binary file not shown.

View File

@ -1,25 +1,23 @@
# Contribution Guidelines
* Include documentation reference(s) at the top of each file:
- Include documentation reference(s) at the top of each file:
```ts
// http://officeopenxml.com/WPdocument.php
```
* Follow Prettier standards, and consider using the [Prettier VSCode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) plugin.
- Follow Prettier standards, and consider using the [Prettier VSCode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) plugin.
* Follow the `TSLint` rules
- Follow the `TSLint` rules
## Always think about the user
The number one pillar for contribution to `docx` is to **ALWAYS** think about how the user will use `docx`.
Put yourself in their position, and imagine how they would feel about your feature you wrote.
1. Is it easy to use?
2. Has it been documented well?
3. Is it intuitive?
4. Is it consistent with the rest of the API?
4. Is it declarative?
5. Is it fun to use?
## Good Commit Names
@ -27,6 +25,7 @@ Put yourself in their position, and imagine how they would feel about your featu
Please write good commit messages when making a commit: https://chris.beams.io/posts/git-commit/
**Do not:**
```
c // What?
rtl // Adding acryonyms without explaining anything else is not helpful
@ -35,34 +34,6 @@ demo updated // Getting better, but capitalize the first letter
Unesesary coment removed // Make sure to use correct spelling
```
## No leaky components in API interface
> This mainly applies to the API the end user will consume.
Try to make method parameters of the outside API accept primitives, or `json` objects, so that child components are created **inside** the component, rather than being **injected** in.
This is so that:
1. Imports are much cleaner for the end user, no need for:
```ts
import { ChildComponent } from "./my-feature/sub-component/deeper/.../my-deep.component";
```
2. This is what I consider "leakage". The code is aware of the underlying implementation of the component.
3. It means the end user does not need to import and create the child component to be injected.
**Do not**
`TableFloatProperties` is a class. The outside world would have to `new` up the object, and inject it in like so:
```ts
public float(tableFloatProperties: TableFloatProperties): Table
```
```ts
table.float(new TableFloatProperties(...));
```
**Do**
`ITableFloatOptions` is an interface for a JSON of primitives. The end user would need to pass in a json object and not need to worry about the internals:
@ -71,30 +42,28 @@ This is so that:
public float(tableFloatOptions: ITableFloatOptions): Table
```
## Delcariative API
Make sure the API is declarative, so no _method calling_ or _mutation_. This is a design decision, consistent with the rest of the project. There are benefits to delcariative code over other styles of code, explained here: https://dzone.com/articles/why-declarative-coding-makes-you-a-better-programm
**Do not:**
```ts
table.float({...});
const paragraph = doc.createParagraph();
const text = paragraph.createText();
text.contents = "Hello World";
```
## Add vs Create
This is just a guideline, and the rules can sometimes be broken.
* Use `create` if the method `new`'s up an element inside:
**Do**
```ts
public createParagraph() {
const paragraph = new Paragraph();
this.root.push(paragraph);
}
```
* Use `add` if you add the element into the method as a parameter.
*Note:* This may look like its breaking the previous guideline, but it has semantically different meanings. The previous one is using data to construct an object, whereas this one is simply adding elements into the document:
```ts
public add(paragraph: Paragraph) {
this.root.push(paragraph);
}
doc.addSection({
children: [
new Paragraph({
children: [new TextRun("Hello World")],
}),
],
});
```
## Getters and Setters
@ -107,7 +76,7 @@ public get Level() {
}
```
There is no performance advantage by doing this. It means we don't need to prefix all private variables with the ugly `_`:
This is the convention of this project. There is no performance advantage by doing this. It means we don't need to prefix all private variables with `_`:
**Do not:**
@ -121,30 +90,6 @@ private get _level: string;
private get level: string;
```
## Temporal Methods
Some methods are `non-temporal`, which means regardless of when you call the method, it will have the same affect on the document. For example, setting the width of a table at the end of the document will have the same effect as setting the width at the start:
```ts
table.setWidth(1000); // now removed as of version 5.0.0
```
Whereas some methods are `temporal`, which means depending on the time-frame they are called, it would produce a difference result. For example, moving `createParagraph()` around your code will physically alter the document.
```ts
doc.createParagraph("hello");
```
If a method is `non-temporal`, put it in the objects `constructor`. For example:
```ts
const table = new Table(width: number);
```
`Non-temporal` methods are usually methods which can only be used one time and one time only. For example, `.float()`. It does not make sense to call `.float()` again if its already floating.
I am not sure what the real term is, but this will do.
## Interfaces over type alias
Do not use `type`, but rather use `Interfaces`. `type` cannot be extended, and a class cannot implement it.
@ -152,14 +97,14 @@ Do not use `type`, but rather use `Interfaces`. `type` cannot be extended, and a
> "In general, use what you want ( type alias / interface ) just be consistent"
> "always use interface for public API's definition when authoring a library or 3rd party ambient type definitions"
>
> * https://medium.com/@martin_hotell/interface-vs-type-alias-in-typescript-2-7-2a8f1777af4c
> - https://medium.com/@martin_hotell/interface-vs-type-alias-in-typescript-2-7-2a8f1777af4c
`Interface` is generally preferred over `type`: https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types
**Do not:**
```ts
type RelationshipFileInfo = { id: number, target: string };
type RelationshipFileInfo = { id: number; target: string };
```
**Do:**
@ -193,26 +138,26 @@ enum WeaponType = {
## Spell correctly, in full and in American English
I am not sure where these habits in software development come from, but I do not believe it is beneficial:
**Do not:**
```ts
readdy // misspelling
perm // abbreviation
conf // abbreviation
cnty // abbreviation
relationFile // abbreviation
colour // U.K. English
readdy; // misspelling
perm; // abbreviation
conf; // abbreviation
cnty; // abbreviation
relationFile; // abbreviation
colour; // U.K. English
```
**Do:**
```ts
ready
permission
config
country
relationshipFile
color
ready;
permission;
config;
country;
relationshipFile;
color;
```
## Keep files small (within reason)

View File

@ -1,219 +0,0 @@
# Examples
> All examples can run independently and can be found in the `/demo` folder of the project
All the examples below can be ran locally, to do so, run the following command:
```sh
npm run demo
```
This command will run the `demo selector app` in the `/demo` folder. It will prompt you to select a demo number, which will run a demo from that folder.
## Simple
A simple hello world of the `docx` library:
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo1.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo1.ts_
## Styles
### Styling with JS
This example shows how to customise the look and feel of a document using JS configuration
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo2.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo2.ts_
### Styling with XML
This example shows how to customise the look and feel of a document using XML configuration
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo13.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo13.ts_
## Numbering
This example shows many levels of numbering
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo3.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo3.ts_
## Table
Example of simple table
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo4.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo4.ts_
### Styling table borders
Styling the borders of a table
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo20.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo20.ts_
## Images
### Add image to the document
Importing Images from file system path
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo5.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo5.ts_
### Add images to header and footer
Example showing how to add image to headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo9.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo9.ts_
### Scaling images
Example showing how to scale images
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo12.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo12.ts_
### Add Image to media before adding to document
This is the best way to add an image to a document because you can add the same image in two locations without increasing document size by re-using the same image
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo23.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo23.ts_
### Add image to table
As before, to add an image to a table, you would need to add it to the `Media` object first
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo24.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo24.ts_
### Images using Base64 URI
If you want to use a Base64 image instead
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo18.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo18.ts_
## Margins
Example showing how to set custom margins
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo6.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo6.ts_
## Orientation
Example showing how to set the document to `landscape` or `portrait`
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo7.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo7.ts_
## Headers & Footers
Example showing how to add headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo8.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo8.ts_
## Multiple headers and footers
Check out `Sections` for this feature
## Page Breaks
### Normal page breaks
Example showing how to page break
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo14.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo14.ts_
### Page break before
Example showing how to page break before like in Word
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo15.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo15.ts_
## Sections
Example of how sections work. Sections allow multiple headers and footers, and `landscape`/`portrait` inside the same document.
Also you can have different page number formats and starts for different sections.
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo16.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo16.ts_
## Footnotes
Example of how to add footnotes. Good for references
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo17.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo17.ts_
## Packers
## Buffer output
Example showing how to use the Buffer packer and then write that buffer to the file system
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo19.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo19.ts_
## Bookmarks
Example showing how to make bookmarks to make internal hyperlinks within the document
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo21.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo21.ts_
## Bidirectional text
Example showing how to use bidirectional text for certain languages such as Hebrew
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo22.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo22.ts_
## Showcase
### My CV
Example showing how to add headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo10.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo10.ts_
### Style and Images
This example shows how to customise the look and feel of a document and add images
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo11.ts ':include')
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo11.ts_

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,31 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta charset="UTF-8" />
<title>docx - Generate .docx documents with JavaScript</title>
<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">
<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="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/style.min.css"
title="docsify-darklight-theme"
type="text/css"
/>
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'docx',
repo: 'https://github.com/dolanmiu/docx',
name: "docx",
repo: "https://github.com/dolanmiu/docx",
loadSidebar: true,
subMaxLevel: 2,
search: 'auto',
}
search: "auto",
coverpage: true,
};
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/emoji.min.js"></script>
<script src="https://unpkg.com/docsify-copy-code@2"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
<script src="//unpkg.com/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>

View File

@ -5,12 +5,21 @@
To make a bullet point, simply make a paragraph into a bullet point:
```ts
const text = new TextRun("Bullet points");
const paragraph = new Paragraph({
doc.addSection({
children: [
new Paragraph({
text: "Bullet points",
bullet: {
level: 0, // How deep you want the bullet to me
},
level: 0 //How deep you want the bullet to be
}
}),
new Paragraph({
text: "Are awesome",
bullet: {
level: 0
}
})
],
});
```

View File

@ -0,0 +1,61 @@
# Change Tracking
> Instead of adding a `TextRun` into a `Paragraph`, you can also add an `InsertedTextRun` or `DeletedTextRun` where you need to supply an `id`, `author` and `date` for the change.
```ts
import { Paragraph, TextRun, InsertedTextRun, DeletedTextRun } from "docx";
const paragraph = new Paragraph({
children: [
new TextRun("This is a simple demo "),
new TextRun({
text: "on how to "
}),
new InsertedTextRun({
text: "mark a text as an insertion ",
id: 0,
author: "Firstname Lastname",
date: "2020-10-06T09:00:00Z",
}),
new DeletedTextRun({
text: "or a deletion.",
id: 1,
author: "Firstname Lastname",
date: "2020-10-06T09:00:00Z",
})
],
});
```
Note that for a `InsertedTextRun` and `DeletedTextRun`, it is not possible to simply call it with only a text as in `new TextRun("some text")`, since the additonal fields for change tracking need to be provided. Similar to a normal `TextRun` you can add additional text properties.
```ts
import { Paragraph, TextRun, InsertedTextRun, DeletedTextRun } from "docx";
const paragraph = new Paragraph({
children: [
new TextRun("This is a simple demo"),
new DeletedTextRun({
text: "with a deletion.",
color: "red",
bold: true,
size: 24,
id: 0,
author: "Firstname Lastname",
date: "2020-10-06T09:00:00Z",
})
],
});
```
In addtion to marking text as inserted or deleted, change tracking can also be added via the document settings. This will enable new changes to be tracked as well.
```ts
import { Document } from "docx";
const doc = new Document({
features: {
trackRevisions: true,
},
});
```

View File

@ -0,0 +1,22 @@
# Convenience functions
OOXML and this library mainly uses a unit called twentieths of a point or `twip` for short. a twip is a typographical measurement, defined as 1/20 of a typographical point. One twip is 1/1440 inch, or 17.64 μm. This unit is not intuitive for many users, so some functions were created to help
More info here: https://en.wikipedia.org/wiki/Twip
## Convert Inches to Twip
```ts
import { convertInchesToTwip } from "docx";
const twip = convertInchesToTwip(1); // returns 1440
const twip = convertInchesToTwip(0.5); // returns 720
```
## Convert Millimeters to Twip
```ts
import { convertMillimetersToTwip } from "docx";
const twip = convertMillimetersToTwip(50); // returns 2834
```

View File

@ -30,6 +30,24 @@ const doc = new docx.Document({
* keywords
* lastModifiedBy
* revision
* externalStyles
* styles
* numbering
* footnotes
* hyperlinks
* background
### Change background color of Document
Set the hex value in the document like so:
```ts
const doc = new docx.Document({
background: {
color: "C45911",
},
});
```
You can mix and match whatever properties you want, or provide no properties.

View File

@ -125,6 +125,7 @@ Full options you can pass into `floating` are:
| lockAnchor | `boolean` | Optional |
| behindDocument | `boolean` | Optional |
| layoutInCell | `boolean` | Optional |
| zIndex | `number` | Optional |
`HorizontalPositionOptions` are:

265
docs/usage/math.md Normal file
View File

@ -0,0 +1,265 @@
# Math
!> Math requires an understanding of [Sections](usage/sections.md) and [Paragraphs](usage/paragraph.md).
## Intro
1. To add math, create a `Math` object
2. Add `MathComponents` inside `Math`
3. `MathComponents` can have nested `MathComponents` inside. e.g. A fraction where the numerator is a square root, and the demoninator as another fraction. More on `MathComponents` below
4. Make sure to add the `Math` object inside a `Paragraph`
## Example
```ts
new Math({
children: [
new MathRun("2+2"),
new MathFraction({
numerator: [new MathRun("hi")],
denominator: [new MathRun("2")],
}),
],
}),
```
This will produce:
<p align="center">
<img alt="clippy the assistant" src="images/math-example.png" width="200">
</p>
## Math Components
`MathComponents` are the unit sized building blocks of an equation in `docx`. A `MathComponent` takes in more nested `MathComponents` until you reach `MathRun`, which has no children. `MathRun` is similar to a [TextRun](usage/text.md).
### Math Run
`MathRun` is the most basic `MathComponent`.
#### Example
```ts
new MathRun("2+2");
```
```ts
new MathRun("hello");
```
An example of it being used inside `Math`:
```ts
new Math({
children: [
new MathRun("2"),
new MathRun("+"),
new MathRun("2"),
],
}),
```
### Math Fraction
`MathFractions` require a `numerator` and a `demoninator`, which are both a list of `MathComponents`
#### Example
```ts
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
```
```ts
new MathFraction({
numerator: [
new MathRun("1"),
new MathRadical({
children: [new MathRun("2")],
}),
],
denominator: [new MathRun("2")],
}),
```
An example of it being used inside `Math`:
```ts
new Math({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
new MathText("+"),
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
new MathText("= 1"),
],
}),
```
### Sum
A `MathComponent` for `Σ`. It can take a `superScript` and/or `subScript` as arguments to add `MathComponents` (usually limits) on the top and bottom
```ts
new MathSum({
children: [new MathRun("i")],
}),
```
```ts
new MathSum({
children: [
new MathSuperScript({
children: [new MathRun("e")],
superScript: [new MathRun("2")],
})
],
subScript: [new MathRun("i")],
superScript: [new MathRun("10")],
}),
```
### Radicals
A `MathComponent` for the `√` symbol. Examples include, square root, cube root etc. There is an optional `degree` parameter to specify the number of times the radicand is multiplied by itself. For example, `3` for cube root.
```ts
new MathRadical({
children: [new MathRun("2")],
}),
```
Cube root example:
```ts
new MathRadical({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
new MathRun('+ 1'),
],
degree: [new MathRun("3")],
}),
```
### Super Script
`MathSuperScripts` are the little numbers written to the top right of numbers or variables. It means the exponent or power if written by itself with the number or variable.
```ts
new MathSuperScript({
children: [new MathRun("x")],
superScript: [new MathRun("2")],
}),
```
An example with cosine:
```ts
new MathSuperScript({
children: [new MathRun("cos")],
superScript: [new MathRun("-1")],
}),
```
### Sub Script
`MathSubScripts` are similar to `MathSuperScripts`, except the little number is written below.
```ts
new MathSubScript({
children: [new MathRun("F")],
subScript: [new MathRun("n-1")],
}),
```
### Sub-Super Script
`MathSubSuperScripts` are a combination of both `MathSuperScript` and `MathSubScript`.
```ts
new MathSubSuperScript({
children: [new MathRun("test")],
superScript: [new MathRun("hello")],
subScript: [new MathRun("world")],
}),
```
### Function
`MathFunctions` are a way of describing what happens to an input variable, in order to get the output result. It takes a `name` parameter to specify the name of the function.
```ts
new MathFunction({
name: [
new MathSuperScript({
children: [new MathRun("cos")],
superScript: [new MathRun("-1")],
}),
],
children: [new MathRun("100")],
}),
```
### Brackets
#### Square brackets
```ts
new MathSquareBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
```
#### Round brackets
```ts
new MathRoundBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
```
#### Curly brackets
```ts
new MathCurlyBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
```
#### Angled brackets
```ts
new MathAngledBrackets({
children: [
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
],
}),
```

View File

@ -2,11 +2,7 @@
> Packers are the way in which `docx` turns your code into `.docx` format. It is completely decoupled from the `docx.Document`.
Packers in `version 4` and above are now one single `Packer`. It works in both a node and browser environment (Angular etc). Now, the packer returns a `Buffer`, `Blob` or `base64 string`. It is up to you to take that and persist it with node's `fs`, send it down as a downloadable file, or anything else you wish. As of version 4, this library will not have options to export to PDF.
## Version 5
Packers in `version 5` and above are now static methods on `Packer`.
Packers works in both a node and browser environment (Angular etc). Now, the packer returns a `Buffer`, `Blob` or `base64 string`. It is up to you to take that and persist it with node's `fs`, send it down as a downloadable file, or anything else you wish. As of `version 4+`, this library will not have options to export to PDF.
### Export as Buffer
@ -36,117 +32,3 @@ Packer.toBlob(doc).then((blob) => {
saveAs(blob, "example.docx");
});
```
## Version 4
The `Packer` in `version 4` requires an instance of `Packer`, so be sure to `new` it.
### Export as Buffer
This will return a NodeJS `Buffer`. If this is used in the browser, it will return a `UInt8Array` instead.
```ts
const packer = new docx.Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});
```
### Export as a `base64` string
```ts
const packer = new docx.Packer();
packer.toBase64String(doc).then((string) => {
console.log(string);
});
```
### Export as Blob
This is useful if you want to send it as an downloadable in a browser environment.
```ts
const packer = new docx.Packer();
packer.toBlob(doc).then((blob) => {
// saveAs from FileSaver will download the file
saveAs(blob, "example.docx");
});
```
## Version 3 and below
### File System Packer
```ts
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");
// Word Document is in file system
```
### Buffer Packer
```ts
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.BufferPacker(doc);
const buffer = exporter.pack();
```
### Stream Packer
Creates a `node` `Readable` stream
```ts
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.StreamPacker(doc);
const stream = exporter.pack();
```
### Express Packer
The old express packer is now deprecated and may disappear soon, so you should upgrade.
The reason for this is because it means this project needs to know about and use `express`, which for a Word document generator, does not sound right. Seperation of concerns.
It will still be usable (for now), but it is ill advised.
I used the express exporter in my [website](http://www.dolan.bio).
The recommended way is to use the `StreamPacker` and handle the `express` magic outside of the library:
```ts
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.StreamPacker(doc);
const stream = exporter.pack();
// Express' response object
res.attachment("yourfile.xlsx");
stream.pipe(res);
```
where `res` is the response object obtained through the Express router. It is that simple. The file will begin downloading in the browser.
### PDF Exporting
You can export your word document as a PDF file like so:
```ts
const exporter = new docx.LocalPacker(doc);
exporter.packPdf("My Document");
// Express
const exporter = new docx.ExpressPacker(doc, res);
exporter.packPdf("My Document");
```

View File

@ -142,6 +142,21 @@ const paragraph = new Paragraph({
});
```
## Shading
Add color to an entire paragraph block
```ts
const paragraph = new Paragraph({
text: "shading",
shading: {
type: ShadingType.REVERSE_DIAGONAL_STRIPE,
color: "00FFFF",
fill: "FF0000",
},
});
```
## Spacing
Adding spacing between paragraphs

View File

@ -19,3 +19,30 @@ doc.addSection({
],
});
```
## Properties
You can specify additional properties to the section, by providing a `properties` attribute.
### Section Type
Setting the section type determines how the contents of the section will be placed relative to the previous section. E.g., There are five different types:
- `CONTINUOUS`
- `EVEN_PAGE`
- `NEXT_COLUMN`
- `NEXT_PAGE`
- `ODD_PAGE`
```ts
doc.addSection({
properties: {
type: SectionType.CONTINUOUS,
}
children: [
new Paragraph({
children: [new TextRun("Hello World")],
}),
],
});
```

View File

@ -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)

View File

@ -51,19 +51,6 @@ const table = new Table({
});
```
### Pagination
#### Prevent row pagination
To prevent breaking contents of a row across multiple pages, call `cantSplit`:
```ts
const table = new Table({
rows: [],
cantSplit: true,
});
```
## Table Row
A table consists of multiple `table rows`. Table rows have a list of `children` which accepts a list of `table cells` explained below. You can create a simple `table row` like so:
@ -103,7 +90,7 @@ Here is a list of options you can add to the `table row`:
| children | `Array<TableCell>` | Required |
| cantSplit | `boolean` | Optional |
| tableHeader | `boolean` | Optional |
| height | `{ value: number, rule: HeightRule }` | Optional |
| height | `{ height: number, rule: HeightRule }` | Optional |
### Repeat row
@ -116,6 +103,19 @@ const row = new TableRow({
});
```
### Pagination
#### Prevent row pagination
To prevent breaking contents of a row across multiple pages, call `cantSplit`:
```ts
const row = new Row({
...,
cantSplit: true,
});
```
## Table Cells
Cells need to be added in the `table row`, you can create a table cell like:
@ -326,6 +326,17 @@ const cell = new TableCell({
});
```
### Visual Right to Left Table
It is possible to reverse how the cells of the table are displayed. The table direction. More info here: https://superuser.com/questions/996912/how-to-change-a-table-direction-in-microsoft-word
```ts
const table = new Table({
visuallyRightToLeft: true,
});
```
## Examples
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/4-basic-table.ts ':include')

View File

@ -68,40 +68,87 @@ const text = new TextRun({
});
```
### Emphasis Mark
```ts
const text = new TextRun({
text: "and then emphasis mark",
emphasisMark: {},
});
```
### Shading and Highlighting
```ts
const text = new TextRun({
text: "shading",
shading: {
type: ShadingType.REVERSE_DIAGONAL_STRIPE,
color: "00FFFF",
fill: "FF0000",
},
});
```
```ts
const text = new TextRun({
text: "highlighting",
highlight: "yellow",
});
```
### Strike through
```ts
text.strike();
const text = new TextRun({
text: "strike",
strike: true,
});
```
### Double strike through
```ts
text.doubleStrike();
const text = new TextRun({
text: "doubleStrike",
doubleStrike: true,
});
```
### Superscript
```ts
text.superScript();
const text = new TextRun({
text: "superScript",
superScript: true,
});
```
### Subscript
```ts
text.subScript();
const text = new TextRun({
text: "subScript",
subScript: true,
});
```
### All Capitals
```ts
text.allCaps();
const text = new TextRun({
text: "allCaps",
allCaps: true,
});
```
### Small Capitals
```ts
text.smallCaps();
const text = new TextRun({
text: "smallCaps",
smallCaps: true,
});
```
## Break
@ -109,13 +156,17 @@ text.smallCaps();
Sometimes you would want to put text underneath another line of text but inside the same paragraph.
```ts
text.break();
const text = new TextRun({
text: "break",
break: 1,
});
```
## Chaining
What if you want to create a paragraph which is **_bold_** and **_italic_**?
Adding two breaks:
```ts
paragraph.bold().italics();
const text = new TextRun({
text: "break",
break: 2,
});
```

3683
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "docx",
"version": "5.0.0",
"version": "5.5.0",
"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": {
@ -50,6 +50,7 @@
"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",
@ -62,11 +63,11 @@
},
"homepage": "https://github.com/dolanmiu/docx#readme",
"devDependencies": {
"@types/chai": "^3.4.35",
"@types/mocha": "^2.2.39",
"@types/chai": "^4.2.15",
"@types/mocha": "^8.0.0",
"@types/request-promise": "^4.1.42",
"@types/shortid": "0.0.29",
"@types/sinon": "^4.3.1",
"@types/sinon": "^9.0.4",
"@types/webpack": "^4.4.24",
"awesome-typescript-loader": "^3.4.1",
"chai": "^3.5.0",
@ -76,24 +77,24 @@
"jszip": "^3.1.5",
"mocha": "^5.2.0",
"mocha-webpack": "^1.0.1",
"nyc": "^14.1.1",
"nyc": "^15.1.0",
"pre-commit": "^1.2.2",
"prettier": "^1.15.2",
"prettier": "^2.1.2",
"prompt": "^1.0.0",
"replace-in-file": "^3.1.0",
"request": "^2.88.0",
"request-promise": "^4.2.2",
"rimraf": "^2.5.2",
"shelljs": "^0.7.7",
"sinon": "^5.0.7",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"tslint-immutable": "^4.9.0",
"typedoc": "^0.11.1",
"typescript": "2.9.2",
"rimraf": "^3.0.2",
"shelljs": "^0.8.4",
"sinon": "^9.0.2",
"ts-node": "^9.0.0",
"tslint": "^6.1.3",
"tslint-immutable": "^6.0.1",
"typedoc": "^0.20.29",
"typescript": "4.2.3",
"webpack": "^3.10.0"
},
"engines": {
"node": ">=8"
"node": ">=10"
}
}

View File

@ -0,0 +1,18 @@
import { expect } from "chai";
import { convertInchesToTwip, convertMillimetersToTwip } from "./convenience-functions";
describe("Utility", () => {
describe("#convertMillimetersToTwip", () => {
it("should call the underlying header's addChildElement for Paragraph", () => {
expect(convertMillimetersToTwip(1000)).to.equal(56692);
});
});
describe("#convertInchesToTwip", () => {
it("should call the underlying header's addChildElement", () => {
expect(convertInchesToTwip(1)).to.equal(1440);
expect(convertInchesToTwip(0.5)).to.equal(720);
expect(convertInchesToTwip(0.25)).to.equal(360);
});
});
});

View File

@ -0,0 +1,8 @@
// Twip - twentieths of a point
export const convertMillimetersToTwip = (millimeters: number): number => {
return Math.floor((millimeters / 25.4) * 72 * 20);
};
export const convertInchesToTwip = (inches: number): number => {
return Math.floor(inches * 72 * 20);
};

View File

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

View File

@ -22,13 +22,15 @@ describe("Compiler", () => {
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(14);
expect(fileNames).has.length(16);
expect(fileNames).to.include("word/document.xml");
expect(fileNames).to.include("word/styles.xml");
expect(fileNames).to.include("docProps/core.xml");
expect(fileNames).to.include("docProps/custom.xml");
expect(fileNames).to.include("docProps/app.xml");
expect(fileNames).to.include("word/numbering.xml");
expect(fileNames).to.include("word/footnotes.xml");
expect(fileNames).to.include("word/_rels/footnotes.xml.rels");
expect(fileNames).to.include("word/settings.xml");
expect(fileNames).to.include("word/_rels/document.xml.rels");
expect(fileNames).to.include("[Content_Types].xml");
@ -62,7 +64,7 @@ describe("Compiler", () => {
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(22);
expect(fileNames).has.length(24);
expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels");
@ -88,7 +90,7 @@ describe("Compiler", () => {
const spy = sinon.spy(compiler["formatter"], "format");
compiler.compile(file);
expect(spy.callCount).to.equal(10);
expect(spy.callCount).to.equal(12);
});
});
});

View File

@ -23,8 +23,10 @@ interface IXmlifyedFileMapping {
readonly HeaderRelationships: IXmlifyedFile[];
readonly FooterRelationships: IXmlifyedFile[];
readonly ContentTypes: IXmlifyedFile;
readonly CustomProperties: IXmlifyedFile;
readonly AppProperties: IXmlifyedFile;
readonly FootNotes: IXmlifyedFile;
readonly FootNotesRelationships: IXmlifyedFile;
readonly Settings: IXmlifyedFile;
}
@ -69,23 +71,23 @@ export class Compiler {
private xmlifyFile(file: File, prettify?: boolean): IXmlifyedFileMapping {
file.verifyUpdateFields();
const documentRelationshipCount = file.DocumentRelationships.RelationshipCount + 1;
const documentRelationshipCount = file.Document.Relationships.RelationshipCount + 1;
const documentXmlData = xml(this.formatter.format(file.Document, file), prettify);
const documentXmlData = xml(this.formatter.format(file.Document.View, file.Document), prettify);
const documentMediaDatas = this.imageReplacer.getMediaData(documentXmlData, file.Media);
return {
Relationships: {
data: (() => {
documentMediaDatas.forEach((mediaData, i) => {
file.DocumentRelationships.createRelationship(
file.Document.Relationships.createRelationship(
documentRelationshipCount + i,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
`media/${mediaData.fileName}`,
);
});
return xml(this.formatter.format(file.DocumentRelationships, file), prettify);
return xml(this.formatter.format(file.Document.Relationships, file.Document), prettify);
})(),
path: "word/_rels/document.xml.rels",
},
@ -99,11 +101,11 @@ export class Compiler {
path: "word/document.xml",
},
Styles: {
data: xml(this.formatter.format(file.Styles, file), prettify),
data: xml(this.formatter.format(file.Styles, file.Document), prettify),
path: "word/styles.xml",
},
Properties: {
data: xml(this.formatter.format(file.CoreProperties, file), {
data: xml(this.formatter.format(file.CoreProperties, file.Document), {
declaration: {
standalone: "yes",
encoding: "UTF-8",
@ -112,15 +114,15 @@ export class Compiler {
path: "docProps/core.xml",
},
Numbering: {
data: xml(this.formatter.format(file.Numbering, file), prettify),
data: xml(this.formatter.format(file.Numbering, file.Document), prettify),
path: "word/numbering.xml",
},
FileRelationships: {
data: xml(this.formatter.format(file.FileRelationships, file), prettify),
data: xml(this.formatter.format(file.FileRelationships, file.Document), prettify),
path: "_rels/.rels",
},
HeaderRelationships: file.Headers.map((headerWrapper, index) => {
const xmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify);
const xmlData = xml(this.formatter.format(headerWrapper.View, headerWrapper), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
@ -132,12 +134,12 @@ export class Compiler {
});
return {
data: xml(this.formatter.format(headerWrapper.Relationships, file), prettify),
data: xml(this.formatter.format(headerWrapper.Relationships, headerWrapper), prettify),
path: `word/_rels/header${index + 1}.xml.rels`,
};
}),
FooterRelationships: file.Footers.map((footerWrapper, index) => {
const xmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify);
const xmlData = xml(this.formatter.format(footerWrapper.View, footerWrapper), prettify);
const mediaDatas = this.imageReplacer.getMediaData(xmlData, file.Media);
mediaDatas.forEach((mediaData, i) => {
@ -149,12 +151,12 @@ export class Compiler {
});
return {
data: xml(this.formatter.format(footerWrapper.Relationships, file), prettify),
data: xml(this.formatter.format(footerWrapper.Relationships, footerWrapper), prettify),
path: `word/_rels/footer${index + 1}.xml.rels`,
};
}),
Headers: file.Headers.map((headerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(headerWrapper.Header, file), prettify);
const tempXmlData = xml(this.formatter.format(headerWrapper.View, headerWrapper), 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);
@ -165,7 +167,7 @@ export class Compiler {
};
}),
Footers: file.Footers.map((footerWrapper, index) => {
const tempXmlData = xml(this.formatter.format(footerWrapper.Footer, file), prettify);
const tempXmlData = xml(this.formatter.format(footerWrapper.View, footerWrapper), 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);
@ -176,19 +178,27 @@ export class Compiler {
};
}),
ContentTypes: {
data: xml(this.formatter.format(file.ContentTypes, file), prettify),
data: xml(this.formatter.format(file.ContentTypes, file.Document), prettify),
path: "[Content_Types].xml",
},
CustomProperties: {
data: xml(this.formatter.format(file.CustomProperties, file.Document), prettify),
path: "docProps/custom.xml",
},
AppProperties: {
data: xml(this.formatter.format(file.AppProperties, file), prettify),
data: xml(this.formatter.format(file.AppProperties, file.Document), prettify),
path: "docProps/app.xml",
},
FootNotes: {
data: xml(this.formatter.format(file.FootNotes, file), prettify),
data: xml(this.formatter.format(file.FootNotes.View, file.FootNotes), prettify),
path: "word/footnotes.xml",
},
FootNotesRelationships: {
data: xml(this.formatter.format(file.FootNotes.Relationships, file.FootNotes), prettify),
path: "word/_rels/footnotes.xml.rels",
},
Settings: {
data: xml(this.formatter.format(file.Settings, file), prettify),
data: xml(this.formatter.format(file.Settings, file.Document), prettify),
path: "word/settings.xml",
},
};

View File

@ -54,6 +54,14 @@ describe("ContentTypes", () => {
},
});
expect(tree["Types"][11]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.custom-properties+xml",
PartName: "/docProps/custom.xml",
},
},
});
expect(tree["Types"][12]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.extended-properties+xml",
@ -61,7 +69,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][12]).to.deep.equal({
expect(tree["Types"][13]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
@ -69,7 +77,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][13]).to.deep.equal({
expect(tree["Types"][14]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
@ -77,7 +85,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][14]).to.deep.equal({
expect(tree["Types"][15]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
@ -94,7 +102,7 @@ describe("ContentTypes", () => {
contentTypes.addFooter(102);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][15]).to.deep.equal({
expect(tree["Types"][16]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
@ -103,7 +111,7 @@ describe("ContentTypes", () => {
},
});
expect(tree["Types"][16]).to.deep.equal({
expect(tree["Types"][17]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
@ -120,7 +128,7 @@ describe("ContentTypes", () => {
contentTypes.addHeader(202);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][15]).to.deep.equal({
expect(tree["Types"][16]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
@ -129,7 +137,7 @@ describe("ContentTypes", () => {
},
});
expect(tree["Types"][16]).to.deep.equal({
expect(tree["Types"][17]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",

View File

@ -27,6 +27,7 @@ export class ContentTypes extends XmlComponent {
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", "/word/styles.xml"));
this.root.push(new Override("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.custom-properties+xml", "/docProps/custom.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml", "/word/numbering.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml", "/word/footnotes.xml"));

View File

@ -1,22 +1,13 @@
import { XmlComponent } from "file/xml-components";
import { ICustomPropertyOptions } from "../custom-properties";
import { IDocumentBackgroundOptions } from "../document";
import { DocumentAttributes } from "../document/document-attributes";
import { INumberingOptions } from "../numbering";
import { HyperlinkType, Paragraph } from "../paragraph";
import { 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;
@ -33,9 +24,12 @@ export interface IPropertiesOptions {
readonly children: Paragraph[];
};
};
readonly hyperlinks?: {
readonly [key: string]: IInternalHyperlinkDefinition | IExternalHyperlinkDefinition;
readonly background?: IDocumentBackgroundOptions;
readonly features?: {
readonly trackRevisions?: boolean;
};
readonly compatabilityModeVersion?: number;
readonly customProperties?: ICustomPropertyOptions[];
}
export class CoreProperties extends XmlComponent {

View File

@ -0,0 +1,13 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface ICustomPropertiesAttributes {
readonly xmlns: string;
readonly vt: string;
}
export class CustomPropertiesAttributes extends XmlAttributeComponent<ICustomPropertiesAttributes> {
protected readonly xmlKeys = {
xmlns: "xmlns",
vt: "xmlns:vt",
};
}

View File

@ -0,0 +1,66 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { CustomProperties } from "./custom-properties";
describe("CustomProperties", () => {
describe("#constructor()", () => {
it("sets the appropriate attributes on the top-level", () => {
const properties = new CustomProperties([]);
const tree = new Formatter().format(properties);
expect(tree).to.deep.equal({
Properties: {
_attr: {
xmlns: "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties",
"xmlns:vt": "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
},
},
});
});
it("should create custom properties with all the attributes given", () => {
const properties = new CustomProperties([
{ name: "Address", value: "123" },
{ name: "Author", value: "456" },
]);
const tree = new Formatter().format(properties);
expect(tree).to.deep.equal({
Properties: [
{
_attr: {
xmlns: "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties",
"xmlns:vt": "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
},
},
{
property: [
{
_attr: {
fmtid: "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
pid: "2",
name: "Address",
},
},
{
"vt:lpwstr": ["123"],
},
],
},
{
property: [
{
_attr: {
fmtid: "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
pid: "3",
name: "Author",
},
},
{
"vt:lpwstr": ["456"],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,37 @@
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { CustomPropertiesAttributes } from "./custom-properties-attributes";
import { CustomProperty, ICustomPropertyOptions } from "./custom-property";
export class CustomProperties extends XmlComponent {
// tslint:disable-next-line:readonly-keyword
private nextId: number;
private readonly properties: CustomProperty[] = [];
constructor(properties: ICustomPropertyOptions[]) {
super("Properties");
this.root.push(
new CustomPropertiesAttributes({
xmlns: "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties",
vt: "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
}),
);
// I'm not sure why, but every example I have seen starts with 2
// https://docs.microsoft.com/en-us/office/open-xml/how-to-set-a-custom-property-in-a-word-processing-document
this.nextId = 2;
for (const property of properties) {
this.addCustomProperty(property);
}
}
public prepForXml(): IXmlableObject | undefined {
this.properties.forEach((x) => this.root.push(x));
return super.prepForXml();
}
public addCustomProperty(property: ICustomPropertyOptions): void {
this.properties.push(new CustomProperty(this.nextId++, property));
}
}

View File

@ -0,0 +1,15 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface ICustomPropertyAttributes {
readonly fmtid: string;
readonly pid: string;
readonly name: string;
}
export class CustomPropertyAttributes extends XmlAttributeComponent<ICustomPropertyAttributes> {
protected readonly xmlKeys = {
fmtid: "fmtid",
pid: "pid",
name: "name",
};
}

View File

@ -0,0 +1,28 @@
import { XmlComponent } from "file/xml-components";
import { CustomPropertyAttributes } from "./custom-property-attributes";
export interface ICustomPropertyOptions {
readonly name: string;
readonly value: string;
}
export class CustomProperty extends XmlComponent {
constructor(id: number, properties: ICustomPropertyOptions) {
super("property");
this.root.push(
new CustomPropertyAttributes({
fmtid: "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}",
pid: id.toString(),
name: properties.name,
}),
);
this.root.push(new CustomPropertyValue(properties.value));
}
}
export class CustomPropertyValue extends XmlComponent {
constructor(value: string) {
super("vt:lpwstr");
this.root.push(value);
}
}

View File

@ -0,0 +1,2 @@
export * from "./custom-properties";
export * from "./custom-property";

View File

@ -0,0 +1,16 @@
import { expect } from "chai";
import { DocumentWrapper } from "./document-wrapper";
describe("DocumentWrapper", () => {
describe("#constructor", () => {
it("should create", () => {
const file = new DocumentWrapper({ background: {} });
// tslint:disable-next-line: no-unused-expression
expect(file.View).to.be.ok;
// tslint:disable-next-line: no-unused-expression
expect(file.Relationships).to.be.ok;
});
});
});

View File

@ -0,0 +1,28 @@
import { Document, IDocumentOptions } from "./document";
import { Footer } from "./footer";
import { FootNotes } from "./footnotes";
import { Header } from "./header/header";
import { Relationships } from "./relationships";
export interface IViewWrapper {
readonly View: Document | Footer | Header | FootNotes;
readonly Relationships: Relationships;
}
export class DocumentWrapper implements IViewWrapper {
private readonly document: Document;
private readonly relationships: Relationships;
constructor(options: IDocumentOptions) {
this.document = new Document(options);
this.relationships = new Relationships();
}
public get View(): Document {
return this.document;
}
public get Relationships(): Relationships {
return this.relationships;
}
}

View File

@ -39,7 +39,7 @@ describe("Body", () => {
},
},
},
{ "w:cols": { _attr: { "w:space": 708, "w:num": 1 } } },
{ "w:cols": { _attr: { "w:space": 708, "w:sep": false, "w:num": 1 } } },
{ "w:docGrid": { _attr: { "w:linePitch": 360 } } },
],
},

View File

@ -1,6 +1,7 @@
import { IViewWrapper } from "file/document-wrapper";
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 {
@ -25,7 +26,7 @@ export class Body extends XmlComponent {
this.sections.push(new SectionProperties(options));
}
public prepForXml(file?: File): IXmlableObject | undefined {
public prepForXml(file?: IViewWrapper): IXmlableObject | undefined {
if (this.sections.length === 1) {
this.root.splice(0, 1);
this.root.push(this.sections.pop() as SectionProperties);
@ -45,7 +46,7 @@ export class Body extends XmlComponent {
private createSectionParagraph(section: SectionProperties): Paragraph {
const paragraph = new Paragraph({});
const properties = new ParagraphProperties({});
properties.addChildElement(section);
properties.push(section);
paragraph.addChildElement(properties);
return paragraph;
}

View File

@ -1,13 +1,13 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface IColumnsAttributes {
export class ColumnsAttributes extends XmlAttributeComponent<{
readonly space?: number;
readonly num?: number;
}
export class ColumnsAttributes extends XmlAttributeComponent<IColumnsAttributes> {
readonly separate?: boolean;
}> {
protected readonly xmlKeys = {
space: "w:space",
num: "w:num",
separate: "w:sep",
};
}

View File

@ -2,12 +2,13 @@ import { XmlComponent } from "file/xml-components";
import { ColumnsAttributes } from "./columns-attributes";
export class Columns extends XmlComponent {
constructor(space: number, num: number) {
constructor(space: number, num: number, separate: boolean) {
super("w:cols");
this.root.push(
new ColumnsAttributes({
space: space,
num: num,
separate: separate,
}),
);
}

View File

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

View File

@ -14,6 +14,7 @@ export enum PageNumberFormat {
ORDINAL_TEXT = "ordinalText",
UPPER_LETTER = "upperLetter",
UPPER_ROMAN = "upperRoman",
DECIMAL_FULL_WIDTH = "decimalFullWidth",
}
export interface IPageNumberTypeAttributes {

View File

@ -1,5 +1,6 @@
import { expect } from "chai";
import { convertInchesToTwip } from "convenience-functions";
import { Formatter } from "export/formatter";
import { FooterWrapper } from "file/footer-wrapper";
import { HeaderWrapper } from "file/header-wrapper";
@ -8,6 +9,7 @@ import { Media } from "file/media";
import { PageBorderOffsetFrom } from "./page-border";
import { PageNumberFormat } from "./page-number";
import { SectionProperties } from "./section-properties";
import { SectionType } from "./type/section-type-attributes";
import { SectionVerticalAlignValue } from "./vertical-align";
describe("SectionProperties", () => {
@ -18,10 +20,10 @@ describe("SectionProperties", () => {
const properties = new SectionProperties({
width: 11906,
height: 16838,
top: 1440,
right: 1440,
bottom: 1440,
left: 1440,
top: convertInchesToTwip(1),
right: convertInchesToTwip(1),
bottom: convertInchesToTwip(1),
left: convertInchesToTwip(1),
header: 708,
footer: 708,
gutter: 0,
@ -29,8 +31,9 @@ describe("SectionProperties", () => {
column: {
space: 708,
count: 1,
separate: true,
},
linePitch: 360,
linePitch: convertInchesToTwip(0.25),
headers: {
default: new HeaderWrapper(media, 100),
},
@ -61,7 +64,7 @@ describe("SectionProperties", () => {
},
});
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": { _attr: { "w:space": 708, "w:num": 1 } } });
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": { _attr: { "w:space": 708, "w:sep": true, "w:num": 1 } } });
expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": { _attr: { "w:linePitch": 360 } } });
expect(tree["w:sectPr"][4]).to.deep.equal({ "w:headerReference": { _attr: { "r:id": "rId100", "w:type": "default" } } });
expect(tree["w:sectPr"][5]).to.deep.equal({ "w:footerReference": { _attr: { "r:id": "rId200", "w:type": "even" } } });
@ -88,7 +91,7 @@ describe("SectionProperties", () => {
},
},
});
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": { _attr: { "w:space": 708, "w:num": 1 } } });
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": { _attr: { "w:space": 708, "w:sep": false, "w:num": 1 } } });
expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": { _attr: { "w:linePitch": 360 } } });
});
@ -198,5 +201,17 @@ describe("SectionProperties", () => {
const pgNumType = tree["w:sectPr"].find((item) => item["w:pgNumType"] !== undefined);
expect(pgNumType).to.equal(undefined);
});
it("should create section properties with section type", () => {
const properties = new SectionProperties({
type: SectionType.CONTINUOUS,
});
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);
const type = tree["w:sectPr"].find((item) => item["w:type"] !== undefined);
expect(type).to.deep.equal({
"w:type": { _attr: { "w:val": "continuous" } },
});
});
});
});

View File

@ -1,4 +1,5 @@
// http://officeopenxml.com/WPsection.php
import { convertInchesToTwip } from "convenience-functions";
import { FooterWrapper } from "file/footer-wrapper";
import { HeaderWrapper } from "file/header-wrapper";
import { XmlComponent } from "file/xml-components";
@ -18,6 +19,8 @@ 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 { Type } from "./type/section-type";
import { SectionType } from "./type/section-type-attributes";
import { ISectionVerticalAlignAttributes, SectionVerticalAlign } from "./vertical-align";
export interface IHeaderFooterGroup<T> {
@ -51,12 +54,16 @@ export type SectionPropertiesOptions = IPageSizeAttributes &
readonly column?: {
readonly space?: number;
readonly count?: number;
readonly separate?: boolean;
};
readonly type?: SectionType;
};
// Need to decouple this from the attributes
export class SectionProperties extends XmlComponent {
private readonly options: SectionPropertiesOptions;
public readonly width: number;
public readonly rightMargin: number;
public readonly leftMargin: number;
constructor(options: SectionPropertiesOptions = { column: {} }) {
super("w:sectPr");
@ -64,10 +71,10 @@ export class SectionProperties extends XmlComponent {
const {
width = 11906,
height = 16838,
top = 1440,
right = 1440,
bottom = 1440,
left = 1440,
top = convertInchesToTwip(1),
right = convertInchesToTwip(1),
bottom = convertInchesToTwip(1),
left = convertInchesToTwip(1),
header = 708,
footer = 708,
gutter = 0,
@ -90,12 +97,16 @@ export class SectionProperties extends XmlComponent {
pageBorderLeft,
titlePage = false,
verticalAlign,
type,
} = options;
this.options = options;
this.leftMargin = left;
this.rightMargin = right;
this.width = width;
this.root.push(new PageSize(width, height, orientation));
this.root.push(new PageMargin(top, right, bottom, left, header, footer, gutter, mirror));
this.root.push(new Columns(column.space ? column.space : 708, column.count ? column.count : 1));
this.root.push(new Columns(column.space ? column.space : 708, column.count ? column.count : 1, column.separate ?? false));
this.root.push(new DocumentGrid(linePitch));
this.addHeaders(headers);
@ -128,6 +139,10 @@ export class SectionProperties extends XmlComponent {
if (verticalAlign) {
this.root.push(new SectionVerticalAlign(verticalAlign));
}
if (type) {
this.root.push(new Type(type));
}
}
private addHeaders(headers?: IHeaderFooterGroup<HeaderWrapper>): void {
@ -136,7 +151,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.DEFAULT,
headerId: headers.default.Header.ReferenceId,
headerId: headers.default.View.ReferenceId,
}),
);
}
@ -145,7 +160,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.FIRST,
headerId: headers.first.Header.ReferenceId,
headerId: headers.first.View.ReferenceId,
}),
);
}
@ -154,7 +169,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.EVEN,
headerId: headers.even.Header.ReferenceId,
headerId: headers.even.View.ReferenceId,
}),
);
}
@ -167,7 +182,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.DEFAULT,
footerId: footers.default.Footer.ReferenceId,
footerId: footers.default.View.ReferenceId,
}),
);
}
@ -176,7 +191,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.FIRST,
footerId: footers.first.Footer.ReferenceId,
footerId: footers.first.View.ReferenceId,
}),
);
}
@ -185,14 +200,10 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.EVEN,
footerId: footers.even.Footer.ReferenceId,
footerId: footers.even.View.ReferenceId,
}),
);
}
}
}
public get Options(): SectionPropertiesOptions {
return this.options;
}
}

View File

@ -0,0 +1,2 @@
export * from "./section-type";
export * from "./section-type-attributes";

View File

@ -0,0 +1,17 @@
import { XmlAttributeComponent } from "file/xml-components";
export enum SectionType {
CONTINUOUS = "continuous",
EVEN_PAGE = "evenPage",
NEXT_COLUMN = "nextColumn",
NEXT_PAGE = "nextPage",
ODD_PAGE = "oddPage",
}
export class SectionTypeAttributes extends XmlAttributeComponent<{
readonly val: SectionType;
}> {
protected readonly xmlKeys = {
val: "w:val",
};
}

View File

@ -0,0 +1,35 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Type } from "./section-type";
import { SectionType } from "./section-type-attributes";
describe("Type", () => {
it("should create with even page section type", () => {
const sectionType = new Type(SectionType.EVEN_PAGE);
const tree = new Formatter().format(sectionType);
expect(tree).to.deep.equal({
"w:type": {
_attr: {
"w:val": "evenPage",
},
},
});
});
it("should create with continuous section type", () => {
const sectionType = new Type(SectionType.CONTINUOUS);
const tree = new Formatter().format(sectionType);
expect(tree).to.deep.equal({
"w:type": {
_attr: {
"w:val": "continuous",
},
},
});
});
});

View File

@ -0,0 +1,10 @@
// http://officeopenxml.com/WPsection.php
import { XmlComponent } from "file/xml-components";
import { SectionType, SectionTypeAttributes } from "./section-type-attributes";
export class Type extends XmlComponent {
constructor(value: SectionType) {
super("w:type");
this.root.push(new SectionTypeAttributes({ val: value }));
}
}

View File

@ -0,0 +1,53 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { DocumentBackground } from "./document-background";
describe("DocumentBackground", () => {
describe("#constructor()", () => {
it("should create a DocumentBackground with no options and set color to auto", () => {
const documentBackground = new DocumentBackground({});
const tree = new Formatter().format(documentBackground);
expect(tree).to.deep.equal({
"w:background": {
_attr: {
"w:color": "FFFFFF",
},
},
});
});
it("should create a DocumentBackground with no options and set color to value", () => {
const documentBackground = new DocumentBackground({ color: "ffff00" });
const tree = new Formatter().format(documentBackground);
expect(tree).to.deep.equal({
"w:background": {
_attr: {
"w:color": "ffff00",
},
},
});
});
it("should create a DocumentBackground with no options and set other values", () => {
const documentBackground = new DocumentBackground({
color: "ffff00",
themeColor: "test",
themeShade: "test",
themeTint: "test",
});
const tree = new Formatter().format(documentBackground);
expect(tree).to.deep.equal({
"w:background": {
_attr: {
"w:color": "ffff00",
"w:themeColor": "test",
"w:themeShade": "test",
"w:themeTint": "test",
},
},
});
});
});
});

View File

@ -0,0 +1,39 @@
// http://officeopenxml.com/WPdocument.php
// http://www.datypic.com/sc/ooxml/e-w_background-1.html
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export class DocumentBackgroundAttributes extends XmlAttributeComponent<{
readonly color: string;
readonly themeColor?: string;
readonly themeShade?: string;
readonly themeTint?: string;
}> {
protected readonly xmlKeys = {
color: "w:color",
themeColor: "w:themeColor",
themeShade: "w:themeShade",
themeTint: "w:themeTint",
};
}
export interface IDocumentBackgroundOptions {
readonly color?: string;
readonly themeColor?: string;
readonly themeShade?: string;
readonly themeTint?: string;
}
export class DocumentBackground extends XmlComponent {
constructor(options: IDocumentBackgroundOptions) {
super("w:background");
this.root.push(
new DocumentBackgroundAttributes({
color: options.color ? options.color : "FFFFFF",
themeColor: options.themeColor,
themeShade: options.themeShade,
themeTint: options.themeTint,
}),
);
}
}

View File

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

View File

@ -8,7 +8,7 @@ describe("Document", () => {
let document: Document;
beforeEach(() => {
document = new Document();
document = new Document({ background: {} });
});
describe("#constructor()", () => {
@ -38,6 +38,13 @@ describe("Document", () => {
"mc:Ignorable": "w14 w15 wp14",
},
},
{
"w:background": {
_attr: {
"w:color": "FFFFFF",
},
},
},
{ "w:body": {} },
],
});

View File

@ -1,15 +1,20 @@
// http://officeopenxml.com/WPdocument.php
import { XmlComponent } from "file/xml-components";
import { Hyperlink, Paragraph } from "../paragraph";
import { ConcreteHyperlink, Paragraph } from "../paragraph";
import { Table } from "../table";
import { TableOfContents } from "../table-of-contents";
import { Body } from "./body";
import { DocumentAttributes } from "./document-attributes";
import { DocumentBackground, IDocumentBackgroundOptions } from "./document-background";
export interface IDocumentOptions {
readonly background: IDocumentBackgroundOptions;
}
export class Document extends XmlComponent {
private readonly body: Body;
constructor() {
constructor(options: IDocumentOptions) {
super("w:document");
this.root.push(
new DocumentAttributes({
@ -33,10 +38,11 @@ export class Document extends XmlComponent {
}),
);
this.body = new Body();
this.root.push(new DocumentBackground(options.background));
this.root.push(this.body);
}
public add(item: Paragraph | Table | TableOfContents | Hyperlink): Document {
public add(item: Paragraph | Table | TableOfContents | ConcreteHyperlink): Document {
this.body.push(item);
return this;
}

View File

@ -1,3 +1,4 @@
export * from "./document";
export * from "./document-attributes";
export * from "./body";
export * from "./document-background";

View File

@ -218,5 +218,150 @@ describe("Anchor", () => {
const textWrap = newJson.root[6];
assert.equal(textWrap.rootKey, "wp:wrapTopAndBottom");
});
it("should create a Drawing with a margin", () => {
anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
margins: {
top: 10,
left: 10,
bottom: 10,
right: 10,
},
},
});
const newJson = Utility.jsonify(anchor);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
distT: 10,
distB: 10,
distL: 10,
distR: 10,
});
});
it("should create a Drawing with a default margin", () => {
anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
margins: {},
},
});
const newJson = Utility.jsonify(anchor);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
distT: 0,
distB: 0,
distL: 0,
distR: 0,
});
});
it("should create a Drawing with allowOverlap being false", () => {
anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
allowOverlap: false,
},
});
const newJson = Utility.jsonify(anchor);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
allowOverlap: "0",
});
});
it("should create a Drawing with behindDocument being true", () => {
anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
behindDocument: true,
},
});
const newJson = Utility.jsonify(anchor);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
behindDoc: "1",
});
});
it("should create a Drawing with locked being true", () => {
anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
lockAnchor: true,
},
});
const newJson = Utility.jsonify(anchor);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
locked: "1",
});
});
it("should create a Drawing with locked being false", () => {
anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
layoutInCell: false,
},
});
const newJson = Utility.jsonify(anchor);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
layoutInCell: "0",
});
});
it("should create a Drawing with a certain z-index", () => {
anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
zIndex: 120,
},
});
const newJson = Utility.jsonify(anchor);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
relativeHeight: 120,
});
});
});
});

View File

@ -11,42 +11,32 @@ import { Extent } from "./../extent/extent";
import { GraphicFrameProperties } from "./../graphic-frame/graphic-frame-properties";
import { AnchorAttributes } from "./anchor-attributes";
const defaultOptions: IFloating = {
export class Anchor extends XmlComponent {
constructor(mediaData: IMediaData, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
super("wp:anchor");
const floating: IFloating = {
allowOverlap: true,
behindDocument: false,
lockAnchor: false,
layoutInCell: true,
verticalPosition: {},
horizontalPosition: {},
};
export class Anchor extends XmlComponent {
constructor(mediaData: IMediaData, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
super("wp:anchor");
const floating = {
margins: {
top: 0,
bottom: 0,
left: 0,
right: 0,
},
...defaultOptions,
...drawingOptions.floating,
};
this.root.push(
new AnchorAttributes({
distT: floating.margins.top || 0,
distB: floating.margins.bottom || 0,
distL: floating.margins.left || 0,
distR: floating.margins.right || 0,
distT: floating.margins ? floating.margins.top || 0 : 0,
distB: floating.margins ? floating.margins.bottom || 0 : 0,
distL: floating.margins ? floating.margins.left || 0 : 0,
distR: floating.margins ? floating.margins.right || 0 : 0,
simplePos: "0", // note: word doesn't fully support - so we use 0
allowOverlap: floating.allowOverlap === true ? "1" : "0",
behindDoc: floating.behindDocument === true ? "1" : "0",
locked: floating.lockAnchor === true ? "1" : "0",
layoutInCell: floating.layoutInCell === true ? "1" : "0",
relativeHeight: dimensions.emus.y,
relativeHeight: floating.zIndex ? floating.zIndex : dimensions.emus.y,
}),
);

View File

@ -1,4 +1,5 @@
// http://officeopenxml.com/drwPicFloating-position.php
// http://officeopenxml.com/drwPicFloating.php
import { ITextWrapping } from "../text-wrap";
export enum HorizontalPositionRelativeFrom {
@ -67,4 +68,5 @@ export interface IFloating {
readonly layoutInCell?: boolean;
readonly margins?: IMargins;
readonly wrap?: ITextWrapping;
readonly zIndex?: number;
}

View File

@ -12,7 +12,7 @@ export class Inline extends XmlComponent {
private readonly extent: Extent;
private readonly graphic: Graphic;
constructor(readonly mediaData: IMediaData, private readonly dimensions: IMediaDataDimensions) {
constructor(mediaData: IMediaData, private readonly dimensions: IMediaDataDimensions) {
super("wp:inline");
this.root.push(

View File

@ -5,7 +5,7 @@ import { Formatter } from "export/formatter";
import { File } from "./file";
import { Footer, Header } from "./header";
import { HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph";
import { Paragraph } from "./paragraph";
import { Table, TableCell, TableRow } from "./table";
import { TableOfContents } from "./table-of-contents";
@ -18,7 +18,7 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
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");
@ -37,7 +37,7 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
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");
@ -56,7 +56,7 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
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");
@ -79,7 +79,7 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
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");
@ -97,7 +97,7 @@ describe("File", () => {
},
]);
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
expect(tree).to.deep.equal({
"w:body": [
@ -148,6 +148,7 @@ describe("File", () => {
"w:cols": {
_attr: {
"w:num": 1,
"w:sep": false,
"w:space": 708,
},
},
@ -164,22 +165,12 @@ describe("File", () => {
],
});
});
it("should add hyperlink child", () => {
const doc = new File(undefined, undefined, [
{
children: [new HyperlinkRef("test")],
},
]);
expect(doc.HyperlinkCache).to.deep.equal({});
});
});
describe("#addSection", () => {
it("should call the underlying document's add a Paragraph", () => {
const file = new File();
const spy = sinon.spy(file.Document, "add");
const spy = sinon.spy(file.Document.View, "add");
file.addSection({
children: [new Paragraph({})],
});
@ -187,19 +178,9 @@ 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");
const spy = sinon.spy(file.Document.View, "add");
file.addSection({
children: [
new Table({
@ -221,7 +202,7 @@ describe("File", () => {
it("should call the underlying document's add when adding an Image (paragraph)", () => {
const file = new File();
const spy = sinon.spy(file.Document, "add");
const spy = sinon.spy(file.Document.View, "add");
// tslint:disable-next-line:no-any
file.addSection({
children: [new Paragraph("")],
@ -234,34 +215,25 @@ describe("File", () => {
describe("#addSection", () => {
it("should call the underlying document's add", () => {
const file = new File();
const spy = sinon.spy(file.Document, "add");
const spy = sinon.spy(file.Document.View, "add");
file.addSection({
children: [new TableOfContents()],
});
expect(spy.called).to.equal(true);
});
});
it.only("should create hyperlinks", () => {
const wrapper = new File({
hyperlinks: {
myHyperLink: {
link: "test.com",
text: "test",
type: HyperlinkType.EXTERNAL,
},
describe("#addTrackRevisionsFeature", () => {
it("should call the underlying document's add", () => {
const file = new File({
features: {
trackRevisions: true,
},
});
expect(wrapper.HyperlinkCache.myHyperLink).to.not.be.undefined("");
});
});
describe("#HyperlinkCache", () => {
it("should initially have empty hyperlink cache", () => {
const file = new File();
expect(file.HyperlinkCache).to.deep.equal({});
// tslint:disable-next-line: no-unused-expression no-string-literal
expect(file.Settings["trackRevisions"]).to.exist;
});
});
@ -275,7 +247,7 @@ describe("File", () => {
},
});
const tree = new Formatter().format(wrapper.FootNotes);
const tree = new Formatter().format(wrapper.FootNotes.View);
expect(tree).to.deep.equal({
"w:footnotes": [

View File

@ -1,9 +1,8 @@
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";
import { Document } from "./document";
import { CustomProperties } from "./custom-properties";
import { DocumentWrapper } from "./document-wrapper";
import {
FooterReferenceType,
HeaderReferenceType,
@ -13,14 +12,13 @@ import {
import { IPageMarginAttributes } from "./document/body/section-properties/page-margin/page-margin-attributes";
import { IFileProperties } from "./file-properties";
import { FooterWrapper, IDocumentFooter } from "./footer-wrapper";
import { FootNotes } from "./footnotes";
import { FootnotesWrapper } from "./footnotes-wrapper";
import { Footer, Header } from "./header";
import { HeaderWrapper, IDocumentHeader } from "./header-wrapper";
import { Media } from "./media";
import { Numbering } from "./numbering";
import { Hyperlink, HyperlinkRef, HyperlinkType, Paragraph } from "./paragraph";
import { Paragraph } from "./paragraph";
import { Relationships } from "./relationships";
import { TargetModeType } from "./relationships/relationship/relationship";
import { Settings } from "./settings";
import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory";
@ -42,27 +40,26 @@ export interface ISectionOptions {
readonly size?: IPageSizeAttributes;
readonly margins?: IPageMarginAttributes;
readonly properties?: SectionPropertiesOptions;
readonly children: Array<Paragraph | Table | TableOfContents | HyperlinkRef>;
readonly children: (Paragraph | Table | TableOfContents)[];
}
export class File {
// tslint:disable-next-line:readonly-keyword
private currentRelationshipId: number = 1;
private readonly document: Document;
private readonly documentWrapper: DocumentWrapper;
private readonly headers: IDocumentHeader[] = [];
private readonly footers: IDocumentFooter[] = [];
private readonly docRelationships: Relationships;
private readonly coreProperties: CoreProperties;
private readonly numbering: Numbering;
private readonly media: Media;
private readonly fileRelationships: Relationships;
private readonly footNotes: FootNotes;
private readonly footnotesWrapper: FootnotesWrapper;
private readonly settings: Settings;
private readonly contentTypes: ContentTypes;
private readonly customProperties: CustomProperties;
private readonly appProperties: AppProperties;
private readonly styles: Styles;
private readonly hyperlinkCache: { readonly [key: string]: Hyperlink } = {};
constructor(
options: IPropertiesOptions = {
@ -81,13 +78,15 @@ export class File {
config: [],
},
);
this.docRelationships = new Relationships();
this.fileRelationships = new Relationships();
this.customProperties = new CustomProperties(options.customProperties ?? []);
this.appProperties = new AppProperties();
this.footNotes = new FootNotes();
this.footnotesWrapper = new FootnotesWrapper();
this.contentTypes = new ContentTypes();
this.document = new Document();
this.settings = new Settings();
this.documentWrapper = new DocumentWrapper({ background: options.background || {} });
this.settings = new Settings({
compatabilityModeVersion: options.compatabilityModeVersion,
});
this.media = fileProperties.template && fileProperties.template.media ? fileProperties.template.media : new Media();
@ -107,7 +106,7 @@ export class File {
this.styles = stylesFactory.newInstance(options.externalStyles);
} else if (options.styles) {
const stylesFactory = new DefaultStylesFactory();
const defaultStyles = stylesFactory.newInstance();
const defaultStyles = stylesFactory.newInstance(options.styles.default);
this.styles = new Styles({
...defaultStyles,
...options.styles,
@ -132,16 +131,10 @@ export class File {
}
for (const section of sections) {
this.document.Body.addSection(section.properties ? section.properties : {});
this.documentWrapper.View.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);
this.documentWrapper.View.add(child);
}
}
@ -151,29 +144,14 @@ export class File {
continue;
}
this.footNotes.createFootNote(parseFloat(key), options.footnotes[key].children);
this.footnotesWrapper.View.createFootNote(parseFloat(key), options.footnotes[key].children);
}
}
if (options.hyperlinks) {
const cache = {};
for (const key in options.hyperlinks) {
if (!options.hyperlinks[key]) {
continue;
if (options.features) {
if (options.features.trackRevisions) {
this.settings.addTrackRevisions();
}
const hyperlinkRef = options.hyperlinks[key];
const hyperlink =
hyperlinkRef.type === HyperlinkType.EXTERNAL
? this.createHyperlink(hyperlinkRef.link, hyperlinkRef.text)
: this.createInternalHyperLink(key, hyperlinkRef.text);
cache[key] = hyperlink;
}
this.hyperlinkCache = cache;
}
}
@ -185,7 +163,7 @@ export class File {
properties,
children,
}: ISectionOptions): void {
this.document.Body.addSection({
this.documentWrapper.View.Body.addSection({
...properties,
headers: {
default: headers.default ? this.createHeader(headers.default) : this.createHeader(new Header()),
@ -202,40 +180,16 @@ export class File {
});
for (const child of children) {
if (child instanceof HyperlinkRef) {
const hyperlink = this.hyperlinkCache[child.id];
this.document.add(hyperlink);
continue;
}
this.document.add(child);
this.documentWrapper.View.add(child);
}
}
public verifyUpdateFields(): void {
if (this.document.getTablesOfContents().length) {
if (this.documentWrapper.View.getTablesOfContents().length) {
this.settings.addUpdateFields();
}
}
private createHyperlink(link: string, text: string = link): Hyperlink {
const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase());
this.docRelationships.createRelationship(
hyperlink.linkId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
link,
TargetModeType.EXTERNAL,
);
return hyperlink;
}
private createInternalHyperLink(anchor: string, text: string = anchor): Hyperlink {
const hyperlink = new Hyperlink(text, shortid.generate().toLowerCase(), anchor);
// NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark
// we don't need to create a new relationship.
return hyperlink;
}
private createHeader(header: Header): HeaderWrapper {
const wrapper = new HeaderWrapper(this.media, this.currentRelationshipId++);
@ -260,8 +214,8 @@ export class File {
private addHeaderToDocument(header: HeaderWrapper, type: HeaderReferenceType = HeaderReferenceType.DEFAULT): void {
this.headers.push({ header, type });
this.docRelationships.createRelationship(
header.Header.ReferenceId,
this.documentWrapper.Relationships.createRelationship(
header.View.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",
`header${this.headers.length}.xml`,
);
@ -270,8 +224,8 @@ export class File {
private addFooterToDocument(footer: FooterWrapper, type: FooterReferenceType = FooterReferenceType.DEFAULT): void {
this.footers.push({ footer, type });
this.docRelationships.createRelationship(
footer.Footer.ReferenceId,
this.documentWrapper.Relationships.createRelationship(
footer.View.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer",
`footer${this.footers.length}.xml`,
);
@ -294,31 +248,36 @@ export class File {
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
"docProps/app.xml",
);
this.fileRelationships.createRelationship(
4,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties",
"docProps/custom.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
"styles.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",
"numbering.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes",
"footnotes.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings",
"settings.xml",
);
}
public get Document(): Document {
return this.document;
public get Document(): DocumentWrapper {
return this.documentWrapper;
}
public get Styles(): Styles {
@ -337,10 +296,6 @@ export class File {
return this.media;
}
public get DocumentRelationships(): Relationships {
return this.docRelationships;
}
public get FileRelationships(): Relationships {
return this.fileRelationships;
}
@ -357,19 +312,19 @@ export class File {
return this.contentTypes;
}
public get CustomProperties(): CustomProperties {
return this.customProperties;
}
public get AppProperties(): AppProperties {
return this.appProperties;
}
public get FootNotes(): FootNotes {
return this.footNotes;
public get FootNotes(): FootnotesWrapper {
return this.footnotesWrapper;
}
public get Settings(): Settings {
return this.settings;
}
public get HyperlinkCache(): { readonly [key: string]: Hyperlink } {
return this.hyperlinkCache;
}
}

View File

@ -10,7 +10,7 @@ describe("FooterWrapper", () => {
describe("#add", () => {
it("should call the underlying footer's addParagraph", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "add");
const spy = sinon.spy(file.View, "add");
file.add(new Paragraph({}));
expect(spy.called).to.equal(true);
@ -18,7 +18,7 @@ describe("FooterWrapper", () => {
it("should call the underlying footer's addParagraph", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "add");
const spy = sinon.spy(file.View, "add");
file.add(
new Table({
rows: [
@ -40,7 +40,7 @@ describe("FooterWrapper", () => {
describe("#addChildElement", () => {
it("should call the underlying footer's addChildElement", () => {
const file = new FooterWrapper(new Media(), 1);
const spy = sinon.spy(file.Footer, "addChildElement");
const spy = sinon.spy(file.View, "addChildElement");
// tslint:disable-next-line:no-any
file.addChildElement({} as any);

View File

@ -1,6 +1,7 @@
import { XmlComponent } from "file/xml-components";
import { FooterReferenceType } from "./document";
import { IViewWrapper } from "./document-wrapper";
import { Footer } from "./footer/footer";
import { Media } from "./media";
import { Paragraph } from "./paragraph";
@ -12,7 +13,7 @@ export interface IDocumentFooter {
readonly type: FooterReferenceType;
}
export class FooterWrapper {
export class FooterWrapper implements IViewWrapper {
private readonly footer: Footer;
private readonly relationships: Relationships;
@ -29,7 +30,7 @@ export class FooterWrapper {
this.footer.addChildElement(childElement);
}
public get Footer(): Footer {
public get View(): Footer {
return this.footer;
}

View File

@ -0,0 +1,16 @@
import { expect } from "chai";
import { FootnotesWrapper } from "./footnotes-wrapper";
describe("FootnotesWrapper", () => {
describe("#constructor", () => {
it("should create", () => {
const file = new FootnotesWrapper();
// tslint:disable-next-line: no-unused-expression
expect(file.View).to.be.ok;
// tslint:disable-next-line: no-unused-expression
expect(file.Relationships).to.be.ok;
});
});
});

View File

@ -0,0 +1,21 @@
import { IViewWrapper } from "./document-wrapper";
import { FootNotes } from "./footnotes/footnotes";
import { Relationships } from "./relationships";
export class FootnotesWrapper implements IViewWrapper {
private readonly footnotess: FootNotes;
private readonly relationships: Relationships;
constructor() {
this.footnotess = new FootNotes();
this.relationships = new Relationships();
}
public get View(): FootNotes {
return this.footnotess;
}
public get Relationships(): Relationships {
return this.relationships;
}
}

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