RenderizadorHtmlPyGtk

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

RenderizadorHtmlPyGtk (editada pela última vez em 2008-09-26 14:07:30 por localhost)