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
1 from Tkinter import *
2 from tkSimpleDialog import *
3 from HTMLParser import HTMLParser
4
5 class HTMLRender(Text, HTMLParser):
6
7 # Aqui definimos as tags alinhadas:
8 __inline = [ "b", "i", "strong" ]
9
10 # Aqui definimos as tags que geram seus próprios blocos
11 __block = [ "h1", "h2", "h3", "p", "dl", "dt", "dd" ]
12
13 # Aqui algumas tags comuns em HTML que não vamos renderizar
14 __ignore = [ "body", "html", "div" ]
15
16 # Aqui definimos tags que são normalmente deixadas abertas
17 __open = [ "dt", "dd", "p" ]
18
19 # Formatos/fontes aplicadas a cada tag:
20 __formats = {
21 'h1': { 'font': ("Helvetica", "16", "bold"), 'justify': CENTER },
22 'h2': { 'font': ("Helvetica", "12", "bold"), 'justify': CENTER },
23 'h3': { 'font': ("Helvetica", "10", "bold italic")} ,
24 'dt': { 'font': ("Helvetica", "10", "bold") } ,
25 'b': { 'font': ("Helvetica", "10", "bold") },
26 'i': { 'font': ("Helvetica", "10", "italic") },
27 'strong': { 'font': ("Helvetica", "10", "bold italic") },
28 'default': { 'font': ("Helvetica", "10"), 'foreground': 'red' },
29 }
30
31 def __init__(self, *cnf, **kw):
32 """
33 Inicializa o HTMLRender, inicializando pelas classes-mãe.
34 O dicionário __tags contém uma lista das tags presentes no
35 texto e suas posições, para podermos alocar as formatações.
36 O __index é a posição de inserção do texto na widget.
37 """
38 Text.__init__(self, *cnf, **kw)
39 HTMLParser.__init__(self)
40 self.__last = None
41 self.__tags = { }
42 self.__index = 0
43
44 def insert(self, text):
45 """
46 A classe Text utiliza a interface insert para inserir texto
47 na widget, enquanto a classe HTMLParser utiliza feed. Este
48 método se sobrepõe ao método de Text, de tal maneira que a
49 inserção do texto causa a sua interpretação pelo parser.
50 """
51 self.feed(text)
52
53 def handle_starttag(self, tag, attr):
54 """
55 Aqui manuseamos cada uma das tags abertas, aplicando a
56 formatação adequada.
57 """
58 # Se a tag deve ser ignorada, nada deve ser feito.
59 if tag in self.__ignore:
60 pass
61 # Se a tag cria um bloco, inserimos quebras de parágrafo
62 # no texto para simular o efeito.
63 elif tag in self.__block:
64 if self.__last in self.__open:
65 self.handle_endtag(self.__last)
66 self.__last = tag
67 Text.insert(self, INSERT, "\n\n")
68
69 # Inserimos a posição da tag no texto no dicionário para
70 # marcarmos a posição em que a formatação deve ser aplicada.
71 if tag in self.__tags:
72 self.__tags[tag].append(self.index(INSERT))
73 else:
74 self.__tags[tag] = [ self.index(INSERT) ]
75
76 def handle_data(self, data):
77 """
78 Este método recebe os dados de uma tag, que, tipicamente,
79 é texto a ser renderizado. Simplesmente inserimos o texto
80 na widget. No entanto, os renderizadores de HTML devem
81 tratar espaços contíguos e caracteres de tabulação e quebra
82 de página como um simples espaço em branco. Na primeira
83 linha nós fazemos esse serviço.
84 """
85 data = ' '.join(data.split()) + ' '
86 Text.insert(self, INSERT, data)
87
88 def handle_endtag(self, tag):
89 """
90 Aqui as tags são fechadas. Suas posições são encontradas
91 e as formatações são aplicadas. O processo consiste em
92 recuperar a posição inicial em que a formatação deve ser
93 aplicada (obtida em handle_starttag), obter a posição
94 atual (após o texto ter sido inserido em handle_data),
95 inserir os marcadores do Text e aplicar a formatação. O
96 processo é extremamente simples.
97 """
98 try:
99 start = self.__tags[tag].pop()
100 end = self.index(INSERT)
101 tag_name = "Tag%05d" % self.__index
102 self.__index = self.__index + 1
103 self.tag_add(tag_name, start, end)
104 self.tag_config(tag_name, **self.__formats[tag])
105 return
106 except KeyError:
107 pass
108
109 # 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:
1 app = Tk() # Criamos uma aplicacao Tk...
2
3 # Note que todas as propriedades de um Text estão
4 # disponíveis. Cuidado, pois alguns efeitos colaterais
5 # meio esquisitos podem aparecer.
6 text = HTMLRender(app, font = ("Helvetica", "10", ""), wrap = WORD)
7 text.insert(open("docs/help.html").read())
8
9 # Uma barra de rolagem pode ser bastante útil (mas os
10 # detalhes fogem ao escopo deste receitinha:
11 sb = Scrollbar(app , orient = VERTICAL, command = text.yview)
12 text.configure(state = DISABLED, yscrollcommand = sb.set)
13
14 # E, claro, precisamos colocar a widget em seu lugar.
15 text.grid(row = 1, column = 1, sticky = N+W+E+S)
16 sb.grid(row = 1, column = 2, sticky = N+S)
17
18 # Por fim, executamos:
19 app.mainloop()
Volta para CookBook.
por AlexandreNalon