= Receita: Renderizador HTML em PyGTK =
O seguinte código implementa um renderizador de textos HTML ultra-simples utilizando o toolkit PyGTK. Ele não é muito capaz, não se dá bem com textos com formatações muito complexas, não lida com imagens nem tabelas. No entanto, para textos simples, seu desempenho é bastante adequado, e é fácil extendê-lo para conter mais tags. Esta é a versão PyGTK do RenderizadorHtml em Tkinter neste mesmo CookBook.
Além disso, é um bom exemplo de uma classe que não se daria bem sem herança múltipla. O renderizador é (precisa ser) uma widget que mostra textos, mas também precisa ser capaz de ler e entender HTML. Assim sendo, a classe é derivada tanto de um Textview do PyGTK quanto de um HTMLParser, da biblioteca padrão.
== Código ==
{{{
#!python
import pygtk
pygtk.require('2.0')
import gtk
import pango
from HTMLParser import HTMLParser
class HTMLRender(gtk.TextView, HTMLParser):
# Aqui definimos as tags alinhadas:
__inline = [ "b", "i", "strong" ]
# Aqui definimos as tags que geram seus próprios blocos:
__block = [ "h1", "h2", "h3", "p", "dl", "dt", "dd" ]
# Aqui algumas tags que não vamos renderizar:
__ignore = [ "body", "html", "div" ]
# Aqui algumas tags que geralmente são deixadas abertas:
__open = [ "dt", "dd", "p" ]
# Formatos e fontes aplicadas às tags
__formats = {
'h1': { 'font': "sans bold 16",
'justification': gtk.JUSTIFY_CENTER,
'pixels-above-lines': 8,
'pixels-below-lines': 4 },
'h2': { 'font': "sans bold 12",
'justification': gtk.JUSTIFY_CENTER,
'pixels-above-lines': 6,
'pixels-below-lines': 3 },
'h3': { 'font': "sans bold italic 10",
'pixels-above-lines': 4,
'pixels-below-lines': 0 },
'dl': { 'font': "sans 10" },
'dd': { 'font': "sans 10",
'left-margin': 10, 'right-margin': 10,
'pixels-above-lines': 2,
'pixels-below-lines': 2 },
'dt': { 'font': "sans bold 10",
'pixels-above-lines': 3,
'pixels-below-lines': 2 },
'p': { 'font': "sans 10",
'pixels-above-lines': 4,
'pixels-below-lines': 4 },
'b': { 'font': "sans bold 10", },
'i': { 'font': "sans italic 10", },
'strong': { 'font': "sans bold italic 10" },
'code': { 'font': "monospace 10" }
}
def __init__(self, *cnf, **kw):
"""
Inicializamos o HTMLParser e o TextView. O TextView deve
ser configurado não editável e com 'word-wraping', ou seja
com quebra de linha nos limites de palavras. A formatação
das tags também é inicializada. O dicionário __tags contém
uma lista das tags presentes no texto e suas posições, para
podermos alocar as formatações.
"""
gtk.TextView.__init__(self, *cnf, **kw)
HTMLParser.__init__(self)
self.set_editable(False)
self.set_wrap_mode(gtk.WRAP_WORD)
self.__tb = self.get_buffer()
self.__last = None
self.__tags = { }
for tag in self.__formats:
self.__tb.create_tag(tag, **self.__formats[tag])
def set_text(self, txt):
"""
O widget TextView do PyGTK é desnecessariamente complicado.
Para a inserção de texto, é necessário indicar um buffer de
texto; para formatar o texto, é preciso encontrar marcas e
tags no texto, etc. Para simplificar, este método alimenta o
texto ao parser HTML que faz a formatação automaticamente.
O nome é para seguir a aparente convenção de nomes do PyGTK.
"""
self.feed(txt)
def handle_starttag(self, tag, attr):
"""
Aqui manipulamos a abertura das tags. Ao ser aberta, a tag
tem sua posição registrada, para que a formatação seja a-
plicada posteriormente, no fechamento.
"""
# Se a tag deve ser ignorada, nada deve ser feito.
if tag in self.__ignore:
pass
# Se a tag deve criar um bloco, adicionamos uma quebra de
# linha ao parágrafo, para simular o efeito de blocagem.
# Adicionalmente, blocos 'fecham' tags previamente abertas.
elif tag in self.__block:
if self.__last in self.__open:
self.handle_endtag(self.__last)
self.__last = tag
end_iter = self.__tb.get_end_iter()
self.__tb.insert(end_iter, "\n")
# Marcamos a posição da tag para posterior aplicação da
# formatação.
end_iter = self.__tb.get_end_iter()
mark = self.__tb.create_mark(None, end_iter, True)
if tag in self.__tags:
self.__tags[tag].append(mark)
else:
self.__tags[tag] = [ mark ]
def handle_data(self, data):
"""
Este método recebe os dados de uma tag, que, tipicamente,
é texto a ser renderizado. Simplesmente inserimos o texto
na widget. No entanto, os renderizadores de HTML devem
tratar espaços contíguos e caracteres de tabulação e quebra
de página como um simples espaço em branco. Na primeira
linha nós fazemos esse serviço.
"""
data = ' '.join(data.split()) + ' '
end_iter = self.__tb.get_end_iter()
self.__tb.insert(end_iter, data)
def handle_endtag(self, tag):
"""
Aqui as tags são fechadas. Suas posições são encontradas
e as formatações são aplicadas. O processo consiste em
recuperar a posição inicial em que a formatação deve ser
aplicada (obtida em handle_starttag), obter a posição
atual (após o texto ter sido inserido em handle_data),
inserir os marcadores do Text e aplicar a formatação. O
processo é extremamente simples.
"""
try:
if tag not in self.__ignore:
start_mark = self.__tags[tag].pop()
start = self.__tb.get_iter_at_mark(start_mark)
end = self.__tb.get_end_iter()
self.__tb.apply_tag_by_name(tag, start, end)
return
except KeyError:
pass
}}}
== Exemplo de uso ==
Como antes, esta widget foi criada para a apresentação de texto de ajuda em uma pequena aplicação. O trecho de código a seguir pode ser utilizado para a apresentação:
{{{
#!python
if __name__ == "__main__":
q = gtk.Window() # Criamos a janela
t = HTMLRender() # Criamos o renderizador
s = open("docs/help.html").read() # O texto precisa ser UTF-8!!!
t.set_text(s)
t.show()
sb = gtk.ScrolledWindow() # Criamos um visualizador com barra de rolagem...
sb.add(t) # e adicionamos o texto
sb.show()
q.add(sb) # Por fim, vamos para o loop principal.
q.show()
gtk.main()
}}}
Volta para CookBook.
----
Por AlexandreNalon