Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
b0a29e26c9 | |||
8826fb010d | |||
97101adb10 | |||
2ec171d4a8 | |||
8876bb1fea | |||
96ca9d9c23 | |||
74a65f02fa | |||
339c017940 | |||
969b52ae05 | |||
eb8f0f4033 | |||
d7229eb049 | |||
644819ed81 | |||
96fd61d3e4 | |||
b96cfe7b19 | |||
d0e7c97a88 | |||
0ede54cfdc | |||
5be1163549 | |||
580f6eb633 | |||
321be35918 | |||
45a18742d7 | |||
7e81da404a | |||
6c2abb4abc | |||
e8b0dbf93b | |||
e59c255d85 | |||
17b28cb724 | |||
410152441b | |||
dfff4b96bd | |||
72cb75a486 | |||
53fe1dd988 | |||
043219f005 | |||
0453f28951 | |||
94716e081b | |||
30f826fd3d | |||
99b7a03b8a | |||
b1c8b2beb8 | |||
a706455a7c | |||
8c388a95c4 | |||
9a41d78ecb | |||
6fc4ad782a | |||
16b9057ac6 |
@ -38,3 +38,6 @@ build-tests
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# docs
|
||||
docs
|
11
.travis.yml
11
.travis.yml
@ -1,10 +1,15 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "node"
|
||||
- "stable"
|
||||
install:
|
||||
- npm install
|
||||
script:
|
||||
- npm test
|
||||
after_failure:
|
||||
- "cat /home/travis/builds/dolanmiu/docx/npm-debug.log"
|
||||
- "cat /home/travis/builds/dolanmiu/docx/npm-debug.log"
|
||||
after_success:
|
||||
- bash ./deploy-docs.sh
|
||||
env:
|
||||
global:
|
||||
- ENCRYPTION_LABEL: "ad385fa3b525"
|
||||
|
||||
|
@ -26,12 +26,14 @@ $ npm install --save docx
|
||||
$ npm run demo
|
||||
```
|
||||
|
||||
will run the demo app in the `demo` folder, which creates a file called "My Document.docx" in the root of the project
|
||||
will run the demo selector app in the `demo` folder. It will prompt you to select a demo number, which will run a demo from that folder.
|
||||
|
||||
## Guide
|
||||
|
||||
Please refer to [the Wiki](https://github.com/dolanmiu/docx/wiki) for details on how to use this library, examples and much more!
|
||||
|
||||
Full documentation can be found here: [http://dolanmiu.github.io/docx](http://dolanmiu.github.io/docx)
|
||||
|
||||
## Simple Usage
|
||||
|
||||
```js
|
||||
|
74
demo/demo2.js
Normal file
74
demo/demo2.js
Normal file
@ -0,0 +1,74 @@
|
||||
const docx = require('../build');
|
||||
|
||||
const styles = new docx.Styles();
|
||||
styles.createParagraphStyle('Heading1', 'Heading 1')
|
||||
.basedOn("Normal")
|
||||
.next("Normal")
|
||||
.quickFormat()
|
||||
.size(28)
|
||||
.bold()
|
||||
.italics()
|
||||
.spacing({after: 120});
|
||||
|
||||
styles.createParagraphStyle('Heading2', 'Heading 2')
|
||||
.basedOn("Normal")
|
||||
.next("Normal")
|
||||
.quickFormat()
|
||||
.size(26)
|
||||
.bold()
|
||||
.underline('double', 'FF0000')
|
||||
.spacing({before: 240, after: 120});
|
||||
|
||||
styles.createParagraphStyle('aside', 'Aside')
|
||||
.basedOn('Normal')
|
||||
.next('Normal')
|
||||
.color('999999')
|
||||
.italics()
|
||||
.indent(720)
|
||||
.spacing({line: 276});
|
||||
|
||||
styles.createParagraphStyle('wellSpaced', 'Well Spaced')
|
||||
.basedOn('Normal')
|
||||
.spacing({line: 276, before: 20 * 72 * .1, after: 20 * 72 * .05});
|
||||
|
||||
styles.createParagraphStyle('ListParagraph', 'List Paragraph')
|
||||
.quickFormat()
|
||||
.basedOn('Normal');
|
||||
|
||||
|
||||
const numbering = new docx.Numbering();
|
||||
const numberedAbstract = numbering.createAbstractNumbering();
|
||||
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left");
|
||||
|
||||
const doc = new docx.Document({
|
||||
creator: 'Clippy',
|
||||
title: 'Sample Document',
|
||||
description: 'A brief example of using docx',
|
||||
});
|
||||
|
||||
doc.createParagraph('Test heading1, bold and italicized').heading1();
|
||||
doc.createParagraph('Some simple content');
|
||||
doc.createParagraph('Test heading2 with double red underline').heading2();
|
||||
|
||||
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
|
||||
const letterNumbering5 = numbering.createConcreteNumbering(numberedAbstract);
|
||||
letterNumbering5.overrideLevel(0, 5);
|
||||
|
||||
doc.createParagraph('Option1').setNumbering(letterNumbering, 0);
|
||||
doc.createParagraph('Option5 -- override 2 to 5').setNumbering(letterNumbering5, 0);
|
||||
doc.createParagraph('Option3').setNumbering(letterNumbering, 0);
|
||||
|
||||
doc.createParagraph()
|
||||
.createTextRun('Some monospaced content')
|
||||
.font('Monospace');
|
||||
|
||||
doc.createParagraph('An aside, in light gray italics and indented').style('aside');
|
||||
doc.createParagraph('This is normal, but well-spaced text').style('wellSpaced');
|
||||
const para = doc.createParagraph();
|
||||
para.createTextRun('This is a bold run,').bold();
|
||||
para.createTextRun(' switching to normal ');
|
||||
para.createTextRun('and then underlined ').underline();
|
||||
para.createTextRun('and back to normal.');
|
||||
|
||||
const exporter = new docx.LocalPacker(doc, styles, undefined, numbering);
|
||||
exporter.pack('test.docx');
|
29
demo/index.js
Normal file
29
demo/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
var prompt = require('prompt');
|
||||
var shelljs = require('shelljs');
|
||||
var fs = require('fs');
|
||||
|
||||
console.log('What demo do you wish to run? (Enter a number)');
|
||||
|
||||
var schema = {
|
||||
properties: {
|
||||
number: {
|
||||
pattern: /^[0-9]+$/,
|
||||
message: 'Please enter a number.',
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
prompt.start();
|
||||
|
||||
prompt.get(schema, function (err, result) {
|
||||
var demoNumber = result.number;
|
||||
var filePath = `./demo/demo${demoNumber}.js`;
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`demo${demoNumber} does not exist: ${filePath}`);
|
||||
return;
|
||||
}
|
||||
console.log(`Running demo ${demoNumber}`);
|
||||
shelljs.exec(`node ${filePath}`);
|
||||
});
|
68
deploy-docs.sh
Normal file
68
deploy-docs.sh
Normal file
@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit with nonzero exit code if anything fails
|
||||
|
||||
SOURCE_BRANCH="master"
|
||||
TARGET_BRANCH="gh-pages"
|
||||
|
||||
function doCompile {
|
||||
npm run typedoc
|
||||
}
|
||||
|
||||
# Pull requests and commits to other branches shouldn't try to deploy, just build to verify
|
||||
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "$SOURCE_BRANCH" ]; then
|
||||
echo "Skipping deploy; just doing a build."
|
||||
doCompile
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Save some useful information
|
||||
REPO=`git config remote.origin.url`
|
||||
SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
|
||||
SHA=`git rev-parse --verify HEAD`
|
||||
|
||||
# Clone the existing gh-pages for this repo into docs/
|
||||
# Create a new empty branch if gh-pages doesn't exist yet (should only happen on first deply)
|
||||
git clone $REPO docs
|
||||
cd docs
|
||||
git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH
|
||||
cd ..
|
||||
|
||||
# Clean out existing contents
|
||||
# echo "Cleaning out existing contents."
|
||||
# rm -rf docs/*
|
||||
|
||||
# Run our compile script
|
||||
doCompile
|
||||
|
||||
# Now let's go have some fun with the cloned repo
|
||||
cd docs
|
||||
git config user.name "Travis CI"
|
||||
git config user.email "dolan_miu@hotmail.com"
|
||||
ls
|
||||
|
||||
# add .nojekyll file
|
||||
touch .nojekyll
|
||||
|
||||
# If there are no changes to the compiled out (e.g. this is a README update) then just bail.
|
||||
if [ -z `git diff --exit-code` ]; then
|
||||
echo "No changes to the output on this push; exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Commit the "changes", i.e. the new version.
|
||||
# The delta will show diffs between new and old versions.
|
||||
git add .
|
||||
git commit -m "Deploy to GitHub Pages: ${SHA}"
|
||||
|
||||
# Get the deploy key by using Travis's stored variables to decrypt deploy-key.enc
|
||||
ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key"
|
||||
ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv"
|
||||
ENCRYPTED_KEY=${!ENCRYPTED_KEY_VAR}
|
||||
ENCRYPTED_IV=${!ENCRYPTED_IV_VAR}
|
||||
openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in deploy-key.enc -out deploy-key -d
|
||||
chmod 600 deploy-key
|
||||
eval `ssh-agent -s`
|
||||
ssh-add deploy-key
|
||||
|
||||
# Now that we're all set up, we can push.
|
||||
git push $SSH_REPO $TARGET_BRANCH
|
BIN
deploy-key.enc
Normal file
BIN
deploy-key.enc
Normal file
Binary file not shown.
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docx",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
@ -9,7 +9,8 @@
|
||||
"prepublishOnly": "npm run build",
|
||||
"lint": "tslint --project ./ts",
|
||||
"build": "rimraf ./build && tsc -p ts",
|
||||
"demo": "npm run build && node ./demo/demo.js"
|
||||
"demo": "npm run build && node ./demo",
|
||||
"typedoc": "typedoc --out docs/ ts/ --module commonjs --target ES6 --disableOutputCheck"
|
||||
},
|
||||
"files": [
|
||||
"ts",
|
||||
@ -31,8 +32,9 @@
|
||||
"officegen",
|
||||
"clippy"
|
||||
],
|
||||
"types": "./build/index.d.ts",
|
||||
"dependencies": {
|
||||
"@types/archiver": "^0.15.37",
|
||||
"@types/archiver": "^1.3.2",
|
||||
"@types/express": "^4.0.35",
|
||||
"archiver": "^1.3.0",
|
||||
"xml": "^1.0.1"
|
||||
@ -48,8 +50,11 @@
|
||||
"@types/mocha": "^2.2.39",
|
||||
"chai": "^3.5.0",
|
||||
"mocha": "^3.2.0",
|
||||
"prompt": "^1.0.0",
|
||||
"rimraf": "^2.5.2",
|
||||
"tslint": "^4.5.1",
|
||||
"shelljs": "^0.7.7",
|
||||
"tslint": "^5.1.0",
|
||||
"typedoc": "^0.5.10",
|
||||
"typescript": "^2.2.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { XmlAttributeComponent } from "../xml-components";
|
||||
|
||||
interface IDocumentAttributesProperties {
|
||||
export interface IDocumentAttributesProperties {
|
||||
wpc?: string;
|
||||
mc?: string;
|
||||
o?: string;
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
type alignmentOptions = "left" | "center" | "right" | "both";
|
||||
export type AlignmentOptions = "left" | "center" | "right" | "both";
|
||||
|
||||
class AlignmentAttributes extends XmlAttributeComponent<{val: alignmentOptions}> {
|
||||
export class AlignmentAttributes extends XmlAttributeComponent<{val: AlignmentOptions}> {
|
||||
protected xmlKeys = {val: "w:val"};
|
||||
}
|
||||
|
||||
export class Alignment extends XmlComponent {
|
||||
|
||||
constructor(type: alignmentOptions) {
|
||||
constructor(type: AlignmentOptions) {
|
||||
super("w:jc");
|
||||
this.root.push(new AlignmentAttributes({val: type}));
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
export { Alignment } from "./alignment";
|
||||
export { ThematicBreak } from "./border";
|
||||
export { Indent } from "./indent";
|
||||
export { KeepLines, KeepNext } from "./keep";
|
||||
export { PageBreak } from "./page-break";
|
||||
export { ParagraphProperties } from "./properties";
|
||||
export { ISpacingProperties, Spacing } from "./spacing";
|
||||
|
@ -8,6 +8,7 @@ import { XmlComponent } from "../xml-components";
|
||||
import { Alignment } from "./alignment";
|
||||
import { ThematicBreak } from "./border";
|
||||
import { Indent } from "./indent";
|
||||
import { KeepLines, KeepNext } from "./keep";
|
||||
import { PageBreak } from "./page-break";
|
||||
import { ParagraphProperties } from "./properties";
|
||||
import { ISpacingProperties, Spacing } from "./spacing";
|
||||
@ -139,5 +140,15 @@ export class Paragraph extends XmlComponent {
|
||||
public spacing(params: ISpacingProperties): Paragraph {
|
||||
this.properties.push(new Spacing(params));
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
public keepNext(): Paragraph {
|
||||
this.properties.push(new KeepNext());
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepLines(): Paragraph {
|
||||
this.properties.push(new KeepLines());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
13
ts/docx/paragraph/keep.ts
Normal file
13
ts/docx/paragraph/keep.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { XmlComponent } from "../xml-components";
|
||||
|
||||
export class KeepLines extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:keepLines");
|
||||
}
|
||||
}
|
||||
|
||||
export class KeepNext extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:keepNext");
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
class TabStop extends XmlComponent {
|
||||
export class TabStop extends XmlComponent {
|
||||
|
||||
constructor(tab: Tab) {
|
||||
super("w:tabs");
|
||||
@ -8,15 +8,15 @@ class TabStop extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export type tabOptions = "left" | "right";
|
||||
export type TabOptions = "left" | "right";
|
||||
|
||||
class TabAttributes extends XmlAttributeComponent<{val: tabOptions, pos: string | number}> {
|
||||
export class TabAttributes extends XmlAttributeComponent<{val: TabOptions, pos: string | number}> {
|
||||
protected xmlKeys = {val: "w:val", pos: "w:pos"};
|
||||
}
|
||||
|
||||
class Tab extends XmlComponent {
|
||||
export class Tab extends XmlComponent {
|
||||
|
||||
constructor(value: tabOptions, position: string | number) {
|
||||
constructor(value: TabOptions, position: string | number) {
|
||||
super("w:tab");
|
||||
this.root.push(new TabAttributes({
|
||||
val: value,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../../../../../xml-components";
|
||||
import { GraphicData } from "./graphic-data";
|
||||
|
||||
|
||||
interface IGraphicProperties {
|
||||
a: string;
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { XmlComponent } from "../../xml-components";
|
||||
import { XmlAttributeComponent, XmlComponent } from "../../xml-components";
|
||||
|
||||
class TextAttributes extends XmlAttributeComponent<{space: "default" | "preserve"}> {
|
||||
protected xmlKeys = {space: "xml:space"};
|
||||
}
|
||||
|
||||
export class Text extends XmlComponent {
|
||||
|
||||
constructor(text: string) {
|
||||
super("w:t");
|
||||
this.root.push(new TextAttributes({space: "preserve"}));
|
||||
if (text) {
|
||||
this.root.push(text);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
|
||||
abstract class VerticalAlign extends XmlComponent {
|
||||
export abstract class VerticalAlign extends XmlComponent {
|
||||
|
||||
constructor(type: string) {
|
||||
super("w:vertAlign");
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Attributes, XmlComponent } from "../xml-components";
|
||||
|
||||
abstract class BaseUnderline extends XmlComponent {
|
||||
export abstract class BaseUnderline extends XmlComponent {
|
||||
|
||||
constructor(underlineType: string, color?: string) {
|
||||
super("w:u");
|
||||
|
@ -2,7 +2,7 @@ import { Paragraph } from "../paragraph";
|
||||
import { XmlComponent } from "../xml-components";
|
||||
|
||||
import { TableGrid } from "./grid";
|
||||
import { TableProperties, widthTypes } from "./properties";
|
||||
import { TableProperties, WidthTypes } from "./properties";
|
||||
|
||||
export class Table extends XmlComponent {
|
||||
private properties: TableProperties;
|
||||
@ -51,7 +51,7 @@ export class Table extends XmlComponent {
|
||||
return this.getRow(row).getCell(col);
|
||||
}
|
||||
|
||||
public setWidth(type: widthTypes, width: number | string): Table {
|
||||
public setWidth(type: WidthTypes, width: number | string): Table {
|
||||
this.properties.setWidth(type, width);
|
||||
return this;
|
||||
}
|
||||
@ -62,7 +62,7 @@ export class Table extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class TableRow extends XmlComponent {
|
||||
export class TableRow extends XmlComponent {
|
||||
private properties: TableRowProperties;
|
||||
private cells: TableCell[];
|
||||
|
||||
@ -79,13 +79,13 @@ class TableRow extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class TableRowProperties extends XmlComponent {
|
||||
export class TableRowProperties extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:trPr");
|
||||
}
|
||||
}
|
||||
|
||||
class TableCell extends XmlComponent {
|
||||
export class TableCell extends XmlComponent {
|
||||
private properties: TableCellProperties;
|
||||
|
||||
constructor() {
|
||||
@ -99,7 +99,7 @@ class TableCell extends XmlComponent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public prepForXml(): object {
|
||||
public prepForXml(): XmlableObject {
|
||||
// Cells must end with a paragraph
|
||||
const retval = super.prepForXml();
|
||||
const content = retval["w:tc"];
|
||||
@ -116,7 +116,7 @@ class TableCell extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class TableCellProperties extends XmlComponent {
|
||||
export class TableCellProperties extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:tcPr");
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../xml-components";
|
||||
|
||||
export type widthTypes = "dxa" | "pct" | "nil" | "auto";
|
||||
export type WidthTypes = "dxa" | "pct" | "nil" | "auto";
|
||||
|
||||
export class TableProperties extends XmlComponent {
|
||||
constructor() {
|
||||
super("w:tblPr");
|
||||
}
|
||||
|
||||
public setWidth(type: widthTypes, w: number | string): TableProperties {
|
||||
public setWidth(type: WidthTypes, w: number | string): TableProperties {
|
||||
this.root.push(new PreferredTableWidth(type, w));
|
||||
return this;
|
||||
}
|
||||
@ -19,7 +19,7 @@ export class TableProperties extends XmlComponent {
|
||||
}
|
||||
|
||||
interface ITableWidth {
|
||||
type: widthTypes;
|
||||
type: WidthTypes;
|
||||
w: number | string;
|
||||
}
|
||||
|
||||
@ -28,20 +28,20 @@ class TableWidthAttributes extends XmlAttributeComponent<ITableWidth> {
|
||||
}
|
||||
|
||||
class PreferredTableWidth extends XmlComponent {
|
||||
constructor(type: widthTypes, w: number | string) {
|
||||
constructor(type: WidthTypes, w: number | string) {
|
||||
super("w:tblW");
|
||||
this.root.push(new TableWidthAttributes({type, w}));
|
||||
}
|
||||
}
|
||||
|
||||
type tableLayout = "autofit" | "fixed";
|
||||
type TableLayoutOptions = "autofit" | "fixed";
|
||||
|
||||
class TableLayoutAttributes extends XmlAttributeComponent<{type: tableLayout}> {
|
||||
class TableLayoutAttributes extends XmlAttributeComponent<{type: TableLayoutOptions}> {
|
||||
protected xmlKeys = {type: "w:type"};
|
||||
}
|
||||
|
||||
class TableLayout extends XmlComponent {
|
||||
constructor(type: tableLayout) {
|
||||
constructor(type: TableLayoutOptions) {
|
||||
super("w:tblLayout");
|
||||
this.root.push(new TableLayoutAttributes({type}));
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { XmlAttributeComponent } from "./default-attributes";
|
||||
|
||||
interface IAttributesProperties {
|
||||
export interface IAttributesProperties {
|
||||
val?: string | number | boolean;
|
||||
color?: string;
|
||||
space?: string;
|
||||
|
@ -5,5 +5,5 @@ export abstract class BaseXmlComponent {
|
||||
this.rootKey = rootKey;
|
||||
}
|
||||
|
||||
public abstract prepForXml(): object;
|
||||
public abstract prepForXml(): XmlableObject;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BaseXmlComponent } from "./base";
|
||||
|
||||
type AttributeMap<T> = {[P in keyof T]: string};
|
||||
export type AttributeMap<T> = {[P in keyof T]: string};
|
||||
|
||||
export abstract class XmlAttributeComponent<T> extends BaseXmlComponent {
|
||||
protected root: T;
|
||||
@ -11,7 +11,7 @@ export abstract class XmlAttributeComponent<T> extends BaseXmlComponent {
|
||||
this.root = properties;
|
||||
}
|
||||
|
||||
public prepForXml(): {_attr: {[key: string]: (string | number | boolean)}} {
|
||||
public prepForXml(): XmlableObject {
|
||||
const attrs = {};
|
||||
Object.keys(this.root).forEach((key) => {
|
||||
const value = this.root[key];
|
||||
|
@ -9,7 +9,7 @@ export abstract class XmlComponent extends BaseXmlComponent {
|
||||
this.root = new Array<BaseXmlComponent>();
|
||||
}
|
||||
|
||||
public prepForXml(): object {
|
||||
public prepForXml(): XmlableObject {
|
||||
const children = this.root.map((comp) => {
|
||||
if (comp instanceof BaseXmlComponent) {
|
||||
return comp.prepForXml();
|
||||
@ -22,5 +22,5 @@ export abstract class XmlComponent extends BaseXmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export * from "./attributes"
|
||||
export * from "./attributes";
|
||||
export * from "./default-attributes";
|
||||
|
3
ts/docx/xml-components/xmlable-object.d.ts
vendored
Normal file
3
ts/docx/xml-components/xmlable-object.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
declare interface XmlableObject extends Object {
|
||||
_attr?: { [key: string]: (string | number | boolean) }
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { BaseXmlComponent } from "../docx/xml-components";
|
||||
|
||||
export class Formatter {
|
||||
public format(input: BaseXmlComponent): any {
|
||||
public format(input: BaseXmlComponent): XmlableObject {
|
||||
return input.prepForXml();
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,6 @@ export class ExpressPacker extends Packer {
|
||||
|
||||
public pack(name: string): void {
|
||||
this.res.attachment(`${name}.docx`);
|
||||
super.pack(this.res);
|
||||
super.compile(this.res);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,6 @@ export class LocalPacker extends Packer {
|
||||
public pack(path: string): void {
|
||||
path = path.replace(/.docx$/, "");
|
||||
this.stream = fs.createWriteStream(`${path}.docx`);
|
||||
super.pack(this.stream);
|
||||
super.compile(this.stream);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import * as archiver from "archiver";
|
||||
import * as express from "express";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as xml from "xml";
|
||||
import { Document } from "../../docx";
|
||||
@ -12,7 +14,7 @@ import { Formatter } from "../formatter";
|
||||
const TEMPLATE_PATH = path.resolve(__dirname, "../../../template");
|
||||
|
||||
export abstract class Packer {
|
||||
protected archive: any;
|
||||
protected archive: archiver.Archiver;
|
||||
private formatter: Formatter;
|
||||
private style: Styles;
|
||||
|
||||
@ -42,15 +44,13 @@ export abstract class Packer {
|
||||
});
|
||||
}
|
||||
|
||||
public pack(output: any): void {
|
||||
protected compile(output: fs.WriteStream | express.Response): void {
|
||||
this.archive.pipe(output);
|
||||
this.archive.glob("**", {
|
||||
expand: true,
|
||||
cwd: TEMPLATE_PATH,
|
||||
});
|
||||
|
||||
this.archive.glob("**/.rels", {
|
||||
expand: true,
|
||||
cwd: TEMPLATE_PATH,
|
||||
});
|
||||
|
||||
|
@ -56,21 +56,29 @@ class LevelJc extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export class Level extends XmlComponent {
|
||||
export class LevelBase extends XmlComponent {
|
||||
private paragraphProperties: ParagraphProperties;
|
||||
private runProperties: RunProperties;
|
||||
|
||||
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) {
|
||||
constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) {
|
||||
super("w:lvl");
|
||||
this.root.push(new LevelAttributes({
|
||||
ilvl: level,
|
||||
tentative: 1,
|
||||
}));
|
||||
|
||||
this.root.push(new Start(1));
|
||||
this.root.push(new NumberFormat(numberFormat));
|
||||
this.root.push(new LevelText(levelText));
|
||||
this.root.push(new LevelJc(lvlJc));
|
||||
if (start !== undefined) {
|
||||
this.root.push(new Start(start));
|
||||
}
|
||||
if (numberFormat !== undefined) {
|
||||
this.root.push(new NumberFormat(numberFormat));
|
||||
}
|
||||
if (levelText !== undefined) {
|
||||
this.root.push(new LevelText(levelText));
|
||||
}
|
||||
if (lvlJc !== undefined) {
|
||||
this.root.push(new LevelJc(lvlJc));
|
||||
}
|
||||
|
||||
this.paragraphProperties = new ParagraphProperties();
|
||||
this.runProperties = new RunProperties();
|
||||
@ -196,5 +204,25 @@ export class Level extends XmlComponent {
|
||||
public spacing(params: paragraph.ISpacingProperties): Level {
|
||||
this.addParagraphProperty(new paragraph.Spacing(params));
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
public keepNext(): Level {
|
||||
this.addParagraphProperty(new paragraph.KeepNext());
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepLines(): Level {
|
||||
this.addParagraphProperty(new paragraph.KeepLines());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class Level extends LevelBase {
|
||||
// This is the level that sits under abstractNum. We make a
|
||||
// handful of properties required
|
||||
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) {
|
||||
super(level, 1, numberFormat, levelText, lvlJc);
|
||||
}
|
||||
}
|
||||
|
||||
export class LevelForOverride extends LevelBase {}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Attributes, XmlAttributeComponent, XmlComponent } from "../docx/xml-components";
|
||||
import { LevelForOverride } from "./level";
|
||||
|
||||
class AbstractNumId extends XmlComponent {
|
||||
|
||||
@ -29,4 +30,51 @@ export class Num extends XmlComponent {
|
||||
this.root.push(new AbstractNumId(abstractNumId));
|
||||
this.id = numId;
|
||||
}
|
||||
|
||||
public overrideLevel(num: number, start?: number): LevelOverride {
|
||||
const olvl = new LevelOverride(num, start);
|
||||
this.root.push(olvl);
|
||||
return olvl;
|
||||
}
|
||||
}
|
||||
|
||||
class LevelOverrideAttributes extends XmlAttributeComponent<{ilvl: number}> {
|
||||
protected xmlKeys = {ilvl: "w:ilvl"};
|
||||
}
|
||||
|
||||
export class LevelOverride extends XmlComponent {
|
||||
private levelNum: number;
|
||||
private lvl?: LevelForOverride;
|
||||
|
||||
constructor(levelNum: number, start?: number) {
|
||||
super("w:lvlOverride");
|
||||
this.root.push(new LevelOverrideAttributes({ilvl: levelNum}));
|
||||
if (start !== undefined) {
|
||||
this.root.push(new StartOverride(start));
|
||||
}
|
||||
this.levelNum = levelNum;
|
||||
}
|
||||
|
||||
get level(): LevelForOverride {
|
||||
let lvl: LevelForOverride;
|
||||
if (!this.lvl) {
|
||||
lvl = new LevelForOverride(this.levelNum);
|
||||
this.root.push(lvl);
|
||||
this.lvl = lvl;
|
||||
} else {
|
||||
lvl = this.lvl;
|
||||
}
|
||||
return lvl;
|
||||
}
|
||||
}
|
||||
|
||||
class StartOverrideAttributes extends XmlAttributeComponent<{val: number}> {
|
||||
protected xmlKeys = {val: "w:val"};
|
||||
}
|
||||
|
||||
class StartOverride extends XmlComponent {
|
||||
constructor(start: number) {
|
||||
super("w:startOverride");
|
||||
this.root.push(new StartOverrideAttributes({val: start}));
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export class Revision extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DateComponent extends XmlComponent {
|
||||
export abstract class DateComponent extends XmlComponent {
|
||||
protected getCurrentDate(): string {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
|
@ -2,7 +2,7 @@ import { DocumentAttributes } from "../docx/document/document-attributes";
|
||||
import { XmlComponent } from "../docx/xml-components";
|
||||
import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components";
|
||||
|
||||
interface IPropertiesOptions {
|
||||
export interface IPropertiesOptions {
|
||||
title?: string;
|
||||
subject?: string;
|
||||
creator?: string;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { XmlAttributeComponent } from "../docx/xml-components";
|
||||
|
||||
interface IRelationshipsAttributesProperties {
|
||||
export interface IRelationshipsAttributesProperties {
|
||||
xmlns: string;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { XmlAttributeComponent, XmlComponent } from "../../docx/xml-components";
|
||||
|
||||
interface ILatentStyleExceptionAttributesProperties {
|
||||
export interface ILatentStyleExceptionAttributesProperties {
|
||||
name?: string;
|
||||
uiPriority?: string;
|
||||
qFormat?: string;
|
||||
@ -8,7 +8,7 @@ interface ILatentStyleExceptionAttributesProperties {
|
||||
unhideWhenUsed?: string;
|
||||
}
|
||||
|
||||
class LatentStyleExceptionAttributes extends XmlAttributeComponent<ILatentStyleExceptionAttributesProperties> {
|
||||
export class LatentStyleExceptionAttributes extends XmlAttributeComponent<ILatentStyleExceptionAttributesProperties> {
|
||||
protected xmlKeys = {
|
||||
name: "w:name",
|
||||
uiPriority: "w:uiPriority",
|
||||
|
@ -179,7 +179,17 @@ export class ParagraphStyle extends Style {
|
||||
public spacing(params: paragraph.ISpacingProperties): ParagraphStyle {
|
||||
this.addParagraphProperty(new paragraph.Spacing(params));
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
public keepNext(): ParagraphStyle {
|
||||
this.addParagraphProperty(new paragraph.KeepNext());
|
||||
return this;
|
||||
}
|
||||
|
||||
public keepLines(): ParagraphStyle {
|
||||
this.addParagraphProperty(new paragraph.KeepLines());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export class HeadingStyle extends ParagraphStyle {
|
||||
|
@ -41,7 +41,7 @@ describe("Document", () => {
|
||||
expect(body[0]).to.have.property("w:p").which.includes({
|
||||
"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": ["sample paragraph text"]},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "sample paragraph text"]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe("Paragraph", () => {
|
||||
expect(tree).to.be.an("array").which.includes({
|
||||
"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": ["this is a test run"]},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "this is a test run"]},
|
||||
],
|
||||
});
|
||||
});
|
||||
@ -269,4 +269,24 @@ describe("Paragraph", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#keepLines", () => {
|
||||
it("should set the paragraph keepLines sub-component", () => {
|
||||
paragraph.keepLines();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [{"w:pPr": [{"w:keepLines": []}]}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#keepNext", () => {
|
||||
it("should set the paragraph keepNext sub-component", () => {
|
||||
paragraph.keepNext();
|
||||
const tree = new Formatter().format(paragraph);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:p": [{"w:pPr": [{"w:keepNext": []}]}],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ describe("Drawing", () => {
|
||||
it("should create a Drawing with correct root key", () => {
|
||||
const newJson = Utility.jsonify(currentBreak);
|
||||
assert.equal(newJson.rootKey, "w:drawing");
|
||||
console.log(JSON.stringify(newJson, null, 2));
|
||||
// console.log(JSON.stringify(newJson, null, 2));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
22
ts/tests/docx/run/run-components/text.ts
Normal file
22
ts/tests/docx/run/run-components/text.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { expect } from "chai";
|
||||
import { Text } from "../../../../docx/run/run-components/text";
|
||||
import { Formatter } from "../../../../export/formatter";
|
||||
|
||||
describe("Text", () => {
|
||||
describe("#constructor", () => {
|
||||
it("creates an empty text run if no text is given", () => {
|
||||
const t = new Text("");
|
||||
const f = new Formatter().format(t);
|
||||
expect(f).to.deep.equal({"w:t": [{_attr: {"xml:space": "preserve"}}]});
|
||||
});
|
||||
|
||||
it("adds the passed in text to the component", () => {
|
||||
const t = new Text(" this is\n text");
|
||||
const f = new Formatter().format(t);
|
||||
expect(f).to.deep.equal({"w:t": [
|
||||
{_attr: {"xml:space": "preserve"}},
|
||||
" this is\n text",
|
||||
]});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { assert } from "chai";
|
||||
import { expect } from "chai";
|
||||
import { TextRun } from "../../../docx/run/text-run";
|
||||
import { Utility } from "../../utility";
|
||||
import { Formatter } from "../../../export/formatter";
|
||||
|
||||
describe("TextRun", () => {
|
||||
let run: TextRun;
|
||||
@ -9,8 +9,11 @@ describe("TextRun", () => {
|
||||
|
||||
it("should add text into run", () => {
|
||||
run = new TextRun("test");
|
||||
const newJson = Utility.jsonify(run);
|
||||
assert.equal(newJson.root[1].root, "test");
|
||||
const f = new Formatter().format(run);
|
||||
expect(f).to.deep.equal({"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "test"]},
|
||||
]});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* tslint:disable:no-unused-expression */
|
||||
import { expect } from "chai";
|
||||
import { Paragraph } from "../../../docx/paragraph";
|
||||
import { Table } from "../../../docx/table";
|
||||
@ -36,7 +37,7 @@ describe("Table", () => {
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [c]}]},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, c]}]},
|
||||
]},
|
||||
]});
|
||||
expect(tree).to.deep.equal({
|
||||
@ -65,7 +66,7 @@ describe("Table", () => {
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [c]}]},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, c]}]},
|
||||
]},
|
||||
]});
|
||||
expect(tree).to.deep.equal({
|
||||
@ -153,7 +154,7 @@ describe("Table", () => {
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": ["Hello"]}]},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": [{_attr: {"xml:space": "preserve"}}, "Hello"]}]},
|
||||
]},
|
||||
],
|
||||
});
|
||||
@ -175,7 +176,10 @@ describe("Table", () => {
|
||||
{"w:tcPr": []},
|
||||
{"w:p": [
|
||||
{"w:pPr": []},
|
||||
{"w:r": [{"w:rPr": []}, {"w:t": ["Test paragraph"]}]},
|
||||
{"w:r": [
|
||||
{"w:rPr": []},
|
||||
{"w:t": [{_attr: {"xml:space": "preserve"}}, "Test paragraph"]},
|
||||
]},
|
||||
]},
|
||||
],
|
||||
});
|
||||
|
@ -40,6 +40,10 @@ describe("Formatter", () => {
|
||||
});
|
||||
let newJson = formatter.format(attributes);
|
||||
newJson = Utility.jsonify(newJson);
|
||||
if (newJson._attr === undefined) {
|
||||
assert.fail();
|
||||
return;
|
||||
}
|
||||
assert.isDefined(newJson._attr["w:rsidSect"]);
|
||||
});
|
||||
|
||||
@ -49,6 +53,10 @@ describe("Formatter", () => {
|
||||
});
|
||||
let newJson = formatter.format(attributes);
|
||||
newJson = Utility.jsonify(newJson);
|
||||
if (newJson._attr === undefined) {
|
||||
assert.fail();
|
||||
return;
|
||||
}
|
||||
assert.isDefined(newJson._attr["w:val"]);
|
||||
});
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { expect } from "chai";
|
||||
import { Formatter } from "../export/formatter";
|
||||
import { Numbering } from "../numbering";
|
||||
import { AbstractNumbering } from "../numbering/abstract-numbering";
|
||||
import { LevelForOverride } from "../numbering/level";
|
||||
import { Num } from "../numbering/num";
|
||||
|
||||
describe("Numbering", () => {
|
||||
@ -221,6 +222,26 @@ describe("AbstractNumbering", () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepLines", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.keepLines();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{"w:keepLines": []}],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepNext", () => {
|
||||
const abstractNumbering = new AbstractNumbering(1);
|
||||
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.")
|
||||
.keepNext();
|
||||
const tree = new Formatter().format(level);
|
||||
expect(tree["w:lvl"]).to.include({
|
||||
"w:pPr": [{"w:keepNext": []}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatting methods: run properties", () => {
|
||||
@ -394,3 +415,50 @@ describe("AbstractNumbering", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("concrete numbering", () => {
|
||||
describe("#overrideLevel", () => {
|
||||
let numbering;
|
||||
let abstractNumbering;
|
||||
let concreteNumbering;
|
||||
beforeEach(() => {
|
||||
numbering = new Numbering();
|
||||
abstractNumbering = numbering.createAbstractNumbering();
|
||||
concreteNumbering = numbering.createConcreteNumbering(abstractNumbering);
|
||||
|
||||
});
|
||||
|
||||
it("sets a new override level for the given level number", () => {
|
||||
concreteNumbering.overrideLevel(3);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({"w:lvlOverride": [{_attr: {"w:ilvl": 3}}]});
|
||||
});
|
||||
|
||||
it("sets the startOverride element if start is given", () => {
|
||||
concreteNumbering.overrideLevel(1, 9);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{_attr: {"w:ilvl": 1}},
|
||||
{"w:startOverride": [{_attr: {"w:val": 9}}]},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets the lvl element if overrideLevel.level is accessed", () => {
|
||||
const ol = concreteNumbering.overrideLevel(1);
|
||||
expect(ol.level).to.be.instanceof(LevelForOverride);
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{_attr: {"w:ilvl": 1}},
|
||||
{"w:lvl": [
|
||||
{_attr: {"w15:tentative": 1, "w:ilvl": 1}},
|
||||
{"w:pPr": []},
|
||||
{"w:rPr": []},
|
||||
]},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -326,6 +326,32 @@ describe("ParagraphStyle", () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepLines", () => {
|
||||
const style = new ParagraphStyle("myStyleId")
|
||||
.keepLines();
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}},
|
||||
{"w:pPr": [{"w:keepLines": []}]},
|
||||
{"w:rPr": []},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("#keepNext", () => {
|
||||
const style = new ParagraphStyle("myStyleId")
|
||||
.keepNext();
|
||||
const tree = new Formatter().format(style);
|
||||
expect(tree).to.deep.equal({
|
||||
"w:style": [
|
||||
{_attr: {"w:type": "paragraph", "w:styleId": "myStyleId"}},
|
||||
{"w:pPr": [{"w:keepNext": []}]},
|
||||
{"w:rPr": []},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatting methods: run properties", () => {
|
||||
|
@ -8,7 +8,8 @@
|
||||
"outDir": "../build",
|
||||
"sourceRoot": "./",
|
||||
"rootDir": "./",
|
||||
"module": "commonjs"
|
||||
"module": "commonjs",
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": [
|
||||
"tests"
|
||||
|
Reference in New Issue
Block a user