@ -1,7 +1,8 @@
|
|||||||
// Numbered lists
|
// Numbered lists
|
||||||
|
// The lists can also be restarted by specifying the instance number
|
||||||
// Import from 'docx' rather than '../build' if you install from npm
|
// Import from 'docx' rather than '../build' if you install from npm
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { AlignmentType, convertInchesToTwip, Document, LevelFormat, Packer, Paragraph } from "../build";
|
import { AlignmentType, convertInchesToTwip, Document, HeadingLevel, LevelFormat, Packer, Paragraph } from "../build";
|
||||||
|
|
||||||
const doc = new Document({
|
const doc = new Document({
|
||||||
numbering: {
|
numbering: {
|
||||||
@ -125,11 +126,16 @@ doc.addSection({
|
|||||||
level: 0,
|
level: 0,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
new Paragraph({
|
||||||
|
text: "Next",
|
||||||
|
heading: HeadingLevel.HEADING_2,
|
||||||
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
text: "test",
|
text: "test",
|
||||||
numbering: {
|
numbering: {
|
||||||
reference: "padded-numbering-reference",
|
reference: "padded-numbering-reference",
|
||||||
level: 0,
|
level: 0,
|
||||||
|
instance: 2,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
@ -137,6 +143,19 @@ doc.addSection({
|
|||||||
numbering: {
|
numbering: {
|
||||||
reference: "padded-numbering-reference",
|
reference: "padded-numbering-reference",
|
||||||
level: 0,
|
level: 0,
|
||||||
|
instance: 2,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new Paragraph({
|
||||||
|
text: "Next",
|
||||||
|
heading: HeadingLevel.HEADING_2,
|
||||||
|
}),
|
||||||
|
new Paragraph({
|
||||||
|
text: "test",
|
||||||
|
numbering: {
|
||||||
|
reference: "padded-numbering-reference",
|
||||||
|
level: 0,
|
||||||
|
instance: 3,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
@ -144,6 +163,7 @@ doc.addSection({
|
|||||||
numbering: {
|
numbering: {
|
||||||
reference: "padded-numbering-reference",
|
reference: "padded-numbering-reference",
|
||||||
level: 0,
|
level: 0,
|
||||||
|
instance: 3,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
@ -151,14 +171,12 @@ doc.addSection({
|
|||||||
numbering: {
|
numbering: {
|
||||||
reference: "padded-numbering-reference",
|
reference: "padded-numbering-reference",
|
||||||
level: 0,
|
level: 0,
|
||||||
|
instance: 3,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
text: "test",
|
text: "Next",
|
||||||
numbering: {
|
heading: HeadingLevel.HEADING_2,
|
||||||
reference: "padded-numbering-reference",
|
|
||||||
level: 0,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
text: "test",
|
text: "test",
|
||||||
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -605,12 +605,6 @@
|
|||||||
"@types/request": "*"
|
"@types/request": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/shortid": {
|
|
||||||
"version": "0.0.29",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
|
|
||||||
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@types/sinon": {
|
"@types/sinon": {
|
||||||
"version": "9.0.11",
|
"version": "9.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz",
|
||||||
@ -4875,7 +4869,7 @@
|
|||||||
},
|
},
|
||||||
"jsesc": {
|
"jsesc": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
|
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
|
||||||
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
|
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
@ -5592,9 +5586,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"nanoid": {
|
"nanoid": {
|
||||||
"version": "2.1.11",
|
"version": "3.1.20",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
|
||||||
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
|
"integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw=="
|
||||||
},
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
@ -7360,14 +7354,6 @@
|
|||||||
"vscode-textmate": "^5.2.0"
|
"vscode-textmate": "^5.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shortid": {
|
|
||||||
"version": "2.2.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz",
|
|
||||||
"integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==",
|
|
||||||
"requires": {
|
|
||||||
"nanoid": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
"@types/jszip": "^3.1.4",
|
"@types/jszip": "^3.1.4",
|
||||||
"@types/node": "^14.0.5",
|
"@types/node": "^14.0.5",
|
||||||
"jszip": "^3.1.5",
|
"jszip": "^3.1.5",
|
||||||
"shortid": "^2.2.15",
|
"nanoid": "^3.1.20",
|
||||||
"xml": "^1.0.1",
|
"xml": "^1.0.1",
|
||||||
"xml-js": "^1.6.8"
|
"xml-js": "^1.6.8"
|
||||||
},
|
},
|
||||||
@ -66,7 +66,6 @@
|
|||||||
"@types/chai": "^4.2.15",
|
"@types/chai": "^4.2.15",
|
||||||
"@types/mocha": "^8.0.0",
|
"@types/mocha": "^8.0.0",
|
||||||
"@types/request-promise": "^4.1.42",
|
"@types/request-promise": "^4.1.42",
|
||||||
"@types/shortid": "0.0.29",
|
|
||||||
"@types/sinon": "^9.0.4",
|
"@types/sinon": "^9.0.4",
|
||||||
"@types/webpack": "^4.4.24",
|
"@types/webpack": "^4.4.24",
|
||||||
"awesome-typescript-loader": "^3.4.1",
|
"awesome-typescript-loader": "^3.4.1",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { convertInchesToTwip, convertMillimetersToTwip } from "./convenience-functions";
|
|
||||||
|
import { convertInchesToTwip, convertMillimetersToTwip, uniqueId, uniqueNumericId } from "./convenience-functions";
|
||||||
|
|
||||||
describe("Utility", () => {
|
describe("Utility", () => {
|
||||||
describe("#convertMillimetersToTwip", () => {
|
describe("#convertMillimetersToTwip", () => {
|
||||||
@ -15,4 +16,18 @@ describe("Utility", () => {
|
|||||||
expect(convertInchesToTwip(0.25)).to.equal(360);
|
expect(convertInchesToTwip(0.25)).to.equal(360);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#uniqueNumericId", () => {
|
||||||
|
it("should generate a unique ID", () => {
|
||||||
|
// tslint:disable-next-line: no-unused-expression
|
||||||
|
expect(uniqueNumericId()).to.not.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#uniqueId", () => {
|
||||||
|
it("should call the underlying header's addChildElement", () => {
|
||||||
|
// tslint:disable-next-line: no-unused-expression
|
||||||
|
expect(uniqueId()).to.not.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import { customAlphabet, nanoid } from "nanoid/non-secure";
|
||||||
|
|
||||||
|
const numericNanoId = customAlphabet("0123456789", 15);
|
||||||
|
|
||||||
// Twip - twentieths of a point
|
// Twip - twentieths of a point
|
||||||
export const convertMillimetersToTwip = (millimeters: number): number => {
|
export const convertMillimetersToTwip = (millimeters: number): number => {
|
||||||
return Math.floor((millimeters / 25.4) * 72 * 20);
|
return Math.floor((millimeters / 25.4) * 72 * 20);
|
||||||
@ -6,3 +10,11 @@ export const convertMillimetersToTwip = (millimeters: number): number => {
|
|||||||
export const convertInchesToTwip = (inches: number): number => {
|
export const convertInchesToTwip = (inches: number): number => {
|
||||||
return Math.floor(inches * 72 * 20);
|
return Math.floor(inches * 72 * 20);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const uniqueNumericId = (): number => {
|
||||||
|
return parseFloat(numericNanoId());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uniqueId = (): string => {
|
||||||
|
return nanoid().toLowerCase();
|
||||||
|
};
|
||||||
|
@ -5,11 +5,10 @@ export class NumberingReplacer {
|
|||||||
let currentXmlData = xmlData;
|
let currentXmlData = xmlData;
|
||||||
|
|
||||||
for (const concreteNumbering of concreteNumberings) {
|
for (const concreteNumbering of concreteNumberings) {
|
||||||
if (!concreteNumbering.reference) {
|
currentXmlData = currentXmlData.replace(
|
||||||
continue;
|
new RegExp(`{${concreteNumbering.reference}-${concreteNumbering.instance}}`, "g"),
|
||||||
}
|
concreteNumbering.numId.toString(),
|
||||||
|
);
|
||||||
currentXmlData = currentXmlData.replace(new RegExp(`{${concreteNumbering.reference}}`, "g"), concreteNumbering.id.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentXmlData;
|
return currentXmlData;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// tslint:disable:object-literal-key-quotes
|
// tslint:disable:object-literal-key-quotes
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import { stub } from "sinon";
|
import { SinonStub, stub } from "sinon";
|
||||||
|
|
||||||
|
import * as convenienceFunctions from "convenience-functions";
|
||||||
import { Formatter } from "export/formatter";
|
import { Formatter } from "export/formatter";
|
||||||
|
|
||||||
import { File } from "../file";
|
import { File } from "../file";
|
||||||
@ -9,6 +10,14 @@ import { Paragraph } from "../paragraph";
|
|||||||
import { Media } from "./media";
|
import { Media } from "./media";
|
||||||
|
|
||||||
describe("Media", () => {
|
describe("Media", () => {
|
||||||
|
before(() => {
|
||||||
|
stub(convenienceFunctions, "uniqueId").callsFake(() => "test");
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
(convenienceFunctions.uniqueId as SinonStub).restore();
|
||||||
|
});
|
||||||
|
|
||||||
describe("#addImage", () => {
|
describe("#addImage", () => {
|
||||||
it("should add image", () => {
|
it("should add image", () => {
|
||||||
const file = new File();
|
const file = new File();
|
||||||
@ -23,7 +32,6 @@ describe("Media", () => {
|
|||||||
|
|
||||||
it("should ensure the correct relationship id is used when adding image", () => {
|
it("should ensure the correct relationship id is used when adding image", () => {
|
||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
stub(Media as any, "generateId").callsFake(() => "testId");
|
|
||||||
|
|
||||||
const file = new File();
|
const file = new File();
|
||||||
const image1 = Media.addImage(file, "test");
|
const image1 = Media.addImage(file, "test");
|
||||||
@ -33,7 +41,7 @@ describe("Media", () => {
|
|||||||
|
|
||||||
expect(graphicData["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"]).to.deep.equal({
|
expect(graphicData["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"]).to.deep.equal({
|
||||||
_attr: {
|
_attr: {
|
||||||
"r:embed": `rId{testId.png}`,
|
"r:embed": `rId{test.png}`,
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -45,7 +53,7 @@ describe("Media", () => {
|
|||||||
|
|
||||||
expect(graphicData2["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"]).to.deep.equal({
|
expect(graphicData2["a:graphic"][1]["a:graphicData"][1]["pic:pic"][2]["pic:blipFill"][0]["a:blip"]).to.deep.equal({
|
||||||
_attr: {
|
_attr: {
|
||||||
"r:embed": `rId{testId.png}`,
|
"r:embed": `rId{test.png}`,
|
||||||
cstate: "none",
|
cstate: "none",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -54,9 +62,6 @@ describe("Media", () => {
|
|||||||
|
|
||||||
describe("#addMedia", () => {
|
describe("#addMedia", () => {
|
||||||
it("should add media", () => {
|
it("should add media", () => {
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
(Media as any).generateId = () => "test";
|
|
||||||
|
|
||||||
const image = new Media().addMedia("");
|
const image = new Media().addMedia("");
|
||||||
expect(image.fileName).to.equal("test.png");
|
expect(image.fileName).to.equal("test.png");
|
||||||
expect(image.dimensions).to.deep.equal({
|
expect(image.dimensions).to.deep.equal({
|
||||||
@ -74,8 +79,6 @@ describe("Media", () => {
|
|||||||
it("should return UInt8Array if atob is present", () => {
|
it("should return UInt8Array if atob is present", () => {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
((process as any).atob as any) = () => "atob result";
|
((process as any).atob as any) = () => "atob result";
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
(Media as any).generateId = () => "test";
|
|
||||||
|
|
||||||
const image = new Media().addMedia("");
|
const image = new Media().addMedia("");
|
||||||
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
||||||
@ -84,8 +87,6 @@ describe("Media", () => {
|
|||||||
it("should use data as is if its not a string", () => {
|
it("should use data as is if its not a string", () => {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
((process as any).atob as any) = () => "atob result";
|
((process as any).atob as any) = () => "atob result";
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
(Media as any).generateId = () => "test";
|
|
||||||
|
|
||||||
const image = new Media().addMedia(new Buffer(""));
|
const image = new Media().addMedia(new Buffer(""));
|
||||||
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
expect(image.stream).to.be.an.instanceof(Uint8Array);
|
||||||
@ -94,9 +95,6 @@ describe("Media", () => {
|
|||||||
|
|
||||||
describe("#getMedia", () => {
|
describe("#getMedia", () => {
|
||||||
it("should get media", () => {
|
it("should get media", () => {
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
(Media as any).generateId = () => "test";
|
|
||||||
|
|
||||||
const media = new Media();
|
const media = new Media();
|
||||||
media.addMedia("");
|
media.addMedia("");
|
||||||
|
|
||||||
@ -124,9 +122,6 @@ describe("Media", () => {
|
|||||||
|
|
||||||
describe("#Array", () => {
|
describe("#Array", () => {
|
||||||
it("Get images as array", () => {
|
it("Get images as array", () => {
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
(Media as any).generateId = () => "test";
|
|
||||||
|
|
||||||
const media = new Media();
|
const media = new Media();
|
||||||
media.addMedia("");
|
media.addMedia("");
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { uniqueId } from "convenience-functions";
|
||||||
|
|
||||||
import { IDrawingOptions } from "../drawing";
|
import { IDrawingOptions } from "../drawing";
|
||||||
import { File } from "../file";
|
import { File } from "../file";
|
||||||
import { PictureRun } from "../paragraph";
|
import { PictureRun } from "../paragraph";
|
||||||
@ -17,11 +19,6 @@ export class Media {
|
|||||||
return new PictureRun(mediaData, drawingOptions);
|
return new PictureRun(mediaData, drawingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly map: Map<string, IMediaData>;
|
private readonly map: Map<string, IMediaData>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -39,7 +36,7 @@ export class Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public addMedia(buffer: Buffer | string | Uint8Array | ArrayBuffer, width: number = 100, height: number = 100): IMediaData {
|
public addMedia(buffer: Buffer | string | Uint8Array | ArrayBuffer, width: number = 100, height: number = 100): IMediaData {
|
||||||
const key = `${Media.generateId()}.png`;
|
const key = `${uniqueId()}.png`;
|
||||||
|
|
||||||
return this.createMedia(
|
return this.createMedia(
|
||||||
key,
|
key,
|
||||||
|
@ -3,12 +3,10 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
|||||||
import { ILevelsOptions, Level } from "./level";
|
import { ILevelsOptions, Level } from "./level";
|
||||||
import { MultiLevelType } from "./multi-level-type";
|
import { MultiLevelType } from "./multi-level-type";
|
||||||
|
|
||||||
interface IAbstractNumberingAttributesProperties {
|
class AbstractNumberingAttributes extends XmlAttributeComponent<{
|
||||||
readonly abstractNumId?: number;
|
readonly abstractNumId: number;
|
||||||
readonly restartNumberingAfterBreak?: number;
|
readonly restartNumberingAfterBreak: number;
|
||||||
}
|
}> {
|
||||||
|
|
||||||
class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberingAttributesProperties> {
|
|
||||||
protected readonly xmlKeys = {
|
protected readonly xmlKeys = {
|
||||||
abstractNumId: "w:abstractNumId",
|
abstractNumId: "w:abstractNumId",
|
||||||
restartNumberingAfterBreak: "w15:restartNumberingAfterBreak",
|
restartNumberingAfterBreak: "w15:restartNumberingAfterBreak",
|
||||||
|
@ -2,35 +2,59 @@ import { expect } from "chai";
|
|||||||
|
|
||||||
import { Formatter } from "export/formatter";
|
import { Formatter } from "export/formatter";
|
||||||
|
|
||||||
import { LevelForOverride } from "./level";
|
|
||||||
import { ConcreteNumbering } from "./num";
|
import { ConcreteNumbering } from "./num";
|
||||||
|
|
||||||
describe("ConcreteNumbering", () => {
|
describe("ConcreteNumbering", () => {
|
||||||
describe("#overrideLevel", () => {
|
describe("#overrideLevel", () => {
|
||||||
let concreteNumbering: ConcreteNumbering;
|
|
||||||
beforeEach(() => {
|
|
||||||
concreteNumbering = new ConcreteNumbering(0, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets a new override level for the given level number", () => {
|
it("sets a new override level for the given level number", () => {
|
||||||
concreteNumbering.overrideLevel(3);
|
const concreteNumbering = new ConcreteNumbering({
|
||||||
|
numId: 0,
|
||||||
|
abstractNumId: 1,
|
||||||
|
reference: "1",
|
||||||
|
instance: 0,
|
||||||
|
overrideLevel: {
|
||||||
|
num: 3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const tree = new Formatter().format(concreteNumbering);
|
const tree = new Formatter().format(concreteNumbering);
|
||||||
expect(tree["w:num"]).to.include({
|
|
||||||
"w:lvlOverride": [
|
expect(tree).to.deep.equal({
|
||||||
{ _attr: { "w:ilvl": 3 } },
|
"w:num": [
|
||||||
{
|
{
|
||||||
"w:lvl": [
|
_attr: {
|
||||||
{ _attr: { "w:ilvl": 3, "w15:tentative": 1 } },
|
"w:numId": 0,
|
||||||
{ "w:start": { _attr: { "w:val": 1 } } },
|
},
|
||||||
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
|
},
|
||||||
],
|
{
|
||||||
|
"w:abstractNumId": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:lvlOverride": {
|
||||||
|
_attr: {
|
||||||
|
"w:ilvl": 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets the startOverride element if start is given", () => {
|
it("sets the startOverride element if start is given", () => {
|
||||||
concreteNumbering.overrideLevel(1, 9);
|
const concreteNumbering = new ConcreteNumbering({
|
||||||
|
numId: 0,
|
||||||
|
abstractNumId: 1,
|
||||||
|
reference: "1",
|
||||||
|
instance: 0,
|
||||||
|
overrideLevel: {
|
||||||
|
num: 1,
|
||||||
|
start: 9,
|
||||||
|
},
|
||||||
|
});
|
||||||
const tree = new Formatter().format(concreteNumbering);
|
const tree = new Formatter().format(concreteNumbering);
|
||||||
expect(tree["w:num"]).to.include({
|
expect(tree["w:num"]).to.include({
|
||||||
"w:lvlOverride": [
|
"w:lvlOverride": [
|
||||||
@ -46,31 +70,41 @@ describe("ConcreteNumbering", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"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", () => {
|
it("sets the lvl element if overrideLevel.Level is accessed", () => {
|
||||||
const ol = concreteNumbering.overrideLevel(1);
|
const concreteNumbering = new ConcreteNumbering({
|
||||||
expect(ol.Level).to.be.instanceof(LevelForOverride);
|
numId: 0,
|
||||||
|
abstractNumId: 1,
|
||||||
|
reference: "1",
|
||||||
|
instance: 0,
|
||||||
|
overrideLevel: {
|
||||||
|
num: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
const tree = new Formatter().format(concreteNumbering);
|
const tree = new Formatter().format(concreteNumbering);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
expect(tree["w:num"]).to.include({
|
"w:num": [
|
||||||
"w:lvlOverride": [
|
|
||||||
{ _attr: { "w:ilvl": 1 } },
|
|
||||||
{
|
{
|
||||||
"w:lvl": [
|
_attr: {
|
||||||
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
|
"w:numId": 0,
|
||||||
{ "w:start": { _attr: { "w:val": 1 } } },
|
},
|
||||||
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
|
},
|
||||||
],
|
{
|
||||||
|
"w:abstractNumId": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:lvlOverride": {
|
||||||
|
_attr: {
|
||||||
|
"w:ilvl": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||||
import { LevelForOverride } from "./level";
|
|
||||||
|
|
||||||
class AbstractNumId extends XmlComponent {
|
class AbstractNumId extends XmlComponent {
|
||||||
constructor(value: number) {
|
constructor(value: number) {
|
||||||
@ -12,32 +11,46 @@ class AbstractNumId extends XmlComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface INumAttributesProperties {
|
class NumAttributes extends XmlAttributeComponent<{
|
||||||
readonly numId: number;
|
readonly numId: number;
|
||||||
}
|
}> {
|
||||||
|
|
||||||
class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
|
|
||||||
protected readonly xmlKeys = { numId: "w:numId" };
|
protected readonly xmlKeys = { numId: "w:numId" };
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConcreteNumbering extends XmlComponent {
|
export interface IConcreteNumberingOptions {
|
||||||
public readonly id: number;
|
readonly numId: number;
|
||||||
|
readonly abstractNumId: number;
|
||||||
|
readonly reference: string;
|
||||||
|
readonly instance: number;
|
||||||
|
readonly overrideLevel?: {
|
||||||
|
readonly num: number;
|
||||||
|
readonly start?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(numId: number, abstractNumId: number, public readonly reference?: string) {
|
export class ConcreteNumbering extends XmlComponent {
|
||||||
|
public readonly numId: number;
|
||||||
|
public readonly reference: string;
|
||||||
|
public readonly instance: number;
|
||||||
|
|
||||||
|
constructor(options: IConcreteNumberingOptions) {
|
||||||
super("w:num");
|
super("w:num");
|
||||||
|
|
||||||
|
this.numId = options.numId;
|
||||||
|
this.reference = options.reference;
|
||||||
|
this.instance = options.instance;
|
||||||
|
|
||||||
this.root.push(
|
this.root.push(
|
||||||
new NumAttributes({
|
new NumAttributes({
|
||||||
numId: numId,
|
numId: options.numId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.root.push(new AbstractNumId(abstractNumId));
|
|
||||||
this.id = numId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public overrideLevel(num: number, start?: number): LevelOverride {
|
this.root.push(new AbstractNumId(options.abstractNumId));
|
||||||
const olvl = new LevelOverride(num, start);
|
|
||||||
this.root.push(olvl);
|
if (options.overrideLevel) {
|
||||||
return olvl;
|
this.root.push(new LevelOverride(options.overrideLevel.num, options.overrideLevel.start));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,23 +59,12 @@ class LevelOverrideAttributes extends XmlAttributeComponent<{ readonly ilvl: num
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class LevelOverride extends XmlComponent {
|
export class LevelOverride extends XmlComponent {
|
||||||
private readonly lvl: LevelForOverride;
|
constructor(levelNum: number, start?: number) {
|
||||||
|
|
||||||
constructor(private readonly levelNum: number, start?: number) {
|
|
||||||
super("w:lvlOverride");
|
super("w:lvlOverride");
|
||||||
this.root.push(new LevelOverrideAttributes({ ilvl: levelNum }));
|
this.root.push(new LevelOverrideAttributes({ ilvl: levelNum }));
|
||||||
if (start !== undefined) {
|
if (start !== undefined) {
|
||||||
this.root.push(new StartOverride(start));
|
this.root.push(new StartOverride(start));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lvl = new LevelForOverride({
|
|
||||||
level: this.levelNum,
|
|
||||||
});
|
|
||||||
this.root.push(this.lvl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get Level(): LevelForOverride {
|
|
||||||
return this.lvl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
import { SinonStub, stub } from "sinon";
|
||||||
|
|
||||||
|
import * as convenienceFunctions from "convenience-functions";
|
||||||
import { Formatter } from "export/formatter";
|
import { Formatter } from "export/formatter";
|
||||||
|
|
||||||
import { Numbering } from "./numbering";
|
import { Numbering } from "./numbering";
|
||||||
|
|
||||||
describe("Numbering", () => {
|
describe("Numbering", () => {
|
||||||
|
before(() => {
|
||||||
|
stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
(convenienceFunctions.uniqueNumericId as SinonStub).restore();
|
||||||
|
});
|
||||||
|
|
||||||
describe("#constructor", () => {
|
describe("#constructor", () => {
|
||||||
it("creates a default numbering with one abstract and one concrete instance", () => {
|
it("creates a default numbering with one abstract and one concrete instance", () => {
|
||||||
const numbering = new Numbering({
|
const numbering = new Numbering({
|
||||||
@ -38,5 +48,69 @@ describe("Numbering", () => {
|
|||||||
// {"w:ind": [{"_attr": {"w:left": 720, "w:hanging": 360}}]}]},
|
// {"w:ind": [{"_attr": {"w:left": 720, "w:hanging": 360}}]}]},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#createConcreteNumberingInstance", () => {
|
||||||
|
it("should create a concrete numbering instance", () => {
|
||||||
|
const numbering = new Numbering({
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
reference: "test-reference",
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(numbering.ConcreteNumbering).to.have.length(1);
|
||||||
|
|
||||||
|
numbering.createConcreteNumberingInstance("test-reference", 0);
|
||||||
|
|
||||||
|
expect(numbering.ConcreteNumbering).to.have.length(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not create a concrete numbering instance if reference is invalid", () => {
|
||||||
|
const numbering = new Numbering({
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
reference: "test-reference",
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(numbering.ConcreteNumbering).to.have.length(1);
|
||||||
|
|
||||||
|
numbering.createConcreteNumberingInstance("invalid-reference", 0);
|
||||||
|
|
||||||
|
expect(numbering.ConcreteNumbering).to.have.length(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not create a concrete numbering instance if one already exists", () => {
|
||||||
|
const numbering = new Numbering({
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
reference: "test-reference",
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(numbering.ConcreteNumbering).to.have.length(1);
|
||||||
|
|
||||||
|
numbering.createConcreteNumberingInstance("test-reference", 0);
|
||||||
|
numbering.createConcreteNumberingInstance("test-reference", 0);
|
||||||
|
|
||||||
|
expect(numbering.ConcreteNumbering).to.have.length(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// http://officeopenxml.com/WPnumbering.php
|
// http://officeopenxml.com/WPnumbering.php
|
||||||
import { convertInchesToTwip } from "convenience-functions";
|
// https://stackoverflow.com/questions/58622437/purpose-of-abstractnum-and-numberinginstance
|
||||||
|
import { convertInchesToTwip, uniqueNumericId } from "convenience-functions";
|
||||||
import { AlignmentType } from "file/paragraph";
|
import { AlignmentType } from "file/paragraph";
|
||||||
import { IContext, IXmlableObject, XmlComponent } from "file/xml-components";
|
import { IContext, IXmlableObject, XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
@ -16,11 +17,8 @@ export interface INumberingOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Numbering extends XmlComponent {
|
export class Numbering extends XmlComponent {
|
||||||
// tslint:disable-next-line:readonly-keyword
|
private readonly abstractNumberingMap = new Map<string, AbstractNumbering>();
|
||||||
private nextId: number;
|
private readonly concreteNumberingMap = new Map<string, ConcreteNumbering>();
|
||||||
|
|
||||||
private readonly abstractNumbering: AbstractNumbering[] = [];
|
|
||||||
private readonly concreteNumbering: ConcreteNumbering[] = [];
|
|
||||||
|
|
||||||
constructor(options: INumberingOptions) {
|
constructor(options: INumberingOptions) {
|
||||||
super("w:numbering");
|
super("w:numbering");
|
||||||
@ -46,9 +44,7 @@ export class Numbering extends XmlComponent {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.nextId = 0;
|
const abstractNumbering = new AbstractNumbering(uniqueNumericId(), [
|
||||||
|
|
||||||
const abstractNumbering = this.createAbstractNumbering([
|
|
||||||
{
|
{
|
||||||
level: 0,
|
level: 0,
|
||||||
format: LevelFormat.BULLET,
|
format: LevelFormat.BULLET,
|
||||||
@ -150,33 +146,67 @@ export class Numbering extends XmlComponent {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.createConcreteNumbering(abstractNumbering);
|
this.concreteNumberingMap.set(
|
||||||
|
"default-bullet-numbering",
|
||||||
|
new ConcreteNumbering({
|
||||||
|
numId: 0,
|
||||||
|
abstractNumId: abstractNumbering.id,
|
||||||
|
reference: "default-bullet-numbering",
|
||||||
|
instance: 0,
|
||||||
|
overrideLevel: {
|
||||||
|
num: 0,
|
||||||
|
start: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.abstractNumberingMap.set("default-bullet-numbering", abstractNumbering);
|
||||||
|
|
||||||
for (const con of options.config) {
|
for (const con of options.config) {
|
||||||
const currentAbstractNumbering = this.createAbstractNumbering(con.levels);
|
this.abstractNumberingMap.set(con.reference, new AbstractNumbering(uniqueNumericId(), con.levels));
|
||||||
this.createConcreteNumbering(currentAbstractNumbering, con.reference);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public prepForXml(context: IContext): IXmlableObject | undefined {
|
public prepForXml(context: IContext): IXmlableObject | undefined {
|
||||||
this.abstractNumbering.forEach((x) => this.root.push(x));
|
for (const numbering of this.abstractNumberingMap.values()) {
|
||||||
this.concreteNumbering.forEach((x) => this.root.push(x));
|
this.root.push(numbering);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const numbering of this.concreteNumberingMap.values()) {
|
||||||
|
this.root.push(numbering);
|
||||||
|
}
|
||||||
return super.prepForXml(context);
|
return super.prepForXml(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createConcreteNumbering(abstractNumbering: AbstractNumbering, reference?: string): ConcreteNumbering {
|
public createConcreteNumberingInstance(reference: string, instance: number): void {
|
||||||
const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id, reference);
|
const abstractNumbering = this.abstractNumberingMap.get(reference);
|
||||||
this.concreteNumbering.push(num);
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createAbstractNumbering(options: ILevelsOptions[]): AbstractNumbering {
|
if (!abstractNumbering) {
|
||||||
const num = new AbstractNumbering(this.nextId++, options);
|
return;
|
||||||
this.abstractNumbering.push(num);
|
}
|
||||||
return num;
|
|
||||||
|
const fullReference = `${reference}-${instance}`;
|
||||||
|
|
||||||
|
if (this.concreteNumberingMap.has(fullReference)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.concreteNumberingMap.set(
|
||||||
|
fullReference,
|
||||||
|
new ConcreteNumbering({
|
||||||
|
numId: uniqueNumericId(),
|
||||||
|
abstractNumId: abstractNumbering.id,
|
||||||
|
reference,
|
||||||
|
instance,
|
||||||
|
overrideLevel: {
|
||||||
|
num: 0,
|
||||||
|
start: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get ConcreteNumbering(): ConcreteNumbering[] {
|
public get ConcreteNumbering(): ConcreteNumbering[] {
|
||||||
return this.concreteNumbering;
|
return Array.from(this.concreteNumberingMap.values());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// http://officeopenxml.com/WPbookmark.php
|
// http://officeopenxml.com/WPbookmark.php
|
||||||
|
import { uniqueId } from "convenience-functions";
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
import * as shortid from "shortid";
|
|
||||||
import { TextRun } from "../run";
|
import { TextRun } from "../run";
|
||||||
import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes";
|
import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes";
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ export class Bookmark {
|
|||||||
public readonly end: BookmarkEnd;
|
public readonly end: BookmarkEnd;
|
||||||
|
|
||||||
constructor(options: { readonly id: string; readonly children: TextRun[] }) {
|
constructor(options: { readonly id: string; readonly children: TextRun[] }) {
|
||||||
const linkId = shortid.generate().toLowerCase();
|
const linkId = uniqueId();
|
||||||
|
|
||||||
this.start = new BookmarkStart(options.id, linkId);
|
this.start = new BookmarkStart(options.id, linkId);
|
||||||
this.children = options.children;
|
this.children = options.children;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// http://officeopenxml.com/WPhyperlink.php
|
// http://officeopenxml.com/WPhyperlink.php
|
||||||
import * as shortid from "shortid";
|
import { uniqueId } from "convenience-functions";
|
||||||
|
|
||||||
import { XmlComponent } from "file/xml-components";
|
import { XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
import { ParagraphChild } from "../paragraph";
|
import { ParagraphChild } from "../paragraph";
|
||||||
@ -33,7 +32,7 @@ export class ConcreteHyperlink extends XmlComponent {
|
|||||||
|
|
||||||
export class InternalHyperlink extends ConcreteHyperlink {
|
export class InternalHyperlink extends ConcreteHyperlink {
|
||||||
constructor(options: { readonly child: ParagraphChild; readonly anchor: string }) {
|
constructor(options: { readonly child: ParagraphChild; readonly anchor: string }) {
|
||||||
super(options.child, shortid.generate().toLowerCase(), options.anchor);
|
super(options.child, uniqueId(), options.anchor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { assert, expect } from "chai";
|
import { assert, expect } from "chai";
|
||||||
import * as shortid from "shortid";
|
import { SinonStub, stub } from "sinon";
|
||||||
import { stub } from "sinon";
|
|
||||||
|
|
||||||
|
import * as convenienceFunctions from "convenience-functions";
|
||||||
import { Formatter } from "export/formatter";
|
import { Formatter } from "export/formatter";
|
||||||
import { EMPTY_OBJECT } from "file/xml-components";
|
import { EMPTY_OBJECT } from "file/xml-components";
|
||||||
|
|
||||||
@ -14,6 +14,16 @@ import { Paragraph } from "./paragraph";
|
|||||||
import { TextRun } from "./run";
|
import { TextRun } from "./run";
|
||||||
|
|
||||||
describe("Paragraph", () => {
|
describe("Paragraph", () => {
|
||||||
|
before(() => {
|
||||||
|
stub(convenienceFunctions, "uniqueId").callsFake(() => {
|
||||||
|
return "test-unique-id";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
(convenienceFunctions.uniqueId as SinonStub).restore();
|
||||||
|
});
|
||||||
|
|
||||||
describe("#constructor()", () => {
|
describe("#constructor()", () => {
|
||||||
it("should create valid JSON", () => {
|
it("should create valid JSON", () => {
|
||||||
const paragraph = new Paragraph("");
|
const paragraph = new Paragraph("");
|
||||||
@ -603,6 +613,7 @@ describe("Paragraph", () => {
|
|||||||
numbering: {
|
numbering: {
|
||||||
reference: "test id",
|
reference: "test id",
|
||||||
level: 0,
|
level: 0,
|
||||||
|
instance: 4,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const tree = new Formatter().format(paragraph);
|
const tree = new Formatter().format(paragraph);
|
||||||
@ -612,7 +623,7 @@ describe("Paragraph", () => {
|
|||||||
"w:pPr": [
|
"w:pPr": [
|
||||||
{ "w:pStyle": { _attr: { "w:val": "ListParagraph" } } },
|
{ "w:pStyle": { _attr: { "w:val": "ListParagraph" } } },
|
||||||
{
|
{
|
||||||
"w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }],
|
"w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id-4}" } } }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -634,7 +645,7 @@ describe("Paragraph", () => {
|
|||||||
{
|
{
|
||||||
"w:pPr": [
|
"w:pPr": [
|
||||||
{
|
{
|
||||||
"w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }],
|
"w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id-0}" } } }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -644,9 +655,6 @@ describe("Paragraph", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("it should add bookmark", () => {
|
it("it should add bookmark", () => {
|
||||||
stub(shortid, "generate").callsFake(() => {
|
|
||||||
return "test-unique-id";
|
|
||||||
});
|
|
||||||
const paragraph = new Paragraph({
|
const paragraph = new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new Bookmark({
|
new Bookmark({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// http://officeopenxml.com/WPparagraph.php
|
// http://officeopenxml.com/WPparagraph.php
|
||||||
import * as shortid from "shortid";
|
|
||||||
|
|
||||||
|
import { uniqueId } from "convenience-functions";
|
||||||
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
|
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
|
||||||
import { IContext, IXmlableObject, XmlComponent } from "file/xml-components";
|
import { IContext, IXmlableObject, XmlComponent } from "file/xml-components";
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export class Paragraph extends XmlComponent {
|
|||||||
for (const element of this.root) {
|
for (const element of this.root) {
|
||||||
if (element instanceof ExternalHyperlink) {
|
if (element instanceof ExternalHyperlink) {
|
||||||
const index = this.root.indexOf(element);
|
const index = this.root.indexOf(element);
|
||||||
const concreteHyperlink = new ConcreteHyperlink(element.options.child, shortid.generate().toLowerCase());
|
const concreteHyperlink = new ConcreteHyperlink(element.options.child, uniqueId());
|
||||||
context.viewWrapper.Relationships.createRelationship(
|
context.viewWrapper.Relationships.createRelationship(
|
||||||
concreteHyperlink.linkId,
|
concreteHyperlink.linkId,
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||||
|
69
src/file/paragraph/properties.spec.ts
Normal file
69
src/file/paragraph/properties.spec.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { Formatter } from "export/formatter";
|
||||||
|
import { DocumentWrapper } from "../document-wrapper";
|
||||||
|
import { File } from "../file";
|
||||||
|
|
||||||
|
import { ParagraphProperties } from "./properties";
|
||||||
|
|
||||||
|
describe("ParagraphProperties", () => {
|
||||||
|
describe("#constructor()", () => {
|
||||||
|
it("creates an initially empty property object", () => {
|
||||||
|
const properties = new ParagraphProperties();
|
||||||
|
|
||||||
|
expect(() => new Formatter().format(properties)).to.throw("XMLComponent did not format correctly");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create", () => {
|
||||||
|
const properties = new ParagraphProperties({
|
||||||
|
numbering: {
|
||||||
|
reference: "test-reference",
|
||||||
|
level: 0,
|
||||||
|
instance: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const tree = new Formatter().format(properties, {
|
||||||
|
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||||
|
file: {
|
||||||
|
Numbering: {
|
||||||
|
createConcreteNumberingInstance: (_: string, __: number) => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as File,
|
||||||
|
// tslint:disable-next-line: no-object-literal-type-assertion
|
||||||
|
viewWrapper: new DocumentWrapper({ background: {} }),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:pPr": [
|
||||||
|
{
|
||||||
|
"w:pStyle": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": "ListParagraph",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:numPr": [
|
||||||
|
{
|
||||||
|
"w:ilvl": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:numId": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": "{test-reference-0}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
// http://officeopenxml.com/WPparagraphProperties.php
|
// http://officeopenxml.com/WPparagraphProperties.php
|
||||||
import { IgnoreIfEmptyXmlComponent, XmlComponent } from "file/xml-components";
|
import { IContext, IgnoreIfEmptyXmlComponent, IXmlableObject, XmlComponent } from "file/xml-components";
|
||||||
|
import { DocumentWrapper } from "../document-wrapper";
|
||||||
import { ShadingType } from "../table/shading";
|
import { ShadingType } from "../table/shading";
|
||||||
import { Alignment, AlignmentType } from "./formatting/alignment";
|
import { Alignment, AlignmentType } from "./formatting/alignment";
|
||||||
import { Bidirectional } from "./formatting/bidirectional";
|
import { Bidirectional } from "./formatting/bidirectional";
|
||||||
@ -44,6 +45,7 @@ export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOp
|
|||||||
readonly numbering?: {
|
readonly numbering?: {
|
||||||
readonly reference: string;
|
readonly reference: string;
|
||||||
readonly level: number;
|
readonly level: number;
|
||||||
|
readonly instance?: number;
|
||||||
readonly custom?: boolean;
|
readonly custom?: boolean;
|
||||||
};
|
};
|
||||||
readonly shading?: {
|
readonly shading?: {
|
||||||
@ -54,6 +56,8 @@ export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
|
export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
|
||||||
|
private readonly numberingReferences: { readonly reference: string; readonly instance: number }[] = [];
|
||||||
|
|
||||||
constructor(options?: IParagraphPropertiesOptions) {
|
constructor(options?: IParagraphPropertiesOptions) {
|
||||||
super("w:pPr");
|
super("w:pPr");
|
||||||
|
|
||||||
@ -128,7 +132,12 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
|
|||||||
if (!options.numbering.custom) {
|
if (!options.numbering.custom) {
|
||||||
this.push(new Style("ListParagraph"));
|
this.push(new Style("ListParagraph"));
|
||||||
}
|
}
|
||||||
this.push(new NumberProperties(options.numbering.reference, options.numbering.level));
|
this.numberingReferences.push({
|
||||||
|
reference: options.numbering.reference,
|
||||||
|
instance: options.numbering.instance ?? 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.push(new NumberProperties(`${options.numbering.reference}-${options.numbering.instance ?? 0}`, options.numbering.level));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.rightTabStop) {
|
if (options.rightTabStop) {
|
||||||
@ -147,4 +156,14 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
|
|||||||
public push(item: XmlComponent): void {
|
public push(item: XmlComponent): void {
|
||||||
this.root.push(item);
|
this.root.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public prepForXml(context: IContext): IXmlableObject | undefined {
|
||||||
|
if (context.viewWrapper instanceof DocumentWrapper) {
|
||||||
|
for (const reference of this.numberingReferences) {
|
||||||
|
context.file.Numbering.createConcreteNumberingInstance(reference.reference, reference.instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.prepForXml(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
12
src/index.spec.ts
Normal file
12
src/index.spec.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { Document } from "./index";
|
||||||
|
|
||||||
|
describe("Index", () => {
|
||||||
|
describe("Document", () => {
|
||||||
|
it("should instantiate the Document", () => {
|
||||||
|
// tslint:disable-next-line: no-unused-expression
|
||||||
|
expect(new Document()).to.be.ok;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user