Merge branch 'master' into add-table-option-styleId

# Conflicts:
#	src/file/table/table.ts
This commit is contained in:
Dolan
2021-03-07 21:40:42 +00:00
319 changed files with 20572 additions and 2876 deletions

View File

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

View File

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

View File

@ -1,6 +1,10 @@
import { XmlComponent } from "file/xml-components";
import { ICustomPropertyOptions } from "../custom-properties";
import { IDocumentBackgroundOptions } from "../document";
import { DocumentAttributes } from "../document/document-attributes";
import { INumberingOptions } from "../numbering";
import { Paragraph } from "../paragraph";
import { IStylesOptions } from "../styles";
import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components";
@ -14,6 +18,14 @@ export interface IPropertiesOptions {
readonly revision?: string;
readonly externalStyles?: string;
readonly styles?: IStylesOptions;
readonly numbering?: INumberingOptions;
readonly footnotes?: Paragraph[];
readonly background?: IDocumentBackgroundOptions;
readonly features?: {
readonly trackRevisions?: boolean;
};
readonly compatabilityModeVersion?: number;
readonly customProperties?: ICustomPropertyOptions[];
}
export class CoreProperties extends XmlComponent {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,9 +22,6 @@ describe("Body", () => {
expect(tree).to.deep.equal({
"w:body": [
{
"w:p": {},
},
{
"w:sectPr": [
{ "w:pgSz": { _attr: { "w:w": 10000, "w:h": 10000, "w:orient": "portrait" } } },
@ -42,7 +39,7 @@ describe("Body", () => {
},
},
},
{ "w:cols": { _attr: { "w:space": 708, "w:num": 1 } } },
{ "w:cols": { _attr: { "w:space": 708, "w:sep": false, "w:num": 1 } } },
{ "w:docGrid": { _attr: { "w:linePitch": 360 } } },
],
},

View File

@ -1,4 +1,6 @@
import { IViewWrapper } from "file/document-wrapper";
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Paragraph, ParagraphProperties, TableOfContents } from "../..";
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
@ -24,12 +26,13 @@ export class Body extends XmlComponent {
this.sections.push(new SectionProperties(options));
}
public prepForXml(): IXmlableObject | undefined {
public prepForXml(file?: IViewWrapper): IXmlableObject | undefined {
if (this.sections.length === 1) {
this.root.splice(0, 1);
this.root.push(this.sections.pop() as SectionProperties);
}
return super.prepForXml();
return super.prepForXml(file);
}
public push(component: XmlComponent): void {
@ -43,7 +46,7 @@ export class Body extends XmlComponent {
private createSectionParagraph(section: SectionProperties): Paragraph {
const paragraph = new Paragraph({});
const properties = new ParagraphProperties({});
properties.addChildElement(section);
properties.push(section);
paragraph.addChildElement(properties);
return paragraph;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
// http://officeopenxml.com/WPsection.php
import { convertInchesToTwip } from "convenience-functions";
import { FooterWrapper } from "file/footer-wrapper";
import { HeaderWrapper } from "file/header-wrapper";
import { XmlComponent } from "file/xml-components";
@ -18,6 +19,9 @@ import { IPageNumberTypeAttributes, PageNumberType } from "./page-number";
import { PageSize } from "./page-size/page-size";
import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes";
import { TitlePage } from "./title-page/title-page";
import { Type } from "./type/section-type";
import { SectionType } from "./type/section-type-attributes";
import { ISectionVerticalAlignAttributes, SectionVerticalAlign } from "./vertical-align";
export interface IHeaderFooterGroup<T> {
readonly default?: T;
@ -45,16 +49,21 @@ export type SectionPropertiesOptions = IPageSizeAttributes &
IPageNumberTypeAttributes &
ILineNumberAttributes &
IPageBordersOptions &
ITitlePageOptions & {
ITitlePageOptions &
ISectionVerticalAlignAttributes & {
readonly column?: {
readonly space?: number;
readonly count?: number;
readonly separate?: boolean;
};
readonly type?: SectionType;
};
// Need to decouple this from the attributes
export class SectionProperties extends XmlComponent {
private readonly options: SectionPropertiesOptions;
public readonly width: number;
public readonly rightMargin: number;
public readonly leftMargin: number;
constructor(options: SectionPropertiesOptions = { column: {} }) {
super("w:sectPr");
@ -62,10 +71,10 @@ export class SectionProperties extends XmlComponent {
const {
width = 11906,
height = 16838,
top = 1440,
right = 1440,
bottom = 1440,
left = 1440,
top = convertInchesToTwip(1),
right = convertInchesToTwip(1),
bottom = convertInchesToTwip(1),
left = convertInchesToTwip(1),
header = 708,
footer = 708,
gutter = 0,
@ -87,12 +96,17 @@ export class SectionProperties extends XmlComponent {
pageBorderBottom,
pageBorderLeft,
titlePage = false,
verticalAlign,
type,
} = options;
this.options = options;
this.leftMargin = left;
this.rightMargin = right;
this.width = width;
this.root.push(new PageSize(width, height, orientation));
this.root.push(new PageMargin(top, right, bottom, left, header, footer, gutter, mirror));
this.root.push(new Columns(column.space ? column.space : 708, column.count ? column.count : 1));
this.root.push(new Columns(column.space ? column.space : 708, column.count ? column.count : 1, column.separate ?? false));
this.root.push(new DocumentGrid(linePitch));
this.addHeaders(headers);
@ -121,6 +135,14 @@ export class SectionProperties extends XmlComponent {
if (titlePage) {
this.root.push(new TitlePage());
}
if (verticalAlign) {
this.root.push(new SectionVerticalAlign(verticalAlign));
}
if (type) {
this.root.push(new Type(type));
}
}
private addHeaders(headers?: IHeaderFooterGroup<HeaderWrapper>): void {
@ -129,7 +151,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.DEFAULT,
headerId: headers.default.Header.ReferenceId,
headerId: headers.default.View.ReferenceId,
}),
);
}
@ -138,7 +160,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.FIRST,
headerId: headers.first.Header.ReferenceId,
headerId: headers.first.View.ReferenceId,
}),
);
}
@ -147,7 +169,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new HeaderReference({
headerType: HeaderReferenceType.EVEN,
headerId: headers.even.Header.ReferenceId,
headerId: headers.even.View.ReferenceId,
}),
);
}
@ -160,7 +182,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.DEFAULT,
footerId: footers.default.Footer.ReferenceId,
footerId: footers.default.View.ReferenceId,
}),
);
}
@ -169,7 +191,7 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.FIRST,
footerId: footers.first.Footer.ReferenceId,
footerId: footers.first.View.ReferenceId,
}),
);
}
@ -178,14 +200,10 @@ export class SectionProperties extends XmlComponent {
this.root.push(
new FooterReference({
footerType: FooterReferenceType.EVEN,
footerId: footers.even.Footer.ReferenceId,
footerId: footers.even.View.ReferenceId,
}),
);
}
}
}
public get Options(): SectionPropertiesOptions {
return this.options;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
export * from "./vertical-align";
export * from "./vertical-align-attributes";

View File

@ -0,0 +1,12 @@
import { XmlAttributeComponent } from "file/xml-components";
import { SectionVerticalAlignValue } from "./vertical-align";
export interface ISectionVerticalAlignAttributes {
readonly verticalAlign?: SectionVerticalAlignValue;
}
export class SectionVerticalAlignAttributes extends XmlAttributeComponent<ISectionVerticalAlignAttributes> {
protected readonly xmlKeys = {
verticalAlign: "w:val",
};
}

View File

@ -0,0 +1,18 @@
// http://officeopenxml.com/WPsection.php
import { XmlComponent } from "file/xml-components";
import { SectionVerticalAlignAttributes } from "./vertical-align-attributes";
export enum SectionVerticalAlignValue {
BOTH = "both",
BOTTOM = "bottom",
CENTER = "center",
TOP = "top",
}
export class SectionVerticalAlign extends XmlComponent {
constructor(value: SectionVerticalAlignValue) {
super("w:vAlign");
this.root.push(new SectionVerticalAlignAttributes({ verticalAlign: value }));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,10 +18,10 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
});
it("should create with correct headers and footers", () => {
@ -37,10 +37,10 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][0]["w:sectPr"][5]["w:footerReference"]._attr["w:type"]).to.equal("default");
});
it("should create with first headers and footers", () => {
@ -56,10 +56,10 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("first");
});
it("should create with correct headers", () => {
@ -79,22 +79,98 @@ describe("File", () => {
children: [],
});
const tree = new Formatter().format(doc.Document.Body);
const tree = new Formatter().format(doc.Document.View.Body);
expect(tree["w:body"][1]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even");
expect(tree["w:body"][0]["w:sectPr"][4]["w:headerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][0]["w:sectPr"][5]["w:headerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][0]["w:sectPr"][6]["w:headerReference"]._attr["w:type"]).to.equal("even");
expect(tree["w:body"][1]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][1]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][1]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even");
expect(tree["w:body"][0]["w:sectPr"][7]["w:footerReference"]._attr["w:type"]).to.equal("default");
expect(tree["w:body"][0]["w:sectPr"][8]["w:footerReference"]._attr["w:type"]).to.equal("first");
expect(tree["w:body"][0]["w:sectPr"][9]["w:footerReference"]._attr["w:type"]).to.equal("even");
});
it("should add child", () => {
const doc = new File(undefined, undefined, [
{
children: [new Paragraph("test")],
},
]);
const tree = new Formatter().format(doc.Document.View.Body);
expect(tree).to.deep.equal({
"w:body": [
{
"w:p": [
{
"w:r": [
{
"w:t": [
{
_attr: {
"xml:space": "preserve",
},
},
"test",
],
},
],
},
],
},
{
"w:sectPr": [
{
"w:pgSz": {
_attr: {
"w:h": 16838,
"w:orient": "portrait",
"w:w": 11906,
},
},
},
{
"w:pgMar": {
_attr: {
"w:bottom": 1440,
"w:footer": 708,
"w:gutter": 0,
"w:header": 708,
"w:left": 1440,
"w:mirrorMargins": false,
"w:right": 1440,
"w:top": 1440,
},
},
},
{
"w:cols": {
_attr: {
"w:num": 1,
"w:sep": false,
"w:space": 708,
},
},
},
{
"w:docGrid": {
_attr: {
"w:linePitch": 360,
},
},
},
],
},
],
});
});
});
describe("#addSection", () => {
it("should call the underlying document's add a Paragraph", () => {
const file = new File();
const spy = sinon.spy(file.Document, "add");
const spy = sinon.spy(file.Document.View, "add");
file.addSection({
children: [new Paragraph({})],
});
@ -104,7 +180,7 @@ describe("File", () => {
it("should call the underlying document's add when adding a Table", () => {
const file = new File();
const spy = sinon.spy(file.Document, "add");
const spy = sinon.spy(file.Document.View, "add");
file.addSection({
children: [
new Table({
@ -126,7 +202,7 @@ describe("File", () => {
it("should call the underlying document's add when adding an Image (paragraph)", () => {
const file = new File();
const spy = sinon.spy(file.Document, "add");
const spy = sinon.spy(file.Document.View, "add");
// tslint:disable-next-line:no-any
file.addSection({
children: [new Paragraph("")],
@ -139,7 +215,7 @@ describe("File", () => {
describe("#addSection", () => {
it("should call the underlying document's add", () => {
const file = new File();
const spy = sinon.spy(file.Document, "add");
const spy = sinon.spy(file.Document.View, "add");
file.addSection({
children: [new TableOfContents()],
});
@ -148,13 +224,201 @@ describe("File", () => {
});
});
describe("#createFootnote", () => {
it("should call the underlying document's createFootnote", () => {
const wrapper = new File();
const spy = sinon.spy(wrapper.FootNotes, "createFootNote");
wrapper.createFootnote(new Paragraph(""));
describe("#addTrackRevisionsFeature", () => {
it("should call the underlying document's add", () => {
const file = new File({
features: {
trackRevisions: true,
},
});
expect(spy.called).to.equal(true);
// tslint:disable-next-line: no-unused-expression no-string-literal
expect(file.Settings["trackRevisions"]).to.exist;
});
});
describe("#createFootnote", () => {
it("should create footnote", () => {
const wrapper = new File({
footnotes: [new Paragraph("hello")],
});
const tree = new Formatter().format(wrapper.FootNotes.View);
expect(tree).to.deep.equal({
"w:footnotes": [
{
_attr: {
"mc:Ignorable": "w14 w15 wp14",
"xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
"xmlns:o": "urn:schemas-microsoft-com:office:office",
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
"xmlns:v": "urn:schemas-microsoft-com:vml",
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"xmlns:w10": "urn:schemas-microsoft-com:office:word",
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
"xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml",
"xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
"xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
"xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
"xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
"xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
"xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
},
},
{
"w:footnote": [
{
_attr: {
"w:id": -1,
"w:type": "separator",
},
},
{
"w:p": [
{
"w:pPr": [
{
"w:spacing": {
_attr: {
"w:after": 0,
"w:line": 240,
"w:lineRule": "auto",
},
},
},
],
},
{
"w:r": [
{
"w:rPr": [
{
"w:rStyle": {
_attr: {
"w:val": "FootnoteReference",
},
},
},
],
},
{
"w:footnoteRef": {},
},
],
},
{
"w:r": [
{
"w:separator": {},
},
],
},
],
},
],
},
{
"w:footnote": [
{
_attr: {
"w:id": 0,
"w:type": "continuationSeparator",
},
},
{
"w:p": [
{
"w:pPr": [
{
"w:spacing": {
_attr: {
"w:after": 0,
"w:line": 240,
"w:lineRule": "auto",
},
},
},
],
},
{
"w:r": [
{
"w:rPr": [
{
"w:rStyle": {
_attr: {
"w:val": "FootnoteReference",
},
},
},
],
},
{
"w:footnoteRef": {},
},
],
},
{
"w:r": [
{
"w:continuationSeparator": {},
},
],
},
],
},
],
},
{
"w:footnote": [
{
_attr: {
"w:id": 1,
},
},
{
"w:p": [
{
"w:r": [
{
"w:rPr": [
{
"w:rStyle": {
_attr: {
"w:val": "FootnoteReference",
},
},
},
],
},
{
"w:footnoteRef": {},
},
],
},
{
"w:r": [
{
"w:t": [
{
_attr: {
"xml:space": "preserve",
},
},
"hello",
],
},
],
},
],
},
],
},
],
});
});
});
});

View File

@ -1,7 +1,8 @@
import { AppProperties } from "./app-properties/app-properties";
import { ContentTypes } from "./content-types/content-types";
import { CoreProperties, IPropertiesOptions } from "./core-properties";
import { Document } from "./document";
import { CustomProperties } from "./custom-properties";
import { DocumentWrapper } from "./document-wrapper";
import {
FooterReferenceType,
HeaderReferenceType,
@ -11,14 +12,13 @@ import {
import { IPageMarginAttributes } from "./document/body/section-properties/page-margin/page-margin-attributes";
import { IFileProperties } from "./file-properties";
import { FooterWrapper, IDocumentFooter } from "./footer-wrapper";
import { FootNotes } from "./footnotes";
import { FootnotesWrapper } from "./footnotes-wrapper";
import { Footer, Header } from "./header";
import { HeaderWrapper, IDocumentHeader } from "./header-wrapper";
import { Media } from "./media";
import { Numbering } from "./numbering";
import { Bookmark, Hyperlink, Paragraph } from "./paragraph";
import { Paragraph } from "./paragraph";
import { Relationships } from "./relationships";
import { TargetModeType } from "./relationships/relationship/relationship";
import { Settings } from "./settings";
import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory";
@ -40,24 +40,24 @@ export interface ISectionOptions {
readonly size?: IPageSizeAttributes;
readonly margins?: IPageMarginAttributes;
readonly properties?: SectionPropertiesOptions;
readonly children: Array<Paragraph | Table | TableOfContents>;
readonly children: (Paragraph | Table | TableOfContents)[];
}
export class File {
// tslint:disable-next-line:readonly-keyword
private currentRelationshipId: number = 1;
private readonly document: Document;
private readonly documentWrapper: DocumentWrapper;
private readonly headers: IDocumentHeader[] = [];
private readonly footers: IDocumentFooter[] = [];
private readonly docRelationships: Relationships;
private readonly coreProperties: CoreProperties;
private readonly numbering: Numbering;
private readonly media: Media;
private readonly fileRelationships: Relationships;
private readonly footNotes: FootNotes;
private readonly footnotesWrapper: FootnotesWrapper;
private readonly settings: Settings;
private readonly contentTypes: ContentTypes;
private readonly customProperties: CustomProperties;
private readonly appProperties: AppProperties;
private readonly styles: Styles;
@ -71,14 +71,22 @@ export class File {
sections: ISectionOptions[] = [],
) {
this.coreProperties = new CoreProperties(options);
this.numbering = new Numbering();
this.docRelationships = new Relationships();
this.numbering = new Numbering(
options.numbering
? options.numbering
: {
config: [],
},
);
this.fileRelationships = new Relationships();
this.customProperties = new CustomProperties(options.customProperties ?? []);
this.appProperties = new AppProperties();
this.footNotes = new FootNotes();
this.footnotesWrapper = new FootnotesWrapper();
this.contentTypes = new ContentTypes();
this.document = new Document();
this.settings = new Settings();
this.documentWrapper = new DocumentWrapper({ background: options.background || {} });
this.settings = new Settings({
compatabilityModeVersion: options.compatabilityModeVersion,
});
this.media = fileProperties.template && fileProperties.template.media ? fileProperties.template.media : new Media();
@ -98,7 +106,7 @@ export class File {
this.styles = stylesFactory.newInstance(options.externalStyles);
} else if (options.styles) {
const stylesFactory = new DefaultStylesFactory();
const defaultStyles = stylesFactory.newInstance();
const defaultStyles = stylesFactory.newInstance(options.styles.default);
this.styles = new Styles({
...defaultStyles,
...options.styles,
@ -123,36 +131,24 @@ export class File {
}
for (const section of sections) {
this.document.Body.addSection(section.properties ? section.properties : {});
this.documentWrapper.View.Body.addSection(section.properties ? section.properties : {});
for (const child of section.children) {
this.document.add(child);
this.documentWrapper.View.add(child);
}
}
}
public createHyperlink(link: string, text?: string): Hyperlink {
const newText = text === undefined ? link : text;
const hyperlink = new Hyperlink(newText, this.docRelationships.RelationshipCount);
this.docRelationships.createRelationship(
hyperlink.linkId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
link,
TargetModeType.EXTERNAL,
);
return hyperlink;
}
if (options.footnotes) {
for (const paragraph of options.footnotes) {
this.footnotesWrapper.View.createFootNote(paragraph);
}
}
public createInternalHyperLink(anchor: string, text?: string): Hyperlink {
const newText = text === undefined ? anchor : text;
const hyperlink = new Hyperlink(newText, this.docRelationships.RelationshipCount, anchor);
// NOTE: unlike File#createHyperlink(), since the link is to an internal bookmark
// we don't need to create a new relationship.
return hyperlink;
}
public createBookmark(name: string, text: string = name): Bookmark {
return new Bookmark(name, text, this.docRelationships.RelationshipCount);
if (options.features) {
if (options.features.trackRevisions) {
this.settings.addTrackRevisions();
}
}
}
public addSection({
@ -163,7 +159,7 @@ export class File {
properties,
children,
}: ISectionOptions): void {
this.document.Body.addSection({
this.documentWrapper.View.Body.addSection({
...properties,
headers: {
default: headers.default ? this.createHeader(headers.default) : this.createHeader(new Header()),
@ -180,16 +176,12 @@ export class File {
});
for (const child of children) {
this.document.add(child);
this.documentWrapper.View.add(child);
}
}
public createFootnote(paragraph: Paragraph): void {
this.footNotes.createFootNote(paragraph);
}
public verifyUpdateFields(): void {
if (this.document.getTablesOfContents().length) {
if (this.documentWrapper.View.getTablesOfContents().length) {
this.settings.addUpdateFields();
}
}
@ -218,8 +210,8 @@ export class File {
private addHeaderToDocument(header: HeaderWrapper, type: HeaderReferenceType = HeaderReferenceType.DEFAULT): void {
this.headers.push({ header, type });
this.docRelationships.createRelationship(
header.Header.ReferenceId,
this.documentWrapper.Relationships.createRelationship(
header.View.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",
`header${this.headers.length}.xml`,
);
@ -228,8 +220,8 @@ export class File {
private addFooterToDocument(footer: FooterWrapper, type: FooterReferenceType = FooterReferenceType.DEFAULT): void {
this.footers.push({ footer, type });
this.docRelationships.createRelationship(
footer.Footer.ReferenceId,
this.documentWrapper.Relationships.createRelationship(
footer.View.ReferenceId,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer",
`footer${this.footers.length}.xml`,
);
@ -252,31 +244,36 @@ export class File {
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties",
"docProps/app.xml",
);
this.fileRelationships.createRelationship(
4,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties",
"docProps/custom.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
"styles.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",
"numbering.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes",
"footnotes.xml",
);
this.docRelationships.createRelationship(
this.documentWrapper.Relationships.createRelationship(
this.currentRelationshipId++,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings",
"settings.xml",
);
}
public get Document(): Document {
return this.document;
public get Document(): DocumentWrapper {
return this.documentWrapper;
}
public get Styles(): Styles {
@ -295,10 +292,6 @@ export class File {
return this.media;
}
public get DocumentRelationships(): Relationships {
return this.docRelationships;
}
public get FileRelationships(): Relationships {
return this.fileRelationships;
}
@ -315,12 +308,16 @@ export class File {
return this.contentTypes;
}
public get CustomProperties(): CustomProperties {
return this.customProperties;
}
public get AppProperties(): AppProperties {
return this.appProperties;
}
public get FootNotes(): FootNotes {
return this.footNotes;
public get FootNotes(): FootnotesWrapper {
return this.footnotesWrapper;
}
public get Settings(): Settings {

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,10 @@ import { Footnote, FootnoteType } from "./footnote";
describe("Footnote", () => {
describe("#constructor", () => {
it("should create a footnote with a footnote type", () => {
const footnote = new Footnote(1, FootnoteType.SEPERATOR);
const footnote = new Footnote({
id: 1,
type: FootnoteType.SEPERATOR,
});
const tree = new Formatter().format(footnote);
expect(Object.keys(tree)).to.deep.equal(["w:footnote"]);
@ -15,7 +18,9 @@ describe("Footnote", () => {
});
it("should create a footnote without a footnote type", () => {
const footnote = new Footnote(1);
const footnote = new Footnote({
id: 1,
});
const tree = new Formatter().format(footnote);
expect(Object.keys(tree)).to.deep.equal(["w:footnote"]);

View File

@ -9,13 +9,18 @@ export enum FootnoteType {
CONTINUATION_SEPERATOR = "continuationSeparator",
}
export interface IFootnoteOptions {
readonly id: number;
readonly type?: FootnoteType;
}
export class Footnote extends XmlComponent {
constructor(id: number, type?: FootnoteType) {
constructor(options: IFootnoteOptions) {
super("w:footnote");
this.root.push(
new FootnoteAttributes({
type: type,
id: id,
type: options.type,
id: options.id,
}),
);
}

View File

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

View File

@ -0,0 +1 @@
export * from "./reference-run";

View File

@ -36,7 +36,10 @@ export class FootNotes extends XmlComponent {
}),
);
const begin = new Footnote(-1, FootnoteType.SEPERATOR);
const begin = new Footnote({
id: -1,
type: FootnoteType.SEPERATOR,
});
begin.add(
new Paragraph({
spacing: {
@ -49,7 +52,10 @@ export class FootNotes extends XmlComponent {
);
this.root.push(begin);
const spacing = new Footnote(0, FootnoteType.CONTINUATION_SEPERATOR);
const spacing = new Footnote({
id: 0,
type: FootnoteType.CONTINUATION_SEPERATOR,
});
spacing.add(
new Paragraph({
spacing: {
@ -64,7 +70,7 @@ export class FootNotes extends XmlComponent {
}
public createFootNote(paragraph: Paragraph): void {
const footnote = new Footnote(this.currentId);
const footnote = new Footnote({ id: this.currentId });
footnote.add(paragraph);
this.root.push(footnote);

View File

@ -1 +1,2 @@
export * from "./footnotes";
export * from "./footnote";

View File

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

View File

@ -1,6 +1,7 @@
import { XmlComponent } from "file/xml-components";
import { HeaderReferenceType } from "./document";
import { IViewWrapper } from "./document-wrapper";
import { Header } from "./header/header";
import { Media } from "./media";
import { Paragraph } from "./paragraph";
@ -12,7 +13,7 @@ export interface IDocumentHeader {
readonly type: HeaderReferenceType;
}
export class HeaderWrapper {
export class HeaderWrapper implements IViewWrapper {
private readonly header: Header;
private readonly relationships: Relationships;
@ -31,7 +32,7 @@ export class HeaderWrapper {
this.header.addChildElement(childElement);
}
public get Header(): Header {
public get View(): Header {
return this.header;
}

View File

@ -2,7 +2,7 @@ import { Paragraph } from "./paragraph";
import { Table } from "./table";
export interface IHeaderOptions {
readonly children: Array<Paragraph | Table>;
readonly children: (Paragraph | Table)[];
}
export class Header {

View File

@ -12,3 +12,5 @@ export * from "./xml-components";
export * from "./header-wrapper";
export * from "./footer-wrapper";
export * from "./header";
export * from "./footnotes";
export * from "./track-revision";

View File

@ -1,14 +1,12 @@
import { IDrawingOptions } from "../drawing";
import { File } from "../file";
import { FooterWrapper } from "../footer-wrapper";
import { HeaderWrapper } from "../header-wrapper";
import { PictureRun } from "../paragraph";
import { IMediaData } from "./data";
// import { Image } from "./image";
export class Media {
public static addImage(
file: File | HeaderWrapper | FooterWrapper,
file: File,
buffer: Buffer | string | Uint8Array | ArrayBuffer,
width?: number,
height?: number,
@ -21,14 +19,7 @@ export class Media {
private static generateId(): string {
// https://gist.github.com/6174/6062387
return (
Math.random()
.toString(36)
.substring(2, 15) +
Math.random()
.toString(36)
.substring(2, 15)
);
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
private readonly map: Map<string, IMediaData>;
@ -89,7 +80,7 @@ export class Media {
return imageData;
}
public get Array(): IMediaData[] {
public get Array(): readonly IMediaData[] {
const array = new Array<IMediaData>();
this.map.forEach((data) => {

View File

@ -0,0 +1,823 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { EMPTY_OBJECT } from "file/xml-components";
import { AlignmentType, EmphasisMarkType, TabStopPosition } from "../paragraph";
import { UnderlineType } from "../paragraph/run/underline";
import { ShadingType } from "../table";
import { AbstractNumbering } from "./abstract-numbering";
import { LevelFormat, LevelSuffix } from "./level";
describe("AbstractNumbering", () => {
it("stores its ID at its .id property", () => {
const abstractNumbering = new AbstractNumbering(5, []);
expect(abstractNumbering.id).to.equal(5);
});
describe("#createLevel", () => {
it("creates a level with the given characteristics", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 3,
format: LevelFormat.LOWER_LETTER,
text: "%1)",
alignment: AlignmentType.END,
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": LevelFormat.LOWER_LETTER } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
it("uses 'start' as the default alignment", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 3,
format: LevelFormat.LOWER_LETTER,
text: "%1)",
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": LevelFormat.LOWER_LETTER } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
it("has suffix", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 3,
format: LevelFormat.LOWER_LETTER,
text: "%1)",
alignment: AlignmentType.END,
suffix: LevelSuffix.SPACE,
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:suff": { _attr: { "w:val": "space" } } });
});
describe("formatting methods: paragraph properties", () => {
it("#indent", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
indent: { left: 720 },
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }],
});
});
it("#spacing", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
spacing: { before: 50, after: 150 },
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }],
});
});
it("#center", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.CENTER,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }],
});
});
it("#left", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.LEFT,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }],
});
});
it("#right", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.RIGHT,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }],
});
});
it("#justified", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.JUSTIFIED,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }],
});
});
it("#thematicBreak", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
thematicBreak: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:pBdr": [
{
"w:bottom": {
_attr: {
"w:color": "auto",
"w:space": 1,
"w:val": "single",
"w:sz": 6,
},
},
},
],
},
],
});
});
it("#leftTabStop", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
leftTabStop: 1200,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }],
},
],
});
});
it("#maxRightTabStop", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
rightTabStop: TabStopPosition.MAX,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }],
},
],
});
});
it("#keepLines", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
keepLines: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:keepLines": EMPTY_OBJECT }],
});
});
it("#keepNext", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
paragraph: {
keepNext: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:keepNext": EMPTY_OBJECT }],
});
});
});
describe("formatting methods: run properties", () => {
const sizeTests = [
{
size: 24,
expected: [{ "w:sz": { _attr: { "w:val": 24 } } }, { "w:szCs": { _attr: { "w:val": 24 } } }],
},
{
size: 24,
sizeComplexScript: true,
expected: [{ "w:sz": { _attr: { "w:val": 24 } } }, { "w:szCs": { _attr: { "w:val": 24 } } }],
},
{
size: 24,
sizeComplexScript: false,
expected: [{ "w:sz": { _attr: { "w:val": 24 } } }],
},
{
size: 24,
sizeComplexScript: 26,
expected: [{ "w:sz": { _attr: { "w:val": 24 } } }, { "w:szCs": { _attr: { "w:val": 26 } } }],
},
];
sizeTests.forEach(({ size, sizeComplexScript, expected }) => {
it(`#size ${size} cs ${sizeComplexScript}`, () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: { size, sizeComplexScript },
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": expected });
});
});
it("#smallCaps", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
smallCaps: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }],
});
});
it("#allCaps", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
allCaps: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }],
});
});
it("#strike", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
strike: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }],
});
});
it("#doubleStrike", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
doubleStrike: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }],
});
});
it("#subScript", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
subScript: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }],
});
});
it("#superScript", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
superScript: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }],
});
});
it("#font by name", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
font: "Times",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [
{
"w:rFonts": {
_attr: {
"w:ascii": "Times",
"w:cs": "Times",
"w:eastAsia": "Times",
"w:hAnsi": "Times",
},
},
},
],
});
});
it("#font for ascii and eastAsia", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
font: {
ascii: "Times",
eastAsia: "KaiTi",
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [
{
"w:rFonts": {
_attr: {
"w:ascii": "Times",
"w:eastAsia": "KaiTi",
},
},
},
],
});
});
const boldTests = [
{
bold: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }],
},
{
bold: true,
boldComplexScript: true,
expected: [{ "w:b": { _attr: { "w:val": true } } }, { "w:bCs": { _attr: { "w:val": true } } }],
},
{
bold: true,
boldComplexScript: false,
expected: [{ "w:b": { _attr: { "w:val": true } } }],
},
];
boldTests.forEach(({ bold, boldComplexScript, expected }) => {
it(`#bold ${bold} cs ${boldComplexScript}`, () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: { bold, boldComplexScript },
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": expected });
});
});
const italicsTests = [
{
italics: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }],
},
{
italics: true,
italicsComplexScript: true,
expected: [{ "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }],
},
{
italics: true,
italicsComplexScript: false,
expected: [{ "w:i": { _attr: { "w:val": true } } }],
},
];
italicsTests.forEach(({ italics, italicsComplexScript, expected }) => {
it(`#italics ${italics} cs ${italicsComplexScript}`, () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: { italics, italicsComplexScript },
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": expected });
});
});
const highlightTests = [
{
highlight: "005599",
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: "005599",
highlightComplexScript: true,
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "005599" } } }],
},
{
highlight: "005599",
highlightComplexScript: false,
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
},
{
highlight: "005599",
highlightComplexScript: "550099",
expected: [{ "w:highlight": { _attr: { "w:val": "005599" } } }, { "w:highlightCs": { _attr: { "w:val": "550099" } } }],
},
];
highlightTests.forEach(({ highlight, highlightComplexScript, expected }) => {
it(`#highlight ${highlight} cs ${highlightComplexScript}`, () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: { highlight, highlightComplexScript },
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": expected });
});
});
const shadingTests = [
{
shadow: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "FF0000",
},
expected: [
{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } },
{ "w:shdCs": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } },
],
},
{
shading: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "FF0000",
},
expected: [
{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } },
{ "w:shdCs": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } },
],
},
{
shading: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "FF0000",
},
shadingComplexScript: true,
expected: [
{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } },
{ "w:shdCs": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } },
],
},
{
shading: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "FF0000",
},
shadingComplexScript: false,
expected: [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
},
{
shading: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "FF0000",
},
shadingComplexScript: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "00FF00",
},
expected: [
{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } },
{ "w:shdCs": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "00FF00" } } },
],
},
];
shadingTests.forEach(({ shadow, shading, shadingComplexScript, expected }) => {
it("#shadow correctly", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: { shadow, shading, shadingComplexScript },
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:rPr": expected });
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
underline: {},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }],
});
});
it("should set the style if given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
underline: {
type: UnderlineType.DOUBLE,
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }],
});
});
it("should set the style and color if given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
underline: {
type: UnderlineType.DOUBLE,
color: "005599",
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }],
});
});
});
describe("#emphasisMark", () => {
it("should set emphasisMark to 'dot' if no arguments are given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
emphasisMark: {},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
});
});
it("should set the style if given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
emphasisMark: {
type: EmphasisMarkType.DOT,
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }],
});
});
});
it("#color", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: LevelFormat.LOWER_ROMAN,
text: "%0.",
style: {
run: {
color: "123456",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }],
});
});
});
});
});

View File

@ -1,5 +1,6 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { Level } from "./level";
import { ILevelsOptions, Level } from "./level";
import { MultiLevelType } from "./multi-level-type";
interface IAbstractNumberingAttributesProperties {
@ -17,7 +18,7 @@ class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberi
export class AbstractNumbering extends XmlComponent {
public readonly id: number;
constructor(id: number) {
constructor(id: number, levelOptions: ILevelsOptions[]) {
super("w:abstractNum");
this.root.push(
new AbstractNumberingAttributes({
@ -27,15 +28,9 @@ export class AbstractNumbering extends XmlComponent {
);
this.root.push(new MultiLevelType("hybridMultilevel"));
this.id = id;
}
public addLevel(level: Level): void {
this.root.push(level);
}
public createLevel(num: number, format: string, text: string, align: string = "start"): Level {
const level = new Level(num, format, text, align);
this.addLevel(level);
return level;
for (const option of levelOptions) {
this.root.push(new Level(option));
}
}
}

View File

@ -0,0 +1,79 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { LevelForOverride } from "./level";
import { ConcreteNumbering } from "./num";
describe("ConcreteNumbering", () => {
describe("#overrideLevel", () => {
let concreteNumbering: ConcreteNumbering;
beforeEach(() => {
concreteNumbering = new ConcreteNumbering(0, 1);
});
it("sets a new override level for the given level number", () => {
concreteNumbering.overrideLevel(3);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{ _attr: { "w:ilvl": 3 } },
{
"w:lvl": [
{ _attr: { "w:ilvl": 3, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
it("sets the startOverride element if start is given", () => {
concreteNumbering.overrideLevel(1, 9);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{
_attr: {
"w:ilvl": 1,
},
},
{
"w:startOverride": {
_attr: {
"w:val": 9,
},
},
},
{
"w:lvl": [
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
it("sets the lvl element if overrideLevel.Level is accessed", () => {
const ol = concreteNumbering.overrideLevel(1);
expect(ol.Level).to.be.instanceof(LevelForOverride);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{ _attr: { "w:ilvl": 1 } },
{
"w:lvl": [
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
});
});

View File

@ -1,21 +1,25 @@
// http://officeopenxml.com/WPnumbering-numFmt.php
import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components";
import {
Alignment,
AlignmentType,
IIndentAttributesProperties,
Indent,
ISpacingProperties,
KeepLines,
KeepNext,
Spacing,
TabStop,
TabStopType,
ThematicBreak,
} from "../paragraph/formatting";
import { ParagraphProperties } from "../paragraph/properties";
import * as formatting from "../paragraph/run/formatting";
import { RunProperties } from "../paragraph/run/properties";
import { UnderlineType } from "../paragraph/run/underline";
import { AlignmentType } from "../paragraph/formatting";
import { IParagraphStylePropertiesOptions, ParagraphProperties } from "../paragraph/properties";
import { IRunStylePropertiesOptions, RunProperties } from "../paragraph/run/properties";
export enum LevelFormat {
BULLET = "bullet",
CARDINAL_TEXT = "cardinalText",
CHICAGO = "chicago",
DECIMAL = "decimal",
DECIMAL_ENCLOSED_CIRCLE = "decimalEnclosedCircle",
DECIMAL_ENCLOSED_FULLSTOP = "decimalEnclosedFullstop",
DECIMAL_ENCLOSED_PARENTHESES = "decimalEnclosedParen",
DECIMAL_ZERO = "decimalZero",
LOWER_LETTER = "lowerLetter",
LOWER_ROMAN = "lowerRoman",
NONE = "none",
ORDINAL_TEXT = "ordinalText",
UPPER_LETTER = "upperLetter",
UPPER_ROMAN = "upperRoman",
}
interface ILevelAttributesProperties {
readonly ilvl?: number;
@ -63,7 +67,7 @@ class LevelText extends XmlComponent {
}
class LevelJc extends XmlComponent {
constructor(value: string) {
constructor(value: AlignmentType) {
super("w:lvlJc");
this.root.push(
new Attributes({
@ -79,6 +83,19 @@ export enum LevelSuffix {
TAB = "tab",
}
export interface ILevelsOptions {
readonly level: number;
readonly format?: LevelFormat;
readonly text?: string;
readonly alignment?: AlignmentType;
readonly start?: number;
readonly suffix?: LevelSuffix;
readonly style?: {
readonly run?: IRunStylePropertiesOptions;
readonly paragraph?: IParagraphStylePropertiesOptions;
};
}
class Suffix extends XmlComponent {
constructor(value: LevelSuffix) {
super("w:suff");
@ -94,7 +111,7 @@ export class LevelBase extends XmlComponent {
private readonly paragraphProperties: ParagraphProperties;
private readonly runProperties: RunProperties;
constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) {
constructor({ level, format, text, alignment = AlignmentType.START, start = 1, style, suffix }: ILevelsOptions) {
super("w:lvl");
this.root.push(
new LevelAttributes({
@ -103,174 +120,34 @@ export class LevelBase extends XmlComponent {
}),
);
if (start !== undefined) {
this.root.push(new Start(start));
}
if (numberFormat !== undefined) {
this.root.push(new NumberFormat(numberFormat));
}
if (levelText !== undefined) {
this.root.push(new LevelText(levelText));
}
if (lvlJc !== undefined) {
this.root.push(new LevelJc(lvlJc));
this.root.push(new Start(start));
this.root.push(new LevelJc(alignment));
if (format) {
this.root.push(new NumberFormat(format));
}
this.paragraphProperties = new ParagraphProperties({});
this.runProperties = new RunProperties();
if (text) {
this.root.push(new LevelText(text));
}
this.paragraphProperties = new ParagraphProperties(style && style.paragraph);
this.runProperties = new RunProperties(style && style.run);
this.root.push(this.paragraphProperties);
this.root.push(this.runProperties);
}
public setSuffix(value: LevelSuffix): LevelBase {
this.root.push(new Suffix(value));
return this;
}
public addParagraphProperty(property: XmlComponent): Level {
this.paragraphProperties.push(property);
return this;
}
public addRunProperty(property: XmlComponent): Level {
this.runProperties.push(property);
return this;
}
// ---------- Run formatting ---------------------- //
public size(twips: number): Level {
this.addRunProperty(new formatting.Size(twips));
return this;
}
public bold(): Level {
this.addRunProperty(new formatting.Bold());
return this;
}
public italics(): Level {
this.addRunProperty(new formatting.Italics());
return this;
}
public smallCaps(): Level {
this.addRunProperty(new formatting.SmallCaps());
return this;
}
public allCaps(): Level {
this.addRunProperty(new formatting.Caps());
return this;
}
public strike(): Level {
this.addRunProperty(new formatting.Strike());
return this;
}
public doubleStrike(): Level {
this.addRunProperty(new formatting.DoubleStrike());
return this;
}
public subScript(): Level {
this.addRunProperty(new formatting.SubScript());
return this;
}
public superScript(): Level {
this.addRunProperty(new formatting.SuperScript());
return this;
}
public underline(underlineType?: UnderlineType, color?: string): Level {
this.addRunProperty(new formatting.Underline(underlineType, color));
return this;
}
public color(color: string): Level {
this.addRunProperty(new formatting.Color(color));
return this;
}
public font(fontName: string): Level {
this.addRunProperty(new formatting.RunFonts(fontName));
return this;
}
public highlight(color: string): Level {
this.addRunProperty(new formatting.Highlight(color));
return this;
}
public shadow(value: string, fill: string, color: string): Level {
this.addRunProperty(new formatting.Shading(value, fill, color));
return this;
}
// --------------------- Paragraph formatting ------------------------ //
public center(): Level {
this.addParagraphProperty(new Alignment(AlignmentType.CENTER));
return this;
}
public left(): Level {
this.addParagraphProperty(new Alignment(AlignmentType.LEFT));
return this;
}
public right(): Level {
this.addParagraphProperty(new Alignment(AlignmentType.RIGHT));
return this;
}
public justified(): Level {
this.addParagraphProperty(new Alignment(AlignmentType.BOTH));
return this;
}
public thematicBreak(): Level {
this.addParagraphProperty(new ThematicBreak());
return this;
}
public rightTabStop(position: number): Level {
return this.addParagraphProperty(new TabStop(TabStopType.RIGHT, position));
}
public leftTabStop(position: number): Level {
this.addParagraphProperty(new TabStop(TabStopType.LEFT, position));
return this;
}
public indent(attrs: IIndentAttributesProperties): Level {
this.addParagraphProperty(new Indent(attrs));
return this;
}
public spacing(params: ISpacingProperties): Level {
this.addParagraphProperty(new Spacing(params));
return this;
}
public keepNext(): Level {
this.addParagraphProperty(new KeepNext());
return this;
}
public keepLines(): Level {
this.addParagraphProperty(new KeepLines());
return this;
if (suffix) {
this.root.push(new Suffix(suffix));
}
}
}
export class Level extends LevelBase {
// This is the level that sits under abstractNum. We make a
// handful of properties required
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) {
super(level, 1, numberFormat, levelText, lvlJc);
constructor(options: ILevelsOptions) {
super(options);
}
}

View File

@ -20,10 +20,10 @@ class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
protected readonly xmlKeys = { numId: "w:numId" };
}
export class Num extends XmlComponent {
export class ConcreteNumbering extends XmlComponent {
public readonly id: number;
constructor(numId: number, abstractNumId: number) {
constructor(numId: number, abstractNumId: number, public readonly reference?: string) {
super("w:num");
this.root.push(
new NumAttributes({
@ -55,7 +55,9 @@ export class LevelOverride extends XmlComponent {
this.root.push(new StartOverride(start));
}
this.lvl = new LevelForOverride(this.levelNum);
this.lvl = new LevelForOverride({
level: this.levelNum,
});
this.root.push(this.lvl);
}

View File

@ -2,24 +2,15 @@ import { expect } from "chai";
import { Formatter } from "export/formatter";
import { AbstractNumbering } from "./abstract-numbering";
import { LevelForOverride } from "./level";
import { Num } from "./num";
import { Numbering } from "./numbering";
import { EMPTY_OBJECT } from "file/xml-components";
import { TabStopPosition } from "../paragraph";
import { UnderlineType } from "../paragraph/run/underline";
describe("Numbering", () => {
let numbering: Numbering;
beforeEach(() => {
numbering = new Numbering();
});
describe("#constructor", () => {
it("creates a default numbering with one abstract and one concrete instance", () => {
const numbering = new Numbering({
config: [],
});
const tree = new Formatter().format(numbering);
expect(Object.keys(tree)).to.deep.equal(["w:numbering"]);
const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]);
@ -48,418 +39,4 @@ describe("Numbering", () => {
});
});
});
describe("#createAbstractNumbering", () => {
it("returns a new AbstractNumbering instance", () => {
const a2 = numbering.createAbstractNumbering();
expect(a2).to.be.instanceof(AbstractNumbering);
});
it("assigns a unique ID to each abstract numbering it creates", () => {
const a2 = numbering.createAbstractNumbering();
const a3 = numbering.createAbstractNumbering();
expect(a2.id).not.to.equal(a3.id);
});
});
describe("#createConcreteNumbering", () => {
it("returns a new Num instance with its abstract ID set to the AbstractNumbering's ID", () => {
const a2 = numbering.createAbstractNumbering();
const n = numbering.createConcreteNumbering(a2);
expect(n).to.be.instanceof(Num);
const tree = new Formatter().format(numbering);
const serializedN = tree["w:numbering"].find((obj) => obj["w:num"] && obj["w:num"][0]._attr["w:numId"] === n.id);
expect(serializedN["w:num"][1]["w:abstractNumId"]._attr["w:val"]).to.equal(a2.id);
});
it("assigns a unique ID to each concrete numbering it creates", () => {
const a2 = numbering.createAbstractNumbering();
const n = numbering.createConcreteNumbering(a2);
const n2 = numbering.createConcreteNumbering(a2);
expect(n.id).not.to.equal(n2.id);
});
});
});
describe("AbstractNumbering", () => {
it("stores its ID at its .id property", () => {
const abstractNumbering = new AbstractNumbering(5);
expect(abstractNumbering.id).to.equal(5);
});
describe("#createLevel", () => {
it("creates a level with the given characteristics", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)", "end");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } });
expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
it("uses 'start' as the default alignment", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } });
expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
describe("formatting methods: paragraph properties", () => {
it("#indent", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").indent({ left: 720 });
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }],
});
});
it("#spacing", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").spacing({ before: 50, after: 150 });
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }],
});
});
it("#center", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").center();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }],
});
});
it("#left", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.", "left").left();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }],
});
});
it("#right", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").right();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }],
});
});
it("#justified", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").justified();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }],
});
});
it("#thematicBreak", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").thematicBreak();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [
{
"w:pBdr": [
{
"w:bottom": {
_attr: {
"w:color": "auto",
"w:space": 1,
"w:val": "single",
"w:sz": 6,
},
},
},
],
},
],
});
});
it("#leftTabStop", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").leftTabStop(1200);
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }],
},
],
});
});
it("#maxRightTabStop", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").rightTabStop(TabStopPosition.MAX);
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }],
},
],
});
});
it("#keepLines", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepLines();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:keepLines": EMPTY_OBJECT }],
});
});
it("#keepNext", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepNext();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:keepNext": EMPTY_OBJECT }],
});
});
});
describe("formatting methods: run properties", () => {
it("#size", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").size(24);
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }],
});
});
it("#smallCaps", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").smallCaps();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }],
});
});
it("#allCaps", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").allCaps();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }],
});
});
it("#strike", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").strike();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }],
});
});
it("#doubleStrike", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").doubleStrike();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }],
});
});
it("#subScript", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").subScript();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }],
});
});
it("#superScript", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").superScript();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }],
});
});
it("#font", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").font("Times");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [
{ "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } },
],
});
});
it("#bold", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").bold();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:b": { _attr: { "w:val": true } } }],
});
});
it("#italics", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").italics();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:i": { _attr: { "w:val": true } } }],
});
});
it("#highlight", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").highlight("005599");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
});
});
it("#shadow", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").shadow("pct10", "00FFFF", "FF0000");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }],
});
});
it("should set the style if given", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE);
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }],
});
});
it("should set the style and color if given", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE, "005599");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }],
});
});
});
it("#color", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").color("123456");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }],
});
});
});
});
});
describe("concrete numbering", () => {
describe("#overrideLevel", () => {
let numbering;
let abstractNumbering;
let concreteNumbering;
beforeEach(() => {
numbering = new Numbering();
abstractNumbering = numbering.createAbstractNumbering();
concreteNumbering = numbering.createConcreteNumbering(abstractNumbering);
});
it("sets a new override level for the given level number", () => {
concreteNumbering.overrideLevel(3);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{
_attr: {
"w:ilvl": 3,
},
},
{
"w:lvl": {
_attr: {
"w:ilvl": 3,
"w15:tentative": 1,
},
},
},
],
});
});
it("sets the startOverride element if start is given", () => {
concreteNumbering.overrideLevel(1, 9);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{
_attr: {
"w:ilvl": 1,
},
},
{
"w:startOverride": {
_attr: {
"w:val": 9,
},
},
},
{
"w:lvl": {
_attr: {
"w:ilvl": 1,
"w15:tentative": 1,
},
},
},
],
});
});
it("sets the lvl element if overrideLevel.Level is accessed", () => {
const ol = concreteNumbering.overrideLevel(1);
expect(ol.Level).to.be.instanceof(LevelForOverride);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{ _attr: { "w:ilvl": 1 } },
{
"w:lvl": { _attr: { "w15:tentative": 1, "w:ilvl": 1 } },
},
],
});
});
});
});

View File

@ -1,17 +1,28 @@
import { Indent } from "file/paragraph";
// http://officeopenxml.com/WPnumbering.php
import { convertInchesToTwip } from "convenience-functions";
import { AlignmentType } from "file/paragraph";
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes";
import { AbstractNumbering } from "./abstract-numbering";
import { Num } from "./num";
import { ILevelsOptions, LevelFormat } from "./level";
import { ConcreteNumbering } from "./num";
export interface INumberingOptions {
readonly config: {
readonly levels: ILevelsOptions[];
readonly reference: string;
}[];
}
export class Numbering extends XmlComponent {
// tslint:disable-next-line:readonly-keyword
private nextId: number;
private readonly abstractNumbering: XmlComponent[] = [];
private readonly concreteNumbering: XmlComponent[] = [];
private readonly abstractNumbering: AbstractNumbering[] = [];
private readonly concreteNumbering: ConcreteNumbering[] = [];
constructor() {
constructor(options: INumberingOptions) {
super("w:numbering");
this.root.push(
new DocumentAttributes({
@ -37,39 +48,114 @@ export class Numbering extends XmlComponent {
this.nextId = 0;
const abstractNumbering = this.createAbstractNumbering();
abstractNumbering.createLevel(0, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 720, hanging: 360 }));
abstractNumbering.createLevel(1, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 1440, hanging: 360 }));
abstractNumbering.createLevel(2, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 2160, hanging: 360 }));
abstractNumbering.createLevel(3, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 2880, hanging: 360 }));
abstractNumbering.createLevel(4, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 3600, hanging: 360 }));
abstractNumbering.createLevel(5, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 4320, hanging: 360 }));
abstractNumbering.createLevel(6, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 5040, hanging: 360 }));
abstractNumbering.createLevel(7, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 5760, hanging: 360 }));
abstractNumbering.createLevel(8, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 6480, hanging: 360 }));
const abstractNumbering = this.createAbstractNumbering([
{
level: 0,
format: LevelFormat.BULLET,
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 1,
format: LevelFormat.BULLET,
text: "\u25CB",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: convertInchesToTwip(1), hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 2,
format: LevelFormat.BULLET,
text: "\u25A0",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 2160, hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 3,
format: LevelFormat.BULLET,
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 2880, hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 4,
format: LevelFormat.BULLET,
text: "\u25CB",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 3600, hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 5,
format: LevelFormat.BULLET,
text: "\u25A0",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 4320, hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 6,
format: LevelFormat.BULLET,
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 5040, hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 7,
format: LevelFormat.BULLET,
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 5760, hanging: convertInchesToTwip(0.25) },
},
},
},
{
level: 8,
format: LevelFormat.BULLET,
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 6480, hanging: convertInchesToTwip(0.25) },
},
},
},
]);
this.createConcreteNumbering(abstractNumbering);
}
public createAbstractNumbering(): AbstractNumbering {
const num = new AbstractNumbering(this.nextId++);
this.abstractNumbering.push(num);
return num;
}
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
const num = new Num(this.nextId++, abstractNumbering.id);
this.concreteNumbering.push(num);
return num;
for (const con of options.config) {
const currentAbstractNumbering = this.createAbstractNumbering(con.levels);
this.createConcreteNumbering(currentAbstractNumbering, con.reference);
}
}
public prepForXml(): IXmlableObject | undefined {
@ -77,4 +163,20 @@ export class Numbering extends XmlComponent {
this.concreteNumbering.forEach((x) => this.root.push(x));
return super.prepForXml();
}
private createConcreteNumbering(abstractNumbering: AbstractNumbering, reference?: string): ConcreteNumbering {
const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id, reference);
this.concreteNumbering.push(num);
return num;
}
private createAbstractNumbering(options: ILevelsOptions[]): AbstractNumbering {
const num = new AbstractNumbering(this.nextId++, options);
this.abstractNumbering.push(num);
return num;
}
public get ConcreteNumbering(): ConcreteNumbering[] {
return this.concreteNumbering;
}
}

View File

@ -1,4 +1,5 @@
// http://officeopenxml.com/WPalignment.php
// http://officeopenxml.com/WPtableAlignment.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export enum AlignmentType {

View File

@ -0,0 +1,15 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Bidirectional } from "./bidirectional";
describe("Bidirectional", () => {
it("should create", () => {
const bidirectional = new Bidirectional();
const tree = new Formatter().format(bidirectional);
expect(tree).to.deep.equal({
"w:bidi": {},
});
});
});

View File

@ -7,6 +7,7 @@ export interface IIndentAttributesProperties {
readonly firstLine?: number;
readonly start?: number;
readonly end?: number;
readonly right?: number;
}
class IndentAttributes extends XmlAttributeComponent<IIndentAttributesProperties> {
@ -16,6 +17,7 @@ class IndentAttributes extends XmlAttributeComponent<IIndentAttributesProperties
firstLine: "w:firstLine",
start: "w:start",
end: "w:end",
right: "w:end", // alias
};
}

View File

@ -11,11 +11,8 @@ export enum HeadingLevel {
}
export class Style extends XmlComponent {
public readonly styleId: string;
constructor(styleId: string) {
super("w:pStyle");
this.styleId = styleId;
this.root.push(
new Attributes({
val: styleId,

View File

@ -1,7 +1,7 @@
import { Attributes, XmlComponent } from "file/xml-components";
export class NumberProperties extends XmlComponent {
constructor(numberId: number, indentLevel: number) {
constructor(numberId: number | string, indentLevel: number) {
super("w:numPr");
this.root.push(new IndentLevel(indentLevel));
this.root.push(new NumberId(numberId));
@ -20,11 +20,11 @@ class IndentLevel extends XmlComponent {
}
class NumberId extends XmlComponent {
constructor(id: number) {
constructor(id: number | string) {
super("w:numId");
this.root.push(
new Attributes({
val: id,
val: typeof id === "string" ? `{${id}}` : id,
}),
);
}

View File

@ -3,3 +3,4 @@ export * from "./paragraph";
export * from "./properties";
export * from "./run";
export * from "./links";
export * from "./math";

View File

@ -1,4 +1,4 @@
import { assert } from "chai";
import { assert, expect } from "chai";
import { Utility } from "tests/utility";
@ -8,7 +8,7 @@ describe("Bookmark", () => {
let bookmark: Bookmark;
beforeEach(() => {
bookmark = new Bookmark("anchor", "Internal Link", 0);
bookmark = new Bookmark("anchor", "Internal Link");
});
it("should create a bookmark with three root elements", () => {
@ -21,11 +21,8 @@ describe("Bookmark", () => {
it("should create a bookmark with the correct attributes on the bookmark start element", () => {
const newJson = Utility.jsonify(bookmark);
const attributes = {
name: "anchor",
id: "1",
};
assert.equal(JSON.stringify(newJson.start.root[0].root), JSON.stringify(attributes));
assert.equal(newJson.start.root[0].root.name, "anchor");
});
it("should create a bookmark with the correct attributes on the text element", () => {
@ -35,9 +32,6 @@ describe("Bookmark", () => {
it("should create a bookmark with the correct attributes on the bookmark end element", () => {
const newJson = Utility.jsonify(bookmark);
const attributes = {
id: "1",
};
assert.equal(JSON.stringify(newJson.end.root[0].root), JSON.stringify(attributes));
expect(newJson.end.root[0].root.id).to.be.a("string");
});
});

View File

@ -1,49 +1,41 @@
// http://officeopenxml.com/WPbookmark.php
import { XmlComponent } from "file/xml-components";
import * as shortid from "shortid";
import { TextRun } from "../run";
import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes";
export class Bookmark {
public readonly linkId: number;
public readonly start: BookmarkStart;
public readonly text: TextRun;
public readonly end: BookmarkEnd;
constructor(name: string, text: string, relationshipsCount: number) {
this.linkId = relationshipsCount + 1;
constructor(name: string, text: string) {
const linkId = shortid.generate().toLowerCase();
this.start = new BookmarkStart(name, this.linkId);
this.start = new BookmarkStart(name, linkId);
this.text = new TextRun(text);
this.end = new BookmarkEnd(this.linkId);
this.end = new BookmarkEnd(linkId);
}
}
export class BookmarkStart extends XmlComponent {
public readonly linkId: number;
constructor(name: string, relationshipsCount: number) {
constructor(name: string, linkId: string) {
super("w:bookmarkStart");
this.linkId = relationshipsCount;
const id = `${this.linkId}`;
const attributes = new BookmarkStartAttributes({
name,
id,
id: linkId,
});
this.root.push(attributes);
}
}
export class BookmarkEnd extends XmlComponent {
public readonly linkId: number;
constructor(relationshipsCount: number) {
constructor(linkId: string) {
super("w:bookmarkEnd");
this.linkId = relationshipsCount;
const id = `${this.linkId}`;
const attributes = new BookmarkEndAttributes({
id,
id: linkId,
});
this.root.push(attributes);
}

View File

@ -2,13 +2,20 @@ import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Hyperlink } from "./";
import { TextRun } from "../run";
import { ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./hyperlink";
describe("Hyperlink", () => {
let hyperlink: Hyperlink;
describe("ConcreteHyperlink", () => {
let hyperlink: ConcreteHyperlink;
beforeEach(() => {
hyperlink = new Hyperlink("https://example.com", 0);
hyperlink = new ConcreteHyperlink(
new TextRun({
text: "https://example.com",
style: "Hyperlink",
}),
"superid",
);
});
describe("#constructor()", () => {
@ -19,7 +26,7 @@ describe("Hyperlink", () => {
{
_attr: {
"w:history": 1,
"r:id": "rId1",
"r:id": "rIdsuperid",
},
},
{
@ -34,7 +41,14 @@ describe("Hyperlink", () => {
describe("with optional anchor parameter", () => {
beforeEach(() => {
hyperlink = new Hyperlink("Anchor Text", 0, "anchor");
hyperlink = new ConcreteHyperlink(
new TextRun({
text: "Anchor Text",
style: "Hyperlink",
}),
"superid2",
"anchor",
);
});
it("should create an internal link with anchor tag", () => {
@ -59,3 +73,54 @@ describe("Hyperlink", () => {
});
});
});
describe("ExternalHyperlink", () => {
describe("#constructor()", () => {
it("should create", () => {
const externalHyperlink = new ExternalHyperlink({
child: new TextRun("test"),
link: "http://www.google.com",
});
expect(externalHyperlink.options.link).to.equal("http://www.google.com");
});
});
});
describe("InternalHyperlink", () => {
describe("#constructor()", () => {
it("should create", () => {
const internalHyperlink = new InternalHyperlink({
child: new TextRun("test"),
anchor: "test-id",
});
const tree = new Formatter().format(internalHyperlink);
expect(tree).to.deep.equal({
"w:hyperlink": [
{
_attr: {
"w:anchor": "test-id",
"w:history": 1,
},
},
{
"w:r": [
{
"w:t": [
{
_attr: {
"xml:space": "preserve",
},
},
"test",
],
},
],
},
],
});
});
});
});

View File

@ -1,16 +1,23 @@
// http://officeopenxml.com/WPhyperlink.php
import * as shortid from "shortid";
import { XmlComponent } from "file/xml-components";
import { TextRun } from "../run";
import { ParagraphChild } from "../paragraph";
import { HyperlinkAttributes, IHyperlinkAttributesProperties } from "./hyperlink-attributes";
export class Hyperlink extends XmlComponent {
public readonly linkId: number;
private readonly textRun: TextRun;
export enum HyperlinkType {
INTERNAL = "INTERNAL",
EXTERNAL = "EXTERNAL",
}
constructor(text: string, relationshipsCount: number, anchor?: string) {
export class ConcreteHyperlink extends XmlComponent {
public readonly linkId: string;
constructor(child: ParagraphChild, relationshipId: string, anchor?: string) {
super("w:hyperlink");
this.linkId = relationshipsCount + 1;
this.linkId = relationshipId;
const props: IHyperlinkAttributesProperties = {
history: 1,
@ -20,14 +27,16 @@ export class Hyperlink extends XmlComponent {
const attributes = new HyperlinkAttributes(props);
this.root.push(attributes);
this.textRun = new TextRun({
text: text,
style: "Hyperlink",
});
this.root.push(this.textRun);
}
public get TextRun(): TextRun {
return this.textRun;
this.root.push(child);
}
}
export class InternalHyperlink extends ConcreteHyperlink {
constructor(options: { readonly child: ParagraphChild; readonly anchor: string }) {
super(options.child, shortid.generate().toLowerCase(), options.anchor);
}
}
export class ExternalHyperlink {
constructor(public readonly options: { readonly child: ParagraphChild; readonly link: string }) {}
}

View File

@ -0,0 +1,4 @@
export * from "./math-round-brackets";
export * from "./math-square-brackets";
export * from "./math-curly-brackets";
export * from "./math-angled-brackets";

View File

@ -0,0 +1,51 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathAngledBrackets } from "./math-angled-brackets";
describe("MathAngledBrackets", () => {
describe("#constructor()", () => {
it("should create a MathAngledBrackets with correct root key", () => {
const mathAngledBrackets = new MathAngledBrackets({
children: [new MathRun("60")],
});
const tree = new Formatter().format(mathAngledBrackets);
expect(tree).to.deep.equal({
"m:d": [
{
"m:dPr": [
{
"m:begChr": {
_attr: {
"m:val": "〈",
},
},
},
{
"m:endChr": {
_attr: {
"m:val": "〉",
},
},
},
],
},
{
"m:e": [
{
"m:r": [
{
"m:t": ["60"],
},
],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,20 @@
// http://www.datypic.com/sc/ooxml/e-m_d-1.html
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
import { MathBase } from "../n-ary";
import { MathBracketProperties } from "./math-bracket-properties";
export class MathAngledBrackets extends XmlComponent {
constructor(options: { readonly children: MathComponent[] }) {
super("m:d");
this.root.push(
new MathBracketProperties({
beginningCharacter: "〈",
endingCharacter: "〉",
}),
);
this.root.push(new MathBase(options.children));
}
}

View File

@ -0,0 +1,22 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathBeginningCharacter } from "./math-beginning-character";
describe("MathBeginningCharacter", () => {
describe("#constructor()", () => {
it("should create a MathBeginningCharacter with correct root key", () => {
const mathBeginningCharacter = new MathBeginningCharacter("[");
const tree = new Formatter().format(mathBeginningCharacter);
expect(tree).to.deep.equal({
"m:begChr": {
_attr: {
"m:val": "[",
},
},
});
});
});
});

View File

@ -0,0 +1,14 @@
// http://www.datypic.com/sc/ooxml/e-m_begChr-1.html
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
class MathBeginningCharacterAttributes extends XmlAttributeComponent<{ readonly character: string }> {
protected readonly xmlKeys = { character: "m:val" };
}
export class MathBeginningCharacter extends XmlComponent {
constructor(character: string) {
super("m:begChr");
this.root.push(new MathBeginningCharacterAttributes({ character }));
}
}

View File

@ -0,0 +1,45 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathBracketProperties } from "./math-bracket-properties";
describe("MathBracketProperties", () => {
describe("#constructor()", () => {
it("should create a MathBracketProperties with correct root key", () => {
const mathBracketProperties = new MathBracketProperties();
const tree = new Formatter().format(mathBracketProperties);
expect(tree).to.deep.equal({
"m:dPr": {},
});
});
it("should create a MathBracketProperties with correct root key and add brackets", () => {
const mathBracketProperties = new MathBracketProperties({
beginningCharacter: "[",
endingCharacter: "]",
});
const tree = new Formatter().format(mathBracketProperties);
expect(tree).to.deep.equal({
"m:dPr": [
{
"m:begChr": {
_attr: {
"m:val": "[",
},
},
},
{
"m:endChr": {
_attr: {
"m:val": "]",
},
},
},
],
});
});
});
});

View File

@ -0,0 +1,16 @@
// http://www.datypic.com/sc/ooxml/e-m_dPr-1.html
import { XmlComponent } from "file/xml-components";
import { MathBeginningCharacter } from "./math-beginning-character";
import { MathEndingCharacter } from "./math-ending-char";
export class MathBracketProperties extends XmlComponent {
constructor(options?: { readonly beginningCharacter: string; readonly endingCharacter: string }) {
super("m:dPr");
if (!!options) {
this.root.push(new MathBeginningCharacter(options.beginningCharacter));
this.root.push(new MathEndingCharacter(options.endingCharacter));
}
}
}

View File

@ -0,0 +1,51 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathCurlyBrackets } from "./math-curly-brackets";
describe("MathCurlyBrackets", () => {
describe("#constructor()", () => {
it("should create a MathCurlyBrackets with correct root key", () => {
const mathCurlyBrackets = new MathCurlyBrackets({
children: [new MathRun("60")],
});
const tree = new Formatter().format(mathCurlyBrackets);
expect(tree).to.deep.equal({
"m:d": [
{
"m:dPr": [
{
"m:begChr": {
_attr: {
"m:val": "{",
},
},
},
{
"m:endChr": {
_attr: {
"m:val": "}",
},
},
},
],
},
{
"m:e": [
{
"m:r": [
{
"m:t": ["60"],
},
],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,20 @@
// http://www.datypic.com/sc/ooxml/e-m_d-1.html
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
import { MathBase } from "../n-ary";
import { MathBracketProperties } from "./math-bracket-properties";
export class MathCurlyBrackets extends XmlComponent {
constructor(options: { readonly children: MathComponent[] }) {
super("m:d");
this.root.push(
new MathBracketProperties({
beginningCharacter: "{",
endingCharacter: "}",
}),
);
this.root.push(new MathBase(options.children));
}
}

View File

@ -0,0 +1,14 @@
// http://www.datypic.com/sc/ooxml/e-m_endChr-1.html
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
class MathEndingCharacterAttributes extends XmlAttributeComponent<{ readonly character: string }> {
protected readonly xmlKeys = { character: "m:val" };
}
export class MathEndingCharacter extends XmlComponent {
constructor(character: string) {
super("m:endChr");
this.root.push(new MathEndingCharacterAttributes({ character }));
}
}

View File

@ -0,0 +1,22 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathEndingCharacter } from "./math-ending-char";
describe("MathEndingCharacter", () => {
describe("#constructor()", () => {
it("should create a MathEndingCharacter with correct root key", () => {
const mathEndingCharacter = new MathEndingCharacter("]");
const tree = new Formatter().format(mathEndingCharacter);
expect(tree).to.deep.equal({
"m:endChr": {
_attr: {
"m:val": "]",
},
},
});
});
});
});

View File

@ -0,0 +1,36 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathRoundBrackets } from "./math-round-brackets";
describe("MathRoundBrackets", () => {
describe("#constructor()", () => {
it("should create a MathRoundBrackets with correct root key", () => {
const mathRoundBrackets = new MathRoundBrackets({
children: [new MathRun("60")],
});
const tree = new Formatter().format(mathRoundBrackets);
expect(tree).to.deep.equal({
"m:d": [
{
"m:dPr": {},
},
{
"m:e": [
{
"m:r": [
{
"m:t": ["60"],
},
],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,15 @@
// http://www.datypic.com/sc/ooxml/e-m_d-1.html
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
import { MathBase } from "../n-ary";
import { MathBracketProperties } from "./math-bracket-properties";
export class MathRoundBrackets extends XmlComponent {
constructor(options: { readonly children: MathComponent[] }) {
super("m:d");
this.root.push(new MathBracketProperties());
this.root.push(new MathBase(options.children));
}
}

View File

@ -0,0 +1,51 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathSquareBrackets } from "./math-square-brackets";
describe("MathSquareBrackets", () => {
describe("#constructor()", () => {
it("should create a MathSquareBrackets with correct root key", () => {
const mathSquareBrackets = new MathSquareBrackets({
children: [new MathRun("60")],
});
const tree = new Formatter().format(mathSquareBrackets);
expect(tree).to.deep.equal({
"m:d": [
{
"m:dPr": [
{
"m:begChr": {
_attr: {
"m:val": "[",
},
},
},
{
"m:endChr": {
_attr: {
"m:val": "]",
},
},
},
],
},
{
"m:e": [
{
"m:r": [
{
"m:t": ["60"],
},
],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,20 @@
// http://www.datypic.com/sc/ooxml/e-m_d-1.html
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
import { MathBase } from "../n-ary";
import { MathBracketProperties } from "./math-bracket-properties";
export class MathSquareBrackets extends XmlComponent {
constructor(options: { readonly children: MathComponent[] }) {
super("m:d");
this.root.push(
new MathBracketProperties({
beginningCharacter: "[",
endingCharacter: "]",
}),
);
this.root.push(new MathBase(options.children));
}
}

View File

@ -0,0 +1,3 @@
export * from "./math-fraction";
export * from "./math-denominator";
export * from "./math-numerator";

View File

@ -0,0 +1,26 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathDenominator } from "./math-denominator";
describe("MathDenominator", () => {
describe("#constructor()", () => {
it("should create a MathDenominator with correct root key", () => {
const mathDenominator = new MathDenominator([new MathRun("2+2")]);
const tree = new Formatter().format(mathDenominator);
expect(tree).to.deep.equal({
"m:den": [
{
"m:r": [
{
"m:t": ["2+2"],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,13 @@
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
export class MathDenominator extends XmlComponent {
constructor(children: MathComponent[]) {
super("m:den");
for (const child of children) {
this.root.push(child);
}
}
}

View File

@ -0,0 +1,44 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathFraction } from "./math-fraction";
describe("MathFraction", () => {
describe("#constructor()", () => {
it("should create a MathFraction with correct root key", () => {
const mathFraction = new MathFraction({
numerator: [new MathRun("2")],
denominator: [new MathRun("2")],
});
const tree = new Formatter().format(mathFraction);
expect(tree).to.deep.equal({
"m:f": [
{
"m:num": [
{
"m:r": [
{
"m:t": ["2"],
},
],
},
],
},
{
"m:den": [
{
"m:r": [
{
"m:t": ["2"],
},
],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,19 @@
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
import { MathDenominator } from "./math-denominator";
import { MathNumerator } from "./math-numerator";
export interface IMathFractionOptions {
readonly numerator: MathComponent[];
readonly denominator: MathComponent[];
}
export class MathFraction extends XmlComponent {
constructor(options: IMathFractionOptions) {
super("m:f");
this.root.push(new MathNumerator(options.numerator));
this.root.push(new MathDenominator(options.denominator));
}
}

View File

@ -0,0 +1,26 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathNumerator } from "./math-numerator";
describe("MathNumerator", () => {
describe("#constructor()", () => {
it("should create a MathNumerator with correct root key", () => {
const mathNumerator = new MathNumerator([new MathRun("2+2")]);
const tree = new Formatter().format(mathNumerator);
expect(tree).to.deep.equal({
"m:num": [
{
"m:r": [
{
"m:t": ["2+2"],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,13 @@
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
export class MathNumerator extends XmlComponent {
constructor(children: MathComponent[]) {
super("m:num");
for (const child of children) {
this.root.push(child);
}
}
}

View File

@ -0,0 +1,3 @@
export * from "./math-function";
export * from "./math-function-name";
export * from "./math-function-properties";

View File

@ -0,0 +1,27 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathFunctionName } from "./math-function-name";
describe("MathFunctionName", () => {
describe("#constructor()", () => {
it("should create a MathFunctionName with correct root key", () => {
const mathFunctionName = new MathFunctionName([new MathRun("2")]);
const tree = new Formatter().format(mathFunctionName);
expect(tree).to.deep.equal({
"m:fName": [
{
"m:r": [
{
"m:t": ["2"],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,13 @@
// http://www.datypic.com/sc/ooxml/e-m_fName-1.html
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
export class MathFunctionName extends XmlComponent {
constructor(children: MathComponent[]) {
super("m:fName");
for (const child of children) {
this.root.push(child);
}
}
}

View File

@ -0,0 +1,18 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathFunctionProperties } from "./math-function-properties";
describe("MathFunctionProperties", () => {
describe("#constructor()", () => {
it("should create a MathFunctionProperties with correct root key", () => {
const mathFunctionProperties = new MathFunctionProperties();
const tree = new Formatter().format(mathFunctionProperties);
expect(tree).to.deep.equal({
"m:funcPr": {},
});
});
});
});

View File

@ -0,0 +1,8 @@
// http://www.datypic.com/sc/ooxml/e-m_radPr-1.html
import { XmlComponent } from "file/xml-components";
export class MathFunctionProperties extends XmlComponent {
constructor() {
super("m:funcPr");
}
}

View File

@ -0,0 +1,48 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { MathRun } from "../math-run";
import { MathFunction } from "./math-function";
describe("MathFunction", () => {
describe("#constructor()", () => {
it("should create a MathFunction with correct root key", () => {
const mathFunction = new MathFunction({
name: [new MathRun("sin")],
children: [new MathRun("60")],
});
const tree = new Formatter().format(mathFunction);
expect(tree).to.deep.equal({
"m:func": [
{
"m:funcPr": {},
},
{
"m:fName": [
{
"m:r": [
{
"m:t": ["sin"],
},
],
},
],
},
{
"m:e": [
{
"m:r": [
{
"m:t": ["60"],
},
],
},
],
},
],
});
});
});
});

View File

@ -0,0 +1,22 @@
// http://www.datypic.com/sc/ooxml/e-m_func-1.html
import { XmlComponent } from "file/xml-components";
import { MathComponent } from "../math-component";
import { MathBase } from "../n-ary";
import { MathFunctionName } from "./math-function-name";
import { MathFunctionProperties } from "./math-function-properties";
export interface IMathFunctionOptions {
readonly children: MathComponent[];
readonly name: MathComponent[];
}
export class MathFunction extends XmlComponent {
constructor(options: IMathFunctionOptions) {
super("m:func");
this.root.push(new MathFunctionProperties());
this.root.push(new MathFunctionName(options.name));
this.root.push(new MathBase(options.children));
}
}

View File

@ -0,0 +1,9 @@
export * from "./math";
export * from "./math-run";
export * from "./fraction";
export * from "./n-ary";
export * from "./script";
export * from "./math-component";
export * from "./radical";
export * from "./function";
export * from "./brackets";

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