o23/n7
provides
- A pipeline step that converts word templates to word, implemented based on Docx-templates.
Name | Type | Default Value | Comments |
---|---|---|---|
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. |
literalXmlDelimiter | string | || |
The delimiter that's used to indicate literal XML that should be inserted into the docx XML tree as-is. |
processLineBreaks | boolean | true | Handle linebreaks in result of commands as actual linebreaks. |
failFast | boolean | true | Whether to fail on the first error encountered in the template. |
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. |
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. |
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. |
export interface PrintWordPipelineStepInFragment {
template: Buffer;
data: any;
jsContext?: Object;
}
export interface PrintWordPipelineStepOutFragment {
file: Buffer;
}
Find template and unit test in /test
folder, syntax for using a Word template is as follows.
The default command delimiters are +++
and +++
. This means that commands are written as +++command+++
. You can change the delimiters
by setting the cmd
option to a string or an array of strings with length 2: ['{', '}']
, ['{#', '#}']
for example.
The following syntaxes are equivalent. You can choose based on your needs:
-
+++INS project.name+++
, -
+++= project.name+++
, -
+++project.name+++
.
It is important to note that the content supports JavaScript syntax. Therefore, the following notation is also equally effective:
-
+++INS ``${project.name ?? ''}``+++
.
Always define the Word document style you need across the entire command. If it is only defined within the command content, the style will not take effect.
Use the IMAGE
syntax to replace images or SVG. This syntax calls a context function to retrieve image details, as follows:
-
+++IMAGE logo()+++
.
When using the syntax above, please ensure that the corresponding function is already provided in the jsContext
context object passed as a
parameter,
jsContext: {
logo: () => {
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');
return {width: 1.66687479, height: 0.5291666, data, extension: '.svg'};
}
}
Please note that the units for
width
andheight
are in centimeters (cm), not in pixel values. Generally, it can be assumed that at a resolution of 96 DPI, approximately one pixel is equal to1.66687479cm
.
Use the LINK
syntax to replace links. This syntax also can call a context function to retrieve link details, as follows:
-
+++LINK ({ url: project.url, label: project.name })+++
, -
+++LINK link()+++
.
When using the syntax above, please ensure that the corresponding function is already provided in the jsContext
context object passed as a
jsContext: {
link: () => {
// data is the data passed to the print step
return {url: data.project.url, label: data.project.name};
};
}
Use the FOR ... IN
, END-FOR
syntax to loop through the array, as follows:
+++FOR group IN groups+++
Group (+++`${$idx + 1}`+++)
Cash In Tax: +++= $group.subTotal.cashInTxn+++
Cash In Amount: +++= $group.subTotal.cashInAmt+++
Departments:
+++FOR dept IN $group.depts+++
Department (+++`${$idx + 1}`+++)
Name: +++$dept.name+++
Cashier: +++$dept.cashierName+++
Cash In Tax: +++$dept.cashInTxn+++
Cash In Amount: +++$dept.cashInAmt+++
+++END-FOR dept+++
+++END-FOR group+++
From the sample above, it can be observed that nested loops are also supported.
It is possible to get the current element index of the inner-most loop with the variable
$idx
, starting from0
.
To obtain the array data (arrayPropertyName
) used in the loop body and define a variable name (variableName
) that represents each array
element in the loop, you can use the FOR variableName IN arrayPropertyName
syntax. In the example above, they are respectively:
-
+++FOR group IN groups+++
, -
+++FOR dept IN $group.depts+++
.
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 follows:
-
+++END-FOR dept+++
, -
+++END-FOR group+++
.
Please note that when ending the loop, you need to use the variable name defined in the loop start.
The following syntax can be used to output a table using loops:
Group | Department | Cashier | Cash In Tax | Cash In Amount |
---|---|---|---|---|
+++For group In groups+++ |
||||
Group (+++``${$idx + 1}``+++) |
+++= $group.subTotal.cashInTxn+++ |
+++= $group.subTotal.cashInAmt+++ |
||
+++FOR dept IN $group.depts+++ |
||||
+++$dept.name+++ |
+++$dept.cashierName+++ |
+++$dept.cashInTxn+++ |
+++$dept.cashInAmt+++ |
|
+++END-FOR dept+++ |
||||
+++END-FOR group+++ |
Use the IF ... END-IF
syntax to implement the if statement, as follows:
+++FOR group IN groups+++
Group (+++`${$idx + 1}`+++)
Cash In Tax: +++= $group.subTotal.cashInTxn+++
Cash In Amount: +++= $group.subTotal.cashInAmt+++
Departments:
+++FOR dept IN $group.depts+++
+++IF $dept.name === 'Development'+++
Department (+++`${$idx + 1}`+++)
Name: +++$dept.name+++
Cashier: +++$dept.cashierName+++
Cash In Tax: +++$dept.cashInTxn+++
Cash In Amount: +++$dept.cashInAmt+++
+++END-IF+++
+++END-FOR dept+++
+++END-FOR group+++
From the sample above, it can be observed that
IF ... END-IF
can be applied inside loop.
The IF statement
actually executes the given JavaScript syntax and determines whether to output the internal content based on the boolean
value of the result:
-
+++IF $dept.name === 'Development'+++
, -
+++END-IF+++
.
Similarly, IF ... END-IF
can also be used in table output:
Group | Department | Cashier | Cash In Tax | Cash In Amount |
---|---|---|---|---|
+++For group In groups+++ |
||||
Group (+++``${$idx + 1}``+++) |
+++= $group.subTotal.cashInTxn+++ |
+++= $group.subTotal.cashInAmt+++ |
||
+++FOR dept IN $group.depts+++ |
||||
+++IF $dept.name === 'Development'+++ |
||||
+++$dept.name+++ |
+++$dept.cashierName+++ |
+++$dept.cashInTxn+++ |
+++$dept.cashInAmt+++ |
|
+++END-IF+++ |
||||
+++END-FOR dept+++ |
||||
+++END-FOR group+++ |
Replace HTML-based text with HTML
syntax, as follows:
+++HTML `
<meta charset="UTF-8">
<body>
<h1>${$film.title}</h1>
<h3>${$film.releaseDate.slice(0, 4)}</h3>
<p>
<strong style="color: red;">This paragraph should be red and strong</strong>
</p>
</body>
`+++
HTML
executes a JavaScript script, which can also be achieved through jsContext
, for example:
-
+++HTML html()+++
.
jsContext: {
html: () => {
return `<meta charset="UTF-8"><body><strong style="color: red;">This paragraph should be red and strong</strong></body>`;
}
}
It should be noted that the content of HTML needs to be wrapped within the
<body>
tag, and it is recommended to use<meta charset="UTF-8">
to define the character set.
- 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 document in Word. Although this does not affect the final content display (the link will not work), it can easily confuse users. However, if it is a static link, it is not affected, - 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 document in Word. Additionally, HTML cannot display correctly and may also affect other content in the header and footer, - Odd and even row background colors are currently not supported.
This benchmark was conducted on the following hardware and environment:
- CPU: 2.6 GHz 6-Core Intel Core i7,
- Memory: 64 GB 2667 MHz DDR4,
- OS: macOS Sonoma 14.2.1,
- MySQL: 8.2.0,
- NodeJS: v18.19.0,
- NPM: v10.2.3.
With scenario:
- Flow:
- Load template from database,
- Print file,
- Write printed file to database,
- Return to client,
- Template size: 258kb, 9 pages,
- No command inside,
- Output size: 304kb, 9 pages.
# | Item | Max CPU Usage | Max Memory Usage | Avg. Response Time (ms) |
---|---|---|---|---|
1 | 100 iterations, single thread | 120% | 450M | 267 |
2 | 100 iterations, 4 concurrent threads | 130% | 650M | 236 |
3 | 100 iterations, 8 concurrent threads | 140% | 750M | 231 |