Usando HTML5 / JavaScript para gerar e salvar um arquivo


317

Ultimamente, tenho brincado com o WebGL e consegui um leitor Collada funcionando. O problema é que é muito lento (Collada é um formato muito detalhado), então vou começar a converter arquivos para um formato mais fácil de usar (provavelmente JSON). Eu já tenho o código para analisar o arquivo em JavaScript, então também posso usá-lo como meu exportador! O problema está salvando.

Agora, eu sei que posso analisar o arquivo, enviar o resultado ao servidor e solicitar que o navegador solicite o arquivo de volta do servidor como um download. Mas, na realidade, o servidor não tem nada a ver com esse processo específico, então por que envolvê-lo? Eu já tenho o conteúdo do arquivo desejado na memória. Existe alguma maneira de eu apresentar ao usuário um download usando JavaScript puro? (Eu duvido, mas é melhor perguntar ...)

E para ser claro: não estou tentando acessar o sistema de arquivos sem o conhecimento dos usuários! O usuário fornecerá um arquivo (provavelmente por meio de arrastar e soltar), o script transformará o arquivo na memória e o usuário será solicitado a baixar o resultado. Todas essas atividades devem ser "seguras" no que diz respeito ao navegador.

[EDIT]: Eu não mencionei isso de antemão, então os pôsteres que responderam "Flash" são válidos o suficiente, mas parte do que estou fazendo é uma tentativa de destacar o que pode ser feito com HTML5 puro ... então o Flash é bem no meu caso. (Embora seja uma resposta perfeitamente válida para qualquer pessoa que faça um aplicativo Web "real".) Nesse caso, parece que estou sem sorte, a menos que queira envolver o servidor. Obrigado mesmo assim!


8
Você poderia pensar em mudar a resposta aceita, parece haver uma maneira puramente HTML agora
Pekka

Respostas:


256

OK, criando dados: o URI definitivamente faz o truque para mim, graças a Matthew e Dennkster apontando essa opção! Aqui está basicamente como eu faço isso:

1) coloque todo o conteúdo em uma string chamada "content" (por exemplo, criando-o inicialmente ou lendo innerHTML da tag de uma página já criada).

2) Crie o URI de dados:

uriContent = "data:application/octet-stream," + encodeURIComponent(content);

Haverá limitações de comprimento, dependendo do tipo de navegador, etc., mas, por exemplo, o Firefox 3.6.12 funciona até pelo menos 256k. Codificar em Base64 em vez de usar encodeURIComponent pode tornar as coisas mais eficientes, mas para mim tudo bem.

3) abra uma nova janela e "redirecione" para este URI solicitando um local de download da minha página gerada por JavaScript:

newWindow = window.open(uriContent, 'neuesDokument');

É isso aí.


34
Se você deseja evitar o uso de um pop-up, que pode ser bloqueado, defina-o location.hrefcomo o conteúdo. location.href = uriContent.
Alex Turpin #

12
Oi eu tentei isso, mas ele baixa o arquivo como arquivo .part. Como posso definir o tipo de arquivo?
Sedat Başar

6
@ Os URIs de dados do SedatBaşar não suportam a configuração de um nome de arquivo, você precisa confiar no navegador para definir uma extensão apropriada usando o tipo MIME. Mas se o tipo mime puder ser renderizado pelo navegador, ele não será baixado, mas será exibido. Existem outras maneiras de fazer isso, mas nenhuma delas funciona no IE <10.
Panzi

7
O IE não suporta realmente URIs de dados e o Firefox parece salvar os arquivos com um nome aleatório.
Nylund

25
Acho que estamos tornando isso mais difícil do que precisa. Abra seu console JS nesta página e coloque-o location.href = "data:application/octet-stream," + encodeURIComponent(jQuery('#answer-4551467 .post-text').first().text());e ele salvará o conteúdo da resposta de @ Nøk em um arquivo. Não tenho o IE para testá-lo, mas funciona para o webkit.
de Bruno Bronosky

278

Solução simples para navegadores prontos para HTML5 ...

function download(filename, text) {
    var pom = document.createElement('a');
    pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    pom.setAttribute('download', filename);

    if (document.createEvent) {
        var event = document.createEvent('MouseEvents');
        event.initEvent('click', true, true);
        pom.dispatchEvent(event);
    }
    else {
        pom.click();
    }
}

Uso

download('test.txt', 'Hello world!');

2
Se você conhece o URL de origem que deseja baixar, esta é a melhor solução!
sidonaldson

31
A capacidade de definir o nome do arquivo faz deste um vencedor.
Omn

9
Esse método funcionou no Chrome até a atualização mais recente que recebi há alguns dias (35.0.1916.114 m). Agora ele funciona parcialmente em que o nome do arquivo e extensão não funcionam mais (sempre nomes do download.txt independentemente do que é passado de arquivo.)
Sevin7

6
Eu tenho o Chrom 42.0.2311.90, e isso está funcionando para mim com o nome de arquivo esperado.
Saurabh Kumar

4
Se houver um limite na quantidade de dados que pode ser incluída?
quer

80

O HTML5 definiu um window.saveAs(blob, filename)método. Não é suportado por nenhum navegador no momento. Mas há uma biblioteca de compatibilidade chamada FileSaver.js que adiciona essa função aos navegadores mais modernos (incluindo o Internet Explorer 10+). O Internet Explorer 10 oferece suporte a um navigator.msSaveBlob(blob, filename)método ( MSDN ), usado no FileSaver.js para suporte ao Internet Explorer.

Eu escrevi uma postagem no blog com mais detalhes sobre esse problema.


1
E quanto ao bloqueio de pop-ups? O comportamento desta biblioteca é semelhante à solução da @ Nøk. O texto sem formatação no Firefox é aberto em um novo. Somente o Chrome tenta salvá-lo, mas ele altera a extensão (eu preciso de um arquivo de pontos sem extensão).
perfil completo de ciembor

1
@ciembor a variante de atributo de download (object url +) (que eu uso com o chrome) permite definir um nome de arquivo. Funciona para mim no chrome.
panzi

1
@ciembor aha e um pop-up não é bloqueado se um clique o causar diretamente.
panzi

6
FileSaver.js suporta IE agora
Eli Grey

15
O W3C diz: O trabalho neste documento foi descontinuado e não deve ser referenciado ou usado como base para implementação.
WaiKit Kung

48

Salvando arquivos grandes

URIs de dados longos podem causar problemas de desempenho nos navegadores. Outra opção para salvar arquivos gerados no lado do cliente é colocar o conteúdo em um objeto Blob (ou Arquivo) e criar um link de download usando URL.createObjectURL(blob). Isso retorna um URL que pode ser usado para recuperar o conteúdo do blob. O blob é armazenado dentro do navegador até que seja URL.revokeObjectURL()chamado no URL ou o documento que o criou seja fechado. A maioria dos navegadores da Web tem suporte para URLs de objetos , o Opera Mini é o único que não os suporta.

Forçando um download

Se os dados forem texto ou imagem, o navegador poderá abrir o arquivo, em vez de salvá-lo no disco. Para fazer o download do arquivo ao clicar no link, você pode usar o downloadatributo No entanto, nem todos os navegadores da Web têm suporte para o atributo de download . Outra opção é usar application/octet-streamcomo o tipo mime do arquivo, mas isso faz com que o arquivo seja apresentado como um blob binário que é especialmente hostil ao usuário se você não especificar ou não puder especificar um nome de arquivo. Consulte também ' Forçar a abrir o pop-up "Salvar como ..." aberto no link de texto, clique em pdf em HTML '.

Especificando um nome de arquivo

Se o blob for criado com o construtor File, você também poderá definir um nome de arquivo, mas apenas alguns navegadores da Web (incluindo Chrome e Firefox) terão suporte para o construtor File . O nome do arquivo também pode ser especificado como argumento para o downloadatributo, mas isso está sujeito a várias considerações de segurança . O Internet Explorer 10 e 11 fornece seu próprio método, msSaveBlob , para especificar um nome de arquivo.

Código de exemplo

var file;
var data = [];
data.push("This is a test\n");
data.push("Of creating a file\n");
data.push("In a browser\n");
var properties = {type: 'text/plain'}; // Specify the file's mime-type.
try {
  // Specify the filename using the File constructor, but ...
  file = new File(data, "file.txt", properties);
} catch (e) {
  // ... fall back to the Blob constructor if that isn't supported.
  file = new Blob(data, properties);
}
var url = URL.createObjectURL(file);
document.getElementById('link').href = url;
<a id="link" target="_blank" download="file.txt">Download</a>


1
Posso mostrar uma caixa de diálogo (pop-up) para especificar uma pasta (diretório) para salvar o arquivo?
24516 Calvin

Calvin @: atualizei a resposta para explicar como forçar um download e fornecer um nome de arquivo. Para o IE, acredito que você pode usar msSaveBlobpara abrir a caixa de diálogo "Salvar como". Para outros navegadores, sua única opção é escolher manualmente "Salvar como" no menu de contexto do link de download.
bcmpinc

1
@ Jek-fdrv: Apenas os URLs de BLOB funcionam no Safari. O atributo de download e o construtor File não são suportados pelo Safari; portanto, não é possível forçar um download, o que significa que o blob provavelmente será aberto no próprio navegador e não é possível especificar um nome de arquivo. Para o exemplo dado, você ainda poderá fazer o download do arquivo com o Safari usando o menu de contexto do link.
bcmpinc


Esta é uma resposta muito útil e informativa. Mais uma coisa que pode ajudar pessoas como eu: após a configuração document.getElementById('link').href = url;, seu código pode prosseguir e disparar o link usando o .click()método do elemento
LarsH

36
function download(content, filename, contentType)
{
    if(!contentType) contentType = 'application/octet-stream';
        var a = document.createElement('a');
        var blob = new Blob([content], {'type':contentType});
        a.href = window.URL.createObjectURL(blob);
        a.download = filename;
        a.click();
}

3
Qual é o efeito do contentType? Para que isso é usado?
Uri

3
Este funciona bem mesmo no Chrome mais recente, ao contrário da resposta de @ Matěj Pokorný. Obrigado.
Alexander Amelkin

2
Isso não funciona para mim no FF36 ou IE11. Se eu substituir a.clickpelo código usando document.createEvent()o Matěj Pokorný sugerido, ele funcionará no FF, mas não no IE. Eu não experimentei o Chrome.
Peter Hull

25

Dê uma olhada no Downloadify de Doug Neiner, que é uma interface JavaScript baseada em Flash para fazer isso.

O Downloadify é uma pequena biblioteca JavaScript + Flash que permite a geração e o salvamento de arquivos dinamicamente, no navegador, sem interação do servidor.


6
Para a maioria das pessoas, essa é provavelmente a resposta que elas precisam. Portanto, mesmo que não atenda aos meus requisitos específicos (como explicado acima), estou marcando-o como a resposta aceita.
Toji

2
@ Ah ah, entendo. Talvez peça novamente e refrase sob o HTML 5banner e marque de acordo? Isso provavelmente atrairá os usuários que conhecem esse campo específico (ainda é uma multidão comparativamente pequena no momento, suponho). Tenho certeza de que isso pode ser feito no HTML 5, mas não sei como.
Pekka

1
O domínio downloadify.info Downloadify foi adquirido / transferido e, em caso afirmativo, existe um novo local? O site atual parece completamente não relacionado à resposta dada.
Christos Hayward 27/05

17
Isso não responde à pergunta intitulada Usando HTML5 ....
Ixx

5
@Ixx bem, para ser justo, que foi adicionado depois que a resposta foi postada. Ainda assim, você está certo. A resposta abaixo devem ser aceitos
Pekka

17

Solução Simples!

<a download="My-FileName.txt" href="data:application/octet-stream,HELLO-WORLDDDDDDDD">Click here</a>

Funciona em todos os navegadores modernos.


Olá, você sabe como especificar o comportamento do atributo "download" usando window.open ou outra função javascript?
yucer

2
@yucer Não há nenhum atributo de download (ou qualquer atributo) com window.open(). É irrelevante. Você pode usar esse método e forçá .click()-lo, mas observe o tempo que o Firefox não gosta se você ligar .click()antes de deixar o elemento anexar ao corpo.
Brad

Mas, infelizmente, todos os espaços são excluídos. No meu caso, isso é muito ruim, pois quero fazer o download do código-fonte para um arquivo SVG.
Frankenapps #

espaços são preservados se você usar encodeURIComponent (conteúdo)
Mike Redrobe

não pode escolher o nome no firefox, mas o chrome funciona
Bhikkhu Subhuti

10

Você pode gerar um URI de dados . No entanto, existem limitações específicas do navegador.


1
Isto é interessante. Vou investigar mais quando tiver uma chance. Obrigado!
Toji

@quantumpotato, na verdade, gerar o URL é um pouco complicado de explicar. Todo o âmago da questão está na RFC 2397 . Você pode usar ferramentas como esta para testar. Em seguida, para o seu aplicativo real, você pode procurar um URI de dados ou uma biblioteca base 64 para o seu idioma. Se você não encontrar, sinta-se à vontade para fazer uma pergunta de acompanhamento. Algumas das limitações específicas do navegador são dadas na Wikipedia artigo . Por exemplo, o IE limita o comprimento e o tipo (por exemplo, não texto / html).
Matthew Flaschen 1/1

Gerar URLs de dados não é tão complicado: "data:text/plain;charset=UTF-8,"+encodeURIComponent(text)mas sim, o IE limita o tamanho dos URLs de dados a uma quantidade inutilizável e não os suporta para coisas como window.open(...)iframes (eu acho).
panzi

@panzi, também não é tão fácil assim. Por um lado, esse não é o tipo MIME correto para Collada ou JSON.
Matthew Flaschen

resposta muito não informativa. por favor, adicione as especificações para navegadores, se você as mencionar.
T.Todua

10

Eu usei o FileSaver ( https://github.com/eligrey/FileSaver.js ) e funciona muito bem.
Por exemplo, eu fiz essa função para exportar logs exibidos em uma página.
Você precisa passar uma matriz para a instanciação do Blob, então talvez eu não tenha escrito da maneira certa, mas funciona para mim.
Apenas no caso, tenha cuidado com a substituição: esta é a sintaxe para torná-la global, caso contrário, ela substituirá apenas a primeira que ele encontrar.

exportLogs : function(){
    var array = new Array();

    var str = $('#logs').html();
    array[0] = str.replace(/<br>/g, '\n\t');

    var blob = new Blob(array, {type: "text/plain;charset=utf-8"});
    saveAs(blob, "example.log");
}

2
FileSaver é ótimo, aqui está um IE Shim para a função preIE10SaveAs pré-IE10: (nome do arquivo, conteúdo do arquivo, tipo mimetico) {var w = window.open (); var doc = w.document; doc.open (mimetype, 'replace'); doc.charset = "utf-8"; doc.write (conteúdo do arquivo); doc.close (); doc.execCommand ("SaveAs", nulo, nome do arquivo); }
aqm 20/03/14

Um aviso sobre o calço de @ aqm: ele executa tags de script.
GameFreak

Além disso, pode ser desejável colocar w.close();após oexecCommand
GameFreak 19/04/19

8

Encontrei duas abordagens simples que funcionam para mim. Primeiro, usando um aelemento já clicado e injetando os dados do download. E segundo, gerando um aelemento com os dados de download, executando a.click()e removendo-os novamente. Mas a segunda abordagem funciona apenas se invocada por uma ação de clique do usuário. (Alguns) Bloqueios de navegador click()de outros contextos, como no carregamento ou acionados após um tempo limite (setTimeout).

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript">
      function linkDownload(a, filename, content) {
        contentType =  'data:application/octet-stream,';
        uriContent = contentType + encodeURIComponent(content);
        a.setAttribute('href', uriContent);
        a.setAttribute('download', filename);
      }
      function download(filename, content) {
        var a = document.createElement('a');
        linkDownload(a, filename, content);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      }
    </script>
   </head>
  <body>
    <a href="#" onclick="linkDownload(this, 'test.txt', 'Hello World!');">download</a>
    <button onclick="download('test.txt', 'Hello World!');">download</button>
  </body>
</html>

2
Você também pode inserir o adocumento (possivelmente com "display:none"), clicar nele e removê-lo.
Teepeemm

1
isso poderia funcionar em navegadores onde atributo de download não é suportado como até mesmo isto é moderno e safari .. caniuse.com/#feat=download
Muhammad Umer

1
Acabei de testar o Safari 5.0 no wine. A primeira versão funciona, a segunda não. Eu testei o IE 8 (vinho) também e não funciona.
Maikel

6

Aqui está um link para o método de URI de dados sugerido por Mathew, que funcionou no safari, mas não muito bem porque eu não consegui definir o tipo de arquivo, ele é salvo como "desconhecido" e, em seguida, tenho que ir lá novamente mais tarde e alterá-lo para para visualizar o arquivo ...

http://www.nihilogic.dk/labs/canvas2image/


4

Você pode usar localStorage. Este é o equivalente em HTML5 dos cookies. Parece funcionar no Chrome e Firefox, mas no Firefox, eu precisava enviá-lo para um servidor. Ou seja, testar diretamente no meu computador doméstico não funcionou.

Estou trabalhando em exemplos HTML5. Vá para http://faculty.purchase.edu/jeanine.meyer/html5/html5explain.html e role até o labirinto. As informações para reconstruir o labirinto são armazenadas usando localStorage.

Eu vim a este artigo procurando por HTML5 JavaScript para carregar e trabalhar com arquivos xml. É o mesmo que o antigo html e JavaScript ????


4

experimentar

let a = document.createElement('a');
a.href = "data:application/octet-stream,"+encodeURIComponent('"My DATA"');
a.download = 'myFile.json';
a.click(); // we not add 'a' to DOM so no need to remove

Se você deseja baixar dados binários, veja aqui

Atualizar

2020.06.14 Eu atualizo o Chrome para 83.0 e acima A parada do snippet SO funciona (devido a restrições de segurança da sandbox ) - mas a versão JSFiddle funciona - aqui


3

Conforme mencionado anteriormente, a API do arquivo , juntamente com as APIs FileWriter e FileSystem, pode ser usada para armazenar arquivos na máquina do cliente a partir do contexto de uma guia / janela do navegador.

No entanto, existem várias coisas relacionadas às duas últimas APIs das quais você deve estar ciente:

  • Atualmente, as implementações das APIs existem apenas em navegadores baseados no Chromium (Chrome e Opera)
  • Ambas as APIs foram retiradas da faixa de padrões do W3C em 24 de abril de 2014 e, atualmente, são proprietárias
  • A remoção das APIs (agora proprietárias) da implementação de navegadores no futuro é uma possibilidade
  • Uma sandbox (um local no disco fora do qual os arquivos não podem produzir efeito) é usada para armazenar os arquivos criados com as APIs
  • Um sistema de arquivos virtual (uma estrutura de diretórios que não existe necessariamente em disco da mesma forma que quando acessada pelo navegador) é usado para representar os arquivos criados com as APIs

Aqui estão exemplos simples de como as APIs são usadas, direta e indiretamente, em conjunto para fazer isso:

BakedGoods *

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});

Usando as APIs File, FileWriter e FileSystem brutas

function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);

Embora as APIs FileSystem e FileWriter não estejam mais no caminho dos padrões, seu uso pode ser justificado em alguns casos, na minha opinião, porque:

  • O interesse renovado dos fornecedores de navegador que não implementam pode colocá-los de volta nele
  • A penetração no mercado de navegadores de implementação (baseados em cromo) é alta
  • O Google (o principal colaborador do Chromium) não forneceu uma data de término de validade para as APIs

Se "alguns casos" abrange os seus, no entanto, é para você decidir.

* BakedGoods é mantido por ninguém menos que esse cara aqui :)


2

Esse encadeamento foi inestimável para descobrir como gerar um arquivo binário e solicitar o download do arquivo nomeado, tudo no código do cliente sem um servidor.

O primeiro passo para mim foi gerar o blob binário a partir dos dados que eu estava salvando. Existem muitos exemplos para fazer isso para um único tipo binário, no meu caso, tenho um formato binário com vários tipos que você pode passar como uma matriz para criar o blob.

saveAnimation: function() {

    var device = this.Device;
    var maxRow = ChromaAnimation.getMaxRow(device);
    var maxColumn = ChromaAnimation.getMaxColumn(device);
    var frames = this.Frames;
    var frameCount = frames.length;

    var writeArrays = [];


    var writeArray = new Uint32Array(1);
    var version = 1;
    writeArray[0] = version;
    writeArrays.push(writeArray.buffer);
    //console.log('version:', version);


    var writeArray = new Uint8Array(1);
    var deviceType = this.DeviceType;
    writeArray[0] = deviceType;
    writeArrays.push(writeArray.buffer);
    //console.log('deviceType:', deviceType);


    var writeArray = new Uint8Array(1);
    writeArray[0] = device;
    writeArrays.push(writeArray.buffer);
    //console.log('device:', device);


    var writeArray = new Uint32Array(1);
    writeArray[0] = frameCount;
    writeArrays.push(writeArray.buffer);
    //console.log('frameCount:', frameCount);

    for (var index = 0; index < frameCount; ++index) {

      var frame = frames[index];

      var writeArray = new Float32Array(1);
      var duration = frame.Duration;
      if (duration < 0.033) {
        duration = 0.033;
      }
      writeArray[0] = duration;
      writeArrays.push(writeArray.buffer);

      //console.log('Frame', index, 'duration', duration);

      var writeArray = new Uint32Array(maxRow * maxColumn);
      for (var i = 0; i < maxRow; ++i) {
        for (var j = 0; j < maxColumn; ++j) {
          var color = frame.Colors[i][j];
          writeArray[i * maxColumn + j] = color;
        }
      }
      writeArrays.push(writeArray.buffer);
    }

    var blob = new Blob(writeArrays, {type: 'application/octet-stream'});

    return blob;
}

A próxima etapa é fazer com que o navegador solicite ao usuário que baixe esse blob com um nome predefinido.

Tudo o que eu precisava era de um link nomeado que eu adicionei no HTML5 que eu pudesse reutilizar para renomear o nome do arquivo inicial. Eu o mantive oculto, pois o link não precisa ser exibido.

<a id="lnkDownload" style="display: none" download="client.chroma" href="" target="_blank"></a>

A última etapa é solicitar ao usuário que baixe o arquivo.

var data = animation.saveAnimation();
var uriContent = URL.createObjectURL(data);
var lnkDownload = document.getElementById('lnkDownload');
lnkDownload.download = 'theDefaultFileName.extension';
lnkDownload.href = uriContent;
lnkDownload.click();

1

Aqui está um tutorial para exportar arquivos como ZIP:

Antes de começar, existe uma biblioteca para salvar arquivos, o nome da biblioteca é fileSaver.js. Você pode encontrar esta biblioteca aqui. Vamos começar, Agora, inclua as bibliotecas necessárias:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.4/jszip.min.js"  type="text/javascript"></script>
<script type="text/javascript" src="https://fastcdn.org/FileSaver.js/1.1.20151003/FileSaver.js" ></script>

Agora copie esse código e ele fará o download de um arquivo zip com um arquivo hello.txt com o conteúdo Hello World. Se tudo funcionar bem, isso fará o download de um arquivo.

<script type="text/javascript">
    var zip = new JSZip();
    zip.file("Hello.txt", "Hello World\n");
    zip.generateAsync({type:"blob"})
    .then(function(content) {
        // see FileSaver.js
        saveAs(content, "file.zip");
    });
</script>

Isso fará o download de um arquivo chamado file.zip. Você pode ler mais aqui: http://www.wapgee.com/story/248/guide-to-create-zip-files-using-javascript-by-using-jszip-library

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.