@ -3,12 +3,10 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
|
||||
import { ILevelsOptions, Level } from "./level";
|
||||
import { MultiLevelType } from "./multi-level-type";
|
||||
|
||||
interface IAbstractNumberingAttributesProperties {
|
||||
readonly abstractNumId?: number;
|
||||
readonly restartNumberingAfterBreak?: number;
|
||||
}
|
||||
|
||||
class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberingAttributesProperties> {
|
||||
class AbstractNumberingAttributes extends XmlAttributeComponent<{
|
||||
readonly abstractNumId: number;
|
||||
readonly restartNumberingAfterBreak: number;
|
||||
}> {
|
||||
protected readonly xmlKeys = {
|
||||
abstractNumId: "w:abstractNumId",
|
||||
restartNumberingAfterBreak: "w15:restartNumberingAfterBreak",
|
||||
|
@ -2,35 +2,59 @@ import { expect } from "chai";
|
||||
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { LevelForOverride } from "./level";
|
||||
import { ConcreteNumbering } from "./num";
|
||||
|
||||
describe("ConcreteNumbering", () => {
|
||||
describe("#overrideLevel", () => {
|
||||
let concreteNumbering: ConcreteNumbering;
|
||||
beforeEach(() => {
|
||||
concreteNumbering = new ConcreteNumbering(0, 1);
|
||||
});
|
||||
|
||||
it("sets a new override level for the given level number", () => {
|
||||
concreteNumbering.overrideLevel(3);
|
||||
const concreteNumbering = new ConcreteNumbering({
|
||||
numId: 0,
|
||||
abstractNumId: 1,
|
||||
reference: "1",
|
||||
instance: 0,
|
||||
overrideLevel: {
|
||||
num: 3,
|
||||
},
|
||||
});
|
||||
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{ _attr: { "w:ilvl": 3 } },
|
||||
|
||||
expect(tree).to.deep.equal({
|
||||
"w:num": [
|
||||
{
|
||||
"w:lvl": [
|
||||
{ _attr: { "w:ilvl": 3, "w15:tentative": 1 } },
|
||||
{ "w:start": { _attr: { "w:val": 1 } } },
|
||||
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
|
||||
],
|
||||
_attr: {
|
||||
"w:numId": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:abstractNumId": {
|
||||
_attr: {
|
||||
"w:val": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"w:lvlOverride": {
|
||||
_attr: {
|
||||
"w:ilvl": 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
expect(tree["w:num"]).to.include({
|
||||
"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", () => {
|
||||
const ol = concreteNumbering.overrideLevel(1);
|
||||
expect(ol.Level).to.be.instanceof(LevelForOverride);
|
||||
const concreteNumbering = new ConcreteNumbering({
|
||||
numId: 0,
|
||||
abstractNumId: 1,
|
||||
reference: "1",
|
||||
instance: 0,
|
||||
overrideLevel: {
|
||||
num: 1,
|
||||
},
|
||||
});
|
||||
const tree = new Formatter().format(concreteNumbering);
|
||||
|
||||
expect(tree["w:num"]).to.include({
|
||||
"w:lvlOverride": [
|
||||
{ _attr: { "w:ilvl": 1 } },
|
||||
expect(tree).to.deep.equal({
|
||||
"w:num": [
|
||||
{
|
||||
"w:lvl": [
|
||||
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
|
||||
{ "w:start": { _attr: { "w:val": 1 } } },
|
||||
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
|
||||
],
|
||||
_attr: {
|
||||
"w:numId": 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"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 { LevelForOverride } from "./level";
|
||||
|
||||
class AbstractNumId extends XmlComponent {
|
||||
constructor(value: number) {
|
||||
@ -12,32 +11,46 @@ class AbstractNumId extends XmlComponent {
|
||||
}
|
||||
}
|
||||
|
||||
interface INumAttributesProperties {
|
||||
class NumAttributes extends XmlAttributeComponent<{
|
||||
readonly numId: number;
|
||||
}
|
||||
|
||||
class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
|
||||
}> {
|
||||
protected readonly xmlKeys = { numId: "w:numId" };
|
||||
}
|
||||
|
||||
export class ConcreteNumbering extends XmlComponent {
|
||||
public readonly id: number;
|
||||
export interface IConcreteNumberingOptions {
|
||||
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");
|
||||
|
||||
this.numId = options.numId;
|
||||
this.reference = options.reference;
|
||||
this.instance = options.instance;
|
||||
|
||||
this.root.push(
|
||||
new NumAttributes({
|
||||
numId: numId,
|
||||
numId: options.numId,
|
||||
}),
|
||||
);
|
||||
this.root.push(new AbstractNumId(abstractNumId));
|
||||
this.id = numId;
|
||||
}
|
||||
|
||||
public overrideLevel(num: number, start?: number): LevelOverride {
|
||||
const olvl = new LevelOverride(num, start);
|
||||
this.root.push(olvl);
|
||||
return olvl;
|
||||
this.root.push(new AbstractNumId(options.abstractNumId));
|
||||
|
||||
if (options.overrideLevel) {
|
||||
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 {
|
||||
private readonly lvl: LevelForOverride;
|
||||
|
||||
constructor(private readonly levelNum: number, start?: number) {
|
||||
constructor(levelNum: number, start?: number) {
|
||||
super("w:lvlOverride");
|
||||
this.root.push(new LevelOverrideAttributes({ ilvl: levelNum }));
|
||||
if (start !== undefined) {
|
||||
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 { SinonStub, stub } from "sinon";
|
||||
|
||||
import * as convenienceFunctions from "convenience-functions";
|
||||
import { Formatter } from "export/formatter";
|
||||
|
||||
import { Numbering } from "./numbering";
|
||||
|
||||
describe("Numbering", () => {
|
||||
before(() => {
|
||||
stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
(convenienceFunctions.uniqueNumericId as SinonStub).restore();
|
||||
});
|
||||
|
||||
describe("#constructor", () => {
|
||||
it("creates a default numbering with one abstract and one concrete instance", () => {
|
||||
const numbering = new Numbering({
|
||||
@ -38,5 +48,69 @@ describe("Numbering", () => {
|
||||
// {"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
|
||||
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 { IContext, IXmlableObject, XmlComponent } from "file/xml-components";
|
||||
|
||||
@ -16,11 +17,8 @@ export interface INumberingOptions {
|
||||
}
|
||||
|
||||
export class Numbering extends XmlComponent {
|
||||
// tslint:disable-next-line:readonly-keyword
|
||||
private nextId: number;
|
||||
|
||||
private readonly abstractNumbering: AbstractNumbering[] = [];
|
||||
private readonly concreteNumbering: ConcreteNumbering[] = [];
|
||||
private readonly abstractNumberingMap = new Map<string, AbstractNumbering>();
|
||||
private readonly concreteNumberingMap = new Map<string, ConcreteNumbering>();
|
||||
|
||||
constructor(options: INumberingOptions) {
|
||||
super("w:numbering");
|
||||
@ -46,9 +44,7 @@ export class Numbering extends XmlComponent {
|
||||
}),
|
||||
);
|
||||
|
||||
this.nextId = 0;
|
||||
|
||||
const abstractNumbering = this.createAbstractNumbering([
|
||||
const abstractNumbering = new AbstractNumbering(uniqueNumericId(), [
|
||||
{
|
||||
level: 0,
|
||||
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) {
|
||||
const currentAbstractNumbering = this.createAbstractNumbering(con.levels);
|
||||
this.createConcreteNumbering(currentAbstractNumbering, con.reference);
|
||||
this.abstractNumberingMap.set(con.reference, new AbstractNumbering(uniqueNumericId(), con.levels));
|
||||
}
|
||||
}
|
||||
|
||||
public prepForXml(context: IContext): IXmlableObject | undefined {
|
||||
this.abstractNumbering.forEach((x) => this.root.push(x));
|
||||
this.concreteNumbering.forEach((x) => this.root.push(x));
|
||||
for (const numbering of this.abstractNumberingMap.values()) {
|
||||
this.root.push(numbering);
|
||||
}
|
||||
|
||||
for (const numbering of this.concreteNumberingMap.values()) {
|
||||
this.root.push(numbering);
|
||||
}
|
||||
return super.prepForXml(context);
|
||||
}
|
||||
|
||||
private createConcreteNumbering(abstractNumbering: AbstractNumbering, reference?: string): ConcreteNumbering {
|
||||
const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id, reference);
|
||||
this.concreteNumbering.push(num);
|
||||
return num;
|
||||
}
|
||||
public createConcreteNumberingInstance(reference: string, instance: number): void {
|
||||
const abstractNumbering = this.abstractNumberingMap.get(reference);
|
||||
|
||||
private createAbstractNumbering(options: ILevelsOptions[]): AbstractNumbering {
|
||||
const num = new AbstractNumbering(this.nextId++, options);
|
||||
this.abstractNumbering.push(num);
|
||||
return num;
|
||||
if (!abstractNumbering) {
|
||||
return;
|
||||
}
|
||||
|
||||
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[] {
|
||||
return this.concreteNumbering;
|
||||
return Array.from(this.concreteNumberingMap.values());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user