Skip to content

Encoding with Office Render

Introduction

The office-render is a template engine inspired by Mustache, but focused on Microsoft Office files. It works by expanding {{tags}} present in the template with values, expressions, functions, and subtemplates provided in JSON format. It can be imported as a service module in node.js runtime or as a remote serverless function using an API.

The key differentiator of this engine is the use of Angular Expressions + Angular Filters. This means that, in addition to the conventional JSON values and Mustache lambdas, you can also use expressions similar to JavaScript, providing greater flexibility while maintaining security.

TL;DR;

The office-render follows a request-response architecture. Its usage involves sending a configuration payload and receiving a promise delivering a Microsoft Office document in case of success, or an error if the document cannot be generated with the provided configurations.

// valores marcados entre '/* */' são opcionais
// , ... significa que valores adicionais podem ser enviados
const payload = {
"template": "url || base64",
"datasource": { "key": "value"/*, ...*/ },
/* "partials": { "name": "url || base64", ... }, */
/* "lambdas": { "name": ["param", ..., "functionBody"], ... }, */
/* "path": "looplex-ged:domain.com/shared/office-render/out/result.docx" */
}

To use as a service:

import { generate as createDocument } from "~/path/to/office-render.js";
const /* base64 */ document = await createDocument(payload);

To use as API:

Set header Content-Type: application/json; charset=utf-8 and send:

POST`${basepath}/api/${version}/office-render`;
JSON.stringify(payload); // { "status": "SUCCESS|FAILURE", "message": "base64" }
For example, imagine the `docx` template below hosted at the url <https://assets.domain.com/templates/office/congratulation.docx>:
```js
Olá {{username}},
Parabéns, Você acabou de ganhar {{amount|numberFormat:'pt-BR':'{ "style": "currency", "currency": "BRL" }'}}!
{{#in_br}}
Bom, {{(amount * 0.3625)|numberFormat:'pt-BR':'{ "style": "currency", "currency": "BRL" }'}}, após pagar os impostos.
{{/}}

Given the following config:

{
"template": "https://assets.domain.com/templates/office/congratulation.docx",
"datasource": {
"username": "João Ninguém",
"amount": 1000.0,
"in_br": true
}
}

Will produce:

Olá João Ninguém,
Parabéns, Você acabou de ganhar R$ 1.000,00!
Bom, R$ 362,50, após pagar os impostos.

MUSTACHE TAGS

Tags are indicated by using double braces {{tag}} or triple braces {{{tag}}}. Let’s explore their different types:

Undefined

The decoupling between the view created by the designer and the model provided by engineering leads to a special case: when the view expects a value, but it is missing from the model. In this case, the tag will not be interpolated and will remain in the final document.

Variables

The simplest type of tag is the {{variable}}. It will look for the value stored in the variable key of the current context. If variable is not found in the current context, the parent contexts will be evaluated recursively.

::: important Variables marked with double braces {{html_escaped}} are HTML escaped by default for security purposes. If the author needs the raw content, triple braces mustache {{{unescaped_html}}} should be used. :::

Sections and Inverted Sections

Sections are useful for altering the context for a portion. The behavior differs depending on the value associated with the key. A section starts with a hash # and ends with a slash /. That is, {{section}} starts a section and {{/section}} ends a section (it can also be simply {{/}}).

While sections are useful for altering the context for a portion, inverted sections are useful for capturing the absence of content. An inverted section starts with a caret ^ and ends with a slash /. That is, {{^inverted_section}} starts an inverted section and {{/inverted_section}} ends the inverted section (it can also be simply {{/}}).

Booleans

If the {{#boolean}} section is true, the content defined in the section will be rendered; if false, the content will be ignored. For example:

Template

Parágrafo fixo.
{{#boolean}}
Parágrafo condicional caso verdadeiro.
{{/}}{{^boolean}}
Parágrafo condicional caso falso.
{{/}}

Config

{
"datasource": {
"boolean": false
}
}

Result

Parágrafo fixo.
Parágrafo condicional caso falso.

Lists

If the section {{#array}} exists and contains content, the part will be re-evaluated for each value in the list; if it is empty, the content will be ignored. For example:

Template

{{#array}}
* {{.}}
{{/}}{{^array}}
A lista está vazia
{{/}}

Config

{
"datasource": {
"list": ["Alice", "Bob", "Charlie"]
}
}

Result

* Alice
* Bob
* Charlie

Objects

If the section {{#object}} exists, the part will be re-evaluated with the context updated to object. For example:

Template:

{{#postalAddress}}
{{streetAddress}}
{{postOfficeBoxNumber}}
{{addressLocality}}, {{addressRegion}} {{postalCode}}
{{addressCountry}}
{{/}}

Config:

{
"datasource": {
"postalAddress": {
"streetAddress": "1600 Amphitheatre Pkwy",
"postOfficeBoxNumber": "1982",
"addressLocality": "Mountain View",
"addressRegion": "California",
"addressCountry": "USA",
"postalCode": "94043"
}
}
}

Result:

1600 Amphitheatre Pkwy
Tower Z, 1982
Mountain View, California 94043
USA

Lambdas

When the value associated with the key is a function, it will be executed with the current scope context as the first argument and the scopeManager context handler as the second argument. For example:

Template:

{{#users}}
{{greeter}}
{{/}}

Config:

{
"datasource": {
"users": [
{ "name": "Alice" },
{ "name": "Bob" },
{ "name": "Charlie" }
]
},
"lambdas": {
"greeter": "return `Olá ${scope.name}, como vai?`"
}
}

Result:

Olá Alice, como vai?
Olá Bob, como vai?
Olá Charlie, como vai?

Another example

Template:

O recorrente {{fullname}}, {{identidade}} ...

Config:

{
"user": {
"fullname": "Alice Bates",
"isOrganization": false,
"federalTaxNumber": "051-96-9495"
},
"lambda": {
"identidade": "if (scope.user.isOrganization) { return `SSN: ${scope.user.federalTaxNumber}` } else { return `DUNS: ${scope.user.federalTaxNumber}` }"
}
}

Result:

O recorrente Alice Bates, SSN 051-96-9495 ...

::: tip This was an illustrative example of if-else. In the previous context, it would be ideal to use a ternary operator. e.g. const {isOrganization, federalTaxNumber} = scope.user; return ${isOrganization ? 'DUNS' : 'SSN'} ${federalTaxNumber} :::

ANGULARJS EXPRESSIONS

In mathematics, when we combine numbers and variables in a valid way, using operators such as addition, subtraction, multiplication, division, exponentiation et cetera and functions, we call this a mathematical expression — the combined aggregation of these symbols.

In programming languages, an expression is a unit of code that can be reduced to a value. In JavaScript, there are two types of expressions: those that produce side effects (such as assigning a value) and those that do not (such as the result of a calculation).

The expression x = 7 is an example of the first type. This expression uses the = operator to assign the value seven to the variable x. The result of the expression is 7.

The expression 3 + 4 is an example of the second type. This expression uses the + operator to add 3 and 4, producing the value 7, known as sum.

As mentioned earlier, the key differentiator of this engine is the use of AngularJS Expressions. Extracted from the source code of the Angular.js project, they allow for simplified creation of complex templates. Examples of AngularJS Expressions are:

Assignment Operators

An assignment operator assigns the value or result of the right operand to the left operand. The simplest assignment operator is the equal sign (=), and a classic example of this operator in use is y = f(x).

OperatorDescriptionExample
Assignment =Assigns the value of the right operand to the left operandx = 1

Comparison Operators

A comparison operator evaluates its operands and returns a logical value based on whether the comparison is true or not. The operands can be numeric, textual, boolean, or object properties. Textual values are compared using the standard lexicographical order with Unicode values.

OperatorDescriptionExample
Equal ==Returns true if the operands are equal1 == '1'
Not Equal !=Returns true if the operands are not equal1 != 2
Strict Equal ===Returns true if the operands are equal and have the same type1 === 1
Strict not equal !==Returns true if the operands are not equal1 !== '1'
Greater than >Returns true if the left operand is greater than the right operand2 > 1
Greater than or equal >=Returns true if the left operand is greater than or equal to the right operand1 >= 1
Less than <Returns true if the left operand is less than the right operand1 < 2
Less than or equalReturns true if the left operand is less than or equal to the right operand1 <= 1

Arithmetic Operators

Arithmetic operators use numeric values, either literals or variables, as operands and return another numeric value as the result. The standard arithmetic operators are addition +, subtraction -, multiplication *, and division /. These operators function similarly to how they are expected in other programming languages when we consider the operator $\circ$:

$$\circ : IEEE754^n \rightarrow IEEE754$$

::: important A peculiarity of division in this engine is that division by zero produces Infinity due to the approximated nature of the floating-point specification. :::

In addition to the well-known +, -, *, /, the following are other available arithmetic operators:

OperatorDescriptionExample
Remainder %Binary operator. Returns the remainder value of the division between two operands12 % 5 results in 2
Unary negation -Unary operator. Returns the additive inverse of the operandIf x = 1, the value of -x is -1
Unary plus +Unary operator. Returns the numeric version of an operand+'1' returns 1

Logical Operators

Logical operators are typically used with boolean values, but in this engine, they work with falsy and truthy values. Operands like && and || return the value of one of their operands, so if the values are not boolean, the expressions will return that value. Below is the definition and table of falsy and truthy values:

Falsy: A value considered false when applied in a boolean context

Truthy: Values that are the complement of Falsy

OperatorDescription
falseThe reserved word false
0The number ZERO (also 0.0, 0x0, i.e. any representation of ZERO)
-0The negation of the number ZERO
"", ''Empty textual content
nullThe reserved word null represents the intentional absence of an object
undefinedThe reserved word undefined represents that the type and value of a variable are still unknown
NaNThe reserved word NaN represents an algebraic result that cannot be represented as a number. Examples: 'render'**2, 'nagao'/2
document.allObjects are considered falsy if, and only if, they have an internal slot IsHTMLDDA defined by the execution engine.

Once falsy and truthy values are defined, we can describe the following logical operators:

OperatorDescriptionExample
Logical AND &&Returns expr1 if it is falsy, and expr2 otherwise. i.e. && results in true when both operands are truthyexpr1 && expr2
Logical OR ``
Logical NOT !Unary operator, returns false if the operand is truthy!expr

Text Operators

In addition to comparison operators, which also work for text, the concatenation operator + concatenates two text operands, returning the sequential union from left to right of the operands.

'ET, ' + 'telefone, ' + 'minha casa...'// imprime 'ET, telefone, minha casa...'

Ternary conditional operator

There is only one standard operator that takes three operands: the ternary conditional operator. In it, the result varies between two values depending on the condition. Its syntax is:

condição ? resultado_caso_verdadeira : resultado_caso_falsa

For Example:

idade >= 18 ? 'adulto' : 'menor'

This expression returns ‘adult’ if the variable idade has a value greater than or equal to eighteen years and ‘minor’ otherwise.

Differences between AngularJS Expressions and Javascript Expressions

  • Context: AngularJS Expressions use the scope object as context;
  • Lenient: In Javascript, evaluating properties with undefined triggers a ReferenceError or TypeError. In AngularJS Expressions, evaluation is permissive for undefined and null;
  • Filters: Additional transformations can be applied to the value of the expression before printing;
  • Absence of flow control: AngularJS Expressions do not support conditional statements, loops, or error triggering;
  • No function declarations: In AngularJS Expressions, it is not possible to declare new functions;
  • No regular expression declarations: In AngularJS Expressions, it is not possible to declare regular expressions;
  • No creation of new objects using the new operator: AngularJS Expressions do not interpret the new operator;
  • Absence of shortcut notation for assignment operations: In AngularJS Expressions, it is not possible to use +=, -=, *=, /=, %=, **=, <<=, >>=, >>>=, &=, ^=, |=, &&=, ||=, ??=;
  • Absence of increment, decrement, and exponentiation arithmetic operators: It is not possible to use operators ++, -- and **,
  • Absence of bitwise, comma, and void operators: It is not possible to use bitwise, ,, or void operators;
  • Absence of relational operators: It is not possible to use relational operators in and instanceof.

Tip: For complex rules, lambda expressions should be used and called by name in the template.

ANGULARJS FILTERS

Filters allow the transformation of the expression result before applying interpolation. To call a filter, the following notation should be used:

{{ expression | filter }}

It is possible to pass arguments to filters. For example:

{{users|separator:', ':' e '}}

Print

Alice, Bob e Charlie

Filters allow chaining. For example:

{{expression | filter_0 | filter_1 | filter_2}}
Below is the list of available filters:
| Identifier | Parameters | Example |
| ------------- | ------------- | ------------------- |
| separator | middle, last | `{{users |
| uppercase | | `{{'Office Render' |
| lowercase | | `{{'Office Render' |
| imageSize | width, height | `{{%image |
| imageMaxSize | width, height | `{{%image |
# IMAGES
It is possible to dynamically insert images using the _tags_ `{{%image}}` for text-level insertion and `{{%%image}}` for block-level insertion. Both base64 binary format and public URL content are allowed. Examples:
**Template:**
![Template Curriculum Vitae](./office-render/inline-image.png)
**Config:**
```json
{
"picture": "/9j/4AAQSkZJRgABAQAAAQABAAD//gAgQ29tcHJlc3NlZCBieSBqcGVnLXJlY29tcHJlc3MA/",
"fullname": "Angelica Astrom",
"role": "Partner",
"bio": "Sócia em direito societário, financeiro e de infraestrutura, Angelica é especialista em operações estruturadas e project finance. Altamente experiente em questões envolvendo os setores de transporte, logística e mineração.",
"email": "angelica.astrom@example.com",
"phone": "(11) 91234-5678",
"linkedin": "/in/angelica.astrom",
"city": "Nova Iorque, NY",
"areas_of_practice": [
"Bancário, seguros e financeiro",
"Financiamento de Projetos e infraestrutura",
"Mercado de capitais",
"Serviços financeiros",
"Reestruturação e insolvência",
"Contratos e negociações complexas"
],
"associations": [
"Ordem dos Advogados do Brasil (OAB);",
"International Bar Association (IBA);",
"Instituto Brasileiro de Estudos de Direito da Energia (IBDE)."
],
// continues ...
}

Another way is to use an image as a placeholder for another image. To do this, right-click on the placeholder image, select Edit Alt Text … and set {{%image}} as the content.

Right Click ButtonAlt Text
Right Click Menu width=290px[Edit Alt Text Screen]

WORD

HTML and Markdown

Web content creators can rejoice, as the engine supports applying Markdown and HTML. In general, allowed HTML elements in the <body> are either block-level or text-level (also known as inline). The distinction is based on several concepts:

  • Content: Generally, block-level elements can contain other block-level elements and also text-level elements; whereas text-level elements only contain data and other text-level elements. The key idea behind this structural separation is that block-level elements create larger structures than text-level elements.
  • Formatting: By default, the formatting of block-level and text-level elements is different. Block-level elements start new lines, whereas text-level elements do not.
  • Directionality: For technical reasons involving the operation of the bidirectional text algorithm unicode, block-level and text-level elements differ in how they handle the directionality of content.

Therefore, it is important to use the correct semantics when sending HTML content to office-render. To indicate that the tag will receive a text-level value (to print inside the paragraph), use the notation {{~inline}}, and to indicate that the tag will receive a block-level value (to create a new paragraph), use the notation {{~~block}}. Similarly, you can use Markdown with {{~inline|markdown}} for text-level content and {{~~block|markdown}} for block-level content.

What is Markdown?

Markdown is the text-to-HTML conversion tool for writers that triumphed over its competitors in the early 2000s and became the industry standard. It defines the writing of semantic textual content using a easy-to-read, easy-to-write notation that is mechanically converted into HTML. For example, this manual was entirely written using Markdown. In WordPress and GitHub, Markdown is used to write articles. For more information about its syntax, visit the playground.

List of supported text-level tags: <br />, <span>, <small>, <ins>, <del>, <strong>, <em>, <a>, <sub>, <sup>.

List of supported block-level tags: <p>, <h1>, <h2>, <h3>, <h4>, <h5>, <h6>, <ul>, <ol>, <li>, <pre>, <code>, <table>, <thead>, <tfoot>, <tr>, <th>, <td>, <img>.

List of supported style properties: font-family, font-size, color, background-color, text-decoration, text-align, vertical-align, border, break-before, break-after, width, height, padding-left.

::: important HTML and Markdown cannot be used in PowerPoint and Excel files. The fact that Word shares the same linear flow nature makes conversion easier. PowerPoint documents, on the other hand, have multiple slides, each stored in a different file with each element positioned absolutely relative to the slide. :::

Footnotes

To insert footnotes on a page, use the tag {{:footnote id}}, where id is the key in the datasource that contains the footnote.

Template:

Grace Brewster Murray Hopper (née Murray; December 9, 1906January 1, 1992) was an American computer scientist, mathematician, and United States Navy rear admiral.{{:footnote gracehopper}}

Config:

{
"datasource": {
"gracehopper": "<w:r><w:rPr><w:b/></w:rPr><w:t xml:space='preserve'>Amazing Grace: Rear Adm. Grace Hopper, USN, was a pioneer in computer science.</w:t></w:r><w:r><w:rPr><w:i/></w:rPr><w:t xml:space='preserve'>Military Officer. Vol. 12, no. 3. Military Officers Association of America. pp. 52–55, 106. Retrieved March 1, 2014.</w:t></w:r>"
}
}

Result:

Amazing Grace: Rear Adm. Grace Hopper, USN, was a pioneer in computer science.
Military Officer. Vol. 12, no. 3. Military Officers Association of America. pp. 52–55, 106. Retrieved March 1, 2014.

Subtemplates and Segments

The operation of inclusion in office-render is performed using subtemplates. That is, including other Word documents. For this, the engine offers two different types of tags: {{:include id}}, {{:segment}}, and {{:includesegment}}.

Subtemplates

The tag {{:include id}} allows the insertion of an “ancestral” document or template into the body of the template being constructed. For example:

Templates:

foo
Lorem ipsum
{{:include B}}
dolor sit amet

Config:

{
"datasource": {},
"partials": {
"B": "base64|url"
}
}

Result:

Lorem ipsum
foo
dolor sit amet
### Segments
The _tag_ `{{:includesegment id}}` allows the insertion of a content snippet or template within the template itself. For example:
**Template:**
| Billing Address | Shipping Address |
| --------------------------------------------------- | ---------------------------------------------------- |
| {{#billingAddress}}{{:includesegment address}}{{/}} | {{#shippingAddress}}{{:includesegment address}}{{/}} |
**Config:**
```json
{
"datasource": {
"billingAddress": {
"name": "HOME",
"line1": "1600 Amphitheatre Pkwy",
"postOfficeBox": "1982",
"city": "Mountain View",
"stateOrProvince": "California",
"country": "USA",
"postalCode": "94043"
},
"shippingAddress": {
"name": "WORK"
"line1": "1600 Amphitheatre Pkwy",
"postOfficeBox": "1982",
"city": "Mountain View",
"stateOrProvince": "California",
"country": "USA",
"postalCode": "94043"
}
}
}
**Result:**
![Segments Result](./office-render/segments-result.png){ width=290px }
# POWERPOINT
It is possible to create PowerPoint files through two different strategies: **explicit**, where the engineer specifies which slides will be used and with which contents, and **implicit**, where the designer uses markings to indicate whether a slide should appear conditionally or repeat during the flow defined in the original template.
## Implicit
The implicit use is inspired by a journey or _story_, very similar to the flow of a Word document. However, in the case of PowerPoint, the story is told from left to right.
**Template:**
| ![Slide 1](./office-render/implicit-slide-1.png){ width=290px } | ![Slide 2](./office-render/implicit-slide-2.png){ width=290px } |
| - | - |
| ![Slide 3](./office-render/implicit-slide-3.png){ width=290px } | ![Slide 4](./office-render/implicit-slide-4.png){ width=290px } |
**Config:**
```json
{
"intro": {
"motto": "Estamos reinterpretando o trabalho dos advogados no século 21",
"company": "Looplex"
},
"differentiation": {
"title": "Nosso diferencial",
"description": "Respostas rápidas, Qualidade, Preço Baixo. Utilizamos tecnologia na construção do seu contrato impecável, da sua contestação que utiliza as melhores estratégias, explora as provas e que não deixa pedra sobre pedra.",
"pain": "Chega de desperdiçar horas e horas, buscando teses que mais ou menos funcionam, com copia e cola, ajustes de texto e formatação!",
"solution": "Legal Digital Experience (DX) Platform"
},
"tooling": {
"title": "Complicado virando Simples",
"description": "Virando o jogo com o apoio da tecnologia e levando sua prática para um novo patamar diferenciando você dos concorrentes.",
"first": {
"title": "Engenharia Jurídica",
"description": "Conversão do seu conhecimento para o formato digital."
},
"second": {
"title": "Interfaces Online",
"description": "Para advogados, clientes e sistemas interajam."
},
"third": {
"title": "Smart Documents",
"description": "Muito além do documento impresso, os documentos inteligentes realizam tarefas e conversam com outros sistemas."
},
"fourth": {
"title": "Dados Estruturados",
"description": "Conteúdo que permite aplicar estatística, gerar reports e analisar estratégias."
},
},
"verifiableCustomerWins": {
"title": "Nossos Clientes",
"description": "Junte-se ao incrível grupo de vanguarda que está revolucionando a prática do direito e economizando até 95% do tempo sem trabalhos repetitivos.",
"brands": [...]
}
}
**Result:**
| ![Slide 1](./office-render/implicit-slide-1-result.png){ width=290px } | ![Slide 2](./office-render/implicit-slide-2-result.png){ width=290px } |
| - | - |
| ![Slide 3](./office-render/implicit-slide-3-result.png){ width=290px } | ![Slide 4](./office-render/implicit-slide-4-result.png){ width=290px } |
## Explicit
The explicit use is inspired by lego _blocks_. Each slide can be considered a block that is available for the engineer to sequence and repeat at will.
**Template:**
| ![Template Slide 1](./office-render/explicit-slide-1.png){ width=290px } | ![Template Slide 2](./office-render/explicit-slide-2.png){ width=290px } |
| - | - |
| ![Template Slide 3](./office-render/explicit-slide-3.png){ width=290px } | ![Template Slide 4](./office-render/explicit-slide-4.png){ width=290px } |
**Config:**
```json
{
"datasource": {
"slides": [
{
"$slide": 1,
"subtitle": "action",
"title": "office-render"
},
{
"$slide": 4,
"motto": "Criação automática de documentos Microsoft Office focada em pessoas"
}
]
}
}
**Result:**
| Slide 1 | Slide 2 |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| ![Slide 1](https://kb.looplex.com.br/pt-BR/technical/code/office-render/explicit-slide-1-result.png){ width=290px } | ![Slide 2](https://kb.looplex.com.br/pt-BR/technical/code/office-render/explicit-slide-4-result.png){ width=290px } |
# ADVANCED TOPICS
## Raw OOXML
The standard that defines `docx`, `pptx`, and `xlsx` files is called [ECMA-376](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/). For those familiar with the dialect, it is possible to inject `ooxml`s using the notation `{{@ooxml}}`. With this notation, the entire paragraph (`<w:p>`) is replaced by the content of the `ooxml` key. For example:
**Template:**
```js
{{@ooxml}}

Config:

{
"datasource": {
"ooxml": "<w:p><w:pPr><w:spacing w:before='120' w:after='120'/><w:ind w:left='720' w:right='720'/><w:rPr><w:sz w:val='18'/></w:rPr></w:pPr><w:r><w:rPr><w:sz w:val='18'/></w:rPr><w:t xml:space='preserve'>Humpty Dumpty sat on a wall. </w:t></w:r></w:p>"
}
}

Dash Syntax

Usually, when we use sections, the engine will assume which Office element it will use in the iteration. For example, if between {{#tags}} ... {{/}}:

  • If there is a table cell (<w:tc> or <a:tc>), the engine will iterate over the table row (<w:tr> or <a:tr>),
  • In other cases, the engine will not expand the loop and will remain restricted to the part defined in the section.

With the Dash Syntax notation, the author can define the ooxml element they wish to iterate over. For example, if the goal is to create a new paragraph for each item in a list, one could do the following:

{{-w:p paragraphs}}{{.}}{{/}}

Javascript

The office-render engine, its extensions, and the lambdas used at runtime utilize the Javascript language. The execution runtime is continuously updated according to the availability of new versions of NodeJS and V8 in serverless hosting environments. This ensures that new features that appear in the engine and language layer are also made available in template creation.