@rainbow-o23/n7
TypeScript icon, indicating that this package has built-in type declarations

1.0.50 • Public • Published

Static Badge

Docx-templates

Module Formats

o23/n7

o23/n7 provides

  • A pipeline step that converts word templates to word, implemented based on Docx-templates.

Word Generate Step

Constructor Parameters

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.

Request and Response

export interface PrintWordPipelineStepInFragment {
	template: Buffer;
	data: any;
	jsContext?: Object;
}

export interface PrintWordPipelineStepOutFragment {
	file: Buffer;
}

Syntax

Find template and unit test in /test folder, syntax for using a Word template is as follows.

Command Delimiters

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.

Property Value

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.

Image or SVG

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 and height 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 to 1.66687479cm.

Link

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};
	};
}

For Loop

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 from 0.

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+++

If Statement

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+++

HTML

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.

Known Issues

  • 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.

Performance Benchmark

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

Readme

Keywords

none

Package Sidebar

Install

npm i @rainbow-o23/n7

Weekly Downloads

81

Version

1.0.50

License

MIT

Unpacked Size

64.1 kB

Total Files

21

Last publish

Collaborators

  • build_admin