Formatando textos com o plugin has_markup no ActiveRecord

14/10/08

À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.

Criando e-books com o Bookmaker

08/06/08

Depois de ver o excelente resultado do livro Ruby on Rails 2.1 - O que há de novo?, uma iniciativa de Carlos Brando e Marcos Tapajós, me empolguei e empacotei todo o processo de criação em uma gem chamada Bookmaker.

Pelo que pude perceber, esse já era um projeto que estava em andamento e se eu não me adiantasse e fizesse o desenvolvimento, o Carlos provavelmente o faria. Isso está longe de ser uma competição (eu até fiz uma rídicula contribuição no Github). Na verdade, eu já estava procurando algo simples para criar PDFs mas não tinha encontrado nada.

Como funciona o Bookmaker

O Bookmaker é basedo em um gerador de PDFs chamado Prince, que converte arquivos HTMLs usando CSS-3 e toda a sua especificação para impressão. A idéia é baseada em um artigo publicado originalmente pela A List Apart chamado Printing a Book with CSS: Boom!

Embora o Prince seja pago (495USD por uma única licença de usuário), a versão gratuita disponível em http://www.princexml.com/download/ funciona muito bem e adiciona um pequeno logotipo da empresa na página inicial, que é removido quando é impresso.

Todo o texto do livro é escrito em Markdown, uma linguagem de marcação muito simples. Em breve irei adicionar suporte ao Textile, uma outra linguagem de marcação.

O usuários de Mac têm uma vantagem adicional: todo o código do livro pode ser convertido usando o Textmate, de modo que seu o syntax highlight fica semelhante ao seu tema preferido. Não custa dizer que tudo é feito automaticamente.

Instalando o Bookmaker

Como o Prince está disponível para todos os sistemas operacionais, em tese, está disponível em qualquer plataforma. Em tese. Na realidade, por pura preguiça não adicionei suporte ao Windows, mas isso provavelmente deve acontecer em futuras versões. Se você precisa deste suporte e não quer esperar, faça um fork do projeto no Github e envie um patch.

O primeiro passo é instalar o Prince. Acesse o site http://www.princexml.com/download/ e baixe a versão específica para a sua plataforma. Para àqueles que usam Mac OS X, use os comandos abaixo para fazer a instalação.

cd ~/Downloads
wget -c http://www.princexml.com/download/prince-6.0r6-macosx.tar.gz
tar xzf prince-6.0r6-macosx.tar.gz 
cd prince-6.0r6-macosx
sudo sh install.sh

O projeto do Bookmaker ainda não foi aprovado no Rubyforge. Por enquanto, você pode baixar a gem em http://f.simplesideias.com.br/bookmaker-0.0.3.gem.

cd ~/Downloads
wget -c http://f.simplesideias.com.br/bookmaker-0.0.3.gem
sudo gem install bookmaker

Agora, vamos instalar a biblioteca que irá converter nossos arquivos Markdown para HTML.

sudo gem install rubigen
sudo gem install discount
sudo gem install redcloth

Se você é usuário de Mac OS X e usa o Textmate, siga os passos abaixo. Caso contrário, pule para Criando seu livro.

Vamos instalar a biblioteca de expressão regular Oniguruma, disponível em http://www.geocities.jp/kosako3/oniguruma/. Para instalá-la, execute os comandos abaixo.

cd ~/Downloads
wget -c http://www.geocities.jp/kosako3/oniguruma/archive/onig-5.9.1.tar.gz
tar zxf onig-5.9.1.tar.gz
cd onig-5.9.1
./configure
make
sudo make install

Instale também a gem Ultraviolet, responsável por converter seu código usando o Textmate.

sudo gem install ultraviolet

Criando seu livro

Para criar seu livro, execute o comando "bookmaker ". Isso irá gerar uma estrutura de arquivos e diretórios a figura abaixo.

Estrutura de arquivos e diretórios gerada pelo Bookmaker

Agora é hora de escrever o seu livro. Todo o o conteúdo que você escrever, deve ser colocado no diretório "text". O Bookmaker obriga você a separar o livro em capítulos, que nada mais é que um diretório com diversos arquivos Markdown ou Textile. Como o livro será organizado alfabeticamente, é necessário que você escreva uma sequência númerica para garantir a orderação.

Organização dos textos usando capítulos

Eventualmente, você irá querer visualizar seu livro. Para gerar um PDF, basta executar o comando "rake pdf" para criar o livro no diretório "output".

Livro gerado com o Bookmaker

E por fim…

É isso! Se você tem sugestões, deixe um comentário. Se quiser colaborar com alguma funcionalidade ou corrigir algum erro, envie um patch pelo Github.

Update: Atualizei a versão da gem para 0.0.2. A maior atualização foi o suporte ao Textile. Adicionei também a dependência das gems Rubigen e RedCloth.