Os literais de modelo ES6 podem ser substituídos em tempo de execução (ou reutilizados)?


129

tl; dr: É possível tornar literal um modelo reutilizável?

Tenho tentado usar literais de modelo, mas acho que não entendi e agora estou ficando frustrado. Quer dizer, acho que entendi, mas "isso" não deveria ser como funciona, ou como deveria ser. Deve ser diferente.

Todos os exemplos que vejo (mesmo os modelos marcados) exigem que as "substituições" sejam feitas no momento da declaração e não no tempo de execução, o que me parece totalmente inútil para um modelo. Talvez eu esteja louco, mas um "modelo" para mim é um documento que contém tokens que são substituídos quando você o usa, não quando você cria, caso contrário, é apenas um documento (ou seja, uma string). Um modelo é armazenado com os tokens como tokens e esses tokens são avaliados quando você ... avalia.

Todo mundo cita um exemplo horrível semelhante a:

var a = 'asd';
return `Worthless ${a}!`

Isso é bom, mas se eu já souber a, eu apenas return 'Worthless asd'ou return 'Worthless '+a. Qual é o ponto? Seriamente. Ok, o ponto é preguiça; menos vantagens, mais legibilidade. Ótimo. Mas isso não é um modelo! Não IMHO. E MHO é tudo o que importa! O problema, IMHO, é que o modelo é avaliado quando é declarado, então, se o fizer, IMHO:

var tpl = `My ${expletive} template`;
function go() { return tpl; }
go(); // SPACE-TIME ENDS!

Uma vez que expletivenão é declarado, ele produz algo como My undefined template. Super. Na verdade, pelo menos no Chrome, não consigo nem declarar o template; ele lança um erro porque expletivenão está definido. O que eu preciso é ser capaz de fazer a substituição após declarar o modelo:

var tpl = `My ${expletive} template`;
function go() { return tpl; }
var expletive = 'great';
go(); // My great template

No entanto, não vejo como isso é possível, já que esses não são realmente modelos. Mesmo quando você diz que devo usar tags, não, elas não funcionam:

> explete = function(a,b) { console.log(a); console.log(b); }
< function (a,b) { console.log(a); console.log(b); }
> var tpl = explete`My ${expletive} template`
< VM2323:2 Uncaught ReferenceError: expletive is not defined...

Tudo isso me levou a acreditar que os literais de modelo têm nomes horrivelmente errados e deveriam ser chamados do que realmente são: heredocos . Eu acho que a parte "literal" deveria ter me avisado (como em, imutável).

Estou esquecendo de algo? Existe uma (boa) maneira de tornar literal um modelo reutilizável?


Eu te dou, literais de modelo reutilizáveis :

> function out(t) { console.log(eval(t)); }
  var template = `\`This is
  my \${expletive} reusable
  template!\``;
  out(template);
  var expletive = 'curious';
  out(template);
  var expletive = 'AMAZING';
  out(template);
< This is
  my undefined reusable
  template!
  This is
  my curious reusable
  template!
  This is
  my AMAZING reusable
  template!

E aqui está uma função ingênua de "auxiliar" ...

function t(t) { return '`'+t.replace('{','${')+'`'; }
var template = t(`This is
my {expletive} reusable
template!`);

... para torná-lo "melhor".

Estou inclinado a chamá-los de gutais de modelo por causa da área a partir da qual eles produzem sentimentos tortuosos.


1
Suporta tachado (mas não em comentários como este). Coloque seu texto em uma <strike>tag.
Pointy

Literais de modelo ES6 são principalmente para interpolação de String antiquada. Se você quiser modelos dinâmicos, use o Handlebars etc, ou a solução de template com tags do Pointy.
joews

1
Strings de modelo não são, apesar do nome, modelos . Consulte também Adiar execução para Strings de modelo ES6
Bergi

8
Você poderia, por favor, tornar sua postagem um pouco menos exigente? Além disso, parece que você pretendia escrever um tutorial no formato de perguntas e respostas . Se você fez isso, remova a parte " Eu te dou ... " da sua pergunta e poste-a como uma resposta .
Bergi

Percebo que há muitas respostas boas aqui. Talvez aceite um.
abalter

Respostas:


86

Para fazer esses literais funcionarem como outros mecanismos de modelo, é necessário que haja um formulário intermediário.

A melhor maneira de fazer isso é usar o Functionconstrutor.

const templateString = "Hello ${this.name}!";
const templateVars = {
    name: "world"    
}

const fillTemplate = function(templateString, templateVars){
    return new Function("return `"+templateString +"`;").call(templateVars);
}

console.log(fillTemplate(templateString, templateVars));

Tal como acontece com outros motores de modelo, você pode obter essa string de outros lugares, como um arquivo.

Pode haver problemas ao usar esse método, como tags de modelo são difíceis de usar, mas eles podem ser adicionados se você for inteligente. Você também não pode ter lógica JavaScript embutida por causa da interpolação tardia. Isso também pode ser remediado com alguma reflexão.


8
Agradável! Você pode até usarnew Function(`return \`${template}\`;`)
Ruben Stolk

E esses modelos podem ser compostos ou "incluídos" por meio de argumentos, chamando um método ou passando o resultado compilado de outro modelo.
Quentin Engles

Quentin, o que significa 'sem tags de modelo'? Obrigado!
mikemaccana

10
Esteja ciente de que esta string de modelo está meio 'escondida' para transpilação (ou seja, webpack) e, portanto, NÃO irá transpilar em algo suficientemente compatível (ou seja, IE11) no lado do cliente ...!
Frank Nocke

9
Vulnerabilidade XSS ? Detalhes neste JSFIDDLE
Kamil Kiełczewski

65

Você pode colocar uma string de modelo em uma função:

function reusable(a, b) {
  return `a is ${a} and b is ${b}`;
}

Você pode fazer a mesma coisa com um modelo com tag:

function reusable(strings) {
  return function(... vals) {
    return strings.map(function(s, i) {
      return `${s}${vals[i] || ""}`;
    }).join("");
  };
}

var tagged = reusable`a is ${0} and b is ${1}`; // dummy "parameters"
console.log(tagged("hello", "world"));
// prints "a is hello b is world"
console.log(tagged("mars", "jupiter"));
// prints "a is mars b is jupiter"

A ideia é permitir que o analisador de modelo separe as strings constantes da variável "slots" e, em seguida, retorne uma função que remenda tudo novamente com base em um novo conjunto de valores a cada vez.


3
@FelixKling que pode ser; Vou verificar e consertar se for o caso. editar sim, parece que deixei de fora uma parte significativa do exemplo, sendo essa a função "reutilizável" :)
Pointy

@FelixKling Não tenho certeza do que fazer porque não consigo me lembrar o que estava pensando na hora!
Pointy de

1
Sim, não faz muito sentido ser tbh;) Você sempre pode removê-lo .... mas reusablepode ser implementado para que retorne uma função, e você usaria ${0}e ${1}dentro do literal em vez de ${a}e ${b}. Então, você pode usar esses valores para se referir aos argumentos da função, semelhante ao que Bergi faz em seu último exemplo: stackoverflow.com/a/22619256/218196 (ou acho que é basicamente o mesmo).
Felix Kling

1
@FelixKling OK, acho que descobri algo que é pelo menos vagamente parecido com o OP.
Pointy,

3
Os modelos com tags podem ser realmente poderosos se o resultado não for uma string. Por exemplo, em um dos meus projetos, eu o uso para fazer a interpolação de nó AST. Por exemplo, pode-se fazer expression`a + ${node}`para construir um nó BinaryExpression com um nó AST existente node. Internamente, inserimos um marcador para gerar código válido, analisamos como um AST e substituímos o marcador com o valor passado.
Felix Kling

45

Provavelmente, a maneira mais limpa de fazer isso é com as funções de seta (porque, neste ponto, já estamos usando ES6)

var reusable = () => `This ${object} was created by ${creator}`;

var object = "template string", creator = "a function";
console.log (reusable()); // "This template string was created by a function"

object = "example", creator = "me";
console.log (reusable()); // "This example was created by me"

... E para literais de modelo marcados:

reusable = () => myTag`The ${noun} go ${verb} and `;

var noun = "wheels on the bus", verb = "round";
var myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb + strings[2] + verb;
};
console.log (reusable()); // "The wheels on the bus go round and round"

noun = "racecars", verb = "fast";
myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb;
};
console.log (reusable()); // "The racecars go fast"

Isso também evita o uso de eval() ou Function()que pode causar problemas com compiladores e causar muita lentidão.


Acho que este é um dos melhores porque você pode injetar alguns códigos dentro da função myTagpara fazer algumas coisas. Por exemplo, use os parâmetros de entrada como a chave para armazenar a saída em cache.
WOW

Acho que essa é a melhor resposta. Você também pode adicionar parâmetros para a função seta que eu acho que torna ainda mais limpo: var reusable = (value: string) => `Value is ${value}`.
haggisandchips

13

Resposta de 2019 :

Nota : A biblioteca originalmente esperava que os usuários limpassem as strings para evitar XSS. A versão 2 da biblioteca não requer mais que as strings do usuário sejam higienizadas (o que os desenvolvedores da web devem fazer de qualquer maneira), pois evitaeval completamente.

O es6-dynamic-templatemódulo no npm faz isso.

const fillTemplate = require('es6-dynamic-template');

Ao contrário das respostas atuais:

  • Ele usa strings de modelo ES6, não um formato semelhante. Atualizar versão de 2 usa um formato semelhante, em vez de strings de modelo ES6, para evitar que os usuários usem strings de entrada não revisadas.
  • Não precisa this da string do template
  • Você pode especificar a string e as variáveis ​​do modelo em uma única função
  • É um módulo atualizado e mantido, em vez de copypasta do StackOverflow

O uso é simples. Use aspas simples, pois a string do modelo será resolvida mais tarde!

const greeting = fillTemplate('Hi ${firstName}', {firstName: 'Joe'});

Se você estiver usando isso com o React Native, ele falhará especialmente no Android. O tempo de execução do nó do Android não oferece suporte a modelos dinâmicos, apenas aos pré-preenchidos
Oliver Dixon,

1
Esta é uma solução que utilizo em meus projetos pessoais e funciona perfeitamente. Na verdade, acho uma má ideia usar muitas bibliotecas, especialmente para pequenos utilitários como este.
Oliver Dixon,

1
Vulnerabilidade XSS ? Detalhes neste FIDDLE
Kamil Kiełczewski

1
@kamil Apenas XSS se você a) dá aos usuários a capacidade de criar b) não higieniza as strings de entrada. Vou adicionar um aviso de que as pessoas devem limpar a entrada do usuário.
mikemaccana

1
Isso não usa remotamente literais de modelo es6. Tente 10 * 20 = ${10 * 20}assim, pode ser um formato semelhante, mas não é nem remotamente es6 literais de modelo
gman

12

Sim, você pode fazer isso analisando sua string com o modelo como JS por Function(ou eval) - mas isso não é recomendado e permite ataques XSS

Em vez disso, você pode inserir campos de objeto com segurançaobj no modelo de strforma dinâmica da seguinte forma

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);


Este é o método que uso e funcionou bem. Bom exemplo! O faz? depois de * na ajuda do RegEx? Não sou um especialista em RegEx, mas estou supondo que, uma vez que * significa zero ou mais (e você quer "mais" neste caso), não há necessidade de restrição gananciosa?
Gen1-1 de

@ Gen1-1 .*?significa não ganancioso - se você remover "?", o snippet fornecerá um resultado errado
Kamil Kiełczewski

Você está certo, erro meu. Não uso $ em meus modelos e uso RegEx: / {(\ w *)} / g porque nunca tenho espaços na tag, mas. *? também funciona. Eu uso:function taggedTemplate(template, data, matcher) { if (!template || !data) { return template; } matcher = matcher || /{(\w*)}/g; // {one or more alphanumeric characters with no spaces} return template.replace(matcher, function (match, key) { var value; try { value = data[key] } catch (e) { // } return value || ""; }); }
Gen1-1

@ Gen1-1 também há dados aninhados possíveis? like data = { a: 1, b: { c:2, d:3 } }-> b.c?
muescha

1
@muescha Você mudaria a linha: value = data [key], para usar recursão e pesquisar todo o seu objeto de dados e objetos aninhados até encontrar a propriedade. Exemplos: codereview.stackexchange.com/questions/73714/… e mikedoesweb.com/2016/es6-depth-first-object-tree-search
Gen1-1

9

Simplificando a resposta fornecida por @metamorphasi;

const fillTemplate = function(templateString, templateVars){
  var func = new Function(...Object.keys(templateVars),  "return `"+templateString +"`;")
  return func(...Object.values(templateVars));
}

// Sample
var hosting = "overview/id/d:${Id}";
var domain = {Id:1234, User:22};
var result = fillTemplate(hosting, domain);

console.log(result);


Este código é mais autoexplicativo do que a resposta principal. Recebi meu voto
favorável

Isso deve permitir que você use variáveis ​​ou arquivos externos (em NodeJS) como modelos ou construa-os dinamicamente em tempo de execução. Sem o uso de eval.
b01

Vulnerabilidade XSS ? Brinque com código malicioso (variável var hosting) AQUI .
Kamil Kiełczewski,

7

Se você não quiser usar os parâmetros ordenados ou contexto / namespaces para referenciar as variáveis no modelo, por exemplo ${0}, ${this.something}ou${data.something} , você pode ter uma função de modelo que cuida do escopo para você.

Exemplo de como você pode chamar esse modelo:

const tempGreet = Template(() => `
  <span>Hello, ${name}!</span>
`);
tempGreet({name: 'Brian'}); // returns "<span>Hello, Brian!</span>"

A função de modelo:

function Template(cb) {
  return function(data) {
    const dataKeys = [];
    const dataVals = [];
    for (let key in data) {
      dataKeys.push(key);
      dataVals.push(data[key]);
    }
    let func = new Function(...dataKeys, 'return (' + cb + ')();');
    return func(...dataVals);
  }
}

A peculiaridade neste caso é que você só precisa passar uma função (no exemplo, usei uma função de seta) que retorna o literal de modelo ES6. Acho que é uma troca menor obter o tipo de interpolação reutilizável que buscamos.

Aqui está no GitHub: https://github.com/Adelphos/ES6-Reuseable-Template


3
Isso é bom, mas a minificação (vals, func, etc) é desnecessária, 'cb' não é um retorno de chamada (este é um código totalmente sincronizado) e você pode apenas usar Object.values()eObject.keys()
mikemaccana

3

A resposta curta é apenas usar _.template no lodash

// Use the ES template literal delimiter as an "interpolate" delimiter.
// Disable support by replacing the "interpolate" delimiter.
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!'

3

Estou esquecendo de algo? Existe uma [boa] maneira de tornar literal um modelo reutilizável?

Talvez eu seja faltando alguma coisa, porque minha solução para este problema me parece tão óbvia que estou muito surpreso que ninguém tenha escrito isso em uma pergunta tão antiga.

Eu tenho quase uma linha para isso:

function defer([first, ...rest]) {
  return (...values) => rest.reduce((acc, str, i) => acc + values[i] + str, first);
}

Isso é tudo. Quando quero reutilizar um template e adiar a resolução das substituições, eu apenas faço:

> t = defer`My template is: ${null} and ${null}`;
> t('simple', 'reusable');          // 'My template is: simple and reusable'
> t('obvious', 'late to the party'; // 'My template is: obvious and late to the party'
> t(null);                          // 'My template is: null and undefined'
>
> defer`Choose: ${'ignore'} / ${undefined}`(true, false); // 'Choose: true / false'

Aplicar essa tag retorna um 'function'(em vez de a 'string') que ignora quaisquer parâmetros passados ​​para o literal. Em seguida, ele pode ser chamado com novos parâmetros posteriormente. Se um parâmetro não tiver substituição correspondente, ele se tornará 'undefined'.


Resposta estendida

Este código simples é funcional, mas se você precisa de um comportamento mais elaborado, essa mesma lógica pode ser aplicada e as possibilidades são infinitas. Você poderia:

  1. Faça uso dos parâmetros originais:

Você pode armazenar os valores originais passados ​​para o literal na construção e usá-los de maneiras criativas ao aplicar o modelo. Eles podem se tornar sinalizadores, validadores de tipo, funções etc. Este é um exemplo que os usa como valores padrão:

    function deferWithDefaults([first, ...rest], ...defaults) {
      return (...values) => rest.reduce((acc, curr, i) => {
        return acc + (i < values.length ? values[i] : defaults[i]) + curr;
      }, first);
    }

Então:

    > t = deferWithDefaults`My template is: ${'extendable'} and ${'versatile'}`;
    > t('awesome');                 // 'My template is: awesome and versatile' 
  1. Escreva uma fábrica de modelos:

Faça isso envolvendo essa lógica em uma função que espera, como argumento, uma função customizada que pode ser aplicada na redução (ao juntar as partes do literal do template) e retorna um novo template com comportamento customizado.

    const createTemplate = fn => function (strings, ...defaults) {
      const [first, ...rest] = strings;
      return (...values) => rest.reduce((acc, curr, i) => {
        return acc + fn(values[i], defaults[i]) + curr;
      }, first);
    };

Em seguida, você poderia, por exemplo, escrever modelos que escapam ou higienizam parâmetros automaticamente ao escrever html, css, sql, bash embutido ...

    function sqlSanitize(token, tag) {
      // this is a gross simplification, don't use in production.
      const quoteName = name => (!/^[a-z_][a-z0-9_$]*$/.test(name) ? `"${name.replace(/"/g, '""')}"` : name);
      const quoteValue = value => (typeof value == 'string' ? `'${value.replace(/'/g, "''")}'` : value);
      switch (tag) {
        case 'table':
          return quoteName(token);
        case 'columns':
          return token.map(quoteName);
        case 'row':
          return token.map(quoteValue);
        default:
          return token;
      }
    }

    const sql = createTemplate(sqlSanitize);

Com este modelo sql ingênuo (repito, ingênuo! ), Poderíamos construir consultas como esta:

    > q  = sql`INSERT INTO ${'table'} (${'columns'})
    ... VALUES (${'row'});`
    > q('user', ['id', 'user name', 'is"Staff"?'], [1, "O'neil", true])
    // `INSERT INTO user (id,"user name","is""Staff""?")
    // VALUES (1,'O''neil',true);`
  1. Aceite os parâmetros nomeados para substituição: Um exercício não tão difícil, baseado no que já foi fornecido. Há uma implementação nesta outra resposta .

  2. Faça com que o objeto de retorno se comporte como um 'string': Bem, isso é controverso, mas pode levar a resultados interessantes. Mostrado nesta outra resposta .

  3. Resolva os parâmetros dentro do namespace global no site de chamada:

Eu te dou, literais de modelo reutilizáveis:

Bem, isso é o que OP mostrou ser seu adendo, usando o comando evil, quero dizer eval,. Isso poderia ser feito sem eval, apenas pesquisando o nome da variável passado no objeto global (ou janela). Não vou mostrar como faço porque não gosto. Os fechamentos são a escolha certa.


2

Esta é minha melhor tentativa:

var s = (item, price) => {return `item: ${item}, price: $${price}`}
s('pants', 10) // 'item: pants, price: $10'
s('shirts', 15) // 'item: shirts, price: $15'

Para generalizar:

var s = (<variable names you want>) => {return `<template with those variables>`}

Se você não estiver executando o E6, também pode fazer:

var s = function(<variable names you want>){return `<template with those variables>`}

Parece um pouco mais conciso do que as respostas anteriores.

https://repl.it/@abalter/reusable-JS-template-literal


2

Em geral, sou contra o mal eval(), mas neste caso faz sentido:

var template = "`${a}.${b}`";
var a = 1, b = 2;
var populated = eval(template);

console.log(populated);         // shows 1.2

Então, se você alterar os valores e chamar eval () novamente, obterá o novo resultado:

a = 3; b = 4;
populated = eval(template);

console.log(populated);         // shows 3.4

Se você quiser em uma função, pode ser escrito assim:

function populate(a, b){
  return `${a}.${b}`;
}

Se você estiver escrevendo uma função que inclui o modelo, definitivamente não deve usá-la eval.
Bergi

@Bergi Por quê? Como é diferente da sua implementação?
isapir

2
Os motivos pelos quais "pareço saber" se aplicam a qualquer código construído dinamicamente. Escrever a função para que crie o resultado sem chamar eval()explicitamente é exatamente o mesmo que eval(), portanto, não há nenhum benefício nisso, pois apenas torna o código mais difícil de ler.
isapir

1
Exatamente. E como sua populatefunção não constrói o código dinamicamente, ela não deve ser usada evalcom todas as suas desvantagens.
Bergi

6
sua função pode ser apenas function populate(a,b) { return `${a}.${b}`; }o eval não acrescenta nada
Vitim.us

1

ATUALIZADO: A resposta a seguir é limitada a nomes de variáveis ​​únicas, portanto, modelos como: 'Result ${a+b}'não são válidos para este caso. No entanto, você sempre pode brincar com os valores do modelo:

format("This is a test: ${a_b}", {a_b: a+b});

RESPOSTA ORIGINAL:

Com base nas respostas anteriores, mas criando uma função de utilidade mais "amigável":

var format = (template, params) => {
    let tpl = template.replace(/\${(?!this\.)/g, "${this.");
    let tpl_func = new Function(`return \`${tpl}\``);

    return tpl_func.call(params);
}

Você pode invocá-lo assim:

format("This is a test: ${hola}, second param: ${hello}", {hola: 'Hola', hello: 'Hi'});

E a string resultante deve ser:

'This is a test: Hola, second param: Hi'

Que tal um modelo como este? `Result: ${a+b}`
Atiris

1
Olá @Atiris, você tem razão, Isso é uma limitação, atualizei minha resposta.
Roberto

1

Se você está procurando por algo bastante simples (apenas campos de variáveis ​​fixas, sem cálculos, condicionais ...), mas isso também funciona do lado do cliente em navegadores sem suporte de string de modelo como o IE 8,9,10,11 ...

aqui vamos nós:

fillTemplate = function (templateString, templateVars) {
    var parsed = templateString;
    Object.keys(templateVars).forEach(
        (key) => {
            const value = templateVars[key]
            parsed = parsed.replace('${'+key+'}',value)
        }
    )
    return parsed
}

Isso fará uma pesquisa para cada variável. Existe outra forma que substitui todas as ocorrências de uma vez que implementei neste módulo: safe-es6-template
Aalex Gabi

1

Eu estava irritado com a redundância extra necessário de digitar this.cada vez, para que eu também acrescentou regex para expandir variáveis como .aa this.a.

Solução:

const interp = template => _thisObj =>
function() {
    return template.replace(/\${([^}]*)}/g, (_, k) =>
        eval(
            k.replace(/([.a-zA-Z0-9$_]*)([a-zA-Z0-9$_]+)/, (r, ...args) =>
                args[0].charAt(0) == '.' ? 'this' + args[0] + args[1] : r
            )
        )
    );
}.call(_thisObj);

Use como tal:

console.log(interp('Hello ${.a}${.b}')({ a: 'World', b: '!' }));
// outputs: Hello World!

1

Acabei de publicar um pacote npm que pode simplesmente fazer esse trabalho. Profundamente inspirado por esta resposta .

const Template = require('dynamic-template-string');

var tpl = new Template('hello ${name}');

tpl.fill({name: 'world'}); // ==> 'hello world';
tpl.fill({name: 'china'}); // ==> 'hello china';

Seu implemento é mortalmente simples. Desejo que você goste.


module.exports = class Template {
  constructor(str) {
    this._func = new Function(`with(this) { return \`${str}\`; }`);
  }

  fill(data) {
    return this._func.call(data);
  }
}

1

você pode usar a função de seta em linha como esta, definição:

const template = (substitute: string) => `[^.?!]*(?<=[.?\s!])${substitute}(?=[\s.?!])[^.?!]*[.?!]`;

uso:

console.log(template('my replaced string'));

1

String de modelo de tempo de execução

var templateString = (template, values) => {
    let output = template;
    Object.keys(values)
        .forEach(key => {
        output = output.replace(new RegExp('\\$' + `{${key}}`, 'g'), values[key]);
    });
    return output;
};

Teste

console.debug(templateString('hello ${word} world', {word: 'wonderful'}));

0

const fillTemplate = (template, values) => {
  template = template.replace(/(?<=\${)\w+(?=})/g, v=>"this."+v);
  return Function.apply(this, ["", "return `"+template+"`;"]).call(values);
};

console.log(fillTemplate("The man ${man} is brother of ${brother}", {man: "John", brother:"Peter"}));
//The man John is brother of Peter

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.