Text-Widget mit formatierter Ausgabe

Fragen zu Tkinter.
Antworten
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Text-Widget mit formatierter Ausgabe

Als Vorlage für eine einfache Hilfeseite hatte ich versucht in einem Textfeld eine formatierte Ausgabe zu erreichen, wobei ich zuerst ein einfaches HTML-Grundgerüst für eine HTML-Quellseite benutzte und diesen HTMLParser.

https://docs.python.org/3/library/html.parser.html

Letztendlich kam ich nicht wirklich weiter, weil ich es nicht schaffte, den Tag von "def handle_starttag" und data von "def handle_data" zusammen in einem Dictionary zu speichern, um die Zuordnung zu erhalten. Vielleicht habe ich mich nur zu dumm angestellt.

Mit einer darauf zugeschnittenen XML-Datei als Quelle hat es dann geklappt, ist aber sicherlich noch verbesserungswürdig.

[codebox=xml file=Unbenannt.xml]<?xml version="1.0" encoding="utf-8"?>
<hilfe>
<einleitung>
<h1>Titel</h1>
<h2>Untertitel</h2>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum.(br)
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor
sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
diam voluptua.</p>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr...</p>
</einleitung>
<punkt>
<h3>Punkt 1</h3>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr...1</p>
<img>testbild.png</img>
<notiz>Notizen unterm Bild</notiz>
</punkt>
<punkt>
<h3>Punkt 2</h3>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr...2</p>
<p>Umlaute ÄÖÜäöüß</p>
</punkt>
</hilfe>[/code]

Code: Alles auswählen

from tkinter import Tk, Text, Scrollbar, PhotoImage, END
from PIL import Image, ImageTk
import xml.etree.ElementTree as ET

class Lesetest:

    def __init__(self):
        self.fenster = Tk()
        self.textfeld = None

    def layout(self):
        self.fenster.title("Lesetest")
        self.fenster.config(bg="#d9cda3")

        self.textfeld = Text(
            self.fenster, height=42, 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=("verdana", 18, "bold"))
        self.textfeld.tag_config("h2", font=("verdana", 12, "bold"))
        self.textfeld.tag_config("h3", font=("verdana", 10, "bold"))
        self.textfeld.tag_config("p",  font=("verdana", 10))
        self.textfeld.tag_config("img", justify="center")
        self.textfeld.tag_config(
            "notiz", font=("verdana", 9), foreground="#867a44", justify="center")
        self.textfeld.tag_config(
            "fehler", font=("verdana", 10), foreground="#ff8808")

    def parse_xml(self):
        try:
            tree = ET.parse("lorem-ipsum.xml")
            root = tree.getroot()
        except FileNotFoundError:
            root = ET.fromstring("<root><hinweis><fehler>"
                                 "Die Datei wurde nicht gefunden!"
                                 "</fehler></hinweis></root>")

        for n in range(len(root)):
            for child in root[n]:
                if child.text:
                    content = child.text.replace("\n", "").replace(
                              "\x20\x20", "").replace("(br)", "\n")
                else:
                    content = ""

                if child.tag == "h1":
                    self.textfeld.insert(1.0, "{}\n".format(content), "h1")
                if child.tag == "h2":
                    self.textfeld.insert(END, "{}\n\n".format(content), "h2")
                if child.tag == "h3":
                    self.textfeld.insert(END, "{}\n\n".format(content), "h3")
                if child.tag == "p":
                    self.textfeld.insert(END, "{}\n\n".format(content), "p")

                # Vor und hinter dem Image ein Leerzeichen einfügen, um es mit
                # den Leerzeichen in Reihe mittig mit dem "img"-Tag auszurichten.
                if child.tag == "img":
                    try:
                        image = Image.open("testbild.png")
                        self.testbild = ImageTk.PhotoImage(image)
                        self.textfeld.insert(END, "\x20", "img")
                        self.textfeld.image_create(END, image = self.testbild)
                        self.textfeld.insert(END, "\x20\n")
                    except FileNotFoundError:
                        self.textfeld.insert(
                            END, "Das Image wurde nicht gefunden.\n\n", "fehler")
                if child.tag == "notiz":
                    self.textfeld.insert(END, "{}\n\n".format(content), "notiz")
                if child.tag == "fehler":
                    self.textfeld.insert(END, "{}\n\n".format(content), "fehler")

    def main(self):
        self.layout()
        self.parse_xml()
        self.fenster.mainloop()

if __name__ == "__main__":
    Lesetest().main()
knut
User
Beiträge: 14
Registriert: Freitag 18. August 2017, 15:23

Lieber Melewo,

Ich versuche gerade aus Büchern Python zu lernen. Tkinter interessierte mich und ich wollte aus einem Programm mal die Praxis kennenlern. Dabei stieß ich auf Dein obiges Programm. Nun habe ich einige Fragen und wäre Dir dankbar wenn Du mir einige Minuten Zeit opfern würdest (ohne über Opa, 81J, zu lachen):

Warum benutzt Du für das obige Programm eine Klasse?

Aus welchem Programm rufst Du das obige Programm (die Klasse) auf und wie sieht dort der Aufruf der Klasse aus?

Vielen Dank im voraus für Deine Mühe!

Herzliche Grüße Knut
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Das Script läuft so wie es ist, im Zusammenspiel mit der XML wie sie ist. Da ist nicht mehr zu erforderlich, außer Python und zusätzlich das Modul PIL (Pillow). Die XML kann selbstverständlich erweitert werden, nur wenn die <Knoten> oder Tags umbenannt werden, muss die Umbenennung in beiden Dateien erfolgen. Wichtig wäre auch noch, dass beide Dateien mit einem geeigneten Editor als UTF-8 Dateien abgespeichert werden, sonst könnte es Probleme geben.

Nun ja, Du kannst auch ohne OOP und Klassen eine kleine Fensteranwendung (GUI) erstellen. Doch spätestens wenn Du nicht weißt, wie eine Variable von einer Funktion in eine andere übernehmen, ohne dabei Variablen global zu machen, solltest Du beginnen, Dich mit Klassen vertraut zu machen. Und dieser Tag wird bei Fensteranwendungen eher früher als später kommen.

Eigentlich war ich auch mehr darauf aus, eine HTML-Seite als Vorlage zu nehmen. Doch damit wurde ich heute nur zur Hälfte fertig.
Oben schrieb ich, läuft wie es ist, tut es auch, ist nur langfristig nicht dafür gedacht, sondern als Hilfe-Seite innerhalb einer anderen Anwendung.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Nun dachte ich, ich würde heute fertig mit einer Vorlage für HTML-Seiten und um diese formatiert in einem Text-Widget auszugeben, nun doch noch ein Problem. Alles wird richtig der Reihe nach eingelesen und ausgegeben, nur bei den Bildern nicht. So wie ich ein zweites in den Quelltext einfügte, bleibt beim ersten Bild nur noch ein leerer Platz in der Größe des Bildes, doch es wird nur das zweite Bild angezeigt. Geladen wird es irgendwie, nur nicht richtig, doch der leere Platz lässt sich in Größe des Images markieren. Oder lässt sich am Ende nur ein Bild plus Text in einem Text-Widget einfügen?

Die HTML-Testseite:
[codebox=html5 file=Unbenannt.html]<!DOCTYPE html>
<html>

<head>
<title>HTML Test</title>
<style type="text/css">
body {font-family:verdana; font-weight:normal; font-size:13px}
h1 {font-family:verdana; font-weight:bold; font-size:20px}
h2 {font-family:verdana; font-weight:bold; font-size:16px}
h3 {font-family:verdana; font-weight:bold; font-size:14px}
.text_center {text-align:center}
.bildnotizen {
font-family:'Times New Roman';
font-weight:normal;
font-size:12px;
color:#867a44
}
</style>
</head>

<body>
<h1>Nur ein HTML-Test</h1>
<h2>Untertitel</h2>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum.<br>
Umbruch 1: Stet clita kasd gubergren, no sea takimata sanctus est Lorem
ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing
elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna
aliquyam erat, sed diam voluptua.</p>
<h3>Punkt 1</h3>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. </p>
<p class="text_center">
<img src="testbild_1.png" alt="Test" width="240" height="192"><br>
<span class="bildnotizen">Bild 1: Notizen unterm Bild</span>
</p>
<h3>Punkt 2</h3>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua.</p>
<h3>Punkt 3</h3>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. </p>
<p class="text_center">
<img src="testbild_2.png" alt="Test" width="280" height="224"><br>
<span class="bildnotizen">Bild 2: Notizen unterm Bild</span>
</p>
<h3>Punkt 3</h3>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua.</p>
</body>
</html>[/code]

Und das Script:

Code: Alles auswählen

from tkinter import Tk, Text, Scrollbar, PhotoImage, END
from PIL import Image, ImageTk
from html.parser import HTMLParser

class MiniHTMLParser(HTMLParser):

    def __init__(self):
        HTMLParser.__init__(self)
        self.recorder = {}
        self.html_tag = None
        self.html_img = None
        self.mitschnitt = False
        self.html_liste = ["h1", "h2", "h3", "p", "span"]
        self.counter = 1

    def handle_starttag(self, tag, attrs):
        for htmltag in self.html_liste:
            if htmltag == tag:
                self.html_tag = tag
                self.mitschnitt = True

        if tag == "img":
            self.html_tag = tag
            self.html_img = attrs
            self.mitschnitt = True

    def handle_endtag(self, tag):
        for htmltag in self.html_liste:
            if htmltag == tag:
                self.html_tag = None
                self.mitschnitt = False

    def handle_data(self, data):
        htmltag = {}
        img_tag = {}

        if self.html_tag and self.mitschnitt:
            if self.html_tag in self.html_liste:
                htmltag[self.html_tag] = data
                self.recorder[self.counter] = htmltag
                self.counter += 1
        if self.html_img and self.mitschnitt:
            img_tag["img"] = self.html_img
            self.recorder[self.counter] = img_tag
            self.mitschnitt = False
            self.html_img = None
            self.counter += 1


class LesetestHTML(MiniHTMLParser):

    def __init__(self):
        MiniHTMLParser.__init__(self)
        self.fenster = Tk()
        self.textfeld = None

    def layout(self):
        self.fenster.title("Lesetest")
        self.fenster.config(bg="#d9cda3")

        self.textfeld = Text(
            self.fenster, height=42, 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=("verdana", 18, "bold"))
        self.textfeld.tag_config("h2", font=("verdana", 12, "bold"))
        self.textfeld.tag_config("h3", font=("verdana", 10, "bold"))
        self.textfeld.tag_config("p",  font=("verdana", 10))
        self.textfeld.tag_config("img", justify="center")
        self.textfeld.tag_config(
            "bildnotizen",
            font=("verdana", 8, "italic"),
            foreground="#867a44",
            justify="center")
        self.textfeld.tag_config(
            "fehler",
            font=("verdana", 10),
            foreground="#ff8808",
            justify="center")

    def oeffne_htmlseite(self):
        try:
            with open("lorem-ipsum.html", "r") as datei:
                dateistring = datei.read().replace("<br>", "[br]")
                self.feed(dateistring)
        except FileNotFoundError:
            self.feed("<h3>Die Datei wurde nicht gefunden!</h3>")

        htmlnodes = self.recorder
        
        for nodes in htmlnodes:
            for node in htmlnodes[nodes]:
                if node == "h1":
                    self.textfeld.insert(1.0, "{}\n"
                        .format(htmlnodes[nodes][node]), "h1")
                elif node == "h2":
                    self.textfeld.insert(END, "{}\n\n"
                        .format(htmlnodes[nodes][node]), "h2")
                elif node == "h3":
                    self.textfeld.insert(END, "{}\n\n"
                        .format(htmlnodes[nodes][node]), "h3")

                # Die Einrückungen aus dem HTML-Quelltext müssen entfernt
                # werden und alle \n, da das Textfeld den Zeilenumbruch
                # übernimmt.
                # Mit <br> gab es Probleme, der wird beim Einlesen der Datei
                # zuerst in BBCode gewandelt und hier wieder in \n.
                elif node == "p":
                    content = htmlnodes[nodes][node].replace("\n", "").replace(
                              "\x20\x20", "").replace("[br]", "\n")
                    if len(content):
                        self.textfeld.insert(END, "{}\n\n"
                            .format(content), "p")
                elif node == "span":
                    self.textfeld.insert(END, "{}\n\n"
                        .format(htmlnodes[nodes][node]), "bildnotizen")

                # Vor und hinter dem Image ein Leerzeichen einfügen, um es mit
                # den Leerzeichen in Reihe mittig mit dem "img"-Tag auszu-
                # richten.
                elif node == "img":
                    try:
                        image = Image.open(htmlnodes[nodes][node][0][1])
                        # print(htmlnodes[nodes][node][0][1])
                        # Ausgabe von print:
                        # testbild_1.png
                        # testbild_2.png
                        # Angezeigt wird nur das letzte Bild.
                        self.testbild = ImageTk.PhotoImage(image)
                        self.textfeld.insert(END, "\x20", "img")
                        self.textfeld.image_create(END, image = self.testbild)
                        self.textfeld.insert(END, "\x20\n")
                    except FileNotFoundError:
                        self.textfeld.insert(
                            END, "Das Image wurde nicht gefunden.\n\n",
                            "fehler")

    def main(self):
        self.layout()
        self.oeffne_htmlseite()
        self.fenster.mainloop()

if __name__ == "__main__":
    LesetestHTML().main()
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

So wie ich das sehe speicherst Du auch nur das letzte Bild in self.testbild . du mußt schon alle Bilder in Speicher halten.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Ja, damit hängt das zusammen, hat mir keine Ruhe gelassen und habe noch etwas experimentiert. Nur mir fehlt gerade jede Vorstellung, wie man da für Abhilfe sorgen könnte. Wenn ich im letzten Experiment beide unterschiedliche Namen gebe, wie self.testbild_1 und self.testbild_2, dann werden auch zwei angezeigt, ansonsten nicht. Und es wird dann wohl so sein, dass die letzte die erste überschreibt. Bei den anderen Tags spielt es wohl keine Rolle, nur bei Images.

Code: Alles auswählen

from tkinter import Tk, Text, Scrollbar, PhotoImage, END
from PIL import Image, ImageTk

class LesetestHTML():

    def __init__(self):
        self.fenster = Tk()
        self.textfeld = None
        self.textfeld = Text(
            self.fenster, height=42, 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)


    def oeffne_image(self):

        image = Image.open("testbild_1.png")
        self.testbild_1 = ImageTk.PhotoImage(image)
        self.textfeld.image_create(END, image=self.testbild_1)

        image = Image.open("testbild_2.png")
        self.testbild_2 = ImageTk.PhotoImage(image)
        self.textfeld.image_create(END, image=self.testbild_2)

    def main(self):
        self.oeffne_image()
        self.fenster.mainloop()

if __name__ == "__main__":
    LesetestHTML().main()
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und wieso steckst du sie nicht einfach in eine Liste?
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Weil ich irgendwie einen Denkfehler habe.
In einem Dictionary mit Liste und Tupels befinden die sich noch innerhalb von self.recorder.

Code: Alles auswählen

6: {'p': '\n      '}, 
7: {'img': [('src', 'testbild_1.png'), ('alt', 'Test'), ('width', '240'), ('height', '192')]}, 
8: {'span': 'Bild 1: Notizen unterm Bild'}, 
9: {'h3': 'Punkt 2'}, 
...
...
...
13: {'p': '\n      '}, 
14: {'img': [('src', 'testbild_2.png'), ('alt', 'Test'), ('width', '280'), ('height', '224')]}, 
15: {'span': 'Bild 2: Notizen unterm Bild'}, 
16: {'h3': 'Punkt 3'},

Die leere p-Tags verschwindet mit "if len(content):" einige Codezeilen vorher und der Rest soll in der Reihenfolge beim Einfügen erhalten bleiben.
Wenn ich an dieser Stelle im eigentlichen Script mit print abfrage,

Code: Alles auswählen

self.testbild = ImageTk.PhotoImage(image)
print(self.testbild)
print(type(self.testbild))
dann erhalte ich:

[codebox=text file=Unbenannt.txt]pyimage1
<class 'PIL.ImageTk.PhotoImage'>
pyimage2
<class 'PIL.ImageTk.PhotoImage'>[/code]
und das würde ich jetzt weniger als Referenz, eher mehr wie eine Instanz auf ImageTk.PhotoImage ansehen (oder?), die durch die zweite überschrieben wird, wenn der Name der Variablen self.testbild sich nicht ändert, damit jede Instanz (falls man das so bezeichnen könnte) ihre eigene Variable erhält.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@Melewo: nachdem ich mir Deinen Code näher angeschaut habe, sehe ich, da ist so einiges falsch. Warum ist LesetestHTML ein MinihtmlParser und kein Fenster? Die Vererbung macht hier so keinen Sinn. Dein Parser an sich funktioniert so nicht. Sobald Du verschachtelte Tags hast, kommst Du komplett durcheinander. HTML in eine lineare Liste parsen zu wollen, funktioniert einfach nicht. Das ist ein Baum und muß auch so gelesen werden. Die Sonderbehandlung von <img>-Tags ist auch überflüssig, da ist nichts besonderes dabei, außer dass einmal der Text und einmal die Attribute gespeichert werden, für einen richtigen HTML-Parser ist aber immer beides wichtig. Der nächste Bug kommt von den von Dir gewählten Datenstrukturen. Wörterbücher sind keine Listen! recorder enthält als Schlüssel aufsteigende Zahlen. Beim Verarbeiten später ist Dir das aber dann egal, denn Du füllst Dein Textfeld in einer beliebigen Reihenfolge (weil Wörterbücher keine Reihenfolge haben); dass Dir das bisher nicht aufgefallen ist, ist reines Glück. In recorder sind Wörterbücher mit jeweils nur einem Schlüssel. Dann brauch ich auch kein Wörterbuch, sondern würde idealerweise ein Tuple nehmen.

Und bei Tk ist es immer so, dass man noch eine Referenz auf alle Bilder speichern muß, weil sonst die Speicherbereinigung von Python die Bilder einfach abräumt. Und das macht man am einfachsten mit einer Liste.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Ok, jetzt habe ich es begriffen, danke, jetzt funktioniert es. Die ergänzten Codezeilen:

Code: Alles auswählen

    def __init__(self):
        MiniHTMLParser.__init__(self)
        self.fenster = Tk()
        self.textfeld = None
        self.testbild = []
        self.countimage = 0
        
        ...
        ...  
        ...
                        image = Image.open(htmlnodes[nodes][node][0][1])
                        self.testbild.append(ImageTk.PhotoImage(image))
                        self.textfeld.insert(END, "\x20", "img")
                        self.textfeld.image_create(END, image=self.testbild[self.countimage])
                        self.textfeld.insert(END, "\x20\n")
                        self.countimage += 1
Bild

Und was die ergänzenden Bemerkungen anbelangt, die Reihenfolge bleibt, so wie ich das bei den Testausgaben sehe, erhalten.

Code: Alles auswählen

htmlnodes = { 
    1: {'h1': 'Nur ein HTML-Test'}, 
    2: {'h2': 'Untertitel'}, 
    3: {'p': 'Lorem ipsum dolor sit amet,
    ...
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@Melewo: was Du siehst und was ein fehlerfreies Programm ist, sind zwei verschiedene Dinge. Programmieren heißt, nachzuweisen, dass ein Stück Code in allen Lagen korrekt arbeitet und nicht, dass es zufällig bei dem, was Du gerade anschaust, funktioniert. Listen, statt Wörterbücher sind nicht nur einfacher sondern auch korrekter. Bis Du versuchst, eine Wort fett zu drucken. Dann fliegt Dir Dein ganzes Konzept um die Ohren.

Zum neuerlichen Code: countimage ist überflüssig. Das letzte Element einer Liste erhält man mit -1. Zudem würde ich die Referenz auf das Bild nochmal in einer lokalen Variable speichern, und nicht auf die Liste zugreifen.

Warum schreibst Du Leerzeichen so kompliziert?
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Sirius3 hat geschrieben:Dann fliegt Dir Dein ganzes Konzept um die Ohren.
Mein Konzept ist für Tkinter und da wird niemand Ansprüche an ein Textfeld stellen, wie an einem modernen Browser und jeder bereits froh darüber sein, überhaupt halbwegs eine formatierte Ausgabe zu erreichen. Etwas mehr könnte man sicherlich noch erreichen, es ist ja nicht mehr als ein erster funktionierender Entwurf, an dem sich noch arbeiten lässt.
Es geht hier aber nicht darum, was mit einem HTML/CSS Parser alles möglich wäre, sondern was mit einem Tkinter Text-Widget sich umsetzen lässt und für einen ersten Entwurf halte ich das bisher zumindest für durchaus brauchbar. Wenn in einer HTML-Seite verschachtelte und mit CSS ausgerichtete Div-Bereiche und anderes auftauchen, lässt sich dieses ob so oder so mit einem einzigen Text-Widget nicht nachbilden.
Sirius3 hat geschrieben:Zum neuerlichen Code: countimage ist überflüssig. Das letzte Element einer Liste erhält man mit -1.
Gut, ich war ja nur erst einmal zufrieden, dass es überhaupt funktionierte.
Sirius3 hat geschrieben:Zudem würde ich die Referenz auf das Bild nochmal in einer lokalen Variable speichern, und nicht auf die Liste zugreifen.
Ich weiß nicht mehr wo ich es bei effbot.org gelesen hatte, ich weiß nur dass ich es dort gelesen hatte: wird ImageTk.PhotoImage innerhalb einer Klasse benutzt, muss "self" benutzt werden oder es wird nichts geladen. Kann sich auch jeder selbst ausprobieren, in dem er erst die eine "def oeffne_image" auskommentiert und dann die andere:

Code: Alles auswählen

from tkinter import Tk, Text, PhotoImage, END
from PIL import Image, ImageTk

class Ladetest():

    def __init__(self):
        self.fenster = Tk()
        self.textfeld = None
        self.textfeld = Text(self.fenster)
        self.textfeld.pack()

    # ohne self vor "testbild" wird ein Bild nicht geladen:
    # def oeffne_image(self):
        # image = Image.open("testbild.png")
        # testbild = ImageTk.PhotoImage(image)
        # self.textfeld.image_create(END, image=testbild)

    # Bild wird nur mit "self.testbild" geladen:
    def oeffne_image(self):
        image = Image.open("testbild.png")
        self.testbild = ImageTk.PhotoImage(image)
        self.textfeld.image_create(END, image=self.testbild)

    def main(self):
        self.oeffne_image()
        self.fenster.mainloop()

if __name__ == "__main__":
    Ladetest().main()
Sirius3 hat geschrieben:Warum schreibst Du Leerzeichen so kompliziert?
Weil man zwei Leerzeichen dann weniger mit einem einzelnen Leerzeichen verwechseln kann und einfache Leerzeichen sollen ja erhalten bleiben sollen.
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ein Wörterbuch die falsche Datenstruktur ist hat nichts mit der Größe bzw. den Ambitionen des Projektes zu tun. Entweder braucht es eine Ordnung, oder nicht. Du brauchst sie, also brauchst du auch eine Datenstruktur welche die Ordnung erhält.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Ich verstehe nicht, was bei der Reihenfolge

1: Erstes Element
...
100: Hundertstes Element

durcheinander kommen soll, wenn den niemand anders sortiert. Und wenn da tatsächlich etwas zu Ungereimtheiten noch späterhin führen sollte, dann brauchte ja nur eine Schleife so angepasst werden, dass die wirklich den von 1 bis 100 durchgeht.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@Melewo: ein Wort in einem Text fett haben zu wollen ist jetzt keine so große Ausnahme. Hat jetzt auch nichts mit kompliziertem CSS und DIV-Elementen zu tun. Eine Lösung, die halbwegs nur für die von Dir verwendete HTML-Struktur funktioniert, ist nicht robust. Das zeigt sich auch an dem Rumgemurkse mit <br>.

Und nochmal, eine Datenstruktur, die keine Ordnung hat, zu verwenden, wenn man eine Ordnung haben will, ist einfach falsch.

Zur Verdeutlichung, wie viel einfacher das Programm mit den richtigen Datenstrukturen ist:

Code: Alles auswählen

from tkinter import Tk, Text, Scrollbar, PhotoImage, END
from PIL import Image, ImageTk
from html.parser import HTMLParser

class MiniHTMLParser(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.items = []
        self.tag = self.attrs = None

    def handle_starttag(self, tag, attrs):
        self.tag = tag
        self.attrs = attrs

    def handle_data(self, data):
        self.items.append((self.tag, self.attrs, data))

class LesetestHTML(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.images = []

        self.title("Lesetest")
        self.config(bg="#d9cda3")

        self.textfeld = Text(
            self, height=42, pady=12, padx=12, wrap="word")
        scrollbar = Scrollbar(self)
        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=("verdana", 18, "bold"))
        self.textfeld.tag_config("h2", font=("verdana", 12, "bold"))
        self.textfeld.tag_config("h3", font=("verdana", 10, "bold"))
        self.textfeld.tag_config("p",  font=("verdana", 10))
        self.textfeld.tag_config("img", justify="center")
        self.textfeld.tag_config(
            "span",
            font=("verdana", 8, "italic"),
            foreground="#867a44",
            justify="center")
        self.textfeld.tag_config(
            "fehler",
            font=("verdana", 10),
            foreground="#ff8808",
            justify="center")

    def oeffne_htmlseite(self, filename):
        parser = MiniHTMLParser()
        try:
            with open(filename) as datei:
                dateistring = datei.read().replace("<br>", "[br]")
                parser.feed(dateistring)
        except FileNotFoundError:
            parser.feed("<h3>Die Datei wurde nicht gefunden!</h3>")

        for tag, attrs, data in parser.items:
            if tag in ("h1", "h2", "h3", "p", "span"):
                content = ' '.join(data.split()).replace("[br]", "\n")
                self.textfeld.insert(END, content + "\n", tag)
            elif tag == "img":
                # Vor und hinter dem Image ein Leerzeichen einfügen, um es mit
                # den Leerzeichen in Reihe mittig mit dem "img"-Tag auszu-
                # richten.
                try:
                    image = Image.open(dict(attrs)['src'])
                    image = ImageTk.PhotoImage(image)
                    self.textfeld.insert(END, " ", "img")
                    self.textfeld.image_create(END, image=image)
                    self.textfeld.insert(END, " \n")
                    self.images.append(image)
                except FileNotFoundError:
                    self.textfeld.insert(
                        END, "Das Image wurde nicht gefunden.\n\n",
                        "fehler")

def main():
    root = LesetestHTML()
    root.oeffne_htmlseite("test.html")
    root.mainloop()

if __name__ == "__main__":
    main()
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

@Sirius3: Ja, Deine Lösung gefällt mir eindeutig besser. Nach so einer übersichtlichen Lösung hatte ich anfänglich gesucht, nichts gefunden, dann erst einmal mit XML probiert und dann noch einmal nach einen Ansatz mit HTML gesucht. Alles was ich für mich verständliches fand, war unten der letzte Code auf dieser Seite mit self.recordh2 = True und False und den hatte ich dann als Ansatz für einen Anfang benutzt, um ein Dictionary mit richtig durchnummerierten Schlüssen zu erhalten:

https://stackoverflow.com/questions/848 ... -from-tags

Doch so wie Du das geschrieben hast, ausprobiert habe ich es, sieht sauberer und übersichtlicher aus.
knut
User
Beiträge: 14
Registriert: Freitag 18. August 2017, 15:23

Liebe Freunde,

Für mich als Newbie waren Eure Kommentare sehr lehrreich und dafür herzlichen Dank.
Euer Wissen verlangt Respekt und Achtung. Man könnte dabei den Mut zum Weiterlernen verlieren. Mich aber spornt das an zu noch vielen weiteren Bemühungen.

Gruß Knut
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Sirius3 hat geschrieben:@Melewo: ein Wort in einem Text fett haben zu wollen ist jetzt keine so große Ausnahme. Hat jetzt auch nichts mit kompliziertem CSS und DIV-Elementen zu tun. Eine Lösung, die halbwegs nur für die von Dir verwendete HTML-Struktur funktioniert, ist nicht robust.
Nun funktioniert das Script auch mit "strong" und mit "span class name" und mit einem hellbrau hinterlegten Bereich für Zitate oder ähnlichen, der dann aber bereits in HTML in einem div-Bereich gelegt werden muss. Beide "div class name" und "p class name" muss dann halt wie bei "span class name" mit den Tkinter-Tags übereinstimmen. Das Herumgemurkse ist geblieben, Tkinter nimmt nur Tags und da folgt dann nach einem Tag ein anderer Tag und wenn ein p oder br Tkinter-Tag normalen Text enthält, muss man halt hinter </span> wieder auf einen Tkinter-Tag "normal" wechseln.

Dass sich der Code noch verbessern ließe und weiter ausbauen, darüber bin ich mir fasst im Klaren, wobei ich mir dann überlegte, ob es einen Sinn ergibt, einzelne Zeilen in Funktionen auszulagern, wenn es in der nächsten Zeile bereits wieder Unterschiede gibt und an Stelle einer ausgelagerten Zeile dann ein Funktionsaufruf notiert wird. Na ja, kann ja nur von meinem jetzigen Kenntnisstand ausgehen und mit dem würde es noch nicht viel kürzer oder eleganter oder übersichtlicher werden.

Code: Alles auswählen

from tkinter import Tk, Text, Scrollbar, PhotoImage, END
from PIL import Image, ImageTk
from html.parser import HTMLParser

class MiniHTMLParser(HTMLParser):

    def __init__(self):
        HTMLParser.__init__(self)
        self.items = []
        self.tag = self.attrs = None

    def handle_starttag(self, tag, attrs):
        self.tag = tag
        self.attrs = attrs
        if tag == "img":
            self.items.append((self.tag, self.attrs, None))

    def handle_data(self, data):
        self.items.append((self.tag, self.attrs, data))


class LesetestHTML(Tk):

    def __init__(self):
        Tk.__init__(self)
        self.images = []

        self.title("Lesetest")
        self.config(bg="#d9cda3")

        self.textfeld = Text(
            self, height=44, pady=12, padx=12, wrap="word")
        scrollbar = Scrollbar(self)
        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(
            "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(
            "fehler",  font=("cambria", 12),
            foreground="#ff8808", justify="center")
        self.textfeld.tag_config(
            "quotes", background="#e6e5da")
        self.textfeld.tag_config(
            "zitate",  font=("cambria", 12, "italic"),
            background="#e6e5da", justify="center",
            rmargin=20, lmargin1=20, lmargin2=20,
            spacing1=22, spacing3=22)

    def insert_content(self, anfang, content, ende, tag):
        self.textfeld.insert(END, "{0}{1}{2}"
            .format(anfang, content, ende), tag)

    def oeffne_htmlseite(self, filename):
        parser = MiniHTMLParser()
        try:
            with open(filename) as datei:
                dateistring = datei.read().replace(
                              "</span>", "[endspan]").replace(
                              "</strong>", "[endstrong]")
                parser.feed(dateistring)
        except FileNotFoundError:
            parser.feed("<h3>Die Datei wurde nicht gefunden!</h3>")

        # Wenn ein 'class name' aus dem HTML-Dokument verwendet werden soll,
        # wird dieser abgefragt und als Tkinter-Tag angelegt.
        # 'letzte_tagdata' enhält die Länge von content, bei einem leeren Endtag
        # ist dieser 0 bis 1, so dass z.B. ermittelt wird, ob der letzte Tag
        # Content enthielt und noch ein Leertag gesetzt werden kann oder ob
        # Span-Bereiche mit einem " " Leerzeichen oder mit "" keinem beginnen.
        # 'br_umbrueche' dient ebenfalls dazu, um nur einen abschließenden leeren
        # Tag zu setzen. 'letzter_tag' ist für br, falls img vorausging.

        br_umbrueche = 1
        letzte_tagdata = 1
        letzter_tag = None
        for tag, attrs, data in parser.items:
            if tag in ("h1", "h2"):
                content = data.replace("  ", "")
                self.insert_content("", content, "", tag)

            elif tag == "h3":
                content = data.replace("  ", "")
                if len(content) > 2:
                    self.insert_content("\n", content, "", tag)
                else:
                    self.insert_content("\n", content, "", "klein")
                letzte_tagdata = len(content)
                letzter_tag = tag

            elif tag == "div":
                if "class" in dict(attrs):
                    if dict(attrs)["class"] == "quotes":
                        self.insert_content("\n", "", "\n", "normal")
    
            elif tag == "p":
                content = " ".join(data.split())
                if not "class" in dict(attrs):
                    self.insert_content("", content, "", "normal")
                elif dict(attrs)["class"] == "mittig":
                    anfang = ("\n" if letzter_tag != "br" else "")
                    self.insert_content(anfang, content, "", "mittig")
                elif dict(attrs)["class"] == "zitate":
                    if len(content):
                        self.insert_content("", content, "\n", "zitate")
                    elif letzte_tagdata > 2:
                        self.insert_content("\n", "", "\n", "klein")
                letzte_tagdata = len(content)
                letzter_tag = tag

            elif tag == "br":
                content = " ".join(data.split())
                if len(content) != br_umbrueche and letzter_tag != "img":
                    self.insert_content("\n", content, "", "normal")
                    br_umbrueche = len(content)
                letzte_tagdata = len(content)
                letzter_tag = tag

            # Inline-Tags ohne "\n" am Ende, mit Ausnahme von "bildnotizen".
            elif tag == "span" and dict(attrs)["class"]:
                tag = dict(attrs)["class"]
                spandata = data.split("[endspan]")

                if tag == "boldbraun" or tag == "boldgrau":
                    anfang = ("" if letzte_tagdata < 2 else " ")

                    content = " ".join(spandata[0].split())
                    self.insert_content(anfang, content, " ", tag)
                    if len(spandata) > 1:
                        content = " ".join(spandata[1].split())
                        self.insert_content(anfang, content, " ", "normal")

                elif tag == "bildnotizen":
                    content = " ".join(spandata[0].split())
                    if len(spandata)> 1:
                        self.insert_content("", content, "\n\n", tag)
                letzte_tagdata = len(spandata)
                letzter_tag = tag

            elif tag == "strong":
                anfang = ("" if letzte_tagdata < 2 else " ")

                strongdata = data.split("[endstrong]")
                content = " ".join(strongdata[0].split())
                self.insert_content(anfang, content, " ", tag)
                if len(strongdata) > 1:
                    content = " ".join(strongdata[1].split())
                    self.insert_content(anfang, content, " ", "normal")
                letzte_tagdata = len(strongdata)
                letzter_tag = tag

            elif 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)
                    letzter_tag = tag
                    self.insert_content(""," ", "\n", "img")
                    self.images.append(image)
                except FileNotFoundError:
                    self.textfeld.insert(
                        END, "Das Image wurde nicht gefunden.\n\n",
                        "fehler")


def main():
    root = LesetestHTML()
    root.oeffne_htmlseite("lorem-ipsum.html")
    root.mainloop()

if __name__ == "__main__":
    main()
Bild

[codebox=html5 file=Unbenannt.html]<!DOCTYPE html>
<html>

<head>
<title>HTML Test</title>
<style type="text/css">
body {with font-family:cambria; font-weight:normal; font-size:16px}
div.wrapper{width:668px; padding:12; margin:auto}
h1 {font-family:cambria; font-weight:bold; font-size:24px}
h2 {font-family:cambria; font-weight:bold; font-size:20px}
h3 {font-family:cambria; font-weight:bold; font-size:16px}
.mittig {text-align:center}
.bildnotizen {
font-family:cambria;
font-style:italic;
font-size:10px;
color:#867a44
}
.boldgrau {font-weight:bold; color:#404040}
.boldbraun {font-weight:bold; color:#9b4c21}
.quotes {background-color:#e6e5da; border:1px solid #ff0000}
.zitate {
font-family:cambria;
font-style:italic;
font-size:16px;
text-align:center;
margin:20px
}
@media only all and (max-width:698px) {
div.wrapper {width:100%}
img.images {max-width:100%; height:auto}
</style>
</head>

<body>
<div class="wrapper">
<h1>Nur ein HTML-Test</h1>
<h2>Untertitel</h2>
<p>Lorem ipsum dolor sit amet, <strong>Lconsetetur sadipscing</strong> elitr, sed
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
diam voluptua.<br>
Stet clita kasd gubergren, no sea <span class="boldbraun">takimata</span> sanctus
est Lorem ipsum dolor sit amet.</p>
<h3>Punkt 1</h3>
<p><span class="boldbraun">Lorem ipsum</span> dolor sit amet, consetetur
sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna
aliquyam erat, sed diam voluptua.</p>
<p class="mittig">
<img src="testbild_1.png" alt="Test" width="240" height="192" class="images"><br>
<span class="bildnotizen">Bild 1: Notizen unterm Bild</span>
</p>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</p>
<div class="quotes">
<p class="zitate">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
diam voluptua.</p>
</div>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.<br>
<br>
Stet clita kasd gubergren.</p>
</div>
</body>
</html>[/code]
Antworten