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
1 import pygtk
2 pygtk.require('2.0')
3 import gtk
4 import pango
5 from HTMLParser import HTMLParser
6
7 class HTMLRender(gtk.TextView, HTMLParser):
8
9 # Aqui definimos as tags alinhadas:
10 __inline = [ "b", "i", "strong" ]
11
12 # Aqui definimos as tags que geram seus próprios blocos:
13 __block = [ "h1", "h2", "h3", "p", "dl", "dt", "dd" ]
14
15 # Aqui algumas tags que não vamos renderizar:
16 __ignore = [ "body", "html", "div" ]
17
18 # Aqui algumas tags que geralmente são deixadas abertas:
19 __open = [ "dt", "dd", "p" ]
20
21 # Formatos e fontes aplicadas às tags
22 __formats = {
23 'h1': { 'font': "sans bold 16",
24 'justification': gtk.JUSTIFY_CENTER,
25 'pixels-above-lines': 8,
26 'pixels-below-lines': 4 },
27 'h2': { 'font': "sans bold 12",
28 'justification': gtk.JUSTIFY_CENTER,
29 'pixels-above-lines': 6,
30 'pixels-below-lines': 3 },
31 'h3': { 'font': "sans bold italic 10",
32 'pixels-above-lines': 4,
33 'pixels-below-lines': 0 },
34 'dl': { 'font': "sans 10" },
35 'dd': { 'font': "sans 10",
36 'left-margin': 10, 'right-margin': 10,
37 'pixels-above-lines': 2,
38 'pixels-below-lines': 2 },
39 'dt': { 'font': "sans bold 10",
40 'pixels-above-lines': 3,
41 'pixels-below-lines': 2 },
42 'p': { 'font': "sans 10",
43 'pixels-above-lines': 4,
44 'pixels-below-lines': 4 },
45 'b': { 'font': "sans bold 10", },
46 'i': { 'font': "sans italic 10", },
47 'strong': { 'font': "sans bold italic 10" },
48 'code': { 'font': "monospace 10" }
49 }
50
51 def __init__(self, *cnf, **kw):
52 """
53 Inicializamos o HTMLParser e o TextView. O TextView deve
54 ser configurado não editável e com 'word-wraping', ou seja
55 com quebra de linha nos limites de palavras. A formatação
56 das tags também é inicializada. O dicionário __tags contém
57 uma lista das tags presentes no texto e suas posições, para
58 podermos alocar as formatações.
59 """
60 gtk.TextView.__init__(self, *cnf, **kw)
61 HTMLParser.__init__(self)
62 self.set_editable(False)
63 self.set_wrap_mode(gtk.WRAP_WORD)
64 self.__tb = self.get_buffer()
65 self.__last = None
66 self.__tags = { }
67 for tag in self.__formats:
68 self.__tb.create_tag(tag, **self.__formats[tag])
69
70
71 def set_text(self, txt):
72 """
73 O widget TextView do PyGTK é desnecessariamente complicado.
74 Para a inserção de texto, é necessário indicar um buffer de
75 texto; para formatar o texto, é preciso encontrar marcas e
76 tags no texto, etc. Para simplificar, este método alimenta o
77 texto ao parser HTML que faz a formatação automaticamente.
78 O nome é para seguir a aparente convenção de nomes do PyGTK.
79 """
80 self.feed(txt)
81
82
83 def handle_starttag(self, tag, attr):
84 """
85 Aqui manipulamos a abertura das tags. Ao ser aberta, a tag
86 tem sua posição registrada, para que a formatação seja a-
87 plicada posteriormente, no fechamento.
88 """
89 # Se a tag deve ser ignorada, nada deve ser feito.
90 if tag in self.__ignore:
91 pass
92 # Se a tag deve criar um bloco, adicionamos uma quebra de
93 # linha ao parágrafo, para simular o efeito de blocagem.
94 # Adicionalmente, blocos 'fecham' tags previamente abertas.
95 elif tag in self.__block:
96 if self.__last in self.__open:
97 self.handle_endtag(self.__last)
98 self.__last = tag
99 end_iter = self.__tb.get_end_iter()
100 self.__tb.insert(end_iter, "\n")
101
102 # Marcamos a posição da tag para posterior aplicação da
103 # formatação.
104 end_iter = self.__tb.get_end_iter()
105 mark = self.__tb.create_mark(None, end_iter, True)
106 if tag in self.__tags:
107 self.__tags[tag].append(mark)
108 else:
109 self.__tags[tag] = [ mark ]
110
111
112 def handle_data(self, data):
113 """
114 Este método recebe os dados de uma tag, que, tipicamente,
115 é texto a ser renderizado. Simplesmente inserimos o texto
116 na widget. No entanto, os renderizadores de HTML devem
117 tratar espaços contíguos e caracteres de tabulação e quebra
118 de página como um simples espaço em branco. Na primeira
119 linha nós fazemos esse serviço.
120 """
121 data = ' '.join(data.split()) + ' '
122 end_iter = self.__tb.get_end_iter()
123 self.__tb.insert(end_iter, data)
124
125
126 def handle_endtag(self, tag):
127 """
128 Aqui as tags são fechadas. Suas posições são encontradas
129 e as formatações são aplicadas. O processo consiste em
130 recuperar a posição inicial em que a formatação deve ser
131 aplicada (obtida em handle_starttag), obter a posição
132 atual (após o texto ter sido inserido em handle_data),
133 inserir os marcadores do Text e aplicar a formatação. O
134 processo é extremamente simples.
135 """
136 try:
137 if tag not in self.__ignore:
138 start_mark = self.__tags[tag].pop()
139 start = self.__tb.get_iter_at_mark(start_mark)
140 end = self.__tb.get_end_iter()
141 self.__tb.apply_tag_by_name(tag, start, end)
142 return
143 except KeyError:
144 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:
1 if __name__ == "__main__":
2 q = gtk.Window() # Criamos a janela
3
4 t = HTMLRender() # Criamos o renderizador
5 s = open("docs/help.html").read() # O texto precisa ser UTF-8!!!
6 t.set_text(s)
7 t.show()
8
9 sb = gtk.ScrolledWindow() # Criamos um visualizador com barra de rolagem...
10 sb.add(t) # e adicionamos o texto
11 sb.show()
12
13 q.add(sb) # Por fim, vamos para o loop principal.
14 q.show()
15 gtk.main()
Volta para CookBook.
Por AlexandreNalon