Merge branch 'master' into feat/deprecate-shelljs

This commit is contained in:
Dolan Miu
2023-05-01 16:36:43 +01:00
17 changed files with 527 additions and 427 deletions

View File

@ -0,0 +1,29 @@
// Patch a document with patches
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { IPatch, patchDocument, PatchType, TextRun } from "../build";
export const font = "Trebuchet MS";
export const getPatches = (fields: { [key: string]: string }) => {
const patches: { [key: string]: IPatch } = {};
for (const field in fields) {
patches[field] = {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: fields[field], font })],
};
}
return patches;
};
const patches = getPatches({
salutation: "Mr.",
"first-name": "John",
});
patchDocument(fs.readFileSync("demo/assets/simple-template-3.docx"), {
patches,
}).then((doc) => {
fs.writeFileSync("My Document.docx", doc);
});

Binary file not shown.

View File

@ -38,6 +38,7 @@
<script src="https://unpkg.com/docsify-copy-code@2"></script> <script src="https://unpkg.com/docsify-copy-code@2"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script> <script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-typescript.min.js"></script> <script src="//unpkg.com/prismjs/components/prism-typescript.min.js"></script>
<script src="https://unpkg.com/docsify-sign-off-sheet@1.0.0/dist/index.iife.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/index.min.js" type="text/javascript"></script> <script src="//cdn.jsdelivr.net/npm/docsify-darklight-theme@latest/dist/index.min.js" type="text/javascript"></script>
</body> </body>
</html> </html>

805
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "docx", "name": "docx",
"version": "8.0.1", "version": "8.0.3",
"description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.", "description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.",
"main": "build/index.js", "main": "build/index.js",
"scripts": { "scripts": {
@ -105,7 +105,7 @@
"tsconfig-paths": "^4.0.0", "tsconfig-paths": "^4.0.0",
"tsconfig-paths-webpack-plugin": "^4.0.0", "tsconfig-paths-webpack-plugin": "^4.0.0",
"typedoc": "^0.23.2", "typedoc": "^0.23.2",
"typescript": "5.0.2", "typescript": "5.0.3",
"unzipper": "^0.10.11", "unzipper": "^0.10.11",
"webpack": "^5.28.0", "webpack": "^5.28.0",
"webpack-cli": "^5.0.0" "webpack-cli": "^5.0.0"

View File

@ -41,11 +41,11 @@ const createAnchor = (drawingOptions: IDrawingOptions): Anchor =>
describe("Anchor", () => { describe("Anchor", () => {
before(() => { before(() => {
stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); stub(convenienceFunctions, "docPropertiesUniqueNumericId").callsFake(() => 0);
}); });
after(() => { after(() => {
(convenienceFunctions.uniqueNumericId as SinonStub).restore(); (convenienceFunctions.docPropertiesUniqueNumericId as SinonStub).restore();
}); });
let anchor: Anchor; let anchor: Anchor;

View File

@ -2,7 +2,7 @@
import { IContext, IXmlableObject, NextAttributeComponent, XmlComponent } from "@file/xml-components"; import { IContext, IXmlableObject, NextAttributeComponent, XmlComponent } from "@file/xml-components";
import { ConcreteHyperlink } from "@file/paragraph"; import { ConcreteHyperlink } from "@file/paragraph";
import { uniqueNumericId } from "@util/convenience-functions"; import { docPropertiesUniqueNumericId } from "@util/convenience-functions";
import { createHyperlinkClick } from "./doc-properties-children"; import { createHyperlinkClick } from "./doc-properties-children";
@ -32,7 +32,7 @@ export class DocProperties extends XmlComponent {
new NextAttributeComponent({ new NextAttributeComponent({
id: { id: {
key: "id", key: "id",
value: uniqueNumericId(), value: docPropertiesUniqueNumericId(),
}, },
name: { name: {
key: "name", key: "name",

View File

@ -31,11 +31,11 @@ const createDrawing = (drawingOptions?: IDrawingOptions): Drawing =>
describe("Drawing", () => { describe("Drawing", () => {
before(() => { before(() => {
stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); stub(convenienceFunctions, "docPropertiesUniqueNumericId").callsFake(() => 0);
}); });
after(() => { after(() => {
(convenienceFunctions.uniqueNumericId as SinonStub).restore(); (convenienceFunctions.docPropertiesUniqueNumericId as SinonStub).restore();
}); });
let currentBreak: Drawing; let currentBreak: Drawing;

View File

@ -8,11 +8,13 @@ import { Numbering } from "./numbering";
describe("Numbering", () => { describe("Numbering", () => {
before(() => { before(() => {
stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); stub(convenienceFunctions, "abstractNumUniqueNumericId").callsFake(() => 0);
stub(convenienceFunctions, "concreteNumUniqueNumericId").callsFake(() => 0);
}); });
after(() => { after(() => {
(convenienceFunctions.uniqueNumericId as SinonStub).restore(); (convenienceFunctions.abstractNumUniqueNumericId as SinonStub).restore();
(convenienceFunctions.concreteNumUniqueNumericId as SinonStub).restore();
}); });
describe("#constructor", () => { describe("#constructor", () => {

View File

@ -2,7 +2,7 @@
// https://stackoverflow.com/questions/58622437/purpose-of-abstractnum-and-numberinginstance // https://stackoverflow.com/questions/58622437/purpose-of-abstractnum-and-numberinginstance
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";
import { convertInchesToTwip, uniqueNumericId } from "@util/convenience-functions"; import { abstractNumUniqueNumericId, concreteNumUniqueNumericId, convertInchesToTwip } from "@util/convenience-functions";
import { DocumentAttributes } from "../document/document-attributes"; import { DocumentAttributes } from "../document/document-attributes";
import { AbstractNumbering } from "./abstract-numbering"; import { AbstractNumbering } from "./abstract-numbering";
@ -55,7 +55,7 @@ export class Numbering extends XmlComponent {
}), }),
); );
const abstractNumbering = new AbstractNumbering(uniqueNumericId(), [ const abstractNumbering = new AbstractNumbering(abstractNumUniqueNumericId(), [
{ {
level: 0, level: 0,
format: LevelFormat.BULLET, format: LevelFormat.BULLET,
@ -176,7 +176,7 @@ export class Numbering extends XmlComponent {
this.abstractNumberingMap.set("default-bullet-numbering", abstractNumbering); this.abstractNumberingMap.set("default-bullet-numbering", abstractNumbering);
for (const con of options.config) { 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); this.referenceConfigMap.set(con.reference, con.levels);
} }
} }
@ -209,7 +209,7 @@ export class Numbering extends XmlComponent {
const firstLevelStartNumber = referenceConfigLevels && referenceConfigLevels[0].start; const firstLevelStartNumber = referenceConfigLevels && referenceConfigLevels[0].start;
const concreteNumberingSettings = { const concreteNumberingSettings = {
numId: uniqueNumericId(), numId: concreteNumUniqueNumericId(),
abstractNumId: abstractNumbering.id, abstractNumId: abstractNumbering.id,
reference, reference,
instance, instance,

View File

@ -1,6 +1,6 @@
// http://officeopenxml.com/WPbookmark.php // http://officeopenxml.com/WPbookmark.php
import { XmlComponent } from "@file/xml-components"; import { XmlComponent } from "@file/xml-components";
import { uniqueNumericId } from "@util/convenience-functions"; import { bookmarkUniqueNumericId } from "@util/convenience-functions";
import { ParagraphChild } from "../paragraph"; import { ParagraphChild } from "../paragraph";
import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes"; import { BookmarkEndAttributes, BookmarkStartAttributes } from "./bookmark-attributes";
@ -11,7 +11,7 @@ export class Bookmark {
public readonly end: BookmarkEnd; public readonly end: BookmarkEnd;
public constructor(options: { readonly id: string; readonly children: readonly ParagraphChild[] }) { public constructor(options: { readonly id: string; readonly children: readonly ParagraphChild[] }) {
const linkId = uniqueNumericId(); const linkId = bookmarkUniqueNumericId();
this.start = new BookmarkStart(options.id, linkId); this.start = new BookmarkStart(options.id, linkId);
this.children = options.children; this.children = options.children;

View File

@ -20,12 +20,12 @@ import { TextRun } from "./run";
describe("Paragraph", () => { describe("Paragraph", () => {
before(() => { before(() => {
stub(convenienceFunctions, "uniqueId").callsFake(() => "test-unique-id"); stub(convenienceFunctions, "uniqueId").callsFake(() => "test-unique-id");
stub(convenienceFunctions, "uniqueNumericId").callsFake(() => -101); stub(convenienceFunctions, "bookmarkUniqueNumericId").callsFake(() => -101);
}); });
after(() => { after(() => {
(convenienceFunctions.uniqueId as SinonStub).restore(); (convenienceFunctions.uniqueId as SinonStub).restore();
(convenienceFunctions.uniqueNumericId as SinonStub).restore(); (convenienceFunctions.bookmarkUniqueNumericId as SinonStub).restore();
}); });
describe("#constructor()", () => { describe("#constructor()", () => {

View File

@ -11,12 +11,12 @@ import { ImageRun } from "./image-run";
describe("ImageRun", () => { describe("ImageRun", () => {
before(() => { before(() => {
stub(convenienceFunctions, "uniqueId").callsFake(() => "test-unique-id"); stub(convenienceFunctions, "uniqueId").callsFake(() => "test-unique-id");
stub(convenienceFunctions, "uniqueNumericId").callsFake(() => 0); stub(convenienceFunctions, "docPropertiesUniqueNumericId").callsFake(() => 0);
}); });
after(() => { after(() => {
(convenienceFunctions.uniqueId as SinonStub).restore(); (convenienceFunctions.uniqueId as SinonStub).restore();
(convenienceFunctions.uniqueNumericId as SinonStub).restore(); (convenienceFunctions.docPropertiesUniqueNumericId as SinonStub).restore();
}); });
describe("#constructor()", () => { describe("#constructor()", () => {

View File

@ -86,6 +86,59 @@ describe("paragraph-split-inject", () => {
), ),
).to.throw(); ).to.throw();
}); });
it("should continue if text run doesn't have text", () => {
expect(() =>
findRunElementIndexWithToken(
{
name: "w:p",
type: "element",
elements: [
{
name: "w:r",
type: "element",
elements: [
{
name: "w:t",
type: "element",
},
],
},
],
},
"hello",
),
).to.throw();
});
it("should continue if text run doesn't have text", () => {
expect(() =>
findRunElementIndexWithToken(
{
name: "w:p",
type: "element",
elements: [
{
name: "w:r",
type: "element",
elements: [
{
name: "w:t",
type: "element",
elements: [
{
type: "text",
},
],
},
],
},
],
},
"hello",
),
).to.throw();
});
}); });
describe("splitRunElement", () => { describe("splitRunElement", () => {

View File

@ -8,7 +8,11 @@ export const findRunElementIndexWithToken = (paragraphElement: Element, token: s
const textElement = (element.elements ?? []).filter((e) => e.type === "element" && e.name === "w:t"); const textElement = (element.elements ?? []).filter((e) => e.type === "element" && e.name === "w:t");
for (const text of textElement) { for (const text of textElement) {
if ((text.elements?.[0].text as string)?.includes(token)) { if (!text.elements?.[0]) {
continue;
}
if ((text.elements[0].text as string)?.includes(token)) {
return i; return i;
} }
} }

View File

@ -1,6 +1,6 @@
import { expect } from "chai"; import { expect } from "chai";
import { convertInchesToTwip, convertMillimetersToTwip, uniqueId, uniqueNumericId } from "./convenience-functions"; import { convertInchesToTwip, convertMillimetersToTwip, uniqueId, uniqueNumericIdCreator } from "./convenience-functions";
describe("Utility", () => { describe("Utility", () => {
describe("#convertMillimetersToTwip", () => { describe("#convertMillimetersToTwip", () => {
@ -17,8 +17,9 @@ describe("Utility", () => {
}); });
}); });
describe("#uniqueNumericId", () => { describe("#uniqueNumericIdCreator", () => {
it("should generate a unique incrementing ID", () => { it("should generate a unique incrementing ID", () => {
const uniqueNumericId = uniqueNumericIdCreator();
expect(uniqueNumericId()).to.not.be.undefined; expect(uniqueNumericId()).to.not.be.undefined;
}); });
}); });

View File

@ -1,12 +1,19 @@
import { nanoid } from "nanoid/non-secure"; import { nanoid } from "nanoid/non-secure";
let currentCount = 0;
// Twip - twentieths of a point // Twip - twentieths of a point
export const convertMillimetersToTwip = (millimeters: number): number => Math.floor((millimeters / 25.4) * 72 * 20); 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 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 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(); export const uniqueId = (): string => nanoid().toLowerCase();