Go further
To go further on the understanding of how watson works, here is the (at the moment) document of the library docx-template.
Supported commands
Currently supported commands are defined below.
QUERY
You can use GraphQL, SQL, whatever you want: the query will be passed unchanged to your data query resolver.
+++QUERY
query getData($projectId: Int!) {
project(id: $projectId) {
name
details { year }
people(sortedBy: "name") { name }
}
}
+++
For the following sections (except where noted), we assume the following dataset:
const data = {
project: {
name: "docx-templates",
details: { year: "2016" },
people: [
{ name: "John", since: 2015 },
{ name: "Robert", since: 2010 },
],
},
};
INS (=, or nothing at all)
Inserts the result of a given JavaScript snippet:
+++INS project.name+++ (+++INS project.details.year+++)
or...
+++INS `${project.name} (${$details.year})`+++
Note that the last evaluated expression is inserted into the document, so you can include more complex code if you wish:
+++INS
const a = Math.random();
const b = Math.round((a - 0.5) * 20);
`A number between -10 and 10: ${b}.`
+++
You can also use this shorthand notation:
+++= project.name+++ (+++= project.details.year+++)
+++= `${project.name} (${$details.year})`+++
Even shorter (and with custom cmdDelimiter: ["{", "}"]):
{project.name} ({project.details.year})
You can also access functions in the additionalJsContext parameter to createReport(), which may even return a Promise. The resolved value of the Promise will be inserted in the document.
Use JavaScript"s ternary operator to implement an if-else structure:
+++= $details.year != null ? `(${$details.year})` : ""+++
EXEC (!)
Executes a given JavaScript snippet, just like INS or =, but doesn"t insert anything in the document. You can use EXEC, for example, to define functions or constants before using them elsewhere in your template.
+++EXEC
myFun = () => Math.random();
MY_CONSTANT = 3;
+++
+++! ANOTHER_CONSTANT = 5; +++
Usage elsewhere will then look like
+++= MY_CONSTANT +++
+++= ANOTHER_CONSTANT +++
+++= myFun() +++
A note on scoping:
When disabling sandbox mode (noSandbox: true), the scoping behaviour is slightly different. Disabling the sandbox will execute each EXEC snippet"s code in a with(this){...} context, where this is the "context" object. This "context" object is re-used between the code snippets of your template. The critical difference outside of sandbox mode is that you are not declaring functions and variables in the global scope by default. The only way to assign to the global scope is to assign declarations as properties of the context object. This is simplified by the with(context){} wrapper: all global declarations are actually added as properties to this context object. Locally scoped declarations are not. _The above examples should work in both noSandbox: true and noSandbox: false.
This example declares the test function in the context object, making it callable from another snippet.
test = () => {};
While the below example only declares test in the local scope of the snippet, meaning it gets garbage collected after the snippet has executed.
function test() {}
IMAGE
Includes an image with the data resulting from evaluating a JavaScript snippet:
+++IMAGE qrCode(project.url)+++
In this case, we use a function from additionalJsContext object passed to createReport() that looks like this:
additionalJsContext: {
qrCode: url => {
const dataUrl = createQrImage(url, { size: 500 });
const data = dataUrl.slice("data:image/gif;base64,".length);
return { width: 6, height: 6, data, extension: ".gif" };
},
}
The JS snippet must return an image object or a Promise of an image object, containing:
width: desired width of the image on the page in cm. Note that the aspect ratio should match that of the input image to avoid stretching.heightdesired height of the image on the page in cm.data: either an ArrayBuffer or a base64 string with the image dataextension: one of".png",".gif",".jpg",".jpeg",".svg".thumbnail[optional]: when injecting an SVG image, a fallback non-SVG (png/jpg/gif, etc.) image can be provided. This thumbnail is used when SVG images are not supported (e.g. older versions of Word) or when the document is previewed by e.g. Windows Explorer. See usage example below.alt[optional]: optional alt text.rotation[optional]: optional rotation in degrees, with positive angles moving clockwise.caption[optional]: optional caption displayed below the image
In the .docx template:
+++IMAGE injectSvg()+++
Note that you can center the image by centering the IMAGE command in the template.
In the createReport call:
additionalJsContext: {
injectSvg: () => {
const svg_data = Buffer.from(
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="10" y="10" height="100" width="100" style="stroke:#ff0000; fill: #0000ff"/>
</svg>`,
"utf-8"
);
// Providing a thumbnail is technically optional, as newer versions of Word will just ignore it.
const thumbnail = {
data: fs.readFileSync("sample.png"),
extension: ".png",
};
return {
width: 6,
height: 6,
data: svg_data,
extension: ".svg",
thumbnail,
};
};
}
LINK
Includes a hyperlink with the data resulting from evaluating a JavaScript snippet:
+++LINK ({ url: project.url, label: project.name })+++
If the label is not specified, the URL is used as a label.
HTML
Takes the HTML resulting from evaluating a JavaScript snippet and converts it to Word contents.
Important: This uses altchunk, which is only supported in Microsoft Word, and not in e.g. LibreOffice or Google Docs.
+++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>
`+++
FOR and END-FOR
Loop over a group of elements (resulting from the evaluation of a JavaScript expression):
+++FOR person IN project.people+++
+++INS $person.name+++ (since +++INS $person.since+++)
+++END-FOR person+++
Note that inside the loop, the variable relative to the current element being processed must be prefixed with $.
It is possible to get the current element index of the inner-most loop with the variable $idx, starting from 0. For example:
+++FOR company IN companies+++
Company (+++$idx+++): +++INS $company.name+++
Executives:
+++FOR executive IN $company.executives+++
- +++$idx+++ +++$executive+++
+++END-FOR executive+++
+++END-FOR company+++
Since JavaScript expressions are supported, you can for example filter the loop domain:
+++FOR person IN project.people.filter(person => person.since > 2013)+++
...
FOR loops also work over table rows:
----------------------------------------------------------
| Name | Since |
----------------------------------------------------------
| +++FOR person IN | |
| project.people+++ | |
----------------------------------------------------------
| +++INS $person.name+++ | +++INS $person.since+++ |
----------------------------------------------------------
| +++END-FOR person+++ | |
----------------------------------------------------------
Finally, you can nest loops (this example assumes a different data set):
+++FOR company IN companies+++
+++INS $company.name+++
+++FOR person IN $company.people+++
* +++INS $person.firstName+++
+++FOR project IN $person.projects+++
- +++INS $project.name+++
+++END-FOR project+++
+++END-FOR person+++
+++END-FOR company+++
IF and END-IF
Include contents conditionally (depending on the evaluation of a JavaScript expression):
+++IF person.name === "Guillermo"+++
+++= person.fullName +++
+++END-IF+++
Similarly to the FOR command, it also works over table rows. You can also nest IF commands
and mix & match IF and FOR commands. In fact, for the technically inclined: the IF command
is implemented as a FOR command with 1 or 0 iterations, depending on the expression value.
ALIAS (and alias resolution with *)
Define a name for a complete command (especially useful for formatting tables):
+++ALIAS name INS $person.name+++
+++ALIAS since INS $person.since+++
----------------------------------------------------------
| Name | Since |
----------------------------------------------------------
| +++FOR person IN | |
| project.people+++ | |
----------------------------------------------------------
| +++*name+++ | +++*since+++ |
----------------------------------------------------------
| +++END-FOR person+++ | |
----------------------------------------------------------