From 85583a3dbd086124c9939bbbb6644918ccc355c8 Mon Sep 17 00:00:00 2001 From: Paul de Groot Date: Tue, 4 May 2021 23:38:25 +0200 Subject: [PATCH 1/3] Added support for w:fldSimple, including convenience class for simple MailMerge fields --- src/file/paragraph/paragraph.ts | 6 ++-- src/file/paragraph/run/index.ts | 1 + src/file/paragraph/run/simple-field.spec.ts | 38 +++++++++++++++++++++ src/file/paragraph/run/simple-field.ts | 23 +++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/file/paragraph/run/simple-field.spec.ts create mode 100644 src/file/paragraph/run/simple-field.ts diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index c4a9c4ee60..a54698e837 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -10,7 +10,7 @@ import { PageBreak } from "./formatting/page-break"; import { Bookmark, ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./links"; import { Math } from "./math"; import { IParagraphPropertiesOptions, ParagraphProperties } from "./properties"; -import { ImageRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run"; +import { ImageRun, Run, SequentialIdentifier, SimpleField, SimpleMailMergeField, SymbolRun, TextRun } from "./run"; export type ParagraphChild = | TextRun @@ -24,7 +24,9 @@ export type ParagraphChild = | ExternalHyperlink | InsertedTextRun | DeletedTextRun - | Math; + | Math + | SimpleField + | SimpleMailMergeField; export interface IParagraphOptions extends IParagraphPropertiesOptions { readonly text?: string; diff --git a/src/file/paragraph/run/index.ts b/src/file/paragraph/run/index.ts index 4179af7cfc..da4b9e4f2e 100644 --- a/src/file/paragraph/run/index.ts +++ b/src/file/paragraph/run/index.ts @@ -8,3 +8,4 @@ export * from "./sequential-identifier"; export * from "./underline"; export * from "./emphasis-mark"; export * from "./tab"; +export * from "./simple-field"; diff --git a/src/file/paragraph/run/simple-field.spec.ts b/src/file/paragraph/run/simple-field.spec.ts new file mode 100644 index 0000000000..4368c1f47c --- /dev/null +++ b/src/file/paragraph/run/simple-field.spec.ts @@ -0,0 +1,38 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; + +import { SimpleField, SimpleMailMergeField } from "./simple-field"; + +describe("SimpleField", () => { + describe("#constructor()", () => { + it("uses the instruction given", () => { + const tree = new Formatter().format(new SimpleField("FILENAME")); + expect(tree).to.deep.equal({ "w:fldSimple": { _attr: { "w:instr": "FILENAME" } } }); + }); + + it("accepts a cached value", () => { + const tree = new Formatter().format(new SimpleField("FILENAME", "ExampleDoc.docx")); + expect(tree).to.deep.equal({ + "w:fldSimple": [ + { _attr: { "w:instr": "FILENAME" } }, + { "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "ExampleDoc.docx"] }] }, + ], + }); + }); + }); +}); + +describe("SimpleMailMergeField", () => { + describe("#constructor()", () => { + it("creates a simple field", () => { + const tree = new Formatter().format(new SimpleMailMergeField("Name")); + expect(tree).to.deep.equal({ + "w:fldSimple": [ + { _attr: { "w:instr": " MERGEFIELD Name " } }, + { "w:r": [{ "w:t": [{ _attr: { "xml:space": "preserve" } }, "«Name»"] }] }, + ], + }); + }); + }); +}); diff --git a/src/file/paragraph/run/simple-field.ts b/src/file/paragraph/run/simple-field.ts new file mode 100644 index 0000000000..719c2dded5 --- /dev/null +++ b/src/file/paragraph/run/simple-field.ts @@ -0,0 +1,23 @@ +// http://www.datypic.com/sc/ooxml/e-w_fldSimple-1.html +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +import { TextRun } from "./text-run"; + +class FldSimpleAttrs extends XmlAttributeComponent<{ readonly instr: string }> { + protected readonly xmlKeys = { instr: "w:instr" }; +} + +export class SimpleField extends XmlComponent { + constructor(instruction: string, cachedValue?: string) { + super("w:fldSimple"); + this.root.push(new FldSimpleAttrs({ instr: instruction })); + if (cachedValue !== undefined) { + this.root.push(new TextRun(cachedValue)); + } + } +} + +export class SimpleMailMergeField extends SimpleField { + constructor(fieldName: string) { + super(` MERGEFIELD ${fieldName} `, `«${fieldName}»`); + } +} From 79c1db9c91035f5f49ebb455a009591eaa7b3537 Mon Sep 17 00:00:00 2001 From: Paul de Groot Date: Wed, 5 May 2021 14:37:02 +0200 Subject: [PATCH 2/3] Added some documentation and demo file --- demo/66-fields.ts | 43 ++++++++++++++++++++++++++++++++++ docs/usage/fields.md | 56 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 demo/66-fields.ts create mode 100644 docs/usage/fields.md diff --git a/demo/66-fields.ts b/demo/66-fields.ts new file mode 100644 index 0000000000..ff1c3a88d5 --- /dev/null +++ b/demo/66-fields.ts @@ -0,0 +1,43 @@ +// Use fields to include dynamic text +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Bookmark, Document, Packer, Paragraph, SimpleField, TextRun } from "../build"; + +const doc = new Document({ + creator: 'Me', + sections: [ + { + properties: {}, + children: [ + new Paragraph({ + children: [ + new TextRun("This document is called "), + new SimpleField("FILENAME", "My Document.docx"), + new TextRun(", was created on "), + new SimpleField('CREATEDATE \\@ "d MMMM yyyy"'), + new TextRun(" by "), + new SimpleField("AUTHOR"), + ], + }), + new Paragraph({ + children: [ + new TextRun("The document has "), + new SimpleField("NUMWORDS", "34"), + new TextRun(" words and if you'd print it "), + new Bookmark({ + id: "TimesPrinted", + children: [new TextRun("42")], + }), + new TextRun(" times two-sided, you would need "), + new SimpleField("=INT((TimesPrinted+1)/2)"), + new TextRun(" sheets of paper."), + ], + }), + ], + }, + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/docs/usage/fields.md b/docs/usage/fields.md new file mode 100644 index 0000000000..cf2d8f9587 --- /dev/null +++ b/docs/usage/fields.md @@ -0,0 +1,56 @@ +# Fields + +Fields are pieces of dynamic text that you can include in your document. Often used fields are page numbers or references, but you can also include document properties like the author name or last saved date. + +## Simple fields + +There are very complicated fields like the table of contents, but in many cases the whole field just has the same properties (like formatting). In those cases, you can use simple fields. + +Word uses field codes to identify what the result of the field should be. You can find these field codes by adding a field in a document (`Insert -> Quick Parts -> Field...`) and clicking the 'Field codes'-button. Some examples include: + +Field type | Example | Description +----------- | ---------------- | --------------------------------------------------------- += (Formula) | `=2 * 21` | Calculates the result of a formula. You can also use bookmarks as variables. +Author | `AUTHOR` | Includes the author mentioned in the document properties. +CreateDate | `CREATEDATE` | Date the document was created. +Date | `DATE` | Today's date. +FileName | `FILENAME \p` | The name of the document. Add `\p` for the complete path. +Info | `INFO NumWords` | Data from the document properties, e.g. the number of words in the document. +NumPages | `NUMPAGES` | Number of pages in the document. +UserName | `USERNAME` | Your user name from the Office personalization settings. + +Fields can be added as a child of a paragraph: + +```ts +const paragraph = new Paragraph({ + children: [new TextRun("This document was created by: "), new SimpleField("AUTHOR")], +}); +``` + +Fields can contain a cached value that gives the word processor a text to show without having to calculate all fields. The cached value can be updated by selecting the field and pressing F9. A cached value can be passed in as the second argument to the constructor. + +```ts +const paragraph = new Paragraph({ + children: [new TextRun("This document was created by: "), new SimpleField("AUTHOR", "Richard Brodie")], +}); +``` + +## Mail merge fields + +Fields are often used in a mail merge where a template document is created and data from another source (Excel or a database) is inserted in the document. + +A convenience class was added to add these mail merge fields to the document easily. You can add these to a paragraph like any other field and only have to supply the name of the field in your data set: + +```ts +const paragraph = new Paragraph({ + children: [new TextRun("Your score was "), new SimpleMailMergeField("Score"), new TextRun(" of 100 points")], +}); +``` + +This code is equivalent to: + +```ts +const paragraph = new Paragraph({ + children: [new TextRun("Your score was "), new SimpleField("MERGEFIELD Score", "«Score»"), new TextRun(" of 100 points")], +}); +``` From a9e4c8880443fe1a5fd34d8aefaacf820a71cc8c Mon Sep 17 00:00:00 2001 From: Paul de Groot Date: Wed, 5 May 2021 15:11:11 +0200 Subject: [PATCH 3/3] Expanded documentation on the formulas a bit --- docs/usage/fields.md | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/docs/usage/fields.md b/docs/usage/fields.md index cf2d8f9587..69475cc425 100644 --- a/docs/usage/fields.md +++ b/docs/usage/fields.md @@ -8,16 +8,16 @@ There are very complicated fields like the table of contents, but in many cases Word uses field codes to identify what the result of the field should be. You can find these field codes by adding a field in a document (`Insert -> Quick Parts -> Field...`) and clicking the 'Field codes'-button. Some examples include: -Field type | Example | Description ------------ | ---------------- | --------------------------------------------------------- -= (Formula) | `=2 * 21` | Calculates the result of a formula. You can also use bookmarks as variables. -Author | `AUTHOR` | Includes the author mentioned in the document properties. -CreateDate | `CREATEDATE` | Date the document was created. -Date | `DATE` | Today's date. -FileName | `FILENAME \p` | The name of the document. Add `\p` for the complete path. -Info | `INFO NumWords` | Data from the document properties, e.g. the number of words in the document. -NumPages | `NUMPAGES` | Number of pages in the document. -UserName | `USERNAME` | Your user name from the Office personalization settings. +Field type | Example | Description +----------- | --------------- | --------------------------------------------------------- += (Formula) | `=2*21` | Calculates the result of a formula. You can also use bookmarks as variables, see below. +Author | `AUTHOR` | Includes the author mentioned in the document properties. +CreateDate | `CREATEDATE` | Date the document was created. +Date | `DATE` | Today's date. +FileName | `FILENAME \p` | The name of the document. Add `\p` for the complete path. +Info | `INFO NumWords` | Data from the document properties, e.g. the number of words in the document. +NumPages | `NUMPAGES` | Number of pages in the document. +UserName | `USERNAME` | Your user name from the Office personalization settings. Fields can be added as a child of a paragraph: @@ -35,7 +35,24 @@ const paragraph = new Paragraph({ }); ``` -## Mail merge fields +### Formulas + +One type of field is the formula that can be used to do some basic calculations. This can be done with static values, e.g. `12 + 34`, but a value from a bookmark can also be used in a calculation. This can be seen in the following example: + +```ts +const paragraph = new Paragraph({ + children: [ + new TextRun("Value one is: "), + new Bookmark({ id: "One", children: [new TextRun("451")]}), + new TextRun(". The second value is: "), + new Bookmark({ id: "Two", children: [new TextRun("886")]}), + new TextRun(". The sum of these values is: "), + new SimpleField("=One+Two"), + ], +}); +``` + +### Mail merge fields Fields are often used in a mail merge where a template document is created and data from another source (Excel or a database) is inserted in the document.