associação pythonbrasil[11] django zope/plone planet Início Logado como (Entrar)

Revisão 6e 2006-10-04 17:40:15

Excluir mensagem

RenderizadorHtml

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