{"id":308,"date":"2009-09-24T15:34:10","date_gmt":"2009-09-24T18:34:10","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=308"},"modified":"2009-09-27T12:05:22","modified_gmt":"2009-09-27T15:05:22","slug":"exportando-dados-para-excel-usando-csv-em-um-aplicativo-rails","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2009\/09\/exportando-dados-para-excel-usando-csv-em-um-aplicativo-rails\/","title":{"rendered":"Exportando dados para Excel usando CSV em um aplicativo Rails"},"content":{"rendered":"

To see this post in English, click here.<\/a><\/p>\n

\u00c0s vezes os usu\u00e1rios querem manipular os dados de diversos modos. Nesses cen\u00e1rios, \u00e9 comum exportar os dados num formato de tabela para que se possa usar um editor de planilhas e ent\u00e3o filtrar, particionar e mudar os dados da forma que o usu\u00e1rio quiser.<\/p>\n

Generalmente, fazemos isso usando CSV, certo? O OpenOffice e outros editores podem abrir arquivos CSV sem problemas. \u00c9 s\u00f3 clicar duas vezes no arquivo CSV e voil\u00e0, os dados aparecem.<\/p>\n

Entretanto, o Excel n\u00e3o funciona exatamente da mesma forma. Se voc\u00ea clicar duas vezes no arquivo, o Excel vai lhe mostrar todo o conte\u00fado em apenas uma coluna. Claro que voc\u00ea poderia ir em Ferramentas > Importar dados, navegar at\u00e9 o arquivo, selecionar a v\u00edrgula como separador de campos e ent\u00e3o, ap\u00f3s alguns cliques voc\u00ea ter\u00e1 seus dados como desejado. Isso n\u00e3o \u00e9 nada amig\u00e1vel para o usu\u00e1rio e \u00e9 o tipo de coisa que n\u00e3o queremos explicar para nossos clientes. Nossos clientes devem ser capazes de simplemente clicar duas vezes no arquivo meus_dados.csv e ver os dados bem estruturados.<\/p>\n

Tendo isso em mente, \u00e9 assim que fazemos na Plataforma<\/a> para lidar com os formatos do Excel.<\/p>\n

\u00c9 TSV, e n\u00e3o CSV, rapaz!<\/h3>\n

O Excel espera que seus dados venham com tabula\u00e7\u00f5es como separador de campo. Ent\u00e3o, o que voc\u00ea precisa na verdade \u00e9 um TSV (tab-separated values).<\/p>\n

Se voc\u00ea est\u00e1 usando o FasterCSV<\/a>, s\u00f3 precisa fazer:<\/p>\n

\r\ntsv_str = FasterCSV.generate(:col_sep => \"\\t\") do |tsv|\r\n  tsv << headers\r\n  # coloque seus bonitos dados aqui...\r\nend\r\n<\/pre>\n

N\u00e3o esque\u00e7a! Excel exige tabula\u00e7\u00f5es, e n\u00e3o v\u00edrgulas. Mas tem coisa pior vindo...<\/p>\n

Sem quebras de linha nos campos<\/h3>\n

O Excel n\u00e3o gosta quando voc\u00ea p\u00f5e um \"\\n\" dentro dos campos. Apesar dos campos serem separados por tabula\u00e7\u00f5es, ele parece n\u00e3o entender o que a quebra de linha est\u00e1 fazendo ali.<\/p>\n

Ent\u00e3o, se voc\u00ea tem campos de texto no seu modelo, tome cuidado. Eles podem conter quebras de linhas e voc\u00ea precisa tir\u00e1-las antes de exportar seus dados para o Excel.<\/p>\n

Esque\u00e7a UTF-8. Use UTF-16!<\/h3>\n

Uma das especifica\u00e7\u00f5es mais escondidas do Excel \u00e9 que ele espera que seus arquivos TSV sejam codificados usando UTF-16 Little Endian. Voc\u00ea sabia disso? Bem, n\u00f3s n\u00e3o!<\/p>\n

Alguns at\u00e9 dizem que este \u00e9 o \u00fanico formato Unicode suportado pelo Excel.<\/p>\n

E qual a diferen\u00e7a entre UTF-8<\/a> e UTF-16<\/a>? O UTF-8 \u00e9 uma codifica\u00e7\u00e3o de tamanho vari\u00e1vel, cujos caracteres podem usar at\u00e9 4 bytes, mas para idiomas ocidentais generalmente \u00e9 usado um ou dois bytes. Caracteres UTF-16 usam sempre 2 bytes para serem representados (lembre-se que caracteres e bytes s\u00e3o duas coisas bem diferentes no mundo Unicode). Simplificando bastante as coisas, UTF-16 usa mais espa\u00e7o (na maioria das vezes).<\/p>\n

E tem tamb\u00e9m a parte do Little Endian. O UTF-16 sempre usa um par de bytes para representar um caractere. Contudo, n\u00f3s precisamos saber qual a ordem certa desses bytes. N\u00e3o vamos entrar em detalhes aqui, mas essa ordem \u00e9 indicada pelo Byte-order Mark (BOM)<\/a>. Na pr\u00e1tica, o BOM no UTF-16 vai adicionar dois bytes no come\u00e7o do seu arquivo (voc\u00ea pode ver isso usando um editor hexadecimal).<\/p>\n

Um dos jeitos de converter sua string TSV (gerado pelo FasterCSV) \u00e9 com o Iconv, uma ferramenta escrita para a Biblioteca C do GNU. Felizmente, o Iconv est\u00e1 bem empacotado em pura felicidade Rubyana. Est\u00e1 dentro do biblioteca padr\u00e3o do Ruby e voc\u00ca s\u00f3 precisar dar um require nela.<\/p>\n

Entretando, se voc\u00ea converter uma string para UTF-16 Little Endiand, Iconv N\u00c3O ir\u00e1 colocar o BOM no come\u00e7o. Isso \u00e9 compat\u00edvel com a especifica\u00e7\u00e3o do Unicode<\/a>. Mas j\u00e1 que o Excel est\u00e1 totalmente fora dos padr\u00f5es, voc\u00ea deve inserir manualmente o BOM para aumentar a compatibilidade.<\/p>\n

Voc\u00ea pode usar o Iconv na hora que enviar o arquivo ao usu\u00e1rio dentro do seu controller. Ficaria mais ou menos assim:<\/p>\n

\r\nrequire 'iconv'\r\n\r\nclass ProjectsController < ActionController::Base\r\n  BOM = \"\\377\\376\" #Byte Order Mark\r\n\r\n  def index\r\n    @projects = Project.all\r\n    respond_to do |format|\r\n      format.html\r\n      format.csv { export_csv(@projects) }\r\n    end\r\n  end\r\n\r\nprotected\r\n\r\n  def export_csv(projects)\r\n    filename = I18n.l(Time.now, :format => :short) + \"- Projects.csv\"\r\n    content = Project.to_csv(projects)\r\n    content = BOM + Iconv.conv(\"utf-16le\", \"utf-8\", content)\r\n    send_data content, :filename => filename\r\n  end\r\nend\r\n<\/pre>\n

Perceba que estamos usando CSV como extens\u00e3o do arquivo. Isso \u00e9 porque geralmente arquivos TSV n\u00e3o est\u00e3o associados ao Excel. Este trecho usa um timestamp como nome de arquivo, o que \u00e9 geralmente uma boa pr\u00e1tica.<\/p>\n

Resumindo<\/h3>\n

Essas s\u00e3o as 3 leis para lidar com CSVs para o Excel:<\/p>\n

    \n
  1. Use tabula\u00e7\u00f5es, e n\u00e3o v\u00edrgulas.<\/li>\n
  2. Campos N\u00c3O podem conter quebras de linha.<\/li>\n
  3. Use UTF-16 Little Endian para enviar o arquivo ao usu\u00e1rio. E adicione o BOM do Little Endian manualmente.<\/li>\n<\/ol>\n

    Tenha isso em mente e voc\u00ea nunca ter\u00e1 que explicar para seus clientes como abrir os dados exportados no Excel.<\/p>\n

    H\u00e1 um \u00faltimo problema. O OpenOffice n\u00e3o ir\u00e1 abrir facilmente arquivos feitos na \"especifica\u00e7\u00e3o\" do Excel. O Google Analytics resolve este problema mostrando dois links ao usu\u00e1rio: \"Exportar para CSV\" e \"Exportar para Excel\". O primeiro \u00e9 o CSV normal e o segundo \u00e9 o arquivo TSV feito especialmente para o Excel.<\/p>\n

    E culpe a Microsoft por este comportamento estranho.<\/p>\n

    UPDATE:<\/strong> @danielvlopes<\/a> nos avisou de uma solu\u00e7\u00e3o existente que encapsula o processo deste post, chamado csv_builder<\/a>. Voc\u00ea s\u00f3 precisa configurar o @output_encoding para usar \"utf-16\" (preste aten\u00e7\u00e3o com o BOM).<\/p>\n

    @jncoward<\/a> tamb\u00e9m enviou um link para a gem spreadsheet<\/a>, que escreve formatos nativos do Excel. TSV \u00e9 um formato mais simples e mais r\u00e1pido, mas a gem spreadsheet pode lhe ajudar em casos mais complexos.<\/p>\n","protected":false},"excerpt":{"rendered":"

    To see this post in English, click here. \u00c0s vezes os usu\u00e1rios querem manipular os dados de diversos modos. Nesses cen\u00e1rios, \u00e9 comum exportar os dados num formato de tabela para que se possa usar um editor de planilhas e ent\u00e3o filtrar, particionar e mudar os dados da forma que o usu\u00e1rio quiser. Generalmente, fazemos … \u00bb<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[3],"tags":[],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/308"}],"collection":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=308"}],"version-history":[{"count":9,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/308\/revisions"}],"predecessor-version":[{"id":322,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/308\/revisions\/322"}],"wp:attachment":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=308"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=308"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=308"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}