@rainbow-o23/n7 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.babelrc ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "presets": [
3
+ "@babel/preset-typescript",
4
+ [
5
+ "@babel/preset-env",
6
+ {
7
+ "modules": false
8
+ }
9
+ ]
10
+ ]
11
+ }
package/.eslintrc ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "root": true,
3
+ "parser": "@typescript-eslint/parser",
4
+ "plugins": [
5
+ "@typescript-eslint"
6
+ ],
7
+ "extends": [
8
+ "eslint:recommended",
9
+ "plugin:@typescript-eslint/eslint-recommended",
10
+ "plugin:@typescript-eslint/recommended"
11
+ ],
12
+ "rules": {
13
+ "no-plusplus": 0,
14
+ "no-mixed-spaces-and-tabs": [
15
+ "error",
16
+ "smart-tabs"
17
+ ],
18
+ "@typescript-eslint/no-empty-interface": "off",
19
+ "@typescript/no-non-null-assertion": "off",
20
+ "@typescript-eslint/adjacent-overload-signatures": "off",
21
+ "@typescript-eslint/ban-ts-comment": "error"
22
+ }
23
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 InsureMO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,235 @@
1
+ ![Static Badge](https://img.shields.io/badge/InsureMO-777AF2.svg)
2
+
3
+ ![Docx-templates](https://img.shields.io/badge/Docx--templates-white.svg?logo=microsoftword&logoColor=2B579A&style=social)
4
+
5
+ ![Module Formats](https://img.shields.io/badge/module%20formats-cjs-green.svg)
6
+
7
+ # o23/n7
8
+
9
+ `o23/n7` provides
10
+
11
+ - A pipeline step that converts word templates to word, implemented based on [Docx-templates](https://github.com/guigrpa/docx-templates).
12
+
13
+ ## Word Generate Step
14
+
15
+ ### Environment Parameters
16
+
17
+ | Name | Type | Default Value | Comments |
18
+ |----------------------------|--------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
19
+ | cmd | string, [string, string] | `+++` | Defines a custom command delimiter. This can be a String e.g. '+++' or an Array of Strings with length 2: ['{', '}'] in which the first element serves as the start delimiter and the second as the end delimiter. |
20
+ | literalXmlDelimiter | string | `\|\|` | The delimiter that's used to indicate literal XML that should be inserted into the docx XML tree as-is. |
21
+ | processLineBreaks | boolean | true | Handle linebreaks in result of commands as actual linebreaks. |
22
+ | failFast | boolean | true | Whether to fail on the first error encountered in the template. |
23
+ | rejectNullish | boolean | false | When set to true, this setting ensures createReport throws a NullishCommandResultError when the result of an INS, HTML, IMAGE, or LINK command is null or undefined. This is useful as nullish return values usually indicate a mistake in the template or the invoking code. |
24
+ | fixSmartQuotes | boolean | false | MS Word usually autocorrects JS string literal quotes with unicode 'smart' quotes ('curly' quotes). E.g. 'aubergine' -> ‘aubergine’. This causes an error when evaluating commands containing these smart quotes, as they are not valid JavaScript. If you set fixSmartQuotes to 'true', these smart quotes will automatically get replaced with straight quotes (') before command evaluation. |
25
+ | processLineBreaksAsNewText | boolean | false | Use the new way of injecting line breaks from command results (only applies when processLineBreaks is true) which has better results in LibreOffice and Google Drive. |
26
+
27
+ ### Request and Response
28
+
29
+ ```typescript
30
+ export interface PrintWordPipelineStepInFragment {
31
+ template: Buffer;
32
+ data: any;
33
+ jsContext?: Object;
34
+ }
35
+
36
+ export interface PrintWordPipelineStepOutFragment {
37
+ file: Buffer;
38
+ }
39
+ ```
40
+
41
+ ### Syntax
42
+
43
+ Find template and unit test in `/test` folder, syntax for using a Word template is as follows.
44
+
45
+ #### Command Delimiters
46
+
47
+ The default command delimiters are `+++` and `+++`. This means that commands are written as `+++command+++`. You can change the delimiters
48
+ by setting the `cmd` option to a string or an array of strings with length 2: `['{', '}']`, `['{#', '#}']` for example.
49
+
50
+ #### Property Value
51
+
52
+ The following syntaxes are equivalent. You can choose based on your needs:
53
+
54
+ - `+++INS project.name+++`,
55
+ - `+++= project.name+++`,
56
+ - `+++project.name+++`.
57
+
58
+ It is important to note that the content supports JavaScript syntax. Therefore, the following notation is also equally effective:
59
+
60
+ - `+++INS ``${project.name ?? ''}``+++`.
61
+
62
+ > Always define the Word document style you need across the entire command. If it is only defined within the command content, the style will
63
+ > not take effect.
64
+
65
+ #### Image or SVG
66
+
67
+ Use the `IMAGE` syntax to replace images or SVG. This syntax calls a context function to retrieve image details, as follows:
68
+
69
+ - `+++IMAGE logo()+++`.
70
+
71
+ When using the syntax above, please ensure that the corresponding function is already provided in the `jsContext` context object passed as a
72
+ parameter,
73
+
74
+ ```typescript
75
+ jsContext: {
76
+ logo: () => {
77
+ const data = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="20" role="img" aria-label="InsureMO"><title>InsureMO</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="63" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#777af2"/><rect x="0" width="63" height="20" fill="#777af2"/><rect width="63" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">InsureMO</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="530">InsureMO</text></g></svg>`, 'utf-8');
78
+ return {width: 1.66687479, height: 0.5291666, data, extension: '.svg'};
79
+ }
80
+ }
81
+ ```
82
+
83
+ > Please note that the units for `width` and `height` are in centimeters (cm), not in pixel values. Generally, it can be assumed that at a
84
+ > resolution of 96 DPI, approximately one pixel is equal to `1.66687479cm`.
85
+
86
+ #### Link
87
+
88
+ Use the `LINK` syntax to replace links. This syntax also can call a context function to retrieve link details, as follows:
89
+
90
+ - `+++LINK ({ url: project.url, label: project.name })+++`,
91
+ - `+++LINK link()+++`.
92
+
93
+ When using the syntax above, please ensure that the corresponding function is already provided in the `jsContext` context object passed as a
94
+
95
+ ```typescript
96
+ jsContext: {
97
+ link: () => {
98
+ // data is the data passed to the print step
99
+ return {url: data.project.url, label: data.project.name};
100
+ };
101
+ }
102
+ ```
103
+
104
+ #### For Loop
105
+
106
+ Use the `FOR ... IN`, `END-FOR` syntax to loop through the array, as follows:
107
+
108
+ ```
109
+ +++FOR group IN groups+++
110
+ Group (+++`${$idx + 1}`+++)
111
+ Cash In Tax: +++= $group.subTotal.cashInTxn+++
112
+ Cash In Amount: +++= $group.subTotal.cashInAmt+++
113
+ Departments:
114
+ +++FOR dept IN $group.depts+++
115
+ Department (+++`${$idx + 1}`+++)
116
+ Name: +++$dept.name+++
117
+ Cashier: +++$dept.cashierName+++
118
+ Cash In Tax: +++$dept.cashInTxn+++
119
+ Cash In Amount: +++$dept.cashInAmt+++
120
+ +++END-FOR dept+++
121
+ +++END-FOR group+++
122
+ ```
123
+
124
+ > From the sample above, it can be observed that nested loops are also supported.
125
+
126
+ > It is possible to get the current element index of the inner-most loop with the variable `$idx`, starting from `0`.
127
+
128
+ To obtain the array data (`arrayPropertyName`) used in the loop body and define a variable name (`variableName`) that represents each array
129
+ element in the loop, you can use the `FOR variableName IN arrayPropertyName` syntax. In the example above, they are respectively:
130
+
131
+ - `+++FOR group IN groups+++`,
132
+ - `+++FOR dept IN $group.depts+++`.
133
+
134
+ Similarly, the loop ending must be defined in the correct order, following the rule of ending the ones that start later. The order is as
135
+ follows:
136
+
137
+ - `+++END-FOR dept+++`,
138
+ - `+++END-FOR group+++`.
139
+
140
+ > Please note that when ending the loop, you need to use the variable name defined in the loop start.
141
+
142
+ The following syntax can be used to output a table using loops:
143
+
144
+ | Group | Department | Cashier | Cash In Tax | Cash In Amount |
145
+ |----------------------------------|--------------------|---------------------------|-------------------------------------|-------------------------------------|
146
+ | `+++For group In groups+++` | | | | |
147
+ | `Group (+++``${$idx + 1}``+++)` | | | `+++= $group.subTotal.cashInTxn+++` | `+++= $group.subTotal.cashInAmt+++` |
148
+ | `+++FOR dept IN $group.depts+++` | | | | |
149
+ | | `+++$dept.name+++` | `+++$dept.cashierName+++` | `+++$dept.cashInTxn+++` | `+++$dept.cashInAmt+++` |
150
+ | `+++END-FOR dept+++` | | | | |
151
+ | `+++END-FOR group+++` | | | | |
152
+
153
+ #### If Statement
154
+
155
+ Use the `IF ... END-IF` syntax to implement the if statement, as follows:
156
+
157
+ ```
158
+ +++FOR group IN groups+++
159
+ Group (+++`${$idx + 1}`+++)
160
+ Cash In Tax: +++= $group.subTotal.cashInTxn+++
161
+ Cash In Amount: +++= $group.subTotal.cashInAmt+++
162
+ Departments:
163
+ +++FOR dept IN $group.depts+++
164
+ +++IF $dept.name === 'Development'+++
165
+ Department (+++`${$idx + 1}`+++)
166
+ Name: +++$dept.name+++
167
+ Cashier: +++$dept.cashierName+++
168
+ Cash In Tax: +++$dept.cashInTxn+++
169
+ Cash In Amount: +++$dept.cashInAmt+++
170
+ +++END-IF+++
171
+ +++END-FOR dept+++
172
+ +++END-FOR group+++
173
+ ```
174
+
175
+ > From the sample above, it can be observed that `IF ... END-IF` can be applied inside loop.
176
+
177
+ The `IF statement` actually executes the given JavaScript syntax and determines whether to output the internal content based on the boolean
178
+ value of the result:
179
+
180
+ - `+++IF $dept.name === 'Development'+++`,
181
+ - `+++END-IF+++`.
182
+
183
+ Similarly, `IF ... END-IF` can also be used in table output:
184
+
185
+ | Group | Department | Cashier | Cash In Tax | Cash In Amount |
186
+ |-----------------------------------------|--------------------|---------------------------|-------------------------------------|-------------------------------------|
187
+ | `+++For group In groups+++` | | | | |
188
+ | `Group (+++``${$idx + 1}``+++)` | | | `+++= $group.subTotal.cashInTxn+++` | `+++= $group.subTotal.cashInAmt+++` |
189
+ | `+++FOR dept IN $group.depts+++` | | | | |
190
+ | `+++IF $dept.name === 'Development'+++` | | | | |
191
+ | | `+++$dept.name+++` | `+++$dept.cashierName+++` | `+++$dept.cashInTxn+++` | `+++$dept.cashInAmt+++` |
192
+ | `+++END-IF+++` | | | | |
193
+ | `+++END-FOR dept+++` | | | | |
194
+ | `+++END-FOR group+++` | | | | |
195
+
196
+ #### HTML
197
+
198
+ Replace HTML-based text with `HTML` syntax, as follows:
199
+
200
+ ```
201
+ +++HTML `
202
+ <meta charset="UTF-8">
203
+ <body>
204
+ <h1>${$film.title}</h1>
205
+ <h3>${$film.releaseDate.slice(0, 4)}</h3>
206
+ <p>
207
+ <strong style="color: red;">This paragraph should be red and strong</strong>
208
+ </p>
209
+ </body>
210
+ `+++
211
+ ```
212
+
213
+ `HTML` executes a JavaScript script, which can also be achieved through `jsContext`, for example:
214
+
215
+ - ```+++HTML html()+++```.
216
+
217
+ ```typescript
218
+ jsContext: {
219
+ html: () => {
220
+ return `<meta charset="UTF-8"><body><strong style="color: red;">This paragraph should be red and strong</strong></body>`;
221
+ }
222
+ }
223
+ ```
224
+
225
+ > It should be noted that the content of HTML needs to be wrapped within the `<body>` tag, and it is recommended to
226
+ > use `<meta charset="UTF-8">` to define the character set.
227
+
228
+ ### Known Issues
229
+
230
+ - If the `LINK` syntax is used in the header or footer, it may result in the issue of "Word found unreachable content" when opening the
231
+ document in Word. Although this does not affect the final content display (the link will not work), it can easily confuse users. However,
232
+ if it is a static link, it is not affected,
233
+ - If the `HTML` syntax is used in the header or footer, it may result in the issue of "Word found unreachable content" when opening the
234
+ document in Word. Additionally, HTML cannot display correctly and may also affect other content in the header and footer,
235
+ - Odd and even row background colors are currently not supported.
package/index.cjs ADDED
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ var n1 = require('@rainbow-o23/n1');
4
+ var n3 = require('@rainbow-o23/n3');
5
+ var docxTemplates = require('docx-templates');
6
+ var n4 = require('@rainbow-o23/n4');
7
+
8
+ const ERR_TEMPLATE_NOT_DEFINED = 'O07-00001';
9
+
10
+ class PrintWordPipelineStep extends n3.AbstractFragmentaryPipelineStep {
11
+ _cmdDelimiter;
12
+ _literalXmlDelimiter;
13
+ _processLineBreaks;
14
+ _noSandbox;
15
+ _failFast;
16
+ _rejectNullish;
17
+ _fixSmartQuotes;
18
+ _processLineBreaksAsNewText;
19
+ constructor(options) {
20
+ super(options);
21
+ this._cmdDelimiter = options.cmdDelimiter ?? '+++';
22
+ this._literalXmlDelimiter = options.literalXmlDelimiter ?? '||';
23
+ this._processLineBreaks = options.processLineBreaks ?? true;
24
+ this._noSandbox = options.noSandbox ?? true;
25
+ this._failFast = options.failFast ?? true;
26
+ this._rejectNullish = options.rejectNullish ?? false;
27
+ this._fixSmartQuotes = options.fixSmartQuotes ?? false;
28
+ this._processLineBreaksAsNewText = options.processLineBreaksAsNewText ?? false;
29
+ }
30
+ getCreateReportOptions() {
31
+ return {
32
+ cmdDelimiter: this._cmdDelimiter,
33
+ literalXmlDelimiter: this._literalXmlDelimiter,
34
+ processLineBreaks: this._processLineBreaks,
35
+ noSandbox: this._noSandbox,
36
+ failFast: this._failFast,
37
+ rejectNullish: this._rejectNullish,
38
+ fixSmartQuotes: this._fixSmartQuotes,
39
+ processLineBreaksAsNewText: this._processLineBreaksAsNewText
40
+ };
41
+ }
42
+ async printWord(templateWord, data, jsContext) {
43
+ const buffer = await docxTemplates.createReport({
44
+ ...this.getCreateReportOptions(),
45
+ template: templateWord, data, additionalJsContext: jsContext
46
+ });
47
+ return Buffer.from(buffer.buffer);
48
+ }
49
+ async doPerform(data, _request) {
50
+ if (data.template == null) {
51
+ throw new n1.UncatchableError(ERR_TEMPLATE_NOT_DEFINED, 'Print template cannot be null.');
52
+ }
53
+ const file = await this.printWord(data.template, data.data, data.jsContext);
54
+ return { file };
55
+ }
56
+ }
57
+
58
+ class PrintWordPipelineStepBuilder extends n4.AbstractFragmentaryPipelineStepBuilder {
59
+ getStepType() {
60
+ return PrintWordPipelineStep;
61
+ }
62
+ readMoreOptions(given, transformed) {
63
+ transformed = super.readMoreOptions(given, transformed);
64
+ transformed.cmdDelimiter = given.cmd;
65
+ transformed.literalXmlDelimiter = given.literalXmlDelimiter;
66
+ transformed.processLineBreaks = given.processLineBreaks;
67
+ transformed.noSandbox = given.noSandbox;
68
+ transformed.failFast = given.failFast;
69
+ transformed.rejectNullish = given.rejectNullish;
70
+ transformed.fixSmartQuotes = given.fixSmartQuotes;
71
+ transformed.processLineBreaksAsNewText = given.processLineBreaksAsNewText;
72
+ return transformed;
73
+ }
74
+ }
75
+
76
+ exports.ERR_TEMPLATE_NOT_DEFINED = ERR_TEMPLATE_NOT_DEFINED;
77
+ exports.PrintWordPipelineStep = PrintWordPipelineStep;
78
+ exports.PrintWordPipelineStepBuilder = PrintWordPipelineStepBuilder;
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './lib/error-codes';
2
+ export * from './lib/print-word-step';
3
+ export * from './lib/print-word-step-builder';
package/index.js ADDED
@@ -0,0 +1,74 @@
1
+ import { UncatchableError } from '@rainbow-o23/n1';
2
+ import { AbstractFragmentaryPipelineStep } from '@rainbow-o23/n3';
3
+ import { createReport } from 'docx-templates';
4
+ import { AbstractFragmentaryPipelineStepBuilder } from '@rainbow-o23/n4';
5
+
6
+ const ERR_TEMPLATE_NOT_DEFINED = 'O07-00001';
7
+
8
+ class PrintWordPipelineStep extends AbstractFragmentaryPipelineStep {
9
+ _cmdDelimiter;
10
+ _literalXmlDelimiter;
11
+ _processLineBreaks;
12
+ _noSandbox;
13
+ _failFast;
14
+ _rejectNullish;
15
+ _fixSmartQuotes;
16
+ _processLineBreaksAsNewText;
17
+ constructor(options) {
18
+ super(options);
19
+ this._cmdDelimiter = options.cmdDelimiter ?? '+++';
20
+ this._literalXmlDelimiter = options.literalXmlDelimiter ?? '||';
21
+ this._processLineBreaks = options.processLineBreaks ?? true;
22
+ this._noSandbox = options.noSandbox ?? true;
23
+ this._failFast = options.failFast ?? true;
24
+ this._rejectNullish = options.rejectNullish ?? false;
25
+ this._fixSmartQuotes = options.fixSmartQuotes ?? false;
26
+ this._processLineBreaksAsNewText = options.processLineBreaksAsNewText ?? false;
27
+ }
28
+ getCreateReportOptions() {
29
+ return {
30
+ cmdDelimiter: this._cmdDelimiter,
31
+ literalXmlDelimiter: this._literalXmlDelimiter,
32
+ processLineBreaks: this._processLineBreaks,
33
+ noSandbox: this._noSandbox,
34
+ failFast: this._failFast,
35
+ rejectNullish: this._rejectNullish,
36
+ fixSmartQuotes: this._fixSmartQuotes,
37
+ processLineBreaksAsNewText: this._processLineBreaksAsNewText
38
+ };
39
+ }
40
+ async printWord(templateWord, data, jsContext) {
41
+ const buffer = await createReport({
42
+ ...this.getCreateReportOptions(),
43
+ template: templateWord, data, additionalJsContext: jsContext
44
+ });
45
+ return Buffer.from(buffer.buffer);
46
+ }
47
+ async doPerform(data, _request) {
48
+ if (data.template == null) {
49
+ throw new UncatchableError(ERR_TEMPLATE_NOT_DEFINED, 'Print template cannot be null.');
50
+ }
51
+ const file = await this.printWord(data.template, data.data, data.jsContext);
52
+ return { file };
53
+ }
54
+ }
55
+
56
+ class PrintWordPipelineStepBuilder extends AbstractFragmentaryPipelineStepBuilder {
57
+ getStepType() {
58
+ return PrintWordPipelineStep;
59
+ }
60
+ readMoreOptions(given, transformed) {
61
+ transformed = super.readMoreOptions(given, transformed);
62
+ transformed.cmdDelimiter = given.cmd;
63
+ transformed.literalXmlDelimiter = given.literalXmlDelimiter;
64
+ transformed.processLineBreaks = given.processLineBreaks;
65
+ transformed.noSandbox = given.noSandbox;
66
+ transformed.failFast = given.failFast;
67
+ transformed.rejectNullish = given.rejectNullish;
68
+ transformed.fixSmartQuotes = given.fixSmartQuotes;
69
+ transformed.processLineBreaksAsNewText = given.processLineBreaksAsNewText;
70
+ return transformed;
71
+ }
72
+ }
73
+
74
+ export { ERR_TEMPLATE_NOT_DEFINED, PrintWordPipelineStep, PrintWordPipelineStepBuilder };
@@ -0,0 +1,2 @@
1
+ import { O23ReservedErrorCode } from '@rainbow-o23/n1';
2
+ export declare const ERR_TEMPLATE_NOT_DEFINED: O23ReservedErrorCode;
@@ -0,0 +1,17 @@
1
+ import { PipelineStepType } from '@rainbow-o23/n1';
2
+ import { AbstractFragmentaryPipelineStepBuilder, FragmentaryPipelineStepBuilderOptions } from '@rainbow-o23/n4';
3
+ import { PrintWordPipelineStep, PrintWordPipelineStepOptions } from './print-word-step';
4
+ export type PrintWordPipelineStepBuilderOptions = FragmentaryPipelineStepBuilderOptions & {
5
+ cmd?: PrintWordPipelineStepOptions['cmdDelimiter'];
6
+ literalXmlDelimiter?: PrintWordPipelineStepOptions['literalXmlDelimiter'];
7
+ processLineBreaks?: PrintWordPipelineStepOptions['processLineBreaks'];
8
+ noSandbox?: PrintWordPipelineStepOptions['noSandbox'];
9
+ failFast?: PrintWordPipelineStepOptions['failFast'];
10
+ rejectNullish?: PrintWordPipelineStepOptions['rejectNullish'];
11
+ fixSmartQuotes?: PrintWordPipelineStepOptions['fixSmartQuotes'];
12
+ processLineBreaksAsNewText?: PrintWordPipelineStepOptions['processLineBreaksAsNewText'];
13
+ };
14
+ export declare class PrintWordPipelineStepBuilder extends AbstractFragmentaryPipelineStepBuilder<PrintWordPipelineStepBuilderOptions, PrintWordPipelineStepOptions, PrintWordPipelineStep> {
15
+ protected getStepType(): PipelineStepType<PrintWordPipelineStep>;
16
+ protected readMoreOptions(given: PrintWordPipelineStepBuilderOptions, transformed: PrintWordPipelineStepOptions): PrintWordPipelineStepOptions;
17
+ }
@@ -0,0 +1,36 @@
1
+ /// <reference types="node" />
2
+ import { PipelineStepData, PipelineStepPayload } from '@rainbow-o23/n1';
3
+ import { AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions } from '@rainbow-o23/n3';
4
+ import { UserOptions } from 'docx-templates/lib/types';
5
+ export interface PrintWordPipelineStepOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out> extends FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment> {
6
+ cmdDelimiter?: string | [string, string];
7
+ literalXmlDelimiter?: string;
8
+ processLineBreaks?: boolean;
9
+ noSandbox?: boolean;
10
+ failFast?: boolean;
11
+ rejectNullish?: boolean;
12
+ fixSmartQuotes?: boolean;
13
+ processLineBreaksAsNewText?: boolean;
14
+ }
15
+ export interface PrintWordPipelineStepInFragment {
16
+ template: Buffer;
17
+ data: any;
18
+ jsContext?: Object;
19
+ }
20
+ export interface PrintWordPipelineStepOutFragment {
21
+ file: Buffer;
22
+ }
23
+ export declare class PrintWordPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload> extends AbstractFragmentaryPipelineStep<In, Out, PrintWordPipelineStepInFragment, PrintWordPipelineStepOutFragment> {
24
+ private readonly _cmdDelimiter?;
25
+ private readonly _literalXmlDelimiter?;
26
+ private readonly _processLineBreaks?;
27
+ private readonly _noSandbox?;
28
+ private readonly _failFast?;
29
+ private readonly _rejectNullish?;
30
+ private readonly _fixSmartQuotes?;
31
+ private readonly _processLineBreaksAsNewText?;
32
+ constructor(options: PrintWordPipelineStepOptions<In, Out, PrintWordPipelineStepInFragment, PrintWordPipelineStepOutFragment>);
33
+ protected getCreateReportOptions(): Omit<UserOptions, 'template' | 'data'>;
34
+ protected printWord(templateWord: Buffer, data: any, jsContext?: Object): Promise<Buffer>;
35
+ protected doPerform(data: PrintWordPipelineStepInFragment, _request: PipelineStepData<In>): Promise<PrintWordPipelineStepOutFragment>;
36
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@rainbow-o23/n7",
3
+ "version": "0.1.16",
4
+ "description": "o23 excel+csv print",
5
+ "main": "index.cjs",
6
+ "module": "index.js",
7
+ "types": "index.d.ts",
8
+ "type": "module",
9
+ "scripts": {
10
+ "build": "rollup -c",
11
+ "build:ci": "rollup -c rollup.config.ci.js",
12
+ "test": "jest"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/InsureMO/rainbow-o23.git"
17
+ },
18
+ "author": "Rainbow Team",
19
+ "license": "MIT",
20
+ "bugs": {
21
+ "url": "https://github.com/InsureMO/rainbow-o23/issues"
22
+ },
23
+ "dependencies": {
24
+ "@rainbow-o23/n3": "0.1.16",
25
+ "@rainbow-o23/n4": "0.1.16",
26
+ "docx-templates": "^4.11.4"
27
+ },
28
+ "devDependencies": {
29
+ "@babel/core": "^7.20.5",
30
+ "@babel/preset-env": "^7.20.2",
31
+ "@babel/preset-typescript": "^7.18.6",
32
+ "@rollup/plugin-babel": "^6.0.4",
33
+ "@rollup/plugin-eslint": "^9.0.3",
34
+ "@types/events": "^3.0.1",
35
+ "@types/node": "18.16.12",
36
+ "@typescript-eslint/eslint-plugin": "^5.46.0",
37
+ "@typescript-eslint/parser": "^5.46.0",
38
+ "eslint": "^8.29.0",
39
+ "rollup": "^3.7.0",
40
+ "rollup-plugin-tslint": "^0.2.2",
41
+ "rollup-plugin-typescript2": "^0.34.1",
42
+ "tslib": "^2.4.1",
43
+ "typescript": "5.1.6"
44
+ },
45
+ "jest": {
46
+ "moduleFileExtensions": [
47
+ "js",
48
+ "json",
49
+ "ts",
50
+ "yaml",
51
+ "yml"
52
+ ],
53
+ "testRegex": "(/test/.*\\.(test|spec))\\.[tj]sx?$",
54
+ "testPathIgnorePatterns": [
55
+ "/node_modules/"
56
+ ],
57
+ "transform": {
58
+ "^.+\\.(t|j)s$": "ts-jest"
59
+ },
60
+ "collectCoverageFrom": [
61
+ "src/**/*.(t|j)s"
62
+ ],
63
+ "coverageDirectory": "./coverage",
64
+ "coverageReporters": [
65
+ "html"
66
+ ],
67
+ "testEnvironment": "node"
68
+ },
69
+ "volta": {
70
+ "node": "18.19.0",
71
+ "yarn": "1.22.21"
72
+ }
73
+ }
@@ -0,0 +1,38 @@
1
+ import {babel} from '@rollup/plugin-babel';
2
+ import eslint from '@rollup/plugin-eslint';
3
+ import typescript from 'rollup-plugin-typescript2';
4
+
5
+ export const buildConfig = (lint) => {
6
+ // ['./index.d.ts', './index.js', './index.cjs', './lib'].forEach(f => {
7
+ // const cwd = path.resolve(process.cwd(), f);
8
+ // if (fs.existsSync(cwd)) {
9
+ // fs.rmSync(cwd, {recursive: true, force: true});
10
+ // }
11
+ // });
12
+
13
+ return {
14
+ input: './src/index.ts',
15
+ output: [
16
+ {format: 'es', dir: '.'},
17
+ {format: 'cjs', file: './index.cjs'}
18
+ ],
19
+ plugins: [
20
+ lint ? eslint({exclude: ['../node_modules/**', 'node_modules/**']}) : null,
21
+ // lint ? tslint({exclude: ['../node_modules/**', 'node_modules/**']}) : null,
22
+ typescript({clean: true}),
23
+ babel({babelHelpers: 'bundled'})
24
+ ].filter(x => x != null),
25
+ external(id) {
26
+ return ["@rainbow-o23/"].some(scope => id.startsWith(scope))
27
+ || [
28
+ "fs", "path", "docx-templates"
29
+ ].includes(id);
30
+ },
31
+ onLog(level, log, handler) {
32
+ if (log.code === 'CIRCULAR_DEPENDENCY') {
33
+ return; // Ignore circular dependency warnings
34
+ }
35
+ handler(level, log); // otherwise, just print the log
36
+ }
37
+ };
38
+ };
@@ -0,0 +1,3 @@
1
+ import {buildConfig} from './rollup.config.base.js';
2
+
3
+ export default [buildConfig(false)];
@@ -0,0 +1,3 @@
1
+ import {buildConfig} from './rollup.config.base.js';
2
+
3
+ export default [buildConfig(true)];
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './lib/error-codes';
2
+
3
+ export * from './lib/print-word-step';
4
+ export * from './lib/print-word-step-builder';
@@ -0,0 +1,3 @@
1
+ import {O23ReservedErrorCode} from '@rainbow-o23/n1';
2
+
3
+ export const ERR_TEMPLATE_NOT_DEFINED: O23ReservedErrorCode = 'O07-00001';
@@ -0,0 +1,34 @@
1
+ import {PipelineStepType} from '@rainbow-o23/n1';
2
+ import {AbstractFragmentaryPipelineStepBuilder, FragmentaryPipelineStepBuilderOptions} from '@rainbow-o23/n4';
3
+ import {PrintWordPipelineStep, PrintWordPipelineStepOptions} from './print-word-step';
4
+
5
+ export type PrintWordPipelineStepBuilderOptions = FragmentaryPipelineStepBuilderOptions & {
6
+ cmd?: PrintWordPipelineStepOptions['cmdDelimiter'];
7
+ literalXmlDelimiter?: PrintWordPipelineStepOptions['literalXmlDelimiter'];
8
+ processLineBreaks?: PrintWordPipelineStepOptions['processLineBreaks'];
9
+ noSandbox?: PrintWordPipelineStepOptions['noSandbox'];
10
+ failFast?: PrintWordPipelineStepOptions['failFast'];
11
+ rejectNullish?: PrintWordPipelineStepOptions['rejectNullish'];
12
+ fixSmartQuotes?: PrintWordPipelineStepOptions['fixSmartQuotes'];
13
+ processLineBreaksAsNewText?: PrintWordPipelineStepOptions['processLineBreaksAsNewText'];
14
+ };
15
+
16
+ export class PrintWordPipelineStepBuilder
17
+ extends AbstractFragmentaryPipelineStepBuilder<PrintWordPipelineStepBuilderOptions, PrintWordPipelineStepOptions, PrintWordPipelineStep> {
18
+ protected getStepType(): PipelineStepType<PrintWordPipelineStep> {
19
+ return PrintWordPipelineStep;
20
+ }
21
+
22
+ protected readMoreOptions(given: PrintWordPipelineStepBuilderOptions, transformed: PrintWordPipelineStepOptions): PrintWordPipelineStepOptions {
23
+ transformed = super.readMoreOptions(given, transformed);
24
+ transformed.cmdDelimiter = given.cmd;
25
+ transformed.literalXmlDelimiter = given.literalXmlDelimiter;
26
+ transformed.processLineBreaks = given.processLineBreaks;
27
+ transformed.noSandbox = given.noSandbox;
28
+ transformed.failFast = given.failFast;
29
+ transformed.rejectNullish = given.rejectNullish;
30
+ transformed.fixSmartQuotes = given.fixSmartQuotes;
31
+ transformed.processLineBreaksAsNewText = given.processLineBreaksAsNewText;
32
+ return transformed;
33
+ }
34
+ }
@@ -0,0 +1,84 @@
1
+ import {PipelineStepData, PipelineStepPayload, UncatchableError} from '@rainbow-o23/n1';
2
+ import {AbstractFragmentaryPipelineStep, FragmentaryPipelineStepOptions} from '@rainbow-o23/n3';
3
+ import {createReport} from 'docx-templates';
4
+ import {UserOptions} from 'docx-templates/lib/types';
5
+ import {ERR_TEMPLATE_NOT_DEFINED} from './error-codes';
6
+
7
+ export interface PrintWordPipelineStepOptions<In = PipelineStepPayload, Out = PipelineStepPayload, InFragment = In, OutFragment = Out>
8
+ extends FragmentaryPipelineStepOptions<In, Out, InFragment, OutFragment> {
9
+ cmdDelimiter?: string | [string, string];
10
+ literalXmlDelimiter?: string;
11
+ processLineBreaks?: boolean;
12
+ noSandbox?: boolean;
13
+ failFast?: boolean;
14
+ rejectNullish?: boolean;
15
+ fixSmartQuotes?: boolean;
16
+ processLineBreaksAsNewText?: boolean;
17
+ }
18
+
19
+ export interface PrintWordPipelineStepInFragment {
20
+ template: Buffer;
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ data: any;
23
+ // eslint-disable-next-line @typescript-eslint/ban-types
24
+ jsContext?: Object;
25
+ }
26
+
27
+ export interface PrintWordPipelineStepOutFragment {
28
+ file: Buffer;
29
+ }
30
+
31
+ export class PrintWordPipelineStep<In = PipelineStepPayload, Out = PipelineStepPayload, >
32
+ extends AbstractFragmentaryPipelineStep<In, Out, PrintWordPipelineStepInFragment, PrintWordPipelineStepOutFragment> {
33
+ private readonly _cmdDelimiter?: string | [string, string];
34
+ private readonly _literalXmlDelimiter?: string;
35
+ private readonly _processLineBreaks?: boolean;
36
+ private readonly _noSandbox?: boolean;
37
+ private readonly _failFast?: boolean;
38
+ private readonly _rejectNullish?: boolean;
39
+ private readonly _fixSmartQuotes?: boolean;
40
+ private readonly _processLineBreaksAsNewText?: boolean;
41
+
42
+ public constructor(options: PrintWordPipelineStepOptions<In, Out, PrintWordPipelineStepInFragment, PrintWordPipelineStepOutFragment>) {
43
+ super(options);
44
+ this._cmdDelimiter = options.cmdDelimiter ?? '+++';
45
+ this._literalXmlDelimiter = options.literalXmlDelimiter ?? '||';
46
+ this._processLineBreaks = options.processLineBreaks ?? true;
47
+ this._noSandbox = options.noSandbox ?? true;
48
+ this._failFast = options.failFast ?? true;
49
+ this._rejectNullish = options.rejectNullish ?? false;
50
+ this._fixSmartQuotes = options.fixSmartQuotes ?? false;
51
+ this._processLineBreaksAsNewText = options.processLineBreaksAsNewText ?? false;
52
+ }
53
+
54
+ protected getCreateReportOptions(): Omit<UserOptions, 'template' | 'data'> {
55
+ return {
56
+ cmdDelimiter: this._cmdDelimiter,
57
+ literalXmlDelimiter: this._literalXmlDelimiter,
58
+ processLineBreaks: this._processLineBreaks,
59
+ noSandbox: this._noSandbox,
60
+ failFast: this._failFast,
61
+ rejectNullish: this._rejectNullish,
62
+ fixSmartQuotes: this._fixSmartQuotes,
63
+ processLineBreaksAsNewText: this._processLineBreaksAsNewText
64
+ };
65
+ }
66
+
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
68
+ protected async printWord(templateWord: Buffer, data: any, jsContext?: Object): Promise<Buffer> {
69
+ const buffer = await createReport({
70
+ ...this.getCreateReportOptions(),
71
+ template: templateWord, data, additionalJsContext: jsContext
72
+ });
73
+ return Buffer.from(buffer.buffer);
74
+ }
75
+
76
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
77
+ protected async doPerform(data: PrintWordPipelineStepInFragment, _request: PipelineStepData<In>): Promise<PrintWordPipelineStepOutFragment> {
78
+ if (data.template == null) {
79
+ throw new UncatchableError(ERR_TEMPLATE_NOT_DEFINED, 'Print template cannot be null.');
80
+ }
81
+ const file = await this.printWord(data.template, data.data, data.jsContext);
82
+ return {file};
83
+ }
84
+ }
@@ -0,0 +1,58 @@
1
+ import {createConfig, createLogger} from '@rainbow-o23/n1';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import {PrintWordPipelineStep} from '../src';
5
+
6
+ const logger = createLogger();
7
+ const config = createConfig(logger);
8
+
9
+ test('Test Print Word', async () => {
10
+ const template = fs.readFileSync(path.resolve(__dirname, 'template.docx'));
11
+ const data = {
12
+ createDate: '2023-12-1',
13
+ project: {
14
+ name: 'Rainbow O23',
15
+ url: 'https://github.com/InsureMO/rainbow-o23'
16
+ },
17
+ groups: [
18
+ {
19
+ depts: [
20
+ {name: 'Development', cashierName: 'John', cashInTxn: 134.4, cashInAmt: 1344},
21
+ {name: 'Research', cashierName: 'Joe', cashInTxn: 159.67, cashInAmt: 1277.36}
22
+ ],
23
+ subTotal: {
24
+ cashInTxn: 294.07, cashInAmt: 2621.36
25
+ }
26
+ },
27
+ {
28
+ depts: [
29
+ {name: 'Development', cashierName: 'John', cashInTxn: 104.4, cashInAmt: 1044},
30
+ {name: 'Research', cashierName: 'Joe', cashInTxn: 129.67, cashInAmt: 977.36}
31
+ ],
32
+ subTotal: {
33
+ cashInTxn: 234.07, cashInAmt: 2021.36
34
+ }
35
+ }
36
+ ]
37
+ };
38
+
39
+ const step = new PrintWordPipelineStep({config, logger});
40
+ const {content: {file}} = await step.perform({
41
+ content: {
42
+ template, data, jsContext: {
43
+ link: () => {
44
+ return {url: data.project.url, label: data.project.name};
45
+ },
46
+ logo: () => {
47
+ const data = Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="20" role="img" aria-label="InsureMO"><title>InsureMO</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="63" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#777af2"/><rect x="0" width="63" height="20" fill="#777af2"/><rect width="63" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">InsureMO</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="530">InsureMO</text></g></svg>`, 'utf-8');
48
+ return {width: 1.66687479, height: 0.5291666, data, extension: '.svg'};
49
+ },
50
+ html: () => {
51
+ return `<meta charset="UTF-8"><body><strong style="color: red;">This paragraph should be red and strong</strong></body>`;
52
+ }
53
+ }
54
+ }
55
+ });
56
+ expect(file).not.toBeNull();
57
+ fs.writeFileSync(path.resolve(__dirname, 'output.docx'), file);
58
+ });
Binary file
package/tsconfig.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "outDir": "./dist",
5
+ "declarationDir": "./lib",
6
+ "module": "ES2015",
7
+ "target": "ESNext",
8
+ "moduleResolution": "node",
9
+ "sourceMap": true,
10
+ // create d.ts file
11
+ "declaration": true,
12
+ "removeComments": true,
13
+ "noImplicitAny": false,
14
+ "allowSyntheticDefaultImports": true,
15
+ "allowUnreachableCode": true,
16
+ "allowUnusedLabels": false,
17
+ "alwaysStrict": true,
18
+ "allowJs": true,
19
+ "experimentalDecorators": true,
20
+ "lib": [
21
+ "es5",
22
+ "es2015",
23
+ "es2016",
24
+ "es2017",
25
+ "es2018",
26
+ "dom"
27
+ ]
28
+ },
29
+ "include": [
30
+ "src/**/*"
31
+ ],
32
+ "exclude": [
33
+ "node_modules",
34
+ "dist"
35
+ ]
36
+ }