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