Formatando textos com o plugin has_markup no ActiveRecord


Leia em 3 minutos

Às vezes é preciso deixar que um usuário entre com textos formatados —seja HTML puro, Markdown ou Textile. O grande problema está na hora de filtrar o que está sendo enviado; não queremos que um markup inválido ou com tags que você não quer sejam salvas no banco de dados.

Infelizmente, o Ruby on Rails não possui nenhuma forma nativa de fazer isso (na verdade, acho meio burra a decisão de manter os métodos de sanitização apenas no escopo de views).

Pensando nesse problema, criei um plugin chamado has_markup, que permite especificar quais tags e atributos são permitidos, eliminando todo o resto. Ele também normaliza o HTML enviado, convertendo-o para XHTML caso ele esteja mal-formatado. Você pode definir se o texto será formatado em HTML puro, Markdown ou Textile.

Bem simples de usar e faz o trabalho muito bem!

Como instalar

Primeiro, vamos instalar o plugin.

script/plugin install git://github.com/fnando/has_markup.git

O has_markup possui algumas dependências se você quiser usar Tidy, Markdown ou Textile. Veja abaixo como você pode instalar estas dependências.

Markdown

Parar ter suporte a Markdown, é necessário instalar a gem RDiscount.

sudo gem install rdiscount

Textile

Se você prefere Textile —eu prefiro!—, instale a gem RedCloth.

sudo gem install RedCloth

Tidy

Se você quer ter certeza de que não irá quebrar o seu próprio HTML, torne-o válido com o Tidy. Primeiro é preciso instalar a biblioteca. Acesse e faça o download do código-fonte. Existem alguns binários disponíveis, então dê uma olhada para descobrir qual é o mais adequado para você. Para compilar, execute os comandos abaixo.

tar xvf tidy4aug00.tgz
cd tidy4aug00
make
sudo make install

O Tidy deu alguns warnings após a compilação, mas ele foi instalado. No Mac OS X ele é instalado em /usr/lib/libtidy.dylib.

cp -f tidy /usr/local/bin
cp -f man_page.txt /usr/local/man/man1/tidy.1
cd /usr/local/bin; \
	chmod 755 tidy; \
	chgrp bin tidy; \
	chown bin tidy;
chown: bin: Invalid argument
make: *** [install] Error 1

 

Agora instale a gem com sudo gem install tidy.

Este plugin tentará encontrar o Tidy em seu sistema, procurando alguns diretórios onde ele normalmente estaria. Se ele não puder ser encontrado, especifique o caminho para o diretório da biblioteca em seu arquivo de ambiente na constante TIDY_PATH.

# config/environments/development.rb
TIDY_PATH = '/custom/path/to/tidy'

Usando o has_markup

O has_markup é um plugin para ActiveRecord, que disponibiliza algumas ferramentas que podem ser usadas em qualquer lugar. No seu modelo, basta adicionar algo como isto:

class Post < ActiveRecord::Base
  has_markup :content,
    :format       => :markdown,
    :tidy         => true,
    :tags         => %w(p a em strong ul li),
    :attributes   => %w(href)
end

No exemplo acima, o atributo content será formatado com Markdown. Apenas algumas poucas tags estão disponíveis e apenas o atributo href pode ser utilizado. O markup também irá passar pelo Tidy, garantindo que é um XHTML válido.

NOTA: o has_markup mantém uma versão final do markup em um atributo formatted_<attribute>. Dessa forma, você não terá problemas de performance por ter que converter o texto toda vez que quiser exibí-lo. O modelo acima poderia ter uma estrutura como esta:

create_table :posts do |t|
  t.string  :title
  t.text    :content, :formatted_content
end

Se você não quer limitar o conteúdo salvo, basta que você não especifique as tags e atributos aceitos.

class Post < ActiveRecord::Base
  has_markup :content,
    :format       => :textile,
    :tidy         => true
end

É possível usar o has_markup apenas para validar um HTML, mesmo sem estar formatado como Markdown ou Textile; basta especificar o formato :html.

class Post < ActiveRecord::Base
  has_markup :content,
    :format       => :html,
    :tidy         => true
end

Para pegar o HTML equivalente a um texto Markdown ou Textile diretamente, você pode instanciar um objeto da classe Markup

markup = Markup.new(:markdown, 'some text')
markup = Markup.new(:textile,  'some text')
puts markup.to_html

Para sanitizar códigos HTML, utilize a classe Sanitize.

# will sanitize and normalize HTML
Sanitize.html('<script>alert(document.cookie)</script>')

# will sanitize and allow only the specified tags
Sanitize.html('<script>alert(document.cookie)</script>',
	:tags => %w(p a em strong img ul li ol)
)

# will sanitize and allow only the specified attributes
Sanitize.html('<script>alert(document.cookie)</script>',
	:attributes => %w(href title alt)
)

# will sanitize and normalize HTML using Tidy
Sanitize.html('<script>alert(document.cookie)</script>',
	:tidy => true
)

Se quiser normalizar um HTML, utilize o método tidy.

Sanitize.tidy('some text', options)

Feedback

É isso! Se você tem alguma sugestão, dúvida ou crítica, envie um comentário. O projeto está hospedado no Github, então sinta-se livre para fazer um fork a qualquer momento.