= Receita: Renderizador HTML em Tkinter =
O seguinte código implementa um renderizador de textos HTML ultra-simples utilizando o toolkit Tkinter. 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.
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 Text do Tkinter quanto de um HTMLParser, da biblioteca padrão.
== Código ==
{{{
#!python
from Tkinter import *
from tkSimpleDialog import *
from HTMLParser import HTMLParser
class HTMLRender(Text, 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 comuns em HTML que não vamos renderizar
__ignore = [ "body", "html", "div" ]
# Aqui definimos tags que são normalmente deixadas abertas
__open = [ "dt", "dd", "p" ]
# Formatos/fontes aplicadas a cada tag:
__formats = {
'h1': { 'font': ("Helvetica", "16", "bold"), 'justify': CENTER },
'h2': { 'font': ("Helvetica", "12", "bold"), 'justify': CENTER },
'h3': { 'font': ("Helvetica", "10", "bold italic")} ,
'dt': { 'font': ("Helvetica", "10", "bold") } ,
'b': { 'font': ("Helvetica", "10", "bold") },
'i': { 'font': ("Helvetica", "10", "italic") },
'strong': { 'font': ("Helvetica", "10", "bold italic") },
'default': { 'font': ("Helvetica", "10"), 'foreground': 'red' },
}
def __init__(self, *cnf, **kw):
"""
Inicializa o HTMLRender, inicializando pelas classes-mãe.
O dicionário __tags contém uma lista das tags presentes no
texto e suas posições, para podermos alocar as formatações.
O __index é a posição de inserção do texto na widget.
"""
Text.__init__(self, *cnf, **kw)
HTMLParser.__init__(self)
self.__last = None
self.__tags = { }
self.__index = 0
def insert(self, text):
"""
A classe Text utiliza a interface insert para inserir texto
na widget, enquanto a classe HTMLParser utiliza feed. Este
método se sobrepõe ao método de Text, de tal maneira que a
inserção do texto causa a sua interpretação pelo parser.
"""
self.feed(text)
def handle_starttag(self, tag, attr):
"""
Aqui manuseamos cada uma das tags abertas, aplicando a
formatação adequada.
"""
# Se a tag deve ser ignorada, nada deve ser feito.
if tag in self.__ignore:
pass
# Se a tag cria um bloco, inserimos quebras de parágrafo
# no texto para simular o efeito.
elif tag in self.__block:
if self.__last in self.__open:
self.handle_endtag(self.__last)
self.__last = tag
Text.insert(self, INSERT, "\n\n")
# Inserimos a posição da tag no texto no dicionário para
# marcarmos a posição em que a formatação deve ser aplicada.
if tag in self.__tags:
self.__tags[tag].append(self.index(INSERT))
else:
self.__tags[tag] = [ self.index(INSERT) ]
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()) + ' '
Text.insert(self, INSERT, 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:
start = self.__tags[tag].pop()
end = self.index(INSERT)
tag_name = "Tag%05d" % self.__index
self.__index = self.__index + 1
self.tag_add(tag_name, start, end)
self.tag_config(tag_name, **self.__formats[tag])
return
except KeyError:
pass
# E é isso aí: 109 linhas, incluindo comentários!!!
}}}
== Exemplo de uso ==
Eu criei esta widget para renderizar texto de ajuda em uma aplicação simples. O trecho de código que pode ser utilizado para testar esse renderizador está abaixo:
{{{
#!python
app = Tk() # Criamos uma aplicacao Tk...
# Note que todas as propriedades de um Text estão
# disponíveis. Cuidado, pois alguns efeitos colaterais
# meio esquisitos podem aparecer.
text = HTMLRender(app, font = ("Helvetica", "10", ""), wrap = WORD)
text.insert(open("docs/help.html").read())
# Uma barra de rolagem pode ser bastante útil (mas os
# detalhes fogem ao escopo deste receitinha:
sb = Scrollbar(app , orient = VERTICAL, command = text.yview)
text.configure(state = DISABLED, yscrollcommand = sb.set)
# E, claro, precisamos colocar a widget em seu lugar.
text.grid(row = 1, column = 1, sticky = N+W+E+S)
sb.grid(row = 1, column = 2, sticky = N+S)
# Por fim, executamos:
app.mainloop()
}}}
Volta para CookBook.
----
por AlexandreNalon