Übersicht / Seite:
In HTML wird zwischen Block- und Inlineelementen unterschieden, wobei Blockelemente
unterschiedliche Inlineelemente enthalten können. Es sei an dieser Stelle angemerkt, die Einteilung der Elemente
wurde in HTML5 überarbeitet und neu kategorisiert. Zum leichteren Verständnis möchten wir an dieser Stelle
bei der alten Einteilung bleiben.
Als Beispiel soll ein p-Element dienen (p steht für Paragraph und erzeugt einen neuen Absatz). Innerhalb
eines HTML-Quelltextes wird der Beginn eines Absatzes mit einem öffnenden <p> notiert und der Absatz
endet erst mit einem schließenden </p>. Ein Browser erkennt, wenn innerhalb eines Absatzes Inlineelemente
wie z.B. <span ...> bis </span> notiert wurde.
Anders bei unseren selbst zu definierenden Tags für ein Tkinter Text-Widget. Eine Unterscheidung zwischen Block-
und Inlinetags würde es nicht geben und ein mit dem Tag-Namen "p" notierter Tag würde mit dem nächsten von uns
definierten Tag enden, also z.B. bei einem Tag mit dem Namen "br" oder "strong".
Am einfachsten erwies es sich bei HTML-Inlineelementen, die schließenden Elemente-Tags beim Einlesen in [end...]
zu wandeln und vor der Ausgabe diese Platzhalter [end...] zum Trennen mit Split zu verwenden. Alles was zwischen
Tag-Beginn und [end...] im eigelesenen String bzw. in der Variablen "data" enthalten ist, gehört dann zum Bereich
des Inlineelementes, was nach [end...] folgt wieder zum Tag "normal" oder zum Tag "zitate".
Ein anderes Problem betrifft die Erkennung der Position. So sollen z.B. span-Tags, für die innerhalb von Tkinter
die Namen der CSS-Klassen benutzt werden, am Zeilenbeginn keine Leerzeichen vorangestellt werden, innerhalb
einer Zeile hingegen immer, damit Worte nicht ohne Zwischenraum miteinander verschmelzen.
Aus dem HTML-Quelltext wurden als Bezeichner für Tkinter-Tags die Bezeichner der CSS-Klassen "boldbraun", "mittig",
"bildnotizen", "quotes" und "zitate" übernommen. Die Namen und Formatierungen für die span-Tags können geändert
werden, auch ist es möglich diesen neue CSS-Eigenschaften zuzuweisen oder weitere CSS-Klassen zu definieren.
Immer sollte dabei bedacht werden, wird ein CSS Class-Name im HTML-Quelltext geändert, so muss auch der Name
im Script und der zugehörige Tkinter-Tag angepasst und geändert werden. Etwas einfacher verhält es sich bei
den Tags für <strong>, <em>, <u>, <li>, da diese in HTML-Quelltext und innerhalb von
Tkinter eine sich gleichende Formatierung besitzen.
Weitere Einzelheiten sind den Kommentaren zu entnehmen.
Das Script mit der Klasse Formatparser:
#!/usr/local/bin/python # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # App-Name: HTML-Formatparser # Description: Diese Anwendung ermöglicht eine formatierte Ausgabe von # HTML-Seiten in einem Tkinter Text-Widget, wenn der Quelltext der # HTML-Seiten auf diese Verwendung abgestimmt wird. # Autor: Horst Müller # Version: 1.0 # Datum: 20. Oktober 2017 # Lizenz: GPLv3, einsehbar unter http://www.gnu.org/licenses/ # ----------------------------------------------------------------------------- from tkinter import Tk, Text, Scrollbar, PhotoImage, END from PIL import Image, ImageTk from html.parser import HTMLParser class Formatparser(HTMLParser): """ Diese Klasse ermöglicht eine formatierte Ausgabe von HTML-Seiten in einem Tkinter Text-Widget, wenn der Quelltext der HTML-Seiten auf diese Verwendung abgestimmt wird. """ def __init__(self): HTMLParser.__init__(self) self.fenster = Tk() self.images = [] self.ausschluss = ["html", "head", "title", "style", "body"] self.auswahltags = [ "boldbraun", "boldgrau", "strong", "em", "underline", "p" ] self.starttag = None self.attrs = None self.letzter_starttag = None self.letzte_datalaenge = 1 self.letzter_bereich = None def layout(self): self.fenster.title("Lesetest") self.fenster.config(bg="#d9cda3") self.textfeld = Text( self.fenster, height=48, pady=12, padx=12, wrap="word") scrollbar = Scrollbar(self.fenster) scrollbar.config(command = self.textfeld.yview) self.textfeld.config(yscrollcommand = scrollbar.set) scrollbar.pack(side="right", fill="y") self.textfeld.pack(pady=24, padx=24) self.textfeld.tag_config( "h1", font=("cambria", 22), foreground="#c7a621", spacing3=10) self.textfeld.tag_config( "h2", font=("cambria", 18), foreground="#98732e", spacing3=28) self.textfeld.tag_config( "h3", font=("cambria", 12, "bold"), foreground="#74521c", spacing1=22) self.textfeld.tag_config( "p", font=("cambria", 12)) self.textfeld.tag_config( "br", font=("cambria", 12)) self.textfeld.tag_config( "normal", font=("cambria", 12)) self.textfeld.tag_config( "klein", font=("cambria", 6)) self.textfeld.tag_config( "strong", font=("cambria", 12, "bold")) self.textfeld.tag_config( "em", font=("cambria", 12, "italic")) self.textfeld.tag_config( "underline", font=("cambria", 12, "underline")) self.textfeld.tag_config( "mittig", font=("cambria", 12), justify="center") self.textfeld.tag_config( "img", justify="center", spacing1=6) self.textfeld.tag_config( "bildnotizen", font=("cambria", 10, "italic"), foreground="#867a44", justify="center") self.textfeld.tag_config( "boldgrau", font=("cambria", 12, "bold"), foreground="#404040") self.textfeld.tag_config( "boldbraun", font=("cambria", 12, "bold"), foreground="#9b4c21") self.textfeld.tag_config( "liste", font=("cambria", 12), rmargin=20, lmargin2=20) self.textfeld.tag_config( "fehler", font=("cambria", 12), foreground="#ff8808", justify="center") # Tags für Div-Bereiche mit class "quotes" self.textfeld.tag_config( "quotes", background="#e6e5da") self.textfeld.tag_config( "zitate", font=("cambria", 12, "italic"), background="#e6e5da", justify="center", rmargin=20, lmargin1=20) self.textfeld.tag_config( "strongzitate", font=("cambria", 12, "bold italic"), background="#e6e5da", justify="center", rmargin=20, lmargin1=20) self.textfeld.tag_config( "underzitate", font=("cambria", 12, "italic underline"), background="#e6e5da", justify="center", rmargin=20, lmargin1=20) self.textfeld.tag_config( "boldbraunzitate", font=("cambria", 12, "bold italic"), foreground="#9b4c21", background="#e6e5da", justify="center", rmargin=20, lmargin1=20) self.textfeld.tag_config( "boldgrauzitate", font=("cambria", 12, "bold italic"), foreground="#404040", background="#e6e5da", justify="center", rmargin=20, lmargin1=20) def oeffne_htmlseite(self, filename): try: with open(filename) as datei: dateistring = datei.read().replace( "</span>", "[endspan]").replace( "</strong>", "[endstrong]").replace( "</em>", "[endem]").replace( "</u>", "[endunderline]") self.feed(dateistring) except FileNotFoundError: self.feed("<h3>Die Datei wurde nicht gefunden!</h3>") def insert_content(self, anfang, content, ende, tag): self.textfeld.insert(END, "{0}{1}{2}" .format(anfang, content, ende), tag) # Für <strong> oder <span class="name"> würde je nach Postion eine # Kombination aus "", " " und "\n" am Anfang und Ende genügen, nicht # hingegen für <u>. Bei <u> würden Leerzeichen " " am Anfang oder Ende mit # unterstrichen werden. Um dieses zu verhindern wurde " " durch "" und # jeweils einem zusätzlichen "folgetag" für Leerzeichen " " ersetzt. def liefere_leerzeichen(self, folgetag): self.insert_content("", " ", "", folgetag) def fuelle_inlinetags(self, folgetag, content, tagauswahl): self.liefere_leerzeichen(folgetag) self.insert_content("", content, "", tagauswahl) self.liefere_leerzeichen(folgetag) def formatiere_inlinetags( self, data, htmlstarttag, bereichstag, htmlendtag): elementedata = data.split(htmlendtag) content = " ".join(elementedata[0].split()) folgetag = ("normal" if self.letzter_bereich != "quotes" else "zitate") tagauswahl = htmlstarttag if self.letzter_bereich == "quotes": tagauswahl = bereichstag # Content vor [endtag] if ((self.letzter_starttag == htmlstarttag or self.letzter_starttag == "br") and self.letzte_datalaenge < 2): self.insert_content("", content, "", tagauswahl) self.liefere_leerzeichen(folgetag) elif (self.letzter_starttag in self.auswahltags and self.letzte_datalaenge < 2): self.insert_content("", content, "", tagauswahl) self.liefere_leerzeichen(folgetag) else: self.fuelle_inlinetags(folgetag, content, tagauswahl) # Content hinter [endtag] if len(elementedata) > 1: content = " ".join(elementedata[1].split()) if self.letzte_datalaenge < 2: self.insert_content("", content, "", folgetag) self.liefere_leerzeichen(folgetag) else: self.fuelle_inlinetags(folgetag, content, folgetag) self.letzte_datalaenge = len(elementedata) self.letzter_starttag = htmlstarttag def handle_starttag(self, tag, attrs): self.starttag = tag self.attrs = attrs # Vor und hinter dem Image ein Leerzeichen einfügen, um es mit den # Leerzeichen in Reihe mittig mit dem "img"-Tag auszurichten. if tag == "img": try: image = Image.open(dict(attrs)["src"]) image = ImageTk.PhotoImage(image) self.insert_content("\n", " ", "", "img") self.textfeld.image_create(END, image=image) self.insert_content(""," ", "\n", "img") self.images.append(image) self.letzter_starttag = tag except FileNotFoundError: self.textfeld.insert( END, "Das Image wurde nicht gefunden.\n\n", "fehler") # Für quotes/zitate wird der Beginn eines Div-Bereiches in Tkinter # nachgebildet und in der Methode "handle_endtag" geschlossen. elif self.starttag == "div": if "class" in dict(self.attrs): if dict(self.attrs)["class"] == "quotes": self.insert_content("", " ", "\n\n", "normal") self.insert_content("", " ", "\n", "zitate") self.letzter_bereich = "quotes" def handle_endtag(self, tag): if tag == "div" and self.letzter_bereich == "quotes": anfang = "" if self.letzter_starttag in ( "boldbraun", "boldgrau", "strong", "u"): anfang = "\n" self.insert_content(anfang, " ", "\n", "zitate") self.insert_content("", " ", "", "normal") self.letzter_bereich = "endquotes" # Einen Bereich wieder verlassen und zurückzusetzen. if tag in ("h3", "p", "li"): if self.letzter_bereich == "endquotes": self.letzter_bereich = None def handle_data(self, data): if self.starttag not in self.ausschluss: # Die Einrückungen aus dem HTML-Quelltext werden entfernt, da das # Textfeld diese Aufgabe und den Zeilenumbruch übernimmt. if self.starttag in ("h1", "h2"): content = data.replace(" ", "") self.insert_content("", content, "", self.starttag) elif self.starttag == "h3": anfang = "\n" if (self.letzter_bereich == "endquotes" and self.letzter_starttag == "br"): anfang = "" content = data.replace(" ", "") if len(content) > 2: self.insert_content(anfang, content, "", self.starttag) # Die Zeile zwischen h3 und dem nachfolgenden Content. else: self.insert_content("\n", content, "", "klein") self.letzte_datalaenge = len(content) letzter_starttag = self.starttag elif self.starttag == "p": content = " ".join(data.split()) # Nach einem "quotes"-Bereich nur "\n" wenn der letzte Tag kein # br-Tag war. if not "class" in dict(self.attrs): if (self.letzter_bereich == "endquotes" and self.letzter_starttag in self.auswahltags): self.insert_content("\n", content, "", "normal") else: self.insert_content("", content, "", "normal") # Bei class "mittig" ebenfalls kein "\n" nach br. elif dict(self.attrs)["class"] == "mittig": anfang = ("\n" if self.letzter_starttag != "br" else "") self.insert_content(anfang, content, "", "mittig") elif dict(self.attrs)["class"] == "zitate": if len(content): self.insert_content("", content, "\n", "zitate") self.letzte_datalaenge = len(content) self.letzter_starttag = self.starttag elif self.starttag == "br": content = " ".join(data.split()) if (self.letzter_starttag != "img" and self.letzter_bereich != "quotes"): self.insert_content("\n", content, "", "normal") # Wenn letzter_bereich gleich "quotes", dann für "br" als # Tkinter-Tag "zitate" verwenden. elif self.letzter_bereich == "quotes": anfang = ("\n" if self.letzter_starttag != "p" else "") self.insert_content(anfang, content, "", "zitate") self.letzte_datalaenge = len(content) self.letzter_starttag = self.starttag # Wenn ein 'class name' aus dem HTML-Dokument verwendet werden soll, # wird dieser abgefragt und als Tkinter-Tag angelegt. # Inline-Tags ohne "\n" am Ende, mit Ausnahme von "bildnotizen". elif self.starttag == "span" and dict(self.attrs)["class"]: tag = dict(self.attrs)["class"] spandata = data.split("[endspan]") if tag == "boldbraun": self.formatiere_inlinetags( data, "boldbraun", "boldbraunzitate", "[endspan]") elif tag == "boldgrau": self.formatiere_inlinetags( data, "boldgrau", "boldgrauzitate", "[endspan]") elif tag == "bildnotizen": content = " ".join(spandata[0].split()) if len(spandata)> 1: self.insert_content("", content, "\n\n", tag) self.letzte_datalaenge = len(spandata) self.letzter_starttag = tag # Für "em" kein eigener Tag "emzitate", da "zitate" bereits in # Schrägschrift formatiert werden. elif self.starttag == "strong": self.formatiere_inlinetags( data, "strong", "strongzitate", "[endstrong]") elif self.starttag == "em": self.formatiere_inlinetags( data, "em", "zitate", "[endem]") elif self.starttag == "u": self.formatiere_inlinetags( data, "underline", "underzitate", "[endunderline]") elif self.starttag == "li": content = data.replace(" ", "") bullet = " \u2022 " if self.letzter_starttag != "li": bullet = "\n\n \u2022 " if len(content) > 2: self.insert_content(bullet, content, "\n", "liste") self.letzte_datalaenge = len(content) self.letzter_starttag = self.starttag def main(): parser = Formatparser() parser.layout() parser.oeffne_htmlseite("lorem-ipsum.html") parser.fenster.mainloop() if __name__ == "__main__": main()
Weiterlesen » Ausgewählte Beispiele
Einstieg in Python
OOP mit Python
Codes & Tutorials
Kleines Projekt