Compare commits

...

53 Commits
3.5.0 ... 3.6.0

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

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

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

* (fix): fixed issue with numbering in document
2018-04-17 15:33:53 +02:00
323f91dd68 Version bump 2018-03-28 16:31:44 +02:00
810ccb40d7 Merge pull request #3 from h4buli/feature/import-styles
styles: support for external styles. parsing external styles.
2018-03-28 16:02:11 +02:00
5242f7d55c styles: support for external styles. parsing external styles. 2018-03-28 14:54:07 +02:00
d293ae516c Merge pull request #2 from h4buli/revert-1-feature/external-styles-support
Revert "(styles): add support to provide external styles (as complete file co…"
2018-03-26 16:29:16 +02:00
3fb563f9c8 Revert "(styles): add support to provide external styles (as complete file co…" 2018-03-26 16:28:40 +02:00
ce306aef07 Version bump 2018-03-23 12:25:30 +01:00
373c850f35 Merge pull request #1 from h4buli/feature/external-styles-support
(styles): add support to provide external styles (as complete file co…
2018-03-23 12:21:00 +01:00
a0e00b8eff (styles): add support to provide external styles (as complete file content) 2018-03-23 12:18:31 +01:00
114c429aed pdf-export: remove option to export to pdf 2018-03-21 10:53:07 +01:00
40251a76f6 setup: rename npm package 2018-03-21 10:46:22 +01:00
38 changed files with 1127 additions and 52 deletions

View File

@ -9,15 +9,14 @@
---
[![NPM version][npm-image]][npm-url]
[![Downloads per month][downloads-image]][downloads-url]
[![Build Status][travis-image]][travis-url]
[![Dependency Status][gemnasium-image]][gemnasium-url]
[![Dependency Status][daviddm-image]][daviddm-url]
[![Known Vulnerabilities][snky-image]][snky-url]
[![Chat on Gitter][gitter-image]][gitter-url]
[![code style: prettier][prettier-image]][prettier-url]
[![PRs Welcome][pr-image]][pr-url]
[![NPM](https://nodei.co/npm/docx.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/docx/)
# docx
## Install
@ -103,6 +102,8 @@ Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contri
[npm-image]: https://badge.fury.io/js/docx.svg
[npm-url]: https://npmjs.org/package/docx
[downloads-image]: https://img.shields.io/npm/dm/docx.svg
[downloads-url]: https://npmjs.org/package/docx
[travis-image]: https://travis-ci.org/dolanmiu/docx.svg?branch=master
[travis-url]: https://travis-ci.org/dolanmiu/docx
[daviddm-image]: https://david-dm.org/dolanmiu/docx.svg?theme=shields.io
@ -111,8 +112,6 @@ Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contri
[snky-url]: https://snyk.io/test/github/dolanmiu/docx
[gitter-image]: https://badges.gitter.im/dolanmiu/docx.svg
[gitter-url]: https://gitter.im/docx-lib/Lobby
[gemnasium-image]: https://gemnasium.com/badges/github.com/dolanmiu/docx.svg
[gemnasium-url]: https://gemnasium.com/github.com/dolanmiu/docx
[prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg
[prettier-url]: https://github.com/prettier/prettier
[pr-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg

File diff suppressed because one or more lines are too long

26
demo/demo13.js Normal file
View File

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

23
demo/demo14.js Normal file
View File

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

View File

@ -1,6 +1,6 @@
{
"name": "docx",
"version": "3.5.0",
"version": "3.6.0",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
"main": "build/index.js",
"scripts": {
@ -46,6 +46,7 @@
"@types/image-size": "0.0.29",
"@types/request-promise": "^4.1.41",
"archiver": "^2.1.1",
"fast-xml-parser": "^3.3.6",
"image-size": "^0.6.2",
"request": "^2.83.0",
"request-promise": "^4.2.2",

View File

@ -34,6 +34,7 @@ export class Compiler {
const xmlRelationships = xml(this.formatter.format(this.file.DocumentRelationships));
const xmlFileRelationships = xml(this.formatter.format(this.file.FileRelationships));
const xmlHeader = xml(this.formatter.format(this.file.Header.Header));
const xmlHeader2 = xml(this.formatter.format(this.file.firstPageHeader.Header));
const xmlFooter = xml(this.formatter.format(this.file.Footer.Footer));
const xmlHeaderRelationships = xml(this.formatter.format(this.file.Header.Relationships));
const xmlFooterRelationships = xml(this.formatter.format(this.file.Footer.Relationships));
@ -64,6 +65,10 @@ export class Compiler {
name: "word/header1.xml",
});
this.archive.append(xmlHeader2, {
name: "word/header2.xml",
});
this.archive.append(xmlFooter, {
name: "word/footer1.xml",
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,15 @@
import { XmlComponent } from "file/xml-components";
import { Indent } from "file/paragraph";
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes";
import { Indent } from "../paragraph/formatting";
import { AbstractNumbering } from "./abstract-numbering";
import { Num } from "./num";
export class Numbering extends XmlComponent {
private nextId: number;
private abstractNumbering: XmlComponent[] = [];
private concreteNumbering: XmlComponent[] = [];
constructor() {
super("w:numbering");
this.root.push(
@ -58,13 +61,19 @@ export class Numbering extends XmlComponent {
public createAbstractNumbering(): AbstractNumbering {
const num = new AbstractNumbering(this.nextId++);
this.root.push(num);
this.abstractNumbering.push(num);
return num;
}
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
const num = new Num(this.nextId++, abstractNumbering.id);
this.root.push(num);
this.concreteNumbering.push(num);
return num;
}
public prepForXml(): IXmlableObject {
this.abstractNumbering.forEach((x) => this.root.push(x));
this.concreteNumbering.forEach((x) => this.root.push(x));
return super.prepForXml();
}
}

View File

@ -147,6 +147,11 @@ export class Paragraph extends XmlComponent {
return this;
}
public setCustomNumbering(numberId: number, indentLevel: number): Paragraph {
this.properties.push(new NumberProperties(numberId, indentLevel));
return this;
}
public style(styleId: string): Paragraph {
this.properties.push(new Style(styleId));
return this;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { DocumentAttributes } from "../document/document-attributes";
import { Color, Italics, Size } from "../paragraph/run/formatting";
import { Styles } from "./";
// import { DocumentDefaults } from "./defaults";
import {
Heading1Style,
Heading2Style,
@ -16,7 +16,15 @@ import {
export class DefaultStylesFactory {
public newInstance(): Styles {
const styles = new Styles();
const documentAttributes = new DocumentAttributes({
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
Ignorable: "w14 w15",
});
const styles = new Styles(documentAttributes);
styles.createDocumentDefaults();
const titleStyle = new TitleStyle();

View File

@ -1,21 +1,13 @@
import { XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes";
import { BaseXmlComponent, XmlComponent } from "file/xml-components";
import { DocumentDefaults } from "./defaults";
import { ParagraphStyle } from "./style";
export class Styles extends XmlComponent {
constructor() {
constructor(initialStyles?: BaseXmlComponent) {
super("w:styles");
this.root.push(
new DocumentAttributes({
mc: "http://schemas.openxmlformats.org/markup-compatibility/2006",
r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
w14: "http://schemas.microsoft.com/office/word/2010/wordml",
w15: "http://schemas.microsoft.com/office/word/2012/wordml",
Ignorable: "w14 w15",
}),
);
if (initialStyles) {
this.root.push(initialStyles);
}
}
public push(style: XmlComponent): Styles {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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