Pular para o conteúdo principal

Vínculo de dados e templates

Cada campo aponta pro JSON real via um Binding:

  • scalar — um valor direto (caminho.no.json).
  • template — texto livre com {token}, ex.: "Cliente: {nome} — Total: {SUM(rows.total)}".
  • array — uma tabela vinda de um array de objetos, coluna por coluna, incluindo coluna calculada (rótulo fixo + fórmula avaliada por linha — pode combinar texto fixo com mais de um token: "{pnr} - {produto}").
  • keyvalue — tabela "campo / valor" a partir de uma lista de paths escolhidos manualmente.
  • section — o array que uma seção repete, um item por repetição.
  • chart — um array a agregar num gráfico pizza/barra (path, labelColumn, valueColumn, filters opcional) — veja Gráficos.
  • kpi — um array a agregar (sum/count/avg/min/max) num número só pra um cartão KPI, sobrescrevendo o value livre — veja Cartões KPI.

Escrevendo um caminho​

Um caminho pode ser escrito nu ou delimitado, e a diferença importa por um motivo só: o caminho nu quebra no ., então uma chave de JSON cujo nome contém ponto não tem forma nua.

EscritoResolve
{id}a chave id
{cliente.nome}caminha cliente → nome
{[id]}a chave id — a forma delimitada da mesma coisa
{[cliente].[nome]}caminha cliente → nome
{[cliente.nome]}a chave literal "cliente.nome"; ponto dentro do bracket não separa
{["token name"]}a chave "token name"
{[total] + 1}conta — o operador está fora do bracket
{CURRENCY([total], "R$", 2)}caminho delimitado como argumento de função

Duas regras que vale dizer na cara:

  • Segmento com espaço tem que vir entre aspas — ["minha chave"], não [minha chave]. Neste formato um operador só é operador com espaço dos dois lados, então [a + b] seria ambíguo entre a chave "a + b" e uma conta dentro do bracket. É recusado em vez de adivinhado.
  • As aspas são " ou ', a que o conteúdo permitir. Não há caractere de escape; chave que contém " se escreve entre '. O tokenFor escolhe por você.

A forma nua não está depreciada e não vai ficar — {cliente.nome}, {my-key} e {my key} são os caminhos que sempre foram. Use a delimitada quando a chave precisar, ou sempre, por uniformidade: o editor escreve ela em todo lugar e mostra a forma curta na célula do canvas, então {[id]} é o que fica gravado e {id} é o que você lê.

Uma chave mudou de significado na 3.2.0

{[a]} era a chave literalmente chamada [a], porque o lexer absorvia os brackets no identificador. Agora é o caminho a. Se você tem uma chave de JSON com bracket no nome, escreva {["[a]"]}.

Funções de template​

Dentro de {...} (texto, coluna calculada, célula de rodapé — em qualquer lugar):

FunçãoO que faz
SUM, COUNT, AVGAgrega um path de array.
CONCAT, UPPER, LOWERHelpers de string.
TRIM(caminho)Tira espaço do início/fim — útil pra campo de sistema legado com largura fixa ("fatura": " 01156189"). {token}/CONCAT preservam o valor exatamente como veio, de propósito — o espaço só some se você pedir.
DATE(caminho, "saída"[, "entrada"])Formata uma data — ver abaixo.
CURRENCY(caminho, "R$")Formata um número com símbolo de moeda.
NUMBER(caminho, casas)Tipo %.2f do C — só casas decimais, sem símbolo/separador de milhar (isso é o CURRENCY).
IF(condição, "então", "senão")Condicional — ver abaixo.

Aritmética simples também funciona: {qtd * preco}, {subtotal - desconto}.

Como uma expressão é lida. * e / ligam mais forte que + e -, e parênteses agrupam:

{qtd * preco + taxa} -> qtd * preco, depois + taxa
{(base + taxa) * qtd} -> base + taxa primeiro, depois * qtd

Uma regra é incomum e vale conhecer, porque é ela que permite uma chave JSON com hífen ou com espaço: um operador só é operador quando tem espaço em branco dos dois lados.

{my-key} -> o path "my-key" (não "my menos key")
{my key} -> o path "my key"
{a - b} -> subtração
{a -b} -> nenhum dos dois: o path "a -b", que normalmente dá vazio

O mesmo vale pros operadores de comparação dentro do IF: IF(a == 2, …) é comparação, IF(a==2, …) é um path chamado a==2.

Expressão que não consegue produzir número resolve pra vazio em vez de falhar: {"x" + 1}, {a / 0} e um path que não existe se comportam assim (um path ausente conta como 0 dentro de uma soma, então {naoexiste + a} dá o valor de a). Erro de sintaxe — aspas não fechadas, parêntese aberto — também resolve aquele campo pra vazio em vez de derrubar o documento inteiro: uma vírgula esquecida não pode custar um relatório de 200 páginas. A posição do erro é reportada no editor (o aviso do campo, e ao vivo dentro do editor ƒx), que é onde ainda dá pra consertar. O expressionError(source, t?) dá a mesma mensagem programaticamente, e o parse estoura se você quiser a versão estrita.

Uma função pode receber outra função ou uma expressão aritmética como argumento ({CURRENCY(SUM(rows.total), "R$")}), incluindo duas combinadas por operador ({SUM(a) - SUM(b)} subtrai certo), com uma exceção: o argumento do próprio SUM, COUNT e AVG é sempre lido como um path de array cru, nunca resolvido como outra chamada de função aninhada — {SUM(CONCAT(a, b))} não funciona, tem que ser um path simples tipo rows.total. O resultado deles continua podendo entrar dentro de outra função por fora sem problema (é isso que faz {CURRENCY(SUM(rows.total), "R$")} acima funcionar — quem resolve o próprio argumento ali é o CURRENCY, não o SUM).

IF(condição, "então", "senão") escolhe um dos dois últimos argumentos — condição é uma comparação (status == "paid", total > 100; operadores ==, !=, >, >=, <, <=, sempre cercados de espaço) ou um path/expressão isolada, checado como verdadeiro/falso (string vazia, "0" e "false" contam como falso, qualquer outra coisa como verdadeiro). Só o lado escolhido é resolvido de verdade — {IF(temDesconto, valorDesconto, "0")} não quebra mesmo que valorDesconto não exista no dado quando temDesconto é falso.

Datas são sempre lidas/escritas em UTC​

O 3º argumento (opcional) do DATE diz o formato de entrada — sem ele, new Date(raw) do JS tenta adivinhar, e uma data tipo "10/04/2025" (dia 10) vira 10 de outubro (formato americano). Informando DATE(vencto, "DD/MM/YYYY", "DD/MM/YYYY"), lê exatamente como escrito, sem ambiguidade.

As datas são sempre lidas/escritas em UTC — não no fuso do navegador/servidor que gera o PDF. Uma data só ("2026-07-01", sem hora) sai igual ao que foi escrito, não importa onde rodar; um datetime com fuso explícito ("...T23:30:00-03:00") é convertido pro instante UTC equivalente.

Escrever uma expressão: o editor ƒx​

O <Designer> tem um botão ƒx ao lado de todo campo que aceita expressão: fórmula de coluna de tabela, cada célula da linha de totais, título/valor/legenda do KPI e conteúdo de um campo de texto. Ele abre uma janela com

  • os campos a que o schema está vinculado, à esquerda, em dois grupos. Campos de cada item do array vinculado (total, sem prefixo) são os que resolvem dentro de uma linha de tabela ou de uma seção repetida; os caminhos completos (faturas.total) são o que uma agregação precisa. Um SUM(total) com o campo do primeiro grupo não acharia nada — os dois escopos são a razão de a lista ser dividida.
  • um editor multilinha com autocomplete, oferecendo as funções acima mais AND/OR/NOT, com a dica de cada uma. Ele escreve os espaços que um operador precisa nos dois lados, então não consegue produzir operador de um lado só. As sugestões aparecem só dentro das chaves — fora delas é texto literal, e uma lista de funções ali só estorva.
  • validação ao vivo — as mesmas mensagens do aviso de campo do editor. Erro de sintaxe bloqueia o salvar; operador suspeito só avisa.

O editor guarda o valor do campo em si, com as chaves, aberto já com o que estava lá. Não há um segundo campo pra compor: a edição é no lugar, e prefixo literal fica onde está (FAT-{fatura}). Clicar num campo da esquerda insere o caminho nu dentro das chaves, ou {caminho} fora delas.

Ele também acusa chave desbalanceada, coisa que nada mais faz: o resolvedor de template casa /\{([^{}]+)\}/g, então uma { sem par simplesmente não casa e aquele trecho sai como texto literal no PDF — {CURRENCY(total impresso na cara. Não é erro de sintaxe de expressão (o parser nunca vê esse trecho) nem falha de geração; era só um campo saindo errado em silêncio.

As peças são exportadas pra quem monta UI própria: suggestAt, applySuggestion, insertAtCaret, wordAtCaret, ALL_SUGGESTIONS, mais tokenAtCaret e braceError pro lado das chaves (também no json-pdf-designer/server — são puras).

Funções públicas pra um vínculo de UI próprio​

buildInputs(data, bindings) // resolve todos os vínculos de uma vez
renderTemplate(template, data) // resolve um template livre "{token}"
resolveToken(token, data) // resolve um token/função isolado
rowsFromArrayBinding(list, columns) // array de objetos -> linhas de tabela
columnLabel(col), columnKey(col)
describeBinding(b, t?), describeBindingShort(b, t?) // t: Dict — default inglês
CUSTOM_FIELD_FUNCTIONS // lista das funções disponíveis