Merge branch 'master' into feat/math

# Conflicts:
#	src/file/paragraph/index.ts
#	src/file/paragraph/paragraph.ts
This commit is contained in:
Dolan Miu
2019-11-01 02:43:14 +00:00
118 changed files with 13170 additions and 2053 deletions

View File

@ -1,11 +1,87 @@
import { assert, expect } from "chai";
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { ThematicBreak } from "./border";
import { Border, ThematicBreak } from "./border";
describe("Border", () => {
// TODO: Need tests here
describe("#constructor", () => {
it("should create", () => {
const border = new Border({
top: {
color: "red",
space: 1,
value: "test",
size: 2,
},
bottom: {
color: "red",
space: 3,
value: "test",
size: 4,
},
left: {
color: "red",
space: 5,
value: "test",
size: 6,
},
right: {
color: "red",
space: 7,
value: "test",
size: 8,
},
});
const tree = new Formatter().format(border);
expect(tree).to.deep.equal({
"w:pBdr": [
{
"w:top": {
_attr: {
"w:color": "red",
"w:space": 1,
"w:sz": 2,
"w:val": "test",
},
},
},
{
"w:bottom": {
_attr: {
"w:color": "red",
"w:space": 3,
"w:sz": 4,
"w:val": "test",
},
},
},
{
"w:left": {
_attr: {
"w:color": "red",
"w:space": 5,
"w:sz": 6,
"w:val": "test",
},
},
},
{
"w:right": {
_attr: {
"w:color": "red",
"w:space": 7,
"w:sz": 8,
"w:val": "test",
},
},
},
],
});
});
});
});
describe("ThematicBreak", () => {
@ -16,17 +92,6 @@ describe("ThematicBreak", () => {
});
describe("#constructor()", () => {
it("should create valid JSON", () => {
const stringifiedJson = JSON.stringify(thematicBreak);
try {
JSON.parse(stringifiedJson);
} catch (e) {
assert.isTrue(false);
}
assert.isTrue(true);
});
it("should create a Thematic Break with correct border properties", () => {
const tree = new Formatter().format(thematicBreak);
expect(tree).to.deep.equal({

View File

@ -2,13 +2,13 @@ import { assert } from "chai";
import { Utility } from "tests/utility";
import { LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./tab-stop";
import { LeaderType, TabStop, TabStopType } from "./tab-stop";
describe("LeftTabStop", () => {
let tabStop: LeftTabStop;
let tabStop: TabStop;
beforeEach(() => {
tabStop = new LeftTabStop(100);
tabStop = new TabStop(TabStopType.LEFT, 100);
});
describe("#constructor()", () => {
@ -29,10 +29,10 @@ describe("LeftTabStop", () => {
});
describe("RightTabStop", () => {
let tabStop: RightTabStop;
let tabStop: TabStop;
beforeEach(() => {
tabStop = new RightTabStop(100, LeaderType.DOT);
tabStop = new TabStop(TabStopType.RIGHT, 100, LeaderType.DOT);
});
describe("#constructor()", () => {
@ -52,28 +52,3 @@ describe("RightTabStop", () => {
});
});
});
describe("MaxRightTabStop", () => {
let tabStop: MaxRightTabStop;
beforeEach(() => {
tabStop = new MaxRightTabStop();
});
describe("#constructor()", () => {
it("should create a Tab Stop with correct attributes", () => {
const newJson = Utility.jsonify(tabStop);
const attributes = {
val: "right",
pos: 9026,
};
assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes));
});
it("should create a Tab Stop with w:tab", () => {
const newJson = Utility.jsonify(tabStop);
assert.equal(newJson.root[0].rootKey, "w:tab");
});
});
});

View File

@ -2,13 +2,13 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export class TabStop extends XmlComponent {
constructor(tab: TabStopItem) {
constructor(type: TabStopType, position: number, leader?: LeaderType) {
super("w:tabs");
this.root.push(tab);
this.root.push(new TabStopItem(type, position, leader));
}
}
export enum TabValue {
export enum TabStopType {
LEFT = "left",
RIGHT = "right",
CENTER = "center",
@ -28,8 +28,12 @@ export enum LeaderType {
UNDERSCORE = "underscore",
}
export enum TabStopPosition {
MAX = 9026,
}
export class TabAttributes extends XmlAttributeComponent<{
readonly val: TabValue;
readonly val: TabStopType;
readonly pos: string | number;
readonly leader?: LeaderType;
}> {
@ -37,7 +41,7 @@ export class TabAttributes extends XmlAttributeComponent<{
}
export class TabStopItem extends XmlComponent {
constructor(value: TabValue, position: string | number, leader?: LeaderType) {
constructor(value: TabStopType, position: string | number, leader?: LeaderType) {
super("w:tab");
this.root.push(
new TabAttributes({
@ -48,27 +52,3 @@ export class TabStopItem extends XmlComponent {
);
}
}
export class MaxRightTabStop extends TabStop {
constructor(leader?: LeaderType) {
super(new TabStopItem(TabValue.RIGHT, 9026, leader));
}
}
export class LeftTabStop extends TabStop {
constructor(position: number, leader?: LeaderType) {
super(new TabStopItem(TabValue.LEFT, position, leader));
}
}
export class RightTabStop extends TabStop {
constructor(position: number, leader?: LeaderType) {
super(new TabStopItem(TabValue.RIGHT, position, leader));
}
}
export class CenterTabStop extends TabStop {
constructor(position: number, leader?: LeaderType) {
super(new TabStopItem(TabValue.CENTER, position, leader));
}
}

View File

@ -1,39 +0,0 @@
// tslint:disable:object-literal-key-quotes
import { assert } from "chai";
import { ImageParagraph } from "./image";
describe("Image", () => {
let image: ImageParagraph;
beforeEach(() => {
image = new ImageParagraph({
stream: new Buffer(""),
path: "",
fileName: "test.png",
dimensions: {
pixels: {
x: 10,
y: 10,
},
emus: {
x: 10,
y: 10,
},
},
});
});
describe("#constructor()", () => {
it("should create valid JSON", () => {
const stringifiedJson = JSON.stringify(image);
try {
JSON.parse(stringifiedJson);
} catch (e) {
assert.isTrue(false);
}
assert.isTrue(true);
});
});
});

View File

@ -1,18 +0,0 @@
import { IDrawingOptions } from "../drawing";
import { IMediaData } from "../media";
import { Paragraph } from "./paragraph";
import { PictureRun } from "./run";
export class ImageParagraph extends Paragraph {
private readonly pictureRun: PictureRun;
constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) {
super({});
this.pictureRun = new PictureRun(imageData, drawingOptions);
this.root.push(this.pictureRun);
}
public get Run(): PictureRun {
return this.pictureRun;
}
}

View File

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

View File

@ -4,7 +4,7 @@ import { Formatter } from "export/formatter";
import { EMPTY_OBJECT } from "file/xml-components";
import { Numbering } from "../numbering";
import { AlignmentType, HeadingLevel, LeaderType } from "./formatting";
import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting";
import { Paragraph } from "./paragraph";
describe("Paragraph", () => {
@ -254,11 +254,14 @@ describe("Paragraph", () => {
});
describe("#maxRightTabStop()", () => {
it("should add maxRightTabStop to JSON", () => {
it("should add right tab stop to JSON", () => {
const paragraph = new Paragraph({
tabStop: {
maxRight: {},
},
tabStops: [
{
type: TabStopType.RIGHT,
position: TabStopPosition.MAX,
},
],
});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
@ -287,12 +290,13 @@ describe("Paragraph", () => {
describe("#leftTabStop()", () => {
it("should add leftTabStop to JSON", () => {
const paragraph = new Paragraph({
tabStop: {
left: {
tabStops: [
{
type: TabStopType.LEFT,
position: 100,
leader: LeaderType.HYPHEN,
},
},
],
});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
@ -322,12 +326,13 @@ describe("Paragraph", () => {
describe("#rightTabStop()", () => {
it("should add rightTabStop to JSON", () => {
const paragraph = new Paragraph({
tabStop: {
right: {
tabStops: [
{
type: TabStopType.RIGHT,
position: 100,
leader: LeaderType.DOT,
},
},
],
});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
@ -357,12 +362,13 @@ describe("Paragraph", () => {
describe("#centerTabStop()", () => {
it("should add centerTabStop to JSON", () => {
const paragraph = new Paragraph({
tabStop: {
center: {
tabStops: [
{
type: TabStopType.CENTER,
position: 100,
leader: LeaderType.MIDDLE_DOT,
},
},
],
});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
@ -492,8 +498,9 @@ describe("Paragraph", () => {
describe("#pageBreak()", () => {
it("should add page break to JSON", () => {
const paragraph = new Paragraph({});
paragraph.pageBreak();
const paragraph = new Paragraph({
children: [new PageBreak()],
});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [

View File

@ -1,6 +1,5 @@
// http://officeopenxml.com/WPparagraph.php
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { Image } from "file/media";
import { Num } from "file/numbering/num";
import { XmlComponent } from "file/xml-components";
@ -12,17 +11,12 @@ import { KeepLines, KeepNext } from "./formatting/keep";
import { PageBreak, PageBreakBefore } from "./formatting/page-break";
import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spacing";
import { HeadingLevel, Style } from "./formatting/style";
import { CenterTabStop, LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop";
import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop";
import { NumberProperties } from "./formatting/unordered-list";
import { Bookmark, Hyperlink, OutlineLevel } from "./links";
import { Math } from "./math";
import { ParagraphProperties } from "./properties";
import { PictureRun, Run, SequentialIdentifier, TextRun } from "./run";
interface ITabStopOptions {
readonly position: number;
readonly leader?: LeaderType;
}
import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run";
export interface IParagraphOptions {
readonly text?: string;
@ -38,14 +32,11 @@ export interface IParagraphOptions {
readonly indent?: IIndentAttributesProperties;
readonly keepLines?: boolean;
readonly keepNext?: boolean;
readonly tabStop?: {
readonly left?: ITabStopOptions;
readonly right?: ITabStopOptions;
readonly maxRight?: {
readonly leader?: LeaderType;
};
readonly center?: ITabStopOptions;
};
readonly tabStops?: Array<{
readonly position: number | TabStopPosition;
readonly type: TabStopType;
readonly leader?: LeaderType;
}>;
readonly style?: string;
readonly bullet?: {
readonly level: number;
@ -55,7 +46,7 @@ export interface IParagraphOptions {
readonly level: number;
readonly custom?: boolean;
};
readonly children?: Array<TextRun | PictureRun | Hyperlink | Math>;
readonly children?: Array<TextRun | PictureRun | Hyperlink | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | Math>;
}
export class Paragraph extends XmlComponent {
@ -132,21 +123,9 @@ export class Paragraph extends XmlComponent {
this.properties.push(new KeepNext());
}
if (options.tabStop) {
if (options.tabStop.left) {
this.properties.push(new LeftTabStop(options.tabStop.left.position, options.tabStop.left.leader));
}
if (options.tabStop.right) {
this.properties.push(new RightTabStop(options.tabStop.right.position, options.tabStop.right.leader));
}
if (options.tabStop.maxRight) {
this.properties.push(new MaxRightTabStop(options.tabStop.maxRight.leader));
}
if (options.tabStop.center) {
this.properties.push(new CenterTabStop(options.tabStop.center.position, options.tabStop.center.leader));
if (options.tabStops) {
for (const tabStop of options.tabStops) {
this.properties.push(new TabStop(tabStop.type, tabStop.position, tabStop.leader));
}
}
@ -168,41 +147,18 @@ export class Paragraph extends XmlComponent {
if (options.children) {
for (const child of options.children) {
if (child instanceof Bookmark) {
this.root.push(child.start);
this.root.push(child.text);
this.root.push(child.end);
continue;
}
this.root.push(child);
}
}
}
public addRun(run: Run): Paragraph {
this.root.push(run);
return this;
}
public addHyperLink(hyperlink: Hyperlink): Paragraph {
this.root.push(hyperlink);
return this;
}
public addBookmark(bookmark: Bookmark): Paragraph {
// Bookmarks by spec have three components, a start, text, and end
this.root.push(bookmark.start);
this.root.push(bookmark.text);
this.root.push(bookmark.end);
return this;
}
public addImage(image: Image): PictureRun {
const run = image.Run;
this.addRun(run);
return run;
}
public pageBreak(): Paragraph {
this.root.push(new PageBreak());
return this;
}
public referenceFootnote(id: number): Paragraph {
this.root.push(new FootnoteReferenceRun(id));
return this;
@ -212,9 +168,4 @@ export class Paragraph extends XmlComponent {
this.root.splice(1, 0, run);
return this;
}
public addSequentialIdentifier(identifier: string): Paragraph {
this.root.push(new SequentialIdentifier(identifier));
return this;
}
}

View File

@ -1,4 +1,7 @@
export * from "./run";
export * from "./text-run";
export * from "./symbol-run";
export * from "./picture-run";
export * from "./run-fonts";
export * from "./sequential-identifier";
export * from "./underline";

View File

@ -2,7 +2,7 @@ import { expect } from "chai";
import { Formatter } from "export/formatter";
import { NumberOfPages, Page } from "./page-number";
import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
describe("Page", () => {
describe("#constructor()", () => {
@ -21,3 +21,12 @@ describe("NumberOfPages", () => {
});
});
});
describe("NumberOfPagesSection", () => {
describe("#constructor()", () => {
it("uses the font name for both ascii and hAnsi", () => {
const tree = new Formatter().format(new NumberOfPagesSection());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "SECTIONPAGES"] });
});
});
});

View File

@ -20,3 +20,11 @@ export class NumberOfPages extends XmlComponent {
this.root.push("NUMPAGES");
}
}
export class NumberOfPagesSection extends XmlComponent {
constructor() {
super("w:instrText");
this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));
this.root.push("SECTIONPAGES");
}
}

View File

@ -0,0 +1,28 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Symbol } from "./symbol";
describe("Symbol", () => {
describe("#constructor", () => {
// Note: if no character is given, the output is a MS Windows logo
it("creates an empty symbol run if no character is given", () => {
const s = new Symbol();
const f = new Formatter().format(s);
expect(f).to.deep.equal({ "w:sym": { _attr: { "w:char": "", "w:font": "Wingdings" } } });
});
it("creates the provided symbol with default font", () => {
const s = new Symbol("F071");
const f = new Formatter().format(s);
expect(f).to.deep.equal({ "w:sym": { _attr: { "w:char": "F071", "w:font": "Wingdings" } } });
});
it("creates the provided symbol with the provided font", () => {
const s = new Symbol("F071", "Arial");
const f = new Formatter().format(s);
expect(f).to.deep.equal({ "w:sym": { _attr: { "w:char": "F071", "w:font": "Arial" } } });
});
});
});

View File

@ -0,0 +1,20 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
interface ISymbolAttributesProperties {
readonly char: string;
readonly symbolfont?: string;
}
class SymbolAttributes extends XmlAttributeComponent<ISymbolAttributesProperties> {
protected readonly xmlKeys = {
char: "w:char",
symbolfont: "w:font",
};
}
export class Symbol extends XmlComponent {
constructor(char: string = "", symbolfont: string = "Wingdings") {
super("w:sym");
this.root.push(new SymbolAttributes({ char: char, symbolfont: symbolfont }));
}
}

View File

@ -284,6 +284,22 @@ describe("Run", () => {
});
});
describe("#numberOfTotalPagesSection", () => {
it("should set the run to the RTL mode", () => {
const run = new Run({});
run.numberOfTotalPagesSection();
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{ "w:fldChar": { _attr: { "w:fldCharType": "begin" } } },
{ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "SECTIONPAGES"] },
{ "w:fldChar": { _attr: { "w:fldCharType": "separate" } } },
{ "w:fldChar": { _attr: { "w:fldCharType": "end" } } },
],
});
});
});
describe("#pageNumber", () => {
it("should set the run to the RTL mode", () => {
const run = new Run({});

View File

@ -2,6 +2,7 @@
import { ShadingType } from "file/table";
import { XmlComponent } from "file/xml-components";
import { FieldInstruction } from "file/table-of-contents/field-instruction";
import { Break } from "./break";
import { Caps, SmallCaps } from "./caps";
import { Begin, End, Separate } from "./field";
@ -21,7 +22,7 @@ import {
SizeComplexScript,
Strike,
} from "./formatting";
import { NumberOfPages, Page } from "./page-number";
import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
@ -56,6 +57,7 @@ export interface IRunOptions {
readonly fill: string;
readonly color: string;
};
readonly children?: Array<Begin | FieldInstruction | Separate | End>;
}
export class Run extends XmlComponent {
@ -134,6 +136,12 @@ export class Run extends XmlComponent {
this.properties.push(new Shading(options.shading.type, options.shading.fill, options.shading.color));
this.properties.push(new ShadowComplexScript(options.shading.type, options.shading.fill, options.shading.color));
}
if (options.children) {
for (const child of options.children) {
this.root.push(child);
}
}
}
public break(): Run {
@ -161,4 +169,12 @@ export class Run extends XmlComponent {
this.root.push(new End());
return this;
}
public numberOfTotalPagesSection(): Run {
this.root.push(new Begin());
this.root.push(new NumberOfPagesSection());
this.root.push(new Separate());
this.root.push(new End());
return this;
}
}

View File

@ -0,0 +1,76 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { UnderlineType } from "./underline";
import { SymbolRun } from "./symbol-run";
describe("SymbolRun", () => {
let run: SymbolRun;
describe("#constructor()", () => {
it("should create symbol run from text input", () => {
run = new SymbolRun("F071");
const f = new Formatter().format(run);
expect(f).to.deep.equal({
"w:r": [{ "w:sym": { _attr: { "w:char": "F071", "w:font": "Wingdings" } } }],
});
});
it("should create symbol run from object input with just 'char' specified", () => {
run = new SymbolRun({ char: "F071" });
const f = new Formatter().format(run);
expect(f).to.deep.equal({
"w:r": [{ "w:sym": { _attr: { "w:char": "F071", "w:font": "Wingdings" } } }],
});
});
it("should create symbol run from object input with just 'char' specified", () => {
run = new SymbolRun({ char: "F071", symbolfont: "Arial" });
const f = new Formatter().format(run);
expect(f).to.deep.equal({
"w:r": [{ "w:sym": { _attr: { "w:char": "F071", "w:font": "Arial" } } }],
});
});
it("should add other standard run properties", () => {
run = new SymbolRun({
char: "F071",
symbolfont: "Arial",
italics: true,
bold: true,
underline: {
color: "red",
type: UnderlineType.DOUBLE,
},
color: "green",
size: 40,
highlight: "yellow",
});
const f = new Formatter().format(run);
expect(f).to.deep.equal({
"w:r": [
{
"w:rPr": [
{ "w:b": { _attr: { "w:val": true } } },
{ "w:bCs": { _attr: { "w:val": true } } },
{ "w:i": { _attr: { "w:val": true } } },
{ "w:iCs": { _attr: { "w:val": true } } },
{ "w:u": { _attr: { "w:val": "double", "w:color": "red" } } },
{ "w:color": { _attr: { "w:val": "green" } } },
{ "w:sz": { _attr: { "w:val": 40 } } },
{ "w:szCs": { _attr: { "w:val": 40 } } },
{ "w:highlight": { _attr: { "w:val": "yellow" } } },
{ "w:highlightCs": { _attr: { "w:val": "yellow" } } },
],
},
{
"w:sym": { _attr: { "w:char": "F071", "w:font": "Arial" } },
},
],
});
});
});
});

View File

@ -0,0 +1,20 @@
import { IRunOptions, Run } from "./run";
import { Symbol } from "./run-components/symbol";
export interface ISymbolRunOptions extends IRunOptions {
readonly char: string;
readonly symbolfont?: string;
}
export class SymbolRun extends Run {
constructor(options: ISymbolRunOptions | string) {
if (typeof options === "string") {
super({});
this.root.push(new Symbol(options));
return;
}
super(options);
this.root.push(new Symbol(options.char, options.symbolfont));
}
}

View File

@ -16,4 +16,23 @@ describe("TextRun", () => {
});
});
});
describe("#referenceFootnote()", () => {
it("should add a valid footnote reference", () => {
run = new TextRun("test");
run.referenceFootnote(1);
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "test"] },
{
"w:r": [
{ "w:rPr": [{ "w:rStyle": { _attr: { "w:val": "FootnoteReference" } } }] },
{ "w:footnoteReference": { _attr: { "w:id": 1 } } },
],
},
],
});
});
});
});

View File

@ -1,3 +1,4 @@
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { IRunOptions, Run } from "./run";
import { Text } from "./run-components/text";
@ -16,4 +17,9 @@ export class TextRun extends Run {
super(options);
this.root.push(new Text(options.text));
}
public referenceFootnote(id: number): TextRun {
this.root.push(new FootnoteReferenceRun(id));
return this;
}
}

View File

@ -27,7 +27,7 @@ describe("Underline", () => {
});
it("should use the given style type and color", () => {
const underline = new u.Underline("double", "FF00CC");
const underline = new u.Underline(u.UnderlineType.DOUBLE, "FF00CC");
const tree = new Formatter().format(underline);
expect(tree).to.deep.equal({
"w:u": { _attr: { "w:val": "double", "w:color": "FF00CC" } },

View File

@ -33,7 +33,7 @@ export abstract class BaseUnderline extends XmlComponent {
}
export class Underline extends BaseUnderline {
constructor(underlineType: string = "single", color?: string) {
constructor(underlineType: UnderlineType = UnderlineType.SINGLE, color?: string) {
super(underlineType, color);
}
}