From c59c5350fdbd55ff5bc71506917c75fbfb202457 Mon Sep 17 00:00:00 2001 From: Lentyaev Pavel Date: Tue, 18 Apr 2023 15:15:38 +0300 Subject: [PATCH 1/2] fix: added unique numeric id creator to avoid numbering render errors --- src/file/drawing/doc-properties/doc-properties.ts | 4 +++- src/file/numbering/numbering.ts | 11 +++++++---- src/file/paragraph/links/bookmark.ts | 4 +++- src/util/convenience-functions.ts | 8 +++++--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/file/drawing/doc-properties/doc-properties.ts b/src/file/drawing/doc-properties/doc-properties.ts index ab5145cd5d8..423317d6c8e 100644 --- a/src/file/drawing/doc-properties/doc-properties.ts +++ b/src/file/drawing/doc-properties/doc-properties.ts @@ -2,7 +2,7 @@ import { IContext, IXmlableObject, NextAttributeComponent, XmlComponent } from "@file/xml-components"; import { ConcreteHyperlink } from "@file/paragraph"; -import { uniqueNumericId } from "@util/convenience-functions"; +import { uniqueNumericIdCreator } from "@util/convenience-functions"; import { createHyperlinkClick } from "./doc-properties-children"; @@ -24,6 +24,8 @@ export interface DocPropertiesOptions { readonly title: string; } +const uniqueNumericId = uniqueNumericIdCreator(); + export class DocProperties extends XmlComponent { public constructor({ name, description, title }: DocPropertiesOptions = { name: "", description: "", title: "" }) { super("wp:docPr"); diff --git a/src/file/numbering/numbering.ts b/src/file/numbering/numbering.ts index b52d6ba56c6..b8eeef2feb2 100644 --- a/src/file/numbering/numbering.ts +++ b/src/file/numbering/numbering.ts @@ -2,7 +2,7 @@ // https://stackoverflow.com/questions/58622437/purpose-of-abstractnum-and-numberinginstance import { AlignmentType } from "@file/paragraph"; import { IContext, IXmlableObject, XmlComponent } from "@file/xml-components"; -import { convertInchesToTwip, uniqueNumericId } from "@util/convenience-functions"; +import { convertInchesToTwip, uniqueNumericIdCreator } from "@util/convenience-functions"; import { DocumentAttributes } from "../document/document-attributes"; import { AbstractNumbering } from "./abstract-numbering"; @@ -16,6 +16,9 @@ export interface INumberingOptions { }[]; } +const abstractNumUniqueNumericId = uniqueNumericIdCreator(); +const concreteNumUniqueNumericId = uniqueNumericIdCreator(1); // Setting initial to 1 as we have numId = 1 for "default-bullet-numbering" + // // // @@ -55,7 +58,7 @@ export class Numbering extends XmlComponent { }), ); - const abstractNumbering = new AbstractNumbering(uniqueNumericId(), [ + const abstractNumbering = new AbstractNumbering(abstractNumUniqueNumericId(), [ { level: 0, format: LevelFormat.BULLET, @@ -176,7 +179,7 @@ export class Numbering extends XmlComponent { this.abstractNumberingMap.set("default-bullet-numbering", abstractNumbering); for (const con of options.config) { - this.abstractNumberingMap.set(con.reference, new AbstractNumbering(uniqueNumericId(), con.levels)); + this.abstractNumberingMap.set(con.reference, new AbstractNumbering(abstractNumUniqueNumericId(), con.levels)); this.referenceConfigMap.set(con.reference, con.levels); } } @@ -209,7 +212,7 @@ export class Numbering extends XmlComponent { const firstLevelStartNumber = referenceConfigLevels && referenceConfigLevels[0].start; const concreteNumberingSettings = { - numId: uniqueNumericId(), + numId: concreteNumUniqueNumericId(), abstractNumId: abstractNumbering.id, reference, instance, diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index 5a74cb1c5d6..d88bfd7f466 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -1,10 +1,12 @@ // http://officeopenxml.com/WPbookmark.php import { XmlComponent } from "@file/xml-components"; -import { uniqueNumericId } from "@util/convenience-functions"; +import { uniqueNumericIdCreator } from "@util/convenience-functions"; import { ParagraphChild } from "../paragraph"; import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; +const uniqueNumericId = uniqueNumericIdCreator(); + export class Bookmark { public readonly start: BookmarkStart; public readonly children: readonly ParagraphChild[]; diff --git a/src/util/convenience-functions.ts b/src/util/convenience-functions.ts index 64f451e0667..8dac4a929bd 100644 --- a/src/util/convenience-functions.ts +++ b/src/util/convenience-functions.ts @@ -1,12 +1,14 @@ import { nanoid } from "nanoid/non-secure"; -let currentCount = 0; - // Twip - twentieths of a point export const convertMillimetersToTwip = (millimeters: number): number => Math.floor((millimeters / 25.4) * 72 * 20); export const convertInchesToTwip = (inches: number): number => Math.floor(inches * 72 * 20); -export const uniqueNumericId = (): number => ++currentCount; +export const uniqueNumericIdCreator = (initial = 0): (() => number) => { + let currentCount = initial; + + return () => ++currentCount; +}; export const uniqueId = (): string => nanoid().toLowerCase(); From 704c678333d7ba2abe0462531b30dd134d5f54ae Mon Sep 17 00:00:00 2001 From: Lentyaev Pavel Date: Wed, 19 Apr 2023 16:51:03 +0300 Subject: [PATCH 2/2] fix: unit tests fix --- src/file/drawing/anchor/anchor.spec.ts | 4 ++-- src/file/drawing/doc-properties/doc-properties.ts | 6 ++---- src/file/drawing/drawing.spec.ts | 4 ++-- src/file/numbering/numbering.spec.ts | 6 ++++-- src/file/numbering/numbering.ts | 5 +---- src/file/paragraph/links/bookmark.ts | 6 ++---- src/file/paragraph/paragraph.spec.ts | 4 ++-- src/file/paragraph/run/image-run.spec.ts | 4 ++-- src/util/convenience-functions.spec.ts | 5 +++-- src/util/convenience-functions.ts | 5 +++++ 10 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/file/drawing/anchor/anchor.spec.ts b/src/file/drawing/anchor/anchor.spec.ts index d2da9127618..b6a368b4e60 100644 --- a/src/file/drawing/anchor/anchor.spec.ts +++ b/src/file/drawing/anchor/anchor.spec.ts @@ -41,11 +41,11 @@ const createAnchor = (drawingOptions: IDrawingOptions): Anchor => describe("Anchor", () => { before(() => { - stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); + stub(convenienceFunctions, "docPropertiesUniqueNumericId").callsFake(() => 0); }); after(() => { - (convenienceFunctions.uniqueNumericId as SinonStub).restore(); + (convenienceFunctions.docPropertiesUniqueNumericId as SinonStub).restore(); }); let anchor: Anchor; diff --git a/src/file/drawing/doc-properties/doc-properties.ts b/src/file/drawing/doc-properties/doc-properties.ts index 423317d6c8e..8b0093c7f1b 100644 --- a/src/file/drawing/doc-properties/doc-properties.ts +++ b/src/file/drawing/doc-properties/doc-properties.ts @@ -2,7 +2,7 @@ import { IContext, IXmlableObject, NextAttributeComponent, XmlComponent } from "@file/xml-components"; import { ConcreteHyperlink } from "@file/paragraph"; -import { uniqueNumericIdCreator } from "@util/convenience-functions"; +import { docPropertiesUniqueNumericId } from "@util/convenience-functions"; import { createHyperlinkClick } from "./doc-properties-children"; @@ -24,8 +24,6 @@ export interface DocPropertiesOptions { readonly title: string; } -const uniqueNumericId = uniqueNumericIdCreator(); - export class DocProperties extends XmlComponent { public constructor({ name, description, title }: DocPropertiesOptions = { name: "", description: "", title: "" }) { super("wp:docPr"); @@ -34,7 +32,7 @@ export class DocProperties extends XmlComponent { new NextAttributeComponent({ id: { key: "id", - value: uniqueNumericId(), + value: docPropertiesUniqueNumericId(), }, name: { key: "name", diff --git a/src/file/drawing/drawing.spec.ts b/src/file/drawing/drawing.spec.ts index d445a01055d..8850881159a 100644 --- a/src/file/drawing/drawing.spec.ts +++ b/src/file/drawing/drawing.spec.ts @@ -31,11 +31,11 @@ const createDrawing = (drawingOptions?: IDrawingOptions): Drawing => describe("Drawing", () => { before(() => { - stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); + stub(convenienceFunctions, "docPropertiesUniqueNumericId").callsFake(() => 0); }); after(() => { - (convenienceFunctions.uniqueNumericId as SinonStub).restore(); + (convenienceFunctions.docPropertiesUniqueNumericId as SinonStub).restore(); }); let currentBreak: Drawing; diff --git a/src/file/numbering/numbering.spec.ts b/src/file/numbering/numbering.spec.ts index 71540f86918..ff4c781ebee 100644 --- a/src/file/numbering/numbering.spec.ts +++ b/src/file/numbering/numbering.spec.ts @@ -8,11 +8,13 @@ import { Numbering } from "./numbering"; describe("Numbering", () => { before(() => { - stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); + stub(convenienceFunctions, "abstractNumUniqueNumericId").callsFake(() => 0); + stub(convenienceFunctions, "concreteNumUniqueNumericId").callsFake(() => 0); }); after(() => { - (convenienceFunctions.uniqueNumericId as SinonStub).restore(); + (convenienceFunctions.abstractNumUniqueNumericId as SinonStub).restore(); + (convenienceFunctions.concreteNumUniqueNumericId as SinonStub).restore(); }); describe("#constructor", () => { diff --git a/src/file/numbering/numbering.ts b/src/file/numbering/numbering.ts index b8eeef2feb2..4ec52063273 100644 --- a/src/file/numbering/numbering.ts +++ b/src/file/numbering/numbering.ts @@ -2,7 +2,7 @@ // https://stackoverflow.com/questions/58622437/purpose-of-abstractnum-and-numberinginstance import { AlignmentType } from "@file/paragraph"; import { IContext, IXmlableObject, XmlComponent } from "@file/xml-components"; -import { convertInchesToTwip, uniqueNumericIdCreator } from "@util/convenience-functions"; +import { abstractNumUniqueNumericId, concreteNumUniqueNumericId, convertInchesToTwip } from "@util/convenience-functions"; import { DocumentAttributes } from "../document/document-attributes"; import { AbstractNumbering } from "./abstract-numbering"; @@ -16,9 +16,6 @@ export interface INumberingOptions { }[]; } -const abstractNumUniqueNumericId = uniqueNumericIdCreator(); -const concreteNumUniqueNumericId = uniqueNumericIdCreator(1); // Setting initial to 1 as we have numId = 1 for "default-bullet-numbering" - // // // diff --git a/src/file/paragraph/links/bookmark.ts b/src/file/paragraph/links/bookmark.ts index d88bfd7f466..8bb7c9b5ed8 100644 --- a/src/file/paragraph/links/bookmark.ts +++ b/src/file/paragraph/links/bookmark.ts @@ -1,19 +1,17 @@ // http://officeopenxml.com/WPbookmark.php import { XmlComponent } from "@file/xml-components"; -import { uniqueNumericIdCreator } from "@util/convenience-functions"; +import { bookmarkUniqueNumericId } from "@util/convenience-functions"; import { ParagraphChild } from "../paragraph"; import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; -const uniqueNumericId = uniqueNumericIdCreator(); - export class Bookmark { public readonly start: BookmarkStart; public readonly children: readonly ParagraphChild[]; public readonly end: BookmarkEnd; public constructor(options: { readonly id: string; readonly children: readonly ParagraphChild[] }) { - const linkId = uniqueNumericId(); + const linkId = bookmarkUniqueNumericId(); this.start = new BookmarkStart(options.id, linkId); this.children = options.children; diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index b74359de86a..7d21b5d1712 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -20,12 +20,12 @@ import { TextRun } from "./run"; describe("Paragraph", () => { before(() => { stub(convenienceFunctions, "uniqueId").callsFake(() => "test-unique-id"); - stub(convenienceFunctions, "uniqueNumericId").callsFake(() => -101); + stub(convenienceFunctions, "bookmarkUniqueNumericId").callsFake(() => -101); }); after(() => { (convenienceFunctions.uniqueId as SinonStub).restore(); - (convenienceFunctions.uniqueNumericId as SinonStub).restore(); + (convenienceFunctions.bookmarkUniqueNumericId as SinonStub).restore(); }); describe("#constructor()", () => { diff --git a/src/file/paragraph/run/image-run.spec.ts b/src/file/paragraph/run/image-run.spec.ts index edc4aa59106..9e9ad89505b 100644 --- a/src/file/paragraph/run/image-run.spec.ts +++ b/src/file/paragraph/run/image-run.spec.ts @@ -11,12 +11,12 @@ import { ImageRun } from "./image-run"; describe("ImageRun", () => { before(() => { stub(convenienceFunctions, "uniqueId").callsFake(() => "test-unique-id"); - stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); + stub(convenienceFunctions, "docPropertiesUniqueNumericId").callsFake(() => 0); }); after(() => { (convenienceFunctions.uniqueId as SinonStub).restore(); - (convenienceFunctions.uniqueNumericId as SinonStub).restore(); + (convenienceFunctions.docPropertiesUniqueNumericId as SinonStub).restore(); }); describe("#constructor()", () => { diff --git a/src/util/convenience-functions.spec.ts b/src/util/convenience-functions.spec.ts index 8fa26f61a3a..4bca649b6bf 100644 --- a/src/util/convenience-functions.spec.ts +++ b/src/util/convenience-functions.spec.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; -import { convertInchesToTwip, convertMillimetersToTwip, uniqueId, uniqueNumericId } from "./convenience-functions"; +import { convertInchesToTwip, convertMillimetersToTwip, uniqueId, uniqueNumericIdCreator } from "./convenience-functions"; describe("Utility", () => { describe("#convertMillimetersToTwip", () => { @@ -17,8 +17,9 @@ describe("Utility", () => { }); }); - describe("#uniqueNumericId", () => { + describe("#uniqueNumericIdCreator", () => { it("should generate a unique incrementing ID", () => { + const uniqueNumericId = uniqueNumericIdCreator(); expect(uniqueNumericId()).to.not.be.undefined; }); }); diff --git a/src/util/convenience-functions.ts b/src/util/convenience-functions.ts index 8dac4a929bd..543b6e9c7c3 100644 --- a/src/util/convenience-functions.ts +++ b/src/util/convenience-functions.ts @@ -11,4 +11,9 @@ export const uniqueNumericIdCreator = (initial = 0): (() => number) => { return () => ++currentCount; }; +export const abstractNumUniqueNumericId = uniqueNumericIdCreator(); +export const concreteNumUniqueNumericId = uniqueNumericIdCreator(1); // Setting initial to 1 as we have numId = 1 for "default-bullet-numbering" +export const docPropertiesUniqueNumericId = uniqueNumericIdCreator(); +export const bookmarkUniqueNumericId = uniqueNumericIdCreator(); + export const uniqueId = (): string => nanoid().toLowerCase();