Merge branch 'master' into feat/math

This commit is contained in:
Dolan
2020-10-08 10:25:45 +01:00
151 changed files with 7913 additions and 3690 deletions

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

@ -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

@ -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

@ -3,12 +3,13 @@ import { expect } from "chai";
import { Formatter } from "export/formatter";
import { Hyperlink } from "./";
import { HyperlinkRef } from "./hyperlink";
describe("Hyperlink", () => {
let hyperlink: Hyperlink;
beforeEach(() => {
hyperlink = new Hyperlink("https://example.com", 0);
hyperlink = new Hyperlink("https://example.com", "superid");
});
describe("#constructor()", () => {
@ -19,7 +20,7 @@ describe("Hyperlink", () => {
{
_attr: {
"w:history": 1,
"r:id": "rId1",
"r:id": "rIdsuperid",
},
},
{
@ -34,7 +35,7 @@ describe("Hyperlink", () => {
describe("with optional anchor parameter", () => {
beforeEach(() => {
hyperlink = new Hyperlink("Anchor Text", 0, "anchor");
hyperlink = new Hyperlink("Anchor Text", "superid2", "anchor");
});
it("should create an internal link with anchor tag", () => {
@ -59,3 +60,11 @@ describe("Hyperlink", () => {
});
});
});
describe("HyperlinkRef", () => {
describe("#constructor()", () => {
const hyperlinkRef = new HyperlinkRef("test-id");
expect(hyperlinkRef.id).to.equal("test-id");
});
});

View File

@ -3,14 +3,23 @@ import { XmlComponent } from "file/xml-components";
import { TextRun } from "../run";
import { HyperlinkAttributes, IHyperlinkAttributesProperties } from "./hyperlink-attributes";
export enum HyperlinkType {
INTERNAL = "INTERNAL",
EXTERNAL = "EXTERNAL",
}
export class HyperlinkRef {
constructor(public readonly id: string) {}
}
export class Hyperlink extends XmlComponent {
public readonly linkId: number;
public readonly linkId: string;
private readonly textRun: TextRun;
constructor(text: string, relationshipsCount: number, anchor?: string) {
constructor(text: string, relationshipId: string, anchor?: string) {
super("w:hyperlink");
this.linkId = relationshipsCount + 1;
this.linkId = relationshipId;
const props: IHyperlinkAttributesProperties = {
history: 1,

View File

@ -1,10 +1,12 @@
import { assert, expect } from "chai";
import * as shortid from "shortid";
import { stub } from "sinon";
import { Formatter } from "export/formatter";
import { EMPTY_OBJECT } from "file/xml-components";
import { Numbering } from "../numbering";
import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting";
import { Bookmark } from "./links";
import { Paragraph } from "./paragraph";
describe("Paragraph", () => {
@ -540,14 +542,8 @@ describe("Paragraph", () => {
},
});
const tree = new Formatter().format(paragraph);
expect(tree)
.to.have.property("w:p")
.which.is.an("array")
.which.has.length.at.least(1);
expect(tree["w:p"][0])
.to.have.property("w:pPr")
.which.is.an("array")
.which.has.length.at.least(1);
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]["w:pPr"][0]).to.deep.equal({
"w:pStyle": { _attr: { "w:val": "ListParagraph" } },
});
@ -560,14 +556,8 @@ describe("Paragraph", () => {
},
});
const tree = new Formatter().format(paragraph);
expect(tree)
.to.have.property("w:p")
.which.is.an("array")
.which.has.length.at.least(1);
expect(tree["w:p"][0])
.to.have.property("w:pPr")
.which.is.an("array")
.which.has.length.at.least(1);
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]["w:pPr"][0]).to.deep.equal({
"w:pStyle": { _attr: { "w:val": "ListParagraph" } },
});
@ -580,14 +570,8 @@ describe("Paragraph", () => {
},
});
const tree = new Formatter().format(paragraph);
expect(tree)
.to.have.property("w:p")
.which.is.an("array")
.which.has.length.at.least(1);
expect(tree["w:p"][0])
.to.have.property("w:pPr")
.which.is.an("array")
.which.has.length.at.least(2);
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(2);
expect(tree["w:p"][0]["w:pPr"][1]).to.deep.equal({
"w:numPr": [{ "w:ilvl": { _attr: { "w:val": 1 } } }, { "w:numId": { _attr: { "w:val": 1 } } }],
});
@ -596,40 +580,24 @@ describe("Paragraph", () => {
describe("#setNumbering", () => {
it("should add list paragraph style to JSON", () => {
const numbering = new Numbering();
const numberedAbstract = numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
const paragraph = new Paragraph({
numbering: {
num: letterNumbering,
reference: "test id",
level: 0,
},
});
const tree = new Formatter().format(paragraph);
expect(tree)
.to.have.property("w:p")
.which.is.an("array")
.which.has.length.at.least(1);
expect(tree["w:p"][0])
.to.have.property("w:pPr")
.which.is.an("array")
.which.has.length.at.least(1);
expect(tree).to.have.property("w:p").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]).to.have.property("w:pPr").which.is.an("array").which.has.length.at.least(1);
expect(tree["w:p"][0]["w:pPr"][0]).to.deep.equal({
"w:pStyle": { _attr: { "w:val": "ListParagraph" } },
});
});
it("it should add numbered properties", () => {
const numbering = new Numbering();
const numberedAbstract = numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
const paragraph = new Paragraph({
numbering: {
num: letterNumbering,
reference: "test id",
level: 0,
},
});
@ -640,10 +608,7 @@ describe("Paragraph", () => {
"w:pPr": [
{ "w:pStyle": { _attr: { "w:val": "ListParagraph" } } },
{
"w:numPr": [
{ "w:ilvl": { _attr: { "w:val": 0 } } },
{ "w:numId": { _attr: { "w:val": letterNumbering.id } } },
],
"w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }],
},
],
},
@ -652,6 +617,49 @@ describe("Paragraph", () => {
});
});
it("it should add bookmark", () => {
stub(shortid, "generate").callsFake(() => {
return "test-unique-id";
});
const paragraph = new Paragraph({
children: [new Bookmark("test-id", "test")],
});
const tree = new Formatter().format(paragraph);
expect(tree).to.deep.equal({
"w:p": [
{
"w:bookmarkStart": {
_attr: {
"w:id": "test-unique-id",
"w:name": "test-id",
},
},
},
{
"w:r": [
{
"w:t": [
{
_attr: {
"xml:space": "preserve",
},
},
"test",
],
},
],
},
{
"w:bookmarkEnd": {
_attr: {
"w:id": "test-unique-id",
},
},
},
],
});
});
describe("#style", () => {
it("should set the paragraph style to the given styleId", () => {
const paragraph = new Paragraph({

View File

@ -1,52 +1,30 @@
// http://officeopenxml.com/WPparagraph.php
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { Num } from "file/numbering/num";
import { XmlComponent } from "file/xml-components";
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Alignment, AlignmentType } from "./formatting/alignment";
import { Bidirectional } from "./formatting/bidirectional";
import { IBorderOptions, ThematicBreak } from "./formatting/border";
import { IIndentAttributesProperties, Indent } from "./formatting/indent";
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 { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop";
import { NumberProperties } from "./formatting/unordered-list";
import { Bookmark, Hyperlink, OutlineLevel } from "./links";
import { PageBreak } from "./formatting/page-break";
import { Bookmark, HyperlinkRef } from "./links";
import { Math } from "./math";
import { ParagraphProperties } from "./properties";
import { File } from "../file";
import { InsertedTextRun, DeletedTextRun } from "../track-revision";
import { IParagraphPropertiesOptions, ParagraphProperties } from "./properties";
import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run";
export interface IParagraphOptions {
export interface IParagraphOptions extends IParagraphPropertiesOptions {
readonly text?: string;
readonly border?: IBorderOptions;
readonly spacing?: ISpacingProperties;
readonly outlineLevel?: number;
readonly alignment?: AlignmentType;
readonly heading?: HeadingLevel;
readonly bidirectional?: boolean;
readonly thematicBreak?: boolean;
readonly pageBreakBefore?: boolean;
readonly contextualSpacing?: boolean;
readonly indent?: IIndentAttributesProperties;
readonly keepLines?: boolean;
readonly keepNext?: boolean;
readonly tabStops?: Array<{
readonly position: number | TabStopPosition;
readonly type: TabStopType;
readonly leader?: LeaderType;
}>;
readonly style?: string;
readonly bullet?: {
readonly level: number;
};
readonly numbering?: {
readonly num: Num;
readonly level: number;
readonly custom?: boolean;
};
readonly children?: Array<TextRun | PictureRun | Hyperlink | SymbolRun | Bookmark | PageBreak | SequentialIdentifier | Math>;
readonly children?: (
| TextRun
| PictureRun
| SymbolRun
| Bookmark
| PageBreak
| SequentialIdentifier
| FootnoteReferenceRun
| HyperlinkRef
| InsertedTextRun
| DeletedTextRun
| Math
)[];
}
export class Paragraph extends XmlComponent {
@ -69,9 +47,7 @@ export class Paragraph extends XmlComponent {
return;
}
this.properties = new ParagraphProperties({
border: options.border,
});
this.properties = new ParagraphProperties(options);
this.root.push(this.properties);
@ -79,72 +55,6 @@ export class Paragraph extends XmlComponent {
this.root.push(new TextRun(options.text));
}
if (options.spacing) {
this.properties.push(new Spacing(options.spacing));
}
if (options.outlineLevel !== undefined) {
this.properties.push(new OutlineLevel(options.outlineLevel));
}
if (options.alignment) {
this.properties.push(new Alignment(options.alignment));
}
if (options.heading) {
this.properties.push(new Style(options.heading));
}
if (options.bidirectional) {
this.properties.push(new Bidirectional());
}
if (options.thematicBreak) {
this.properties.push(new ThematicBreak());
}
if (options.pageBreakBefore) {
this.properties.push(new PageBreakBefore());
}
if (options.contextualSpacing) {
this.properties.push(new ContextualSpacing(options.contextualSpacing));
}
if (options.indent) {
this.properties.push(new Indent(options.indent));
}
if (options.keepLines) {
this.properties.push(new KeepLines());
}
if (options.keepNext) {
this.properties.push(new KeepNext());
}
if (options.tabStops) {
for (const tabStop of options.tabStops) {
this.properties.push(new TabStop(tabStop.type, tabStop.position, tabStop.leader));
}
}
if (options.style) {
this.properties.push(new Style(options.style));
}
if (options.bullet) {
this.properties.push(new Style("ListParagraph"));
this.properties.push(new NumberProperties(1, options.bullet.level));
}
if (options.numbering) {
if (!options.numbering.custom) {
this.properties.push(new Style("ListParagraph"));
}
this.properties.push(new NumberProperties(options.numbering.num.id, options.numbering.level));
}
if (options.children) {
for (const child of options.children) {
if (child instanceof Bookmark) {
@ -159,9 +69,15 @@ export class Paragraph extends XmlComponent {
}
}
public referenceFootnote(id: number): Paragraph {
this.root.push(new FootnoteReferenceRun(id));
return this;
public prepForXml(file: File): IXmlableObject | undefined {
for (const element of this.root) {
if (element instanceof HyperlinkRef) {
const index = this.root.indexOf(element);
this.root[index] = file.HyperlinkCache[element.id];
}
}
return super.prepForXml();
}
public addRunToFront(run: Run): Paragraph {

View File

@ -1,19 +1,136 @@
// http://officeopenxml.com/WPparagraphProperties.php
import { IgnoreIfEmptyXmlComponent, XmlComponent } from "file/xml-components";
import { Alignment, AlignmentType } from "./formatting/alignment";
import { Bidirectional } from "./formatting/bidirectional";
import { Border, IBorderOptions, ThematicBreak } from "./formatting/border";
import { IIndentAttributesProperties, Indent } from "./formatting/indent";
import { KeepLines, KeepNext } from "./formatting/keep";
import { PageBreakBefore } from "./formatting/page-break";
import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spacing";
import { HeadingLevel, Style } from "./formatting/style";
import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop";
import { NumberProperties } from "./formatting/unordered-list";
import { OutlineLevel } from "./links";
import { Border, IBorderOptions } from "./formatting/border";
export interface IParagraphStylePropertiesOptions {
readonly alignment?: AlignmentType;
readonly thematicBreak?: boolean;
readonly contextualSpacing?: boolean;
readonly rightTabStop?: number;
readonly leftTabStop?: number;
readonly indent?: IIndentAttributesProperties;
readonly spacing?: ISpacingProperties;
readonly keepNext?: boolean;
readonly keepLines?: boolean;
readonly outlineLevel?: number;
}
interface IParagraphPropertiesOptions {
export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOptions {
readonly border?: IBorderOptions;
readonly heading?: HeadingLevel;
readonly bidirectional?: boolean;
readonly pageBreakBefore?: boolean;
readonly tabStops?: {
readonly position: number | TabStopPosition;
readonly type: TabStopType;
readonly leader?: LeaderType;
}[];
readonly style?: string;
readonly bullet?: {
readonly level: number;
};
readonly numbering?: {
readonly reference: string;
readonly level: number;
readonly custom?: boolean;
};
}
export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
constructor(options: IParagraphPropertiesOptions) {
constructor(options?: IParagraphPropertiesOptions) {
super("w:pPr");
if (!options) {
return;
}
if (options.border) {
this.push(new Border(options.border));
}
if (options.spacing) {
this.push(new Spacing(options.spacing));
}
if (options.outlineLevel !== undefined) {
this.push(new OutlineLevel(options.outlineLevel));
}
if (options.alignment) {
this.push(new Alignment(options.alignment));
}
if (options.heading) {
this.push(new Style(options.heading));
}
if (options.bidirectional) {
this.push(new Bidirectional());
}
if (options.thematicBreak) {
this.push(new ThematicBreak());
}
if (options.pageBreakBefore) {
this.push(new PageBreakBefore());
}
if (options.contextualSpacing) {
this.push(new ContextualSpacing(options.contextualSpacing));
}
if (options.indent) {
this.push(new Indent(options.indent));
}
if (options.keepLines) {
this.push(new KeepLines());
}
if (options.keepNext) {
this.push(new KeepNext());
}
if (options.tabStops) {
for (const tabStop of options.tabStops) {
this.push(new TabStop(tabStop.type, tabStop.position, tabStop.leader));
}
}
if (options.style) {
this.push(new Style(options.style));
}
if (options.bullet) {
this.push(new Style("ListParagraph"));
this.push(new NumberProperties(1, options.bullet.level));
}
if (options.numbering) {
if (!options.numbering.custom) {
this.push(new Style("ListParagraph"));
}
this.push(new NumberProperties(options.numbering.reference, options.numbering.level));
}
if (options.rightTabStop) {
this.push(new TabStop(TabStopType.RIGHT, options.rightTabStop));
}
if (options.leftTabStop) {
this.push(new TabStop(TabStopType.LEFT, options.leftTabStop));
}
}
public push(item: XmlComponent): void {

View File

@ -1,13 +0,0 @@
import { XmlComponent } from "file/xml-components";
export class SmallCaps extends XmlComponent {
constructor() {
super("w:smallCaps");
}
}
export class Caps extends XmlComponent {
constructor() {
super("w:caps");
}
}

View File

@ -0,0 +1,29 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import * as em from "./emphasis-mark";
describe("EmphasisMark", () => {
describe("#constructor()", () => {
it("should create a new EmphasisMark object with w:em as the rootKey", () => {
const emphasisMark = new em.EmphasisMark();
const tree = new Formatter().format(emphasisMark);
expect(tree).to.deep.equal({
"w:em": { _attr: { "w:val": "dot" } },
});
});
});
});
describe("DotEmphasisMark", () => {
describe("#constructor()", () => {
it("should put value in attribute", () => {
const emphasisMark = new em.DotEmphasisMark();
const tree = new Formatter().format(emphasisMark);
expect(tree).to.deep.equal({
"w:em": { _attr: { "w:val": "dot" } },
});
});
});
});

View File

@ -0,0 +1,28 @@
import { Attributes, XmlComponent } from "file/xml-components";
export enum EmphasisMarkType {
DOT = "dot",
}
export abstract class BaseEmphasisMark extends XmlComponent {
protected constructor(emphasisMarkType: EmphasisMarkType) {
super("w:em");
this.root.push(
new Attributes({
val: emphasisMarkType,
}),
);
}
}
export class EmphasisMark extends BaseEmphasisMark {
constructor(emphasisMarkType: EmphasisMarkType = EmphasisMarkType.DOT) {
super(emphasisMarkType);
}
}
export class DotEmphasisMark extends BaseEmphasisMark {
constructor() {
super(EmphasisMarkType.DOT);
}
}

View File

@ -1,7 +1,9 @@
import { Attributes, XmlComponent } from "file/xml-components";
export { Underline } from "./underline";
export { EmphasisMark } from "./emphasis-mark";
export { SubScript, SuperScript } from "./script";
export { RunFonts } from "./run-fonts";
export { RunFonts, IFontAttributesProperties } from "./run-fonts";
export class Bold extends XmlComponent {
constructor() {
@ -113,17 +115,6 @@ export class Imprint extends XmlComponent {
}
}
/* export class Shadow extends XmlComponent {
constructor() {
super("w:shadow");
this.root.push(
new Attributes({
val: true,
}),
);
}
} */
export class SmallCaps extends XmlComponent {
constructor() {
super("w:smallCaps");

View File

@ -1,7 +1,10 @@
export * from "./run";
export * from "./properties";
export * from "./text-run";
export * from "./symbol-run";
export * from "./picture-run";
export * from "./run-fonts";
export * from "./sequential-identifier";
export * from "./underline";
export * from "./emphasis-mark";
export * from "./tab";

View File

@ -7,10 +7,6 @@ export class PictureRun extends Run {
constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) {
super({});
if (imageData === undefined) {
throw new Error("imageData cannot be undefined");
}
const drawing = new Drawing(imageData, drawingOptions);
this.root.push(drawing);

View File

@ -1,8 +1,183 @@
import { ShadingType } from "file/table";
import { IgnoreIfEmptyXmlComponent, XmlComponent } from "file/xml-components";
import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark";
import {
Bold,
BoldComplexScript,
Caps,
CharacterSpacing,
Color,
DoubleStrike,
Highlight,
HighlightComplexScript,
Italics,
ItalicsComplexScript,
RightToLeft,
Shading,
ShadowComplexScript,
Size,
SizeComplexScript,
SmallCaps,
Strike,
} from "./formatting";
import { IFontAttributesProperties, RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
import { Style } from "./style";
import { Underline, UnderlineType } from "./underline";
interface IFontOptions {
readonly name: string;
readonly hint?: string;
}
export interface IRunStylePropertiesOptions {
readonly bold?: boolean;
readonly boldComplexScript?: boolean;
readonly italics?: boolean;
readonly italicsComplexScript?: boolean;
readonly underline?: {
readonly color?: string;
readonly type?: UnderlineType;
};
readonly emphasisMark?: {
readonly type?: EmphasisMarkType;
};
readonly color?: string;
readonly size?: number;
readonly sizeComplexScript?: boolean | number;
readonly rightToLeft?: boolean;
readonly smallCaps?: boolean;
readonly allCaps?: boolean;
readonly strike?: boolean;
readonly doubleStrike?: boolean;
readonly subScript?: boolean;
readonly superScript?: boolean;
readonly font?: string | IFontOptions | IFontAttributesProperties;
readonly highlight?: string;
readonly highlightComplexScript?: boolean | string;
readonly characterSpacing?: number;
readonly shading?: {
readonly type: ShadingType;
readonly fill: string;
readonly color: string;
};
readonly shadingComplexScript?: boolean | IRunStylePropertiesOptions["shading"];
readonly shadow?: IRunStylePropertiesOptions["shading"];
}
export interface IRunPropertiesOptions extends IRunStylePropertiesOptions {
readonly style?: string;
}
export class RunProperties extends IgnoreIfEmptyXmlComponent {
constructor() {
constructor(options?: IRunPropertiesOptions) {
super("w:rPr");
if (!options) {
return;
}
if (options.bold) {
this.push(new Bold());
}
if ((options.boldComplexScript === undefined && options.bold) || options.boldComplexScript) {
this.push(new BoldComplexScript());
}
if (options.italics) {
this.push(new Italics());
}
if ((options.italicsComplexScript === undefined && options.italics) || options.italicsComplexScript) {
this.push(new ItalicsComplexScript());
}
if (options.underline) {
this.push(new Underline(options.underline.type, options.underline.color));
}
if (options.emphasisMark) {
this.push(new EmphasisMark(options.emphasisMark.type));
}
if (options.color) {
this.push(new Color(options.color));
}
if (options.size) {
this.push(new Size(options.size));
}
const szCs =
options.sizeComplexScript === undefined || options.sizeComplexScript === true ? options.size : options.sizeComplexScript;
if (szCs) {
this.push(new SizeComplexScript(szCs));
}
if (options.rightToLeft) {
this.push(new RightToLeft());
}
if (options.smallCaps) {
this.push(new SmallCaps());
}
if (options.allCaps) {
this.push(new Caps());
}
if (options.strike) {
this.push(new Strike());
}
if (options.doubleStrike) {
this.push(new DoubleStrike());
}
if (options.subScript) {
this.push(new SubScript());
}
if (options.superScript) {
this.push(new SuperScript());
}
if (options.style) {
this.push(new Style(options.style));
}
if (options.font) {
if (typeof options.font === "string") {
this.push(new RunFonts(options.font));
} else if ("name" in options.font) {
this.push(new RunFonts(options.font.name, options.font.hint));
} else {
this.push(new RunFonts(options.font));
}
}
if (options.highlight) {
this.push(new Highlight(options.highlight));
}
const highlightCs =
options.highlightComplexScript === undefined || options.highlightComplexScript === true
? options.highlight
: options.highlightComplexScript;
if (highlightCs) {
this.push(new HighlightComplexScript(highlightCs));
}
if (options.characterSpacing) {
this.push(new CharacterSpacing(options.characterSpacing));
}
const shading = options.shading || options.shadow;
if (shading) {
this.push(new Shading(shading.type, shading.fill, shading.color));
}
const shdCs =
options.shadingComplexScript === undefined || options.shadingComplexScript === true ? shading : options.shadingComplexScript;
if (shdCs) {
this.push(new ShadowComplexScript(shdCs.type, shdCs.fill, shdCs.color));
}
}
public push(item: XmlComponent): void {

View File

@ -6,12 +6,6 @@ import { Text } from "./text";
describe("Text", () => {
describe("#constructor", () => {
it("creates an empty text run if no text is given", () => {
const t = new Text("");
const f = new Formatter().format(t);
expect(f).to.deep.equal({ "w:t": { _attr: { "xml:space": "preserve" } } });
});
it("adds the passed in text to the component", () => {
const t = new Text(" this is\n text");
const f = new Formatter().format(t);

View File

@ -9,8 +9,7 @@ export class Text extends XmlComponent {
constructor(text: string) {
super("w:t");
this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));
if (text) {
this.root.push(text);
}
this.root.push(text);
}
}

View File

@ -21,5 +21,12 @@ describe("RunFonts", () => {
},
});
});
it("uses the font attrs for ascii and eastAsia", () => {
const tree = new Formatter().format(new RunFonts({ ascii: "Times", eastAsia: "KaiTi" }));
expect(tree).to.deep.equal({
"w:rFonts": { _attr: { "w:ascii": "Times", "w:eastAsia": "KaiTi" } },
});
});
});
});

View File

@ -1,14 +1,14 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
interface IRunFontAttributesProperties {
readonly ascii: string;
readonly cs: string;
readonly eastAsia: string;
readonly hAnsi: string;
export interface IFontAttributesProperties {
readonly ascii?: string;
readonly cs?: string;
readonly eastAsia?: string;
readonly hAnsi?: string;
readonly hint?: string;
}
class RunFontAttributes extends XmlAttributeComponent<IRunFontAttributesProperties> {
class RunFontAttributes extends XmlAttributeComponent<IFontAttributesProperties> {
protected readonly xmlKeys = {
ascii: "w:ascii",
cs: "w:cs",
@ -19,16 +19,26 @@ class RunFontAttributes extends XmlAttributeComponent<IRunFontAttributesProperti
}
export class RunFonts extends XmlComponent {
constructor(ascii: string, hint?: string) {
constructor(name: string, hint?: string);
constructor(attrs: string | IFontAttributesProperties);
constructor(nameOrAttrs: string | IFontAttributesProperties, hint?: string) {
super("w:rFonts");
this.root.push(
new RunFontAttributes({
ascii: ascii,
cs: ascii,
eastAsia: ascii,
hAnsi: ascii,
hint: hint,
}),
);
if (typeof nameOrAttrs === "string") {
// use constructor(name: string, hint?: string);
const name = nameOrAttrs;
this.root.push(
new RunFontAttributes({
ascii: name,
cs: name,
eastAsia: name,
hAnsi: name,
hint: hint,
}),
);
} else {
// use constructor(attrs: IRunFontAttributesProperties);
const attrs = nameOrAttrs;
this.root.push(new RunFontAttributes(attrs));
}
}
}

View File

@ -1,9 +1,12 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
// import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { ShadingType } from "file/table";
import { Run } from "./";
import { EmphasisMarkType } from "./emphasis-mark";
import { PageNumber } from "./run";
import { UnderlineType } from "./underline";
describe("Run", () => {
@ -82,6 +85,30 @@ describe("Run", () => {
});
});
describe("#emphasisMark()", () => {
it("should default to 'dot'", () => {
const run = new Run({
emphasisMark: {},
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }] }],
});
});
it("should set the style type if given", () => {
const run = new Run({
emphasisMark: {
type: EmphasisMarkType.DOT,
},
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }] }],
});
});
});
describe("#smallCaps()", () => {
it("it should add smallCaps to the properties", () => {
const run = new Run({
@ -89,7 +116,7 @@ describe("Run", () => {
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:smallCaps": {} }] }],
"w:r": [{ "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }] }],
});
});
});
@ -101,7 +128,7 @@ describe("Run", () => {
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:caps": {} }] }],
"w:r": [{ "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }] }],
});
});
});
@ -130,6 +157,30 @@ describe("Run", () => {
});
});
describe("#subScript()", () => {
it("it should add subScript to the properties", () => {
const run = new Run({
subScript: true,
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }] }],
});
});
});
describe("#superScript()", () => {
it("it should add superScript to the properties", () => {
const run = new Run({
superScript: true,
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [{ "w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }] }],
});
});
});
describe("#highlight()", () => {
it("it should add highlight to the properties", () => {
const run = new Run({
@ -197,17 +248,6 @@ describe("Run", () => {
});
});
describe("#tab()", () => {
it("it should add break to the run", () => {
const run = new Run({});
run.tab();
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [{ "w:tab": {} }],
});
});
});
describe("#font()", () => {
it("should set the font as named", () => {
const run = new Run({
@ -220,7 +260,42 @@ describe("Run", () => {
"w:r": [
{
"w:rPr": [
{ "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } },
{
"w:rFonts": {
_attr: {
"w:ascii": "Times",
"w:cs": "Times",
"w:eastAsia": "Times",
"w:hAnsi": "Times",
},
},
},
],
},
],
});
});
it("should set the font for ascii and eastAsia", () => {
const run = new Run({
font: {
ascii: "Times",
eastAsia: "KaiTi",
},
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{
"w:rPr": [
{
"w:rFonts": {
_attr: {
"w:ascii": "Times",
"w:eastAsia": "KaiTi",
},
},
},
],
},
],
@ -270,8 +345,10 @@ describe("Run", () => {
describe("#numberOfTotalPages", () => {
it("should set the run to the RTL mode", () => {
const run = new Run({});
run.numberOfTotalPages();
const run = new Run({
children: [PageNumber.TOTAL_PAGES],
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
@ -286,8 +363,10 @@ describe("Run", () => {
describe("#numberOfTotalPagesSection", () => {
it("should set the run to the RTL mode", () => {
const run = new Run({});
run.numberOfTotalPagesSection();
const run = new Run({
children: [PageNumber.TOTAL_PAGES_IN_SECTION],
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
@ -302,8 +381,9 @@ describe("Run", () => {
describe("#pageNumber", () => {
it("should set the run to the RTL mode", () => {
const run = new Run({});
run.pageNumber();
const run = new Run({
children: [PageNumber.CURRENT],
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [

View File

@ -1,63 +1,23 @@
// http://officeopenxml.com/WPtext.php
import { ShadingType } from "file/table";
import { XmlComponent } from "file/xml-components";
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { FieldInstruction } from "file/table-of-contents/field-instruction";
import { Break } from "./break";
import { Caps, SmallCaps } from "./caps";
import { Begin, End, Separate } from "./field";
import {
Bold,
BoldComplexScript,
Color,
DoubleStrike,
Highlight,
HighlightComplexScript,
Italics,
ItalicsComplexScript,
RightToLeft,
Shading,
ShadowComplexScript,
Size,
SizeComplexScript,
Strike,
} from "./formatting";
import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script";
import { Style } from "./style";
import { Tab } from "./tab";
import { Underline, UnderlineType } from "./underline";
import { IRunPropertiesOptions, RunProperties } from "./properties";
import { Text } from "./run-components/text";
export interface IRunOptions {
readonly bold?: true;
readonly italics?: true;
readonly underline?: {
readonly color?: string;
readonly type?: UnderlineType;
};
readonly color?: string;
readonly size?: number;
readonly rightToLeft?: boolean;
readonly smallCaps?: boolean;
readonly allCaps?: boolean;
readonly strike?: boolean;
readonly doubleStrike?: boolean;
readonly subScript?: boolean;
readonly superScript?: boolean;
readonly style?: string;
readonly font?: {
readonly name: string;
readonly hint?: string;
};
readonly highlight?: string;
readonly shading?: {
readonly type: ShadingType;
readonly fill: string;
readonly color: string;
};
readonly children?: Array<Begin | FieldInstruction | Separate | End>;
export interface IRunOptions extends IRunPropertiesOptions {
readonly children?: (Begin | FieldInstruction | Separate | End | PageNumber | FootnoteReferenceRun | string)[];
readonly text?: string;
}
export enum PageNumber {
CURRENT = "CURRENT",
TOTAL_PAGES = "TOTAL_PAGES",
TOTAL_PAGES_IN_SECTION = "TOTAL_PAGES_IN_SECTION",
}
export class Run extends XmlComponent {
@ -65,82 +25,42 @@ export class Run extends XmlComponent {
constructor(options: IRunOptions) {
super("w:r");
this.properties = new RunProperties();
this.properties = new RunProperties(options);
this.root.push(this.properties);
if (options.bold) {
this.properties.push(new Bold());
this.properties.push(new BoldComplexScript());
}
if (options.italics) {
this.properties.push(new Italics());
this.properties.push(new ItalicsComplexScript());
}
if (options.underline) {
this.properties.push(new Underline(options.underline.type, options.underline.color));
}
if (options.color) {
this.properties.push(new Color(options.color));
}
if (options.size) {
this.properties.push(new Size(options.size));
this.properties.push(new SizeComplexScript(options.size));
}
if (options.rightToLeft) {
this.properties.push(new RightToLeft());
}
if (options.smallCaps) {
this.properties.push(new SmallCaps());
}
if (options.allCaps) {
this.properties.push(new Caps());
}
if (options.strike) {
this.properties.push(new Strike());
}
if (options.doubleStrike) {
this.properties.push(new DoubleStrike());
}
if (options.subScript) {
this.properties.push(new SubScript());
}
if (options.superScript) {
this.properties.push(new SuperScript());
}
if (options.style) {
this.properties.push(new Style(options.style));
}
if (options.font) {
this.properties.push(new RunFonts(options.font.name, options.font.hint));
}
if (options.highlight) {
this.properties.push(new Highlight(options.highlight));
this.properties.push(new HighlightComplexScript(options.highlight));
}
if (options.shading) {
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) {
if (typeof child === "string") {
switch (child) {
case PageNumber.CURRENT:
this.root.push(new Begin());
this.root.push(new Page());
this.root.push(new Separate());
this.root.push(new End());
break;
case PageNumber.TOTAL_PAGES:
this.root.push(new Begin());
this.root.push(new NumberOfPages());
this.root.push(new Separate());
this.root.push(new End());
break;
case PageNumber.TOTAL_PAGES_IN_SECTION:
this.root.push(new Begin());
this.root.push(new NumberOfPagesSection());
this.root.push(new Separate());
this.root.push(new End());
break;
default:
this.root.push(new Text(child));
break;
}
continue;
}
this.root.push(child);
}
} else if (options.text) {
this.root.push(new Text(options.text));
}
}
@ -148,33 +68,4 @@ export class Run extends XmlComponent {
this.root.splice(1, 0, new Break());
return this;
}
public tab(): Run {
this.root.splice(1, 0, new Tab());
return this;
}
public pageNumber(): Run {
this.root.push(new Begin());
this.root.push(new Page());
this.root.push(new Separate());
this.root.push(new End());
return this;
}
public numberOfTotalPages(): Run {
this.root.push(new Begin());
this.root.push(new NumberOfPages());
this.root.push(new Separate());
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

@ -1,5 +1,7 @@
import { expect } from "chai";
import { EmphasisMarkType } from "./emphasis-mark";
import { Formatter } from "export/formatter";
import { UnderlineType } from "./underline";
@ -44,6 +46,9 @@ describe("SymbolRun", () => {
color: "red",
type: UnderlineType.DOUBLE,
},
emphasisMark: {
type: EmphasisMarkType.DOT,
},
color: "green",
size: 40,
highlight: "yellow",
@ -59,6 +64,7 @@ describe("SymbolRun", () => {
{ "w:i": { _attr: { "w:val": true } } },
{ "w:iCs": { _attr: { "w:val": true } } },
{ "w:u": { _attr: { "w:val": "double", "w:color": "red" } } },
{ "w:em": { _attr: { "w:val": "dot" } } },
{ "w:color": { _attr: { "w:val": "green" } } },
{ "w:sz": { _attr: { "w:val": 40 } } },
{ "w:szCs": { _attr: { "w:val": 40 } } },

View File

@ -1,6 +1,7 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { TextRun } from "./text-run";
@ -19,9 +20,11 @@ describe("TextRun", () => {
describe("#referenceFootnote()", () => {
it("should add a valid footnote reference", () => {
run = new TextRun("test");
run.referenceFootnote(1);
run = new TextRun({
children: ["test", new FootnoteReferenceRun(1)],
});
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "test"] },

View File

@ -1,13 +1,8 @@
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { IRunOptions, Run } from "./run";
import { Text } from "./run-components/text";
export interface ITextRunOptions extends IRunOptions {
readonly text: string;
}
export class TextRun extends Run {
constructor(options: ITextRunOptions | string) {
constructor(options: IRunOptions | string) {
if (typeof options === "string") {
super({});
this.root.push(new Text(options));
@ -15,11 +10,5 @@ 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;
}
}