Wie formatierten Text speichern/laden?

Fragen zu Tkinter.
Antworten
marlon_germany
User
Beiträge: 33
Registriert: Samstag 30. April 2022, 23:32

Hallo zusammen,

Ich habe eine kleine mini App mit 2 Text-Widgets erstellt die
ausgewählten Text in beiden Textfeldern unabhängig voneinander
in "fett/unterstreichen/kursiv" formatieren kann.
Nun möchte ich, dass der formatierte Text genau so, in einer Datei wie
.txt / .json oder .html gespeichert wird.
Und per Knopfdruck genau so wieder im ursprünglichen Text-Format in die
Text-Widgets zurück geladen werden kann.

Den formatierten Text mittels json.dump zu speichern funktioniert soweit.

Das Problem dabei ist allerdings beim Laden, da der Text immer nur im .json-Format
zurück geladen wird?!

Also statt ein Text-Format wie ( Test, eins - zwei - drei ) beim Laden zu erhalten,
erscheint im Textwidget:

Code: Alles auswählen

[["text", "Test, ", "1.0"], ["tagon", "bold", "1.6"], ["text", "eins", "1.6"], ["tagoff", "bold", "1.10"], ["text", " - ", "1.10"], ["tagon", "underline", "1.13"], ["text", "zwei", "1.13"], ["tagoff", "underline", "1.17"], ["text", " - ", "1.17"], ["tagon", "italic", "1.20"], ["text", "drei", "1.20"]]
PS: Und den Text mit PyQt5 formatiert in eine .html zu speichern funktioniert soweit auch.
PyQt5 ist allerdings eine Welt, mit der ich mich kaum auskenne.

Kennt ihr einen Weg die Texte formatiert wieder zurück zu laden?

PS: Ich habe im Internet gelesen, dass es wohl leichter ist, das Ganze über html-Format abzuwickeln, statt über .json oder Text.


Ich würde mich wirklich absolut über Eure Hilfe freuen.
Hier der bisherige Code:

Code: Alles auswählen

import tkinter as tk
from tkinter import font
import json
              
class Gui():
    def __init__(self, root):        
        root.geometry("500x600")
        root.title("Bold and Unbold")

        self.selected_field = None  
        
        #textfield_1 
        self.textfield_1 = tk.Text(root, width=20, height=10, wrap=tk.WORD,font=("Levetica", 12, 'normal'))
        self.textfield_1.bind('<<Selection>>', self.select_field) 
        self.textfield_1.place(x=29,y=50,width=245,height=200)
        
        #textfield_2
        self.textfield_2 = tk.Text(root, width=20, height=10, wrap=tk.WORD,font=("Levetica",12))
        self.textfield_2.bind('<<Selection>>', self.select_field)
        self.textfield_2.place(x=29,y=300,width=245,height=200)

        #button_1 - bold
        self.ButtonMakeBold = tk.Button(root, text="fett", font=("Levetica", "9", "bold"), 
        command=self.make_bold)
        self.ButtonMakeBold.place(x=28, y=5, width=40, height=40)

        #button_2 - unbold
        self.ButtonMakeUnbold = tk.Button(root, text="normal", font=("Levetica", "9", "normal"), 
        command=self.make_unbold)
        self.ButtonMakeUnbold.place(x=75, y=5, width=50, height=40)

        #button_3 - underline
        self.ButtonMakeUnbold = tk.Button(root, text="unterstreichen", font=("Levetica", "9", "underline"), 
        command=self.make_underline)
        self.ButtonMakeUnbold.place(x=130, y=5, width=90, height=40)

        #button_4 - italic
        self.ButtonMakeItalic = tk.Button(root, text="kursiv", font=("Levetica", "9", "italic"), 
        command=self.make_italic)
        self.ButtonMakeItalic.place(x=225, y=5, width=50, height=40)

        #button_5 - save textfields (in json format)
        self.ButtonSave = tk.Button(root, text="als .json speichern", font=("Levetica", "9", "normal"),
        command=self.save3)
        self.ButtonSave.place(x=28, y=520, width=120, height=40)

        #button_6 - load textfields (in json format)
        self.ButtonLoad = tk.Button(root, text="von .json laden", font=("Levetica", "9", "normal"),
        command=self.load3)
        self.ButtonLoad.place(x=155, y=520, width=100, height=40)

        #button_7 - save textfields (in html format)
        self.ButtonSave = tk.Button(root, text="als html speichern", font=("Levetica", "9", "normal"), 
        command=lambda:[self.save1(), self.save2()])
        self.ButtonSave.place(x=260, y=520, width=110, height=40)

        #button_8 - load textfields (in html format)
        self.ButtonLoad = tk.Button(root, text="als html laden", font=("Levetica", "9", "normal"), 
        command=lambda:[self.load1(), self.load2()])
        self.ButtonLoad.place(x=375, y=520, width=100, height=40)





    #bold the selected text in the textfield
    def make_bold(self):

                field = self.selected_field
                field.tag_remove("normal", "sel.first", "sel.last")
                field.tag_remove("underline", "sel.first", "sel.last")
                field.tag_remove("italic", "sel.first", "sel.last")
                field.tag_add("bold", "sel.first", "sel.last")
                field.tag_config("bold", font=("Levetica", 12, "bold"))
    
    #unbold the selected text in the textfield
    def make_unbold(self):
                field = self.selected_field
                field.tag_remove("underline", "sel.first", "sel.last")
                field.tag_remove("bold", "sel.first", "sel.last")
                field.tag_remove("italic", "sel.first", "sel.last")
                field.tag_add("normal", "sel.first", "sel.last")   
                field.tag_config("normal", font=("Levetica", "12", "normal"))

    #underline the selected text in the textfield
    def make_underline(self):
                field = self.selected_field
                field.tag_remove("normal", "sel.first", "sel.last")
                field.tag_remove("bold", "sel.first", "sel.last")
                field.tag_remove("italic", "sel.first", "sel.last")
                field.tag_add("underline", "sel.first", "sel.last")
                field.tag_config("underline", font=("Levetica", 12, "underline"))
    
    #italic the selected text in the textfield
    def make_italic(self):
                field = self.selected_field
                field.tag_remove("normal", "sel.first", "sel.last")
                field.tag_remove("bold", "sel.first", "sel.last")
                field.tag_remove("underline", "sel.first", "sel.last")
                field.tag_add("italic", "sel.first", "sel.last")
                field.tag_config("italic", font=("Levetica", 12, "italic"))                
      
    #select the text in the textfield
    def select_field(self, event): 
        self.selected_field = event.widget #event.widget is the text widget that received the event

   
    #convert the text in the textfield_1 to a html file fomat
    def save1(self):
        text = self.textfield_1.get("1.0", "end-1c")
        f = open("text1.html", "w")
        f.write(text)
        f.close()
        print("saved")

         

    #convert the text in the textfield_2 to a html file fomat
    def save2(self):
        text = self.textfield_2.get("1.0", "end-1c")
        f = open("text2.html", "w")
        f.write(text)
        f.close()
        print("saved")

    #save the text in the textfield_1 and textfield_2 to json files with dump and utf-8
    def save3(self):
        with open("text1.json", "w", encoding="utf-8") as f:
            json.dump(self.textfield_1.dump("1.0", "end-1c"), f)
        with open("text2.json", "w", encoding="utf-8") as f:
            json.dump(self.textfield_2.dump("1.0", "end-1c"), f)
        print("saved")
   
    


    #load the text from the html file to the textfield_1
    def load1(self):
        f = open("text1.html", "r")
        text = f.read()
        self.textfield_1.insert("1.0", text)
        f.close()
        print("loaded")    

    #load the text from the html file to the textfield_2
    def load2(self):
        f = open("text2.html", "r")
        text = f.read()
        self.textfield_2.insert("1.0", text)
        f.close()
        print("loaded")

    #load the text from the json files to the textfield_1 and textfield_2 with load and utf-8
    def load3(self):
        with open("text1.json", "r", encoding="utf-8") as f:
            text = json.load(f)
            self.textfield_1.insert("1.0", text)
        with open("text2.json", "r", encoding="utf-8") as f:
            text = json.load(f)
            self.textfield_2.insert("1.0", text)
        print("loaded")



def main():
    root = tk.Tk()
    Gui(root)
    root.mainloop()
    
if __name__ == '__main__':
    main()

Liebe Grüße,
Marlon
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

`place` benutzt man nicht. Hier bietet es sich an, für die Buttons jeweils Frames zu definieren. Du hast sehr viel kopierten Code. Das Erzeugen der Buttons und das Ändern der Schriftart lassen sich alles einfach parametrisieren und mit Hilfe von einer passenden Datenstruktur vereinfachen.
Bei manchen Knöpfen erzeugst Du Listen aus None-Werten, die Du gar nicht brauchst. Dafür sind Listen nicht gedacht. Definiere Dir passende Funktionen, die mehrere Aufgaben zusammenfassen.
Methodennamen nummeriert man nicht durch, wenn Du save_as_json meinst, dann nenne die Methoden nicht save3.
save1 und save2 machen das selbe, save_as_html. Dateien öffnet man mit Hilfe des with-Statements (das Du ja bei json schon benutzt).
Du mußt das, was dump liefert beim Einfügen wieder richtig umwandeln. Dafür gibt es keine fertige Methode.
Beim Speichern von HTML darfst Du natürlich nicht nur den Text speichern, sondern mußt auch die Formatierungsinformation aus dem Dump mit speichern!

Code: Alles auswählen

import tkinter as tk
from tkinter import font
from functools import partial

TEXTMODES = [
    ("fett", "bold"),
    ("normal", "normal"),
    ("unterstreichen", "underline"),
    ("kursiv", "italic"),
]

class Gui():
    def __init__(self, root):
        root.title("Bold and Unbold")

        self.selected_field = None

        text_format_frame = tk.Frame(root)
        text_format_frame.pack(pady=5)
        for name, mode in TEXTMODES:
            button = tk.Button(text_format_frame, text=name, font=("Levetica", "9", mode),
            command=partial(self.change_text_mode, mode))
            button.pack(side=tk.LEFT, padx=5)

        self.textfield_1 = tk.Text(root, width=20, height=10, wrap=tk.WORD,font=("Levetica", 12, 'normal'))
        self.textfield_1.bind('<<Selection>>', self.select_field)
        self.textfield_1.pack(fill=tk.X)

        self.textfield_2 = tk.Text(root, width=20, height=10, wrap=tk.WORD,font=("Levetica", 12))
        self.textfield_2.bind('<<Selection>>', self.select_field)
        self.textfield_2.pack(fill=tk.X)

        output_frame = tk.Frame(root)
        output_frame.pack(pady=5)

        tk.Button(root, text="als .json speichern", font=("Levetica", "9", "normal"),
            command=self.save_all_as_json).pack(side=tk.LEFT, padx=5)

        tk.Button(root, text="von .json laden", font=("Levetica", "9", "normal"),
            command=self.load_all_as_json).pack(side=tk.LEFT, padx=5)

        tk.Button(root, text="als html speichern", font=("Levetica", "9", "normal"),
            command=self.save_all_as_html).pack(side=tk.LEFT, padx=5)

        tk.Button(root, text="als html laden", font=("Levetica", "9", "normal"),
            command=self.load_all_as_html).pack(side=tk.LEFT, padx=5)

    def change_text_mode(self, mode):
        field = self.selected_field
        if field is None:
            return
        for _, text_mode in TEXTMODES:
            if mode != text_mode:
                field.tag_remove(text_mode, "sel.first", "sel.last")
        field.tag_add(mode, "sel.first", "sel.last")
        field.tag_config(mode, font=("Levetica", 12, mode))

    def select_field(self, event):
        # event.widget is the text widget that received the event
        try:
            event.widget.get("sel.first", "sel.last")
            self.selected_field = event.widget
        except tk.TclError:
            self.selected_field = None

    def save_as_json(self, filename, textfield):
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(textfield.dump("1.0", "end-1c"), f)

    def save_all_as_json(self):
        self.save_as_json("text1.json", self.textfield_1)
        self.save_as_json("text2.json", self.textfield_2)

    def load_all_as_json(self):
        # Todo
        pass

    def save_all_as_html(self):
        # Todo
        pass

    def load_all_as_html(self):
        # Todo
        pass


def main():
    root = tk.Tk()
    Gui(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@marlon_germany: Eingerückt wird per Konvention vier Leerzeichen pro Ebene.

Die Umbrüche wo dann ``command=self.make_bold)`` am Anfang der folgenden Zeile steht sind schwer lesbar, weil nur die Klammer am Ende verrät, dass das gar keine Zuweisung an den Namen `command` ist, sondern immer noch zum Aufruf in der Zeile davor gehört.

Die Kommentare vor/zu den Methoden sollten eher Docstrings sein wenn sie beschreiben was die Methode macht.

`place()` und absolute Positionen und Grössen sollte man nicht verwenden. Hier wie das bei mir aussieht, da passen nicht alle Texte in die Schaltflächen:
Bild

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Man nummeriert keine Namen. Man will dann entweder bessere Namen verwenden, oder gar keine Einzelnamen, sondern eine Datenstruktur. Oft eine Liste. So wohl auch im Fall der `Text`-Widgets.

Bei den Lade- und Speichermethoden wären bessere Namen sinnvoller, also beispielsweise `save_as_json()` statt dem nichtssagenden `save3()`.

Die Kommentare zu den Button-Objekten sind überflüssig.

Die ganzen `make_*`-Methoden machen im Grunde das gleiche — erst alle Formatierungen entfernen und dann eine neu setzen. Das sollte *eine* Methode sein, welche die neue Formatierung als Argument übergeben bekommt.

Wenn es für Zeichenketten mit einer festen Bedeutung ein Konstante im `tkinter`-Modul gibt, sollte man die verwenden, statt die Zeichenkette als Literal in den Quelltext zu schreiben. Dann weiss der Leser, dass es sich um einen besonderen Wert handelt, und kann auch leichter in der Dokumentation danach suchen.

Die Listen bei den ``lambda``-Ausdrücken zum laden/speichern als HTML sind falsch. Wenn es sich nicht um einen einfachen Ausdruck handelt, kann man halt kein ``lambda`` verwenden sondern muss eine Funktion oder Methode schreiben.

Warum wird bei den HTML-Methoden nicht ``with`` beim öffnen der Dateien verwendet?

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import json
import tkinter as tk
from functools import partial


class Gui:
    def __init__(self, master):
        self.selected_field = None

        frame = tk.Frame(master)

        for text, format_attribute in [
            ("fett", "bold"),
            ("normal", "normal"),
            ("unterstreichen", "underline"),
            ("kursiv", "italic"),
        ]:
            tk.Button(
                frame,
                text=text,
                font=("Levetica", "9", format_attribute),
                command=partial(self.format_selected_text, format_attribute),
            ).pack(side=tk.LEFT)

        frame.pack()

        self.text_fields = []
        for _ in range(2):
            text_widget = tk.Text(
                master,
                width=20,
                height=10,
                wrap=tk.WORD,
                font=("Levetica", 12),
            )
            text_widget.bind("<<Selection>>", self.select_field)
            text_widget.pack()
            self.text_fields.append(text_widget)

        frame = tk.Frame(master)

        tk.Button(
            frame,
            text="als .json speichern",
            font=("Levetica", "9", "normal"),
            command=self.save_as_json,
        ).pack(side=tk.LEFT)

        tk.Button(
            frame,
            text="von .json laden",
            font=("Levetica", "9", "normal"),
            command=self.load_from_json,
        ).pack(side=tk.LEFT)

        tk.Button(
            frame,
            text="als html speichern",
            font=("Levetica", "9", "normal"),
            command=self.save_as_html,
        ).pack(side=tk.LEFT)

        tk.Button(
            frame,
            text="als html laden",
            font=("Levetica", "9", "normal"),
            command=self.load_from_html,
        ).pack(side=tk.LEFT)

        frame.pack()

    def format_selected_text(self, format_attribute):
        for tag_name in ["bold", "normal", "underline", "italic"]:
            self.selected_field.remove(tag_name, tk.SEL_FIRST, tk.SEL_LAST)

        self.selected_field.tag_add(
            format_attribute, tk.SEL_FIRST, tk.SEL_LAST
        )
        self.selected_field.tag_config(
            format_attribute, font=("Levetica", 12, format_attribute)
        )

    def select_field(self, event):
        self.selected_field = event.widget

    def save_as_json(self):
        for i, text_field in enumerate(self.text_fields, 1):
            with open(f"text{i}.json", "w", encoding="utf-8") as file:
                json.dump(text_field.dump("1.0", "end-1c"), file)

    def load_from_json(self):
        for i, text_field in enumerate(self.text_fields, 1):
            with open(f"text{i}.json", "rb") as file:
                text_field.delete("1.0", tk.END)
                #
                # FIXME Das reicht so nicht aus, man muss aus dem JSON den Text
                # und die Formatierung(en) wieder herstellen.
                #
                text_field.insert("1.0", json.load(file))

    def save_as_html(self):
        #
        # FIXME Diese Methode schreibt keine valide HTML-Datei und die
        # Formatierung geht verloren.
        #
        for i, text_field in enumerate(self.text_fields, 1):
            with open(f"text{i}.html", "w", encoding="utf-8") as file:
                file.write(text_field.get("1.0", "end-1c"))

    def load_from_html(self):
        #
        # FIXME Diese Methode behandelt HTML einfach als Plain-Text und ob UTF-8
        # als Kodierung stimmt, kann auch nicht garantiert werden.  Hier müsste
        # man eigentlich HTML parsen und in formatierten Text im `Text`-Widget
        # umwandeln.
        #
        for i, text_field in enumerate(self.text_fields, 1):
            with open(f"text{i}.html", "r", encoding="utf-8") as file:
                text_field.delete("1.0", tk.END)
                text_field.insert("1.0", file.read())


def main():
    root = tk.Tk()
    root.title("Bold and Unbold")
    Gui(root)
    root.mainloop()


if __name__ == "__main__":
    main()
Beim laden aus JSON und sowohl beim laden als auch beim speichern als HTML fehlt Code der tatsächlich für die Umwandlung zwischen dem Speicherformat und dem formatierten Text im `Text`-Widget durchführt. JSON ist da noch halbwegs einfach, weil nur beim laden selbst Code geschrieben werden muss, der aus den Tripletts wieder formatierten Text macht. Bei HTML ist das etwas umfangreicher, weil man beide Richtungen programmieren muss. Und auch mit so Sachen umgehen muss wie HTML-Code den der Benutzer eingegeben hat davor zu schützen als HTML interpretiert zu werden, und das beim laden vielleicht auch Elemente vorhanden sind, die man nicht darstellen kann/will.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
marlon_germany
User
Beiträge: 33
Registriert: Samstag 30. April 2022, 23:32

Danke für eure Tipps wie man überflüssigen Text kürzt, den Hinweis Code lesbarer zu machen und Dateien über den 'with-Statement' Weg zu öffnen.

@Sirius3:
Sirius3 hat geschrieben: Mittwoch 20. Juli 2022, 09:47 Du mußt das, was dump liefert beim Einfügen wieder richtig umwandeln. Dafür gibt es keine fertige Methode.
@__blackjack__:
__blackjack__ hat geschrieben: Mittwoch 20. Juli 2022, 10:37 Beim laden aus JSON...fehlt Code der tatsächlich für die Umwandlung zwischen dem Speicherformat und dem formatierten Text im `Text`-Widget durchführt.
Ich kenne leider nur den Weg über json.load() den Inhalt einer .json Datei in das Tkinter-Textfeld zu laden,
oder wie man einzelne Werte von name/value Paaren mit json.loads() als Rohtext anzeigt.

Wie wandelt man denn das, was 'dump' liefert, wieder richtig um?

Und warum kann das json-Modul mit 'json.dump' Tkinter-Formatierungen in json umwandeln,
aber nicht umgekehrt mit 'json.load' von json zu Tkinter-Formatierung?

Ich habe lange in Büchern, Webseiten und Video-Tutorials nach einer Lösungen gesucht.
Aber ohne Erfolg...

Ich möchte einfach nur, dass ein "zufälliger" Text innerhalb des Tkinter Text-Widget wie z.B:
Dieser Text hier, besteht aus verschiedenen Formatierungen.

in eine json-Datei gespeichert wird und beim laden wieder genau so im Text-Widget landet:
Dieser Text hier, besteht aus verschiedenen Formatierungen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Woher kommt eigentlich die Haltung, dass es für alles schon fertige Lösungen gibt? Da mußt Du schon selbst ran.
Aber es gibt für fast alles schon Vorlagen: https://stackoverflow.com/questions/529 ... et-as-html
marlon_germany
User
Beiträge: 33
Registriert: Samstag 30. April 2022, 23:32

Sirius3 hat geschrieben: Sonntag 24. Juli 2022, 18:00 Woher kommt eigentlich die Haltung, dass es für alles schon fertige Lösungen gibt? Da mußt Du schon selbst ran.
Aber es gibt für fast alles schon Vorlagen: https://stackoverflow.com/questions/529 ... et-as-html
Danke für den Hinweis. Ich habe es nun endlich geschafft weiter zu kommen, allerdings bedingt.

Wenn ich folgenden HTML Code habe,

Code: Alles auswählen

<b>Hallo</b><i>Test</i><u>123</u>
wird es nur teilhaft richtig ins Tkinter Format konvertiert, wie hier:
HalloTest123
Warum wird nur "123" umgewandelt, aber der Anfang vom Text "Hallo" und "Test" bleibt unformatiert?

Meine Funktion sieht wie folgt aus:

Code: Alles auswählen

def convert(self):
        if self.txt1.get("1.0", tk.END) == "":
            pass
        else:
            self.txt2.delete("1.0", tk.END)
            text = self.txt1.get("1.0", tk.END)
            text = text.split("<b>")
            for i in text:
                if i == "":
                    pass
                else:
                    if "</b>" in i:
                        i = i.split("</b>")
                        self.txt2.insert(tk.END, i[0], "bold")
                        self.txt2.insert(tk.END, i[1])
                    else:
                        self.txt2.insert(tk.END, i)

            text = self.txt2.get("1.0", tk.END)
            text = text.split("<i>")
            self.txt2.delete("1.0", tk.END)
            for i in text:
                if i == "":
                    pass
                else:
                    if "</i>" in i:
                        i = i.split("</i>")
                        self.txt2.insert(tk.END, i[0], "italic")
                        self.txt2.insert(tk.END, i[1])
                    else:
                        self.txt2.insert(tk.END, i)

            text = self.txt2.get("1.0", tk.END)
            text = text.split("<u>")
            self.txt2.delete("1.0", tk.END)
            for i in text:
                if i == "":
                    pass
                else:
                    if "</u>" in i:
                        i = i.split("</u>")
                        self.txt2.insert(tk.END, i[0], "underline")
                        self.txt2.insert(tk.END, i[1])
                    else:
                        self.txt2.insert(tk.END, i)

            self.txt2.tag_configure("bold", font=("Arial", 12, "bold"))
            self.txt2.tag_configure("italic", font=("Arial", 12, "italic"))
            self.txt2.tag_configure("underline", font=("Arial", 12, "underline"))
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@marlon_germany: Weil Du das so programmiert hast. Du erwartest ja anscheinend etwas anderes, also geh den Code einfach mal durch und überlege was Du an welcher Stelle für Werte erwartest, und gib mit `print()` aus was für Werte da tatsächlich sind.

Das Problem könnte sich lösen wenn Du aufhörst die GUI als Speicher für Zwischenwerte zu missbrauchen.

`txt1` und `txt2` sind übrigens auch keine guten Namen — es macht keinen Spass die beim Lesen auseinanderhalten zu müssen. Die sollten deutlich machen was der Text in diesen Eingabeelementen *bedeutet*, nicht das das ein Text ist und noch ein Text.

Wenn in einem ``if``/``elif``/``else`` ein ``pass`` steht, dann stimmt was nicht. Da denkt der Leser der Code ist noch nicht komplett, denn im endgültigen Code macht ``pass`` keinen Sinn an diesen Stellen. Da würde man entweder einen TODO-Kommentar erwarten, der beschreibt was da noch hinprogrammiert werden soll/muss. Oder das es diesen sinnfreien Zweig nicht gibt.

`i` ist kein Name für einen Text. Schon gar nicht als Laufvariable in einer ``for``-Schleife. Da erwartet jeder Leser eine ganze Zahl und keine Zeichenkette.

Da steht letztlich drei mal der gleiche Code, nur für andere HTML-Tags. Da würde man eine Schleife und eventuell auch eine Methode für verwenden.

HTML verarbeitet man nicht mit Zeichenkettenoperationen. Selbst wenn man nichts externes dafür installieren möchte, gibt es in der Standardbibliothek ja einen HTML-Parser der das robust kann. Und so eine Lösung käme dann auch mit verschachtelten Tags klar, und man könnte auch auf ”unbekannte“ Tags reagieren. Mit Fehlermeldung, oder in dem man sie einfach ignoriert und damit aus dem Text filtert.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
marlon_germany
User
Beiträge: 33
Registriert: Samstag 30. April 2022, 23:32

__blackjack__ hat geschrieben: Sonntag 16. Oktober 2022, 13:43 @marlon_germany: Weil Du das so programmiert hast. Du erwartest ja anscheinend etwas anderes, also geh den Code einfach mal durch und überlege was Du an welcher Stelle für Werte erwartest, und gib mit `print()` aus was für Werte da tatsächlich sind.

Das Problem könnte sich lösen wenn Du aufhörst die GUI als Speicher für Zwischenwerte zu missbrauchen.

`txt1` und `txt2` sind übrigens auch keine guten Namen — es macht keinen Spass die beim Lesen auseinanderhalten zu müssen. Die sollten deutlich machen was der Text in diesen Eingabeelementen *bedeutet*, nicht das das ein Text ist und noch ein Text.

Wenn in einem ``if``/``elif``/``else`` ein ``pass`` steht, dann stimmt was nicht. Da denkt der Leser der Code ist noch nicht komplett, denn im endgültigen Code macht ``pass`` keinen Sinn an diesen Stellen. Da würde man entweder einen TODO-Kommentar erwarten, der beschreibt was da noch hinprogrammiert werden soll/muss. Oder das es diesen sinnfreien Zweig nicht gibt.

`i` ist kein Name für einen Text. Schon gar nicht als Laufvariable in einer ``for``-Schleife. Da erwartet jeder Leser eine ganze Zahl und keine Zeichenkette.

Da steht letztlich drei mal der gleiche Code, nur für andere HTML-Tags. Da würde man eine Schleife und eventuell auch eine Methode für verwenden.

HTML verarbeitet man nicht mit Zeichenkettenoperationen. Selbst wenn man nichts externes dafür installieren möchte, gibt es in der Standardbibliothek ja einen HTML-Parser der das robust kann. Und so eine Lösung käme dann auch mit verschachtelten Tags klar, und man könnte auch auf ”unbekannte“ Tags reagieren. Mit Fehlermeldung, oder in dem man sie einfach ignoriert und damit aus dem Text filtert.
Danke für deine Hinweise & Tipps! Ich habe nun das Problem über einen etwas anderen Weg gelöst. Allerdings über Zeichenkettenoperationen und ohne HTML Parser,
da ich es über den Weg mit einem HTML Parser nur teilweise geschafft habe und nicht weiterkomme.

Hier vorab erstmal meine Art das zu lösen:

Code: Alles auswählen

import tkinter as tk
from tkinter import *

class Application(Frame):
    def __init__(self, master):
        super().__init__(master)
        self.grid()
        self.create_widgets()

    #create widgets (textfields, button)
    def create_widgets(self):
        self.textfield_1 = tk.Text(self, height=8, width=30, font=("Arial", 12))
        self.textfield_1.grid(row=0, column=0)

        self.textfield_2 = tk.Text(self, height=8, width=30, font=("Arial", 12))
        self.textfield_2.grid(row=0, column=1)

        self.button_1 = tk.Button(self, text="Convert", command=self.convert)
        self.button_1.grid(row=1, column=0, columnspan=2)


    #get html-code and convert it into tkinter format 
    def convert(self):
        text = self.textfield_1.get(1.0, END)
        self.textfield_2.delete(1.0, END)
        self.textfield_2.insert(1.0, text)
        self.textfield_2.tag_config("bold", font=("Arial", 12, "bold")) 
        self.textfield_2.tag_config("italic", font=("Arial", 12, "italic"))
        self.textfield_2.tag_config("underline", font=("Arial", 12, "underline"))
        self.textfield_2.tag_config("normal", font=("Arial", 12))
        self.textfield_2.tag_add("normal", 1.0, END)
        

        #bold (the text that is between "<b>" and "</b>")
        start = self.textfield_2.search("<b>", 1.0, END)
        end = self.textfield_2.search("</b>", 1.0, END)
        while start and end:
            self.textfield_2.tag_add("bold", start, end)
            self.textfield_2.tag_remove("normal", start, end)
            self.textfield_2.delete(start, start + "+3c")
            self.textfield_2.delete(end + "-3c", end) 
            start = self.textfield_2.search("<b>", 1.0, END)
            end = self.textfield_2.search("</b>", 1.0, END)
            

        #italic (the text that is between "<i>" and "</i>")
        start = self.textfield_2.search("<i>", 1.0, END)
        end = self.textfield_2.search("</i>", 1.0, END)
        while start and end:
            self.textfield_2.tag_add("italic", start, end)
            self.textfield_2.tag_remove("normal", start, end)
            self.textfield_2.delete(start, start + "+3c")
            self.textfield_2.delete(end + "-3c", end)
            start = self.textfield_2.search("<i>", 1.0, END)
            end = self.textfield_2.search("</i>", 1.0, END)

        #underline (the text that is between "<u>" and "</u>")
        start = self.textfield_2.search("<u>", 1.0, END)
        end = self.textfield_2.search("</u>", 1.0, END)
        while start and end:
            self.textfield_2.tag_add("underline", start, end)
            self.textfield_2.tag_remove("normal", start, end)
            self.textfield_2.delete(start, start + "+3c")
            self.textfield_2.delete(end + "-3c", end)
            start = self.textfield_2.search("<u>", 1.0, END)
            end = self.textfield_2.search("</u>", 1.0, END)

        #lastly delete the ">" character from textfield 2
        end = self.textfield_2.search(">", 1.0, END)
        while end:
            self.textfield_2.delete(end, end + "+1c")
            end = self.textfield_2.search(">", 1.0, END)



root = tk.Tk()
root.title("Convert HTML to Tkinter")
app = Application(root)
root.mainloop()
Mein Fehler scheint zu sein, dass ich wie du schon sagtest, die GUI für das zwischenspeichern benutze:
Das Problem könnte sich lösen wenn Du aufhörst die GUI als Speicher für Zwischenwerte zu missbrauchen.
Ich habe leider keine Ahnung wie man Daten/Werte außerhalb der GUI speichert? Ich würde anhand meines ersten Versuches aber trotzdem gerne wissen,
wie z.B. die Daten für Bold, Italic und Underline alle individuell und unabhängig von der GUI zwischengespeichert werden können, damit sie nicht wie bei mir kollidieren
und wie in meinem Fall ,immer nur die zuletzt zwischen-gespeicherten Daten richtig verarbeitet werden? In dem Fall wird nur der Text mit "Underline" im Textfeld 2 richtig angezeigt, da die Codezeilen für Underline am Ende des Scripts stehen.

Hier mein kompletter ursprünglicher nicht funktionierender Code:

Code: Alles auswählen

import tkinter as tk
from tkinter import *

class Application(Frame):
    def __init__(self, master):
        super().__init__(master)
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        
        #create textfield 1
        self.texfield_1 = tk.Text(self, height=8, width=30, font=("Arial", 12))
        self.texfield_1.grid(row=0, column=0)

        #create textfield 2
        self.texfield_2 = tk.Text(self, height=8, width=30, font=("Arial", 12))
        self.texfield_2.grid(row=0, column=1)

        #create button
        self.button_1 = tk.Button(self, text="Convert", command=self.convert)
        self.button_1.grid(row=1, column=0, columnspan=2)



#create  methods that converts html code to tkinter format in a loop
    def convert(self):
        self.texfield_2.delete(1.0, END)
        html = self.texfield_1.get(1.0, END)

        #make everything bold
        html = html.split("<b>")
        for text in html:
            text = text.split("</b>")
            if len(text) > 1:
                self.texfield_2.insert(END, text[0], "bold")
                self.texfield_2.insert(END, text[1])
            else:
                self.texfield_2.insert(END, text[0])
                self.texfield_2.tag_config("bold", font=("Arial", 12, "bold"))

                
        #make everything italic
        html = self.texfield_2.get(1.0, END)
        self.texfield_2.delete(1.0, END)
        html = html.split("<i>")
        for text in html:
            text = text.split("</i>")
            if len(text) > 1:
                self.texfield_2.insert(END, text[0], "italic")
                self.texfield_2.insert(END, text[1])
            else:
                self.texfield_2.insert(END, text[0])
                self.texfield_2.tag_config("italic", font=("Arial", 12, "italic"))
                
        #make everything underline
        html = self.texfield_2.get(1.0, END)
        self.texfield_2.delete(1.0, END)
        html = html.split("<u>")
        for text in html:
            text = text.split("</u>")
            if len(text) > 1:
                self.texfield_2.insert(END, text[0], "underline")
                self.texfield_2.insert(END, text[1])
            else:
                self.texfield_2.insert(END, text[0])
                self.texfield_2.tag_config("underline", font=("Arial", 12, "underline"))

        

root = tk.Tk()
root.title("Convert HTML to Tkinter")
app = Application(root)
root.mainloop()
Und zuletzt, hier noch mein Versuch das ganze mit einem HTML Parser zu lösen:

Code: Alles auswählen

import tkinter as tk
from tkinter import *
from html.parser import HTMLParser

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        #textfield for the html code
        self.textfield_1 = tk.Text(self, width=40, height=10)
        self.textfield_1.pack(side="top")

        #textfield for the tkinter output
        self.textfield_2 = tk.Text(self, width=40, height=10)
        self.textfield_2.pack(side="top")

        #button to convert the html code into tkinter code
        self.button = tk.Button(self, text="Convert", command=self.convert)
        self.button.pack(side="top")

    
    def html_parser(self, html):
        class MyHTMLParser(HTMLParser):
            def __init__(self):
                HTMLParser.__init__(self)
                self.tkinter = ""
                self.bold = False 
                self.italic = False
                self.underline = False

            #handle the starttag and set the bold, italic and underline to true
            def handle_starttag(self, tag, attrs): 
                if tag == "b": 
                    self.bold = True
                if tag == "i":
                    self.italic = True
                if tag == "u":
                    self.underline = True

            #hadle data and add the tkinter code to the tkinter variable
            def handle_data(self, data):
                if self.bold:
                    self.tkinter += data + " "
                if self.italic:
                    self.tkinter += data + " "
                if self.underline:
                    self.tkinter += data + " "
                if not self.bold and not self.italic and not self.underline:
                    self.tkinter += data 

            #handle the endtag and set the bold, italic and underline to false
            def handle_endtag(self, tag):
                if tag == "b":
                    self.bold = False
                if tag == "i":
                    self.italic = False
                if tag == "u":
                    self.underline = False

        #create an instance of the parser and give it the html code
        parser = MyHTMLParser()
        parser.feed(html)
        return parser.tkinter



    #convert the html code into tkinter format
    def convert(self):
        html = self.textfield_1.get("1.0", END)
        tkinter = self.html_parser(html)
        self.textfield_2.delete("1.0", END)
        self.textfield_2.insert("1.0", tkinter)

        
        
        
    #set text to bold, italic and underline,
    #if the text contains the corresponding html tags

        #bold
        if "<b>" in html:
            self.textfield_2.tag_add("bold", "1.0", "end")
            self.textfield_2.tag_config("bold", font=("Arial", 12, "bold"))
        #italic
        if "<i>" in html:
            self.textfield_2.tag_add("italic", "1.0", "end")
            self.textfield_2.tag_config("italic", font=("Arial", 12, "italic"))
        #underline
        elif "<u>" in html:
            self.textfield_2.tag_add("underline", "1.0", "end")
            self.textfield_2.tag_config("underline", font=("Arial", 12, "underline"))


root = tk.Tk()
app = Application(master=root)
app.mainloop()
Auch hier werden die Daten von Textfeld 1 falsch zum Textfeld 2 übersetzt, da ich ich nicht weiß, wie man richtig die Daten für Bold, Italic und Underline unabhängig voneinander in einen eigenen Zwischenspeicher ablegt.

Wenn ich z.B. in Textfeld 1 folgendes eingebe:
<b>this is bold</b>
<i>this is italic</i>
<u>this is underline</u>
sieht das Ergebnis wie folgt aus:
this is bold
this is italic
this is underline
Also alles nur in kursiv.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

*-Importe sind immer noch schlecht. Warum taucht das bei Dir immer noch auf?
Frames plazieren sich nicht selbst im Master. Das create_widgets-Methode ist überflüssig, das kann alles in __init__ stehen.
Klassen definiert man nicht irgendwo innerhalb von Methoden, weil das dann nicht testbar ist.
HTML ist halt nunmal kein normaler Text, und da mit Stringoperationen darauf. Das ganze Textsuchen, -auschneiden, -verändern ist auch alles andere als übersichtlich.

Code: Alles auswählen

import tkinter as tk
from html.parser import HTMLParser

class TKinterHTMLParser(HTMLParser):
    def __init__(self, textfield):
        HTMLParser.__init__(self)
        self.textfield = textfield
        self.textfield.tag_config("bold", font=("Arial", 12, "bold")) 
        self.textfield.tag_config("italic", font=("Arial", 12, "italic"))
        self.textfield.tag_config("underline", font=("Arial", 12, "underline"))
        self.textfield.tag_config("normal", font=("Arial", 12))
        self.tags = dict.fromkeys("biu", 0)

    def handle_starttag(self, tag, attrs): 
        if tag in self.tags:
            self.tags[tag] += 1

    def handle_endtag(self, tag):
        if tag in self.tags:
            self.tags[tag] -= 1

    def handle_data(self, data):
        if self.tags["b"]:
            tag = "bold"
        elif self.tags["i"]:
            tag = "italic"
        elif self.tags["u"]:
            tag = "underline"
        else:
            tag = "normal"
        self.textfield.insert(tk.END, data, tag)


class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master

        #textfield for the html code
        self.textfield_html = tk.Text(self, width=40, height=10)
        self.textfield_html.pack(side="top")

        #textfield for the tkinter output
        self.textfield_formatted = tk.Text(self, width=40, height=10)
        self.textfield_formatted.pack(side="top")

        #button to convert the html code into tkinter code
        self.button = tk.Button(self, text="Convert", command=self.convert)
        self.button.pack(side="top")

    def convert(self):
        html = self.textfield_html.get("1.0", tk.END)
        parser = TKinterHTMLParser(self.textfield_formatted)
        parser.feed(html)


def main():
    root = tk.Tk()
    app = Application(master=root)
    app.pack()
    root.mainloop()

if __name__ == "__main__":
    main()
Der Code kann noch nicht damit umgehen, wenn gleichzeitig Fett und Unterstrichen wird.
marlon_germany
User
Beiträge: 33
Registriert: Samstag 30. April 2022, 23:32

Vielen Dank mal wieder für dein Exemplar Sirius3.
Das hat hilft mir zu verstehen wie der HTML Parser für mein Vorhaben richtig funktioniert.
Habe mir den Code ausführlich angeguckt und viel daraus gelernt.

Und die *-Importe habe ich benutzt, weil ich mal irgendwann in der Vergangenheit das Problem hatte,
dass ein Skript nicht funktionierte, bis ich den "from tkinter import *" gemacht hatte und es lief.
Eine alte Gewohnheit, da ich die Unterschiede zwischen "from Tkinter import *" und "import Tkinter as tk" nicht kannte und mir dachte es wäre leichter und schneller einfacher immer "from Tkinter import *" zu benutzen.
Antworten