Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
ba0ca78696 | |||
22ba53ad4c | |||
81304f50d1 | |||
3c22372f49 | |||
7584671312 | |||
7296c9e744 | |||
c63a8982e4 | |||
8d35dc1bb0 | |||
0fedfcf709 | |||
6c2eb882bb | |||
59560d96ba | |||
864c9afd93 | |||
b4f07f51ae | |||
e9b153095c | |||
7968c1efcf | |||
87648a742c | |||
ac512b2eab | |||
fdf6a59c4c | |||
4b9a6a6735 | |||
53ab822dbc | |||
0c9c292291 | |||
573dd753a7 | |||
79b5b3a1f6 | |||
0d34d2d92e | |||
534c601068 | |||
424436579b | |||
a716360faa | |||
af485c678d | |||
84e298e7ee | |||
753287d9d1 | |||
21bb8f9016 | |||
dc136daeab | |||
e67f5f80e1 | |||
3691d79a4a | |||
4f48c8fb80 | |||
20ba081308 | |||
2119ae769b | |||
c618ca7539 | |||
8b11140be2 | |||
bebfec7755 | |||
124aac4888 | |||
b3bfd063d8 | |||
c92cab5e5b | |||
323f91dd68 | |||
810ccb40d7 | |||
5242f7d55c | |||
d293ae516c | |||
3fb563f9c8 | |||
ce306aef07 | |||
373c850f35 | |||
a0e00b8eff | |||
114c429aed | |||
40251a76f6 |
@ -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]
|
||||
|
||||
[](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
|
||||
|
2
demo/assets/custom-styles.xml
Normal file
2
demo/assets/custom-styles.xml
Normal file
File diff suppressed because one or more lines are too long
26
demo/demo13.js
Normal file
26
demo/demo13.js
Normal 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
23
demo/demo14.js
Normal 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!');
|
@ -10,4 +10,4 @@ doc.Footer.createParagraph("Footer text");
|
||||
var exporter = new docx.LocalPacker(doc);
|
||||
exporter.pack('My Document');
|
||||
|
||||
console.log('Document created successfully at project root!');
|
||||
console.log('Document created successfully at project root!');
|
@ -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",
|
||||
|
@ -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",
|
||||
});
|
||||
|
@ -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"));
|
||||
|
@ -10,6 +10,7 @@ export interface IPropertiesOptions {
|
||||
description?: string;
|
||||
lastModifiedBy?: string;
|
||||
revision?: string;
|
||||
externalStyles?: string;
|
||||
}
|
||||
|
||||
export class CoreProperties extends XmlComponent {
|
||||
|
@ -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}`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
};
|
||||
}
|
@ -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" } });
|
||||
});
|
||||
});
|
||||
});
|
@ -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",
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -5,3 +5,4 @@ export * from "./numbering";
|
||||
export * from "./media";
|
||||
export * from "./drawing";
|
||||
export * from "./styles";
|
||||
export * from "./xml-components";
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -1 +1,2 @@
|
||||
export * from "./numbering";
|
||||
export * from "./abstract-numbering";
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
38
src/file/paragraph/run/page-number.ts
Normal file
38
src/file/paragraph/run/page-number.ts
Normal 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" }));
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
159
src/file/styles/external-styles-factory.spec.ts
Normal file
159
src/file/styles/external-styles-factory.spec.ts
Normal 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",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
64
src/file/styles/external-styles-factory.ts
Normal file
64
src/file/styles/external-styles-factory.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -1 +1,2 @@
|
||||
export * from "./table";
|
||||
export * from "./table-cell";
|
||||
|
181
src/file/table/table-cell.spec.ts
Normal file
181
src/file/table/table-cell.spec.ts
Normal 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,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
207
src/file/table/table-cell.ts
Normal file
207
src/file/table/table-cell.ts
Normal 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,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,27 +9,32 @@ 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();
|
||||
|
||||
const gridCols: number[] = [];
|
||||
for (let i = 0; i < cols; i++) {
|
||||
/*
|
||||
0-width columns don't get rendered correctly, so we need
|
||||
to give them some value. A reasonable default would be
|
||||
~6in / numCols, but if we do that it becomes very hard
|
||||
to resize the table using setWidth, unless the layout
|
||||
algorithm is set to 'fixed'. Instead, the approach here
|
||||
means even in 'auto' layout, setting a width on the
|
||||
table will make it look reasonable, as the layout
|
||||
algorithm will expand columns to fit its content
|
||||
*/
|
||||
gridCols.push(1);
|
||||
if (colSizes && colSizes.length > 0) {
|
||||
this.grid = new TableGrid(colSizes);
|
||||
} else {
|
||||
const gridCols: number[] = [];
|
||||
for (let i = 0; i < cols; i++) {
|
||||
/*
|
||||
0-width columns don't get rendered correctly, so we need
|
||||
to give them some value. A reasonable default would be
|
||||
~6in / numCols, but if we do that it becomes very hard
|
||||
to resize the table using setWidth, unless the layout
|
||||
algorithm is set to 'fixed'. Instead, the approach here
|
||||
means even in 'auto' layout, setting a width on the
|
||||
table will make it look reasonable, as the layout
|
||||
algorithm will expand columns to fit its content
|
||||
*/
|
||||
gridCols.push(1);
|
||||
}
|
||||
this.grid = new TableGrid(gridCols);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
34
src/file/xml-components/imported-xml-component.spec.ts
Normal file
34
src/file/xml-components/imported-xml-component.spec.ts
Normal 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": [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
74
src/file/xml-components/imported-xml-component.ts
Normal file
74
src/file/xml-components/imported-xml-component.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export * from "./xml-component";
|
||||
export * from "./attributes";
|
||||
export * from "./default-attributes";
|
||||
export * from "./imported-xml-component";
|
||||
export * from "./xmlable-object";
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user