Home-Produkte-Testarea-Kontakt-Datenschutz-Aktualisiert: 15-Jul-2013
< Voriger Tag   Nächster Tag >

Montag, 15. Juli 2013

SWB: Blockelemente und Schriften

Blockelementen sorgen nun für einen Zeilenumbruch und es gibt unterschiedliche Schriften für die Überschriften, den Fließtext und andere Elemente.

Neue Zeile bei Blockelementen wie h1, p, ul

Bei Blockelementen wie h1, p, ul, ... wird eine neue Zeile angefangen. Dazu muss jetzt nicht nur der Text aus dem HTML-Quelltext extrahiert werden, sondern die einzelnen HTML-Elemente verarbeitet werden. lxml_print_tags.py:

def print_tags(element, indent=''):
    print(indent, element.tag)
    for child in element:
        print_tags(child, indent + '    ')
lxml: tail-Text

Eine Besonderheit von lxml ist dabei der tail-Text. lxml verzichtet auf Text-Nodes, stattdessen steht der Text eines Elements in .text und der dem Element folgende Text in .tail. lxml_text_tail.py:

<p>Text <strong>strong-text <em>em-text</em> em-tail</strong> strong-tail</p>
def print_tags(element, indent=''):
    print("%stag: '%s' text: '%s' tail: '%s'" % (indent, element.tag, encode(element.text), encode(element.tail)))

    for child in element:
        print_tags(child, indent + '    ')
tag: 'p'          text: 'b'Text ''        tail: 'None'
    tag: 'strong' text: 'b'strong-text '' tail: 'b' strong-tail''
        tag: 'em' text: 'b'em-text''      tail: 'b' em-tail''

Um selbst den Text aus einem HTML-Quelltext zu extrahieren, benötigt man daher zunächst den .text des Elements, dann den extrahierten Text der Child-Elemente und zum Schluß den .tail-Text des Elements. lxml_extract_text.py:

def extract_text(element):
    text = ''

    if element.text != None:
        text = text + element.text

    for child in element:
        text = text + extract_text(child)

    if element.tail != None:
        text = text + element.tail


    return text
VSpace-Widget

Für den Zeilenumbruch vor Blockelementen wie h1, p, ul, ... sorgt das VSpaceWidget. Dies wird in parse_html() der TextBox hinzugefügt, wenn Blockelemente gefunden werden. Die Liste dieser Elemente, inklusive ihrer VSpace-Höhen, steht in self.VSPACES. simplewebbrowser.py:

self.VSPACES    = {'h1': 10, 'h2': 8, 'h3': 6, 'h4': 4, 'h5': 4, 'h6': 4,
...

def parse_html(self, element):
    if element.tag in self.VSPACES:
        self.text_box.widgets.append(VSpaceWidget(self.VSPACES[element.tag]))

    if element.text != None:
        for word in element.text.split():
            self.text_box.widgets.append(TextWidget(word))

    for child in element:
        self.parse_html(child)

    if element.tail != None:
        for word in element.tail.split():
            self.text_box.widgets.append(TextWidget(word))

Zur Berücksichtigung der VSpaces benutzt layout() nun mehrere if isinstance(widget, VSpaceWidget)-Abfragen, um eine neue Zeile anzufangen. textbox.py:

# Fill line with following widgets
if not isinstance(widget, VSpaceWidget):
    widget = next(widgetsIter)
    widget.calculate_size(g)
    widget.left = self.left + line_width
    widget.top  = self.text_height

while not isinstance(widget, VSpaceWidget) and line_width + self.WORD_GAP + widget.width < self.width:
    line_width  = line_width + self.WORD_GAP + widget.width
    line_height = max(line_height, widget.height)
...
self.IGNORE_TAGS

Der Inhalt von Elementen wie style und script wird derzeit nicht benötigt. Diese sind in in self.IGNORE_TAGS aufgelistet und werden gleich zu Anfang in parse_html() verworfen. simplewebbrowser.py:

self.IGNORE_TAGS = ['title', 'style', 'script']


def parse_html(self, element):
    if element.tag in self.IGNORE_TAGS:
        return
Download: Revision 127
svn checkout --revision 127 https://svn.sven-drieling.de/repos/trunk/playground/webbrowser/ yd-playground-webbrowser-127
Uendliche Breite für VSpaceWidget

Im letzten Schritt habe ich noch dem VSpaceWidget eine uendliche Breite gegeben. Damit braucht in layout() das VSpaceWidget keine eigene Behandlung mehr mit dem if isinstance(widget, VSpaceWidget)-Abfragen, sondern wird wie jedes anderen Widget, das breiter als TextBox.width ist, automatisch an den Anfang der nächsten Zeile gesetzt. Allerdings muss jetzt dem Setzen der TextBox.text_width ein if line_width < float('inf'): vorangestellt werden, damit die Textbreite nicht unendlich wird. textbox.py:

class VSpaceWidget(Widget):
    def __init__(self, height=0):
        super().__init__(width=float('inf'), height=height)
while line_width + self.WORD_GAP + widget.width < self.width:
    line_width  = line_width + self.WORD_GAP + widget.width
    ...

# Next line
if line_width < float('inf'):
    self.text_width  = max(self.text_width, line_width)
Download: Revision 127
svn checkout --revision 127 https://svn.sven-drieling.de/repos/trunk/playground/webbrowser/ yd-playground-webbrowser-127

Schriften

Die Schriften und Farben werden mit dem AttributeWidget gesetzt. Das Widget hat eine Breite und Höhe von 0. Beim Aufruf der draw()-Methode ändert das Widget nur die Attribute der Grafik.

class AttributeWidget(Widget):
   ...

    def draw(self, g, left, top):
        if self.font != None:
            g.font = self.font

        if self.fg != None:
            g.fg = self.fg

        if self.bg != None:
            g.bg = self.bg

Dies muss auch in layout() erfolgen, damit die Höhe und Breite der Wörter mit der richtigen Schrift berechnet werden. textbox.py:

widget = next(widgetsIter)
widget.calculate_size(g)
if isinstance(widget, AttributeWidget):
    widget.draw(g, 0, 0)

Die Attribute werden in parse_html() hinzugefügt, dabei muss nach dem Element immer wieder auf die vorigen Attribute zurückgeschaltet werden. Die Grundidee am Beispiel des h1-Elements. simplewebbrowser.py:

def parse_html(self, element):
    ...
    if element.tag == 'h1':
        self.text_box.widgets.append(AttributeWidget(font=self.fonts.h1))

    if element.text != None:
        for word in element.text.split():
            self.text_box.widgets.append(TextWidget(word))

    for child in element:
        self.parse_html(child)

    if element.tag == 'h1':
        self.text_box.widgets.append(AttributeWidget(font=self.fonts.normal))

    if element.tail != None:
        for word in element.tail.split():
            self.text_box.widgets.append(TextWidget(word))

Und die allgemeine Implementierung mit einem Attribut-Stack, so dass das Zurücksetzen auch bei verschachtelten Elementen (<p>text <strong><a href=""><em></em></a> text</strong></p>) funktioniert. simplewebbrowser.py:

self.ATTR = {'default': AttributeWidget(font=self.fonts.normal,fg=(0, 0, 0, 255),bg=(255, 255, 255, 255)),

            'h1': AttributeWidget(font=self.fonts.h1),
            'h2': AttributeWidget(font=self.fonts.h2),
                ...
                }

self.attr_stack   = []
self.attr_current = self.ATTR['default']


def parse_html(self, element):
   ...

    if element.tag in self.ATTR:
        self.attr_stack.append(self.attr_current)
        self.attr_current = self.ATTR[element.tag]
        self.text_box.widgets.append(self.attr_current)

    if element.text != None:
        for word in element.text.split():
            self.text_box.widgets.append(TextWidget(word))

    for child in element:
        self.parse_html(child)

    if element.tag in self.ATTR:
        self.attr_current = self.attr_stack.pop()
        self.text_box.widgets.append(self.attr_current)

    if element.tail != None:
        for word in element.tail.split():
            self.text_box.widgets.append(TextWidget(word))
Download: Revision 133
svn checkout --revision 133 https://svn.sven-drieling.de/repos/trunk/playground/webbrowser/ yd-playground-webbrowser-133

[Direktlink]

< Voriger Tag   Nächster Tag >

  RSS V0.91

<Juli 2013 >
01020304050607
08091011121314
15161718192021
22232425262728
293031    

Home-Produkte-Testarea-Kontakt-Datenschutz-Aktualisiert: 15-Jul-2013
(C) 2000-2018 by Sven Drieling