From 168e902e36d572b85d95e5fef054ba2d55f804a0 Mon Sep 17 00:00:00 2001 From: "DESKTOP-JVKHS7I\\Adam" Date: Wed, 20 May 2020 16:49:01 +0530 Subject: [PATCH] 0.3.4 removed tkhtmlview from deps --- fishy/__init__.py | 2 +- fishy/gui/notification.py | 3 +- fishy/libs/__init__.py | 1 + fishy/libs/tkhtmlview/__init__.py | 129 +++++ fishy/libs/tkhtmlview/html_parser.py | 688 +++++++++++++++++++++++++++ requirements.txt | 2 - test.ps1 | 2 +- 7 files changed, 822 insertions(+), 5 deletions(-) create mode 100644 fishy/libs/__init__.py create mode 100644 fishy/libs/tkhtmlview/__init__.py create mode 100644 fishy/libs/tkhtmlview/html_parser.py diff --git a/fishy/__init__.py b/fishy/__init__.py index f877530..2405c5e 100644 --- a/fishy/__init__.py +++ b/fishy/__init__.py @@ -1,2 +1,2 @@ from fishy.__main__ import main -__version__ = "0.3.3" +__version__ = "0.3.4" diff --git a/fishy/gui/notification.py b/fishy/gui/notification.py index c91ab02..aff6d09 100644 --- a/fishy/gui/notification.py +++ b/fishy/gui/notification.py @@ -2,11 +2,12 @@ import time from tkinter import * from tkinter import messagebox from tkinter.ttk import * -from tkhtmlview import HTMLLabel from fishy import web import typing +from fishy.libs.tkhtmlview import HTMLLabel + if typing.TYPE_CHECKING: from . import GUI diff --git a/fishy/libs/__init__.py b/fishy/libs/__init__.py new file mode 100644 index 0000000..d4a018d --- /dev/null +++ b/fishy/libs/__init__.py @@ -0,0 +1 @@ +from . import tkhtmlview diff --git a/fishy/libs/tkhtmlview/__init__.py b/fishy/libs/tkhtmlview/__init__.py new file mode 100644 index 0000000..89056f7 --- /dev/null +++ b/fishy/libs/tkhtmlview/__init__.py @@ -0,0 +1,129 @@ +""" +tkinter HTML text widgets +""" +import sys +import tkinter as tk +from . import html_parser + +VERSION = "0.1.0.post1" + + +class _ScrolledText(tk.Text): + # ---------------------------------------------------------------------------------------------- + def __init__(self, master=None, **kw): + self.frame = tk.Frame(master) + + self.vbar = tk.Scrollbar(self.frame) + kw.update({'yscrollcommand': self.vbar.set}) + self.vbar.pack(side=tk.RIGHT, fill=tk.Y) + self.vbar['command'] = self.yview + + tk.Text.__init__(self, self.frame, **kw) + self.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + text_meths = vars(tk.Text).keys() + methods = vars(tk.Pack).keys() | vars(tk.Grid).keys() | vars(tk.Place).keys() + methods = methods.difference(text_meths) + + for m in methods: + if m[0] != '_' and m != 'config' and m != 'configure': + setattr(self, m, getattr(self.frame, m)) + + def __str__(self): + return str(self.frame) + + +class HTMLScrolledText(_ScrolledText): + # ---------------------------------------------------------------------------------------------- + """ + HTML scrolled text widget + """ + + def __init__(self, *args, html=None, **kwargs): + # ------------------------------------------------------------------------------------------ + super().__init__(*args, **kwargs) + self._w_init(kwargs) + self.html_parser = html_parser.HTMLTextParser() + if isinstance(html, str): + self.set_html(html) + + def _w_init(self, kwargs): + # ------------------------------------------------------------------------------------------ + if not 'wrap' in kwargs.keys(): + self.config(wrap='word') + if not 'background' in kwargs.keys(): + if sys.platform.startswith('win'): + self.config(background='SystemWindow') + else: + self.config(background='white') + + def fit_height(self): + # ------------------------------------------------------------------------------------------ + """ + Fit widget height to wrapped lines + """ + for h in range(1, 4): + self.config(height=h) + self.master.update() + if self.yview()[1] >= 1: + break + else: + self.config(height=0.5 + 3 / self.yview()[1]) + + def set_html(self, html, strip=True): + # ------------------------------------------------------------------------------------------ + """ + Set HTML widget text. If strip is enabled (default) it ignores spaces and new lines. + + """ + prev_state = self.cget('state') + self.config(state=tk.NORMAL) + self.delete('1.0', tk.END) + self.tag_delete(self.tag_names) + self.html_parser.w_set_html(self, html, strip=strip) + self.config(state=prev_state) + + +class HTMLText(HTMLScrolledText): + # ---------------------------------------------------------------------------------------------- + """ + HTML text widget + """ + + def _w_init(self, kwargs): + # ------------------------------------------------------------------------------------------ + super()._w_init(kwargs) + self.vbar.pack_forget() + + def fit_height(self): + # ------------------------------------------------------------------------------------------ + super().fit_height() + # self.master.update() + self.vbar.pack_forget() + + +class HTMLLabel(HTMLText): + # ---------------------------------------------------------------------------------------------- + """ + HTML label widget + """ + + def _w_init(self, kwargs): + # ------------------------------------------------------------------------------------------ + super()._w_init(kwargs) + if not 'background' in kwargs.keys(): + if sys.platform.startswith('win'): + self.config(background='SystemButtonFace') + else: + self.config(background='#d9d9d9') + + if not 'borderwidth' in kwargs.keys(): + self.config(borderwidth=0) + + if not 'padx' in kwargs.keys(): + self.config(padx=3) + + def set_html(self, *args, **kwargs): + # ------------------------------------------------------------------------------------------ + super().set_html(*args, **kwargs) + self.config(state=tk.DISABLED) diff --git a/fishy/libs/tkhtmlview/html_parser.py b/fishy/libs/tkhtmlview/html_parser.py new file mode 100644 index 0000000..b5a90b2 --- /dev/null +++ b/fishy/libs/tkhtmlview/html_parser.py @@ -0,0 +1,688 @@ +""" +HTML parser +""" +import os +import webbrowser +import tkinter as tk +from tkinter import font +from copy import deepcopy +from PIL import Image, ImageTk +from html.parser import HTMLParser +from collections import OrderedDict +import requests +from io import BytesIO + + +# __________________________________________________________________________________________________ +class Defs: + DEFAULT_TEXT_FONT_FAMILY = ("Segoe ui", "Calibri", "Helvetica", "TkTextFont") + FONT_SIZE = 14 + PREFORMATTED_FONT_FAMILY = ("Courier", "DejaVu Sans Mono", "TkFixedFont") + HEADINGS_FONT_SIZE = { + 'h1': 32, + 'h2': 24, + 'h3': 18, + 'h4': 16, + 'h5': 13, + 'h6': 10, + } + + +class HTML: + # ---------------------------------------------------------------------------------------------- + """ + List of supported HTML tags and attrs + """ + + class Tag(): + BR = 'br' + UL = 'ul' + OL = 'ol' + LI = 'li' + IMG = 'img' + A = 'a' + B = 'b' + STRONG = 'strong' + I = 'i' + EM = 'em' + U = 'u' + MARK = 'mark' + SPAN = 'span' + DIV = 'div' + P = 'p' + PRE = 'pre' + CODE = 'code' + H1 = 'h1' + H2 = 'h2' + H3 = 'h3' + H4 = 'h4' + H5 = 'h5' + H6 = 'h6' + + class Attrs(): + STYLE = 'style' + HREF = 'href' + SRC = 'src' + WIDTH = 'width' + HEIGHT = 'height' + TYPE = 'type' + + class TypeOrderedList(): + _1 = '1' + a = 'a' + A = 'A' + + class Style(): + COLOR = 'color' + BACKGROUD_COLOR = 'background-color' + FONT_FAMILY = 'font-family' + FONT_SIZE = 'font-size' + TEXT_ALIGN = 'text-align' + TEXT_DECORATION = 'text-decoration' + + class StyleTextDecoration(): + UNDERLINE = 'underline' + LINE_THROUGH = 'line-through' + + HEADING_TAGS = ( + Tag.H1, + Tag.H2, + Tag.H3, + Tag.H4, + Tag.H5, + Tag.H6, + ) + + TEXT_ALIGN_TAGS = HEADING_TAGS + ( + Tag.UL, + Tag.OL, + Tag.LI, + Tag.DIV, + Tag.P, + Tag.PRE, + Tag.CODE, + ) + + NEW_LINE_TAGS = HEADING_TAGS + ( + Tag.UL, + Tag.OL, + Tag.DIV, + Tag.P, + Tag.PRE, + Tag.CODE, + ) + + STYLE_TAGS = TEXT_ALIGN_TAGS + ( + Tag.A, + Tag.B, + Tag.STRONG, + Tag.I, + Tag.EM, + Tag.U, + Tag.MARK, + Tag.SPAN, + ) + + +# -------------------------------------------------------------------------------------------------- +# Text widget defs + +class WCfg(): + KEY = "config" + BACKGROUND = "background" + FOREGROUND = "foreground" + JUSTIFY = "justify" + TABS = "tabs" + + +class Fnt(): + KEY = "font" + FAMILY = "family" + SIZE = "size" + WEIGHT = "weight" + SLANT = "slant" + UNDERLINE = "underline" + OVERSTRIKE = "overstrike" + + +class Bind(): + KEY = "bind" + LINK = "link" + IMAGE = "image" + + +class WTag(): + START_INDEX = "start_index" + END_INDEX = "end_index" + + +DEFAULT_STACK = { + WCfg.KEY: { + WCfg.BACKGROUND: [], + WCfg.FOREGROUND: [("__DEFAULT__", "black")], + WCfg.JUSTIFY: [("__DEFAULT__", 'left')], + WCfg.TABS: [("__DEFAULT__", ())], + }, + Fnt.KEY: { + Fnt.FAMILY: [], + Fnt.SIZE: [("__DEFAULT__", Defs.FONT_SIZE)], + Fnt.WEIGHT: [("__DEFAULT__", 'normal')], + Fnt.SLANT: [("__DEFAULT__", 'roman')], + Fnt.UNDERLINE: [("__DEFAULT__", False)], + Fnt.OVERSTRIKE: [("__DEFAULT__", False)], + }, + Bind.KEY: { + Bind.LINK: [("__DEFAULT__", None)], + }, +} + + +# __________________________________________________________________________________________________ +# functions +def get_existing_font(font_families): + # ------------------------------------------------------------------------------------------ + try: + return next(filter(lambda f: f.lower() in (f.lower() for f in font.families()), font_families)) + except: + return "TkTextFont" + + +# __________________________________________________________________________________________________ +# classes +class HLinkSlot(): + # ---------------------------------------------------------------------------------------------- + + def __init__(self, w, tag_name, url): + # ------------------------------------------------------------------------------------------ + self._w = w + self.tag_name = tag_name + self.URL = url + + def call(self, event): + # ------------------------------------------------------------------------------------------ + webbrowser.open(self.URL) + self._w.tag_config(self.tag_name, foreground="purple") + + def enter(self, event): + # ------------------------------------------------------------------------------------------ + self._w.config(cursor="hand2") + + def leave(self, event): + # ------------------------------------------------------------------------------------------ + self._w.config(cursor="") + + +class ListTag(): + # ---------------------------------------------------------------------------------------------- + def __init__(self, ordered: bool, list_type=None): + # ------------------------------------------------------------------------------------------ + self.ordered = ordered + self.type = list_type + self.index = 0 + + def add(self): + # ------------------------------------------------------------------------------------------ + if self.ordered: + self.index += 1 + + def line_index(self): + # ------------------------------------------------------------------------------------------ + if self.ordered: + if self.type == HTML.TypeOrderedList._1: + return str(self.index) + elif self.type == HTML.TypeOrderedList.a: + return self._index_to_str(self.index).lower() + elif self.type == HTML.TypeOrderedList.A: + return self._index_to_str(self.index).upper() + else: + return chr(8226) + + def _index_to_str(self, index): + # ------------------------------------------------------------------------------------------ + prefix = "" + if index > 26: + prefix = self._index_to_str(index // 26) + index = index % 26 + + return prefix + chr(0x60 + index) + + +class HTMLTextParser(HTMLParser): + # ---------------------------------------------------------------------------------------------- + + def __init__(self): + # ------------------------------------------------------------------------------------------ + super().__init__() + # set list tabs + self.cached_images = {} + + self.DEFAULT_TEXT_FONT_FAMILY = get_existing_font(Defs.DEFAULT_TEXT_FONT_FAMILY) + self.PREFORMATTED_FONT_FAMILY = get_existing_font(Defs.PREFORMATTED_FONT_FAMILY) + + def _parse_attrs(self, attrs): + # ------------------------------------------------------------------------------------------ + attrs_dict = { + HTML.Attrs.STYLE: {}, + HTML.Attrs.HREF: None, + HTML.Attrs.SRC: None, + HTML.Attrs.WIDTH: None, + HTML.Attrs.HEIGHT: None, + HTML.Attrs.TYPE: None, + } + for k, v in attrs: + k = k.lower() + if k == HTML.Attrs.STYLE: + for p in v.split(";"): + try: + p_key = p.split(":")[0].strip().lower() + p_value = p.split(":")[1].strip().lower() + attrs_dict[HTML.Attrs.STYLE][p_key] = p_value + except: + pass + elif k in (HTML.Attrs.HREF, HTML.Attrs.SRC, HTML.Attrs.WIDTH, HTML.Attrs.HEIGHT, HTML.Attrs.TYPE): + attrs_dict[k] = v + return attrs_dict + + def _w_tags_add(self): + # ------------------------------------------------------------------------------------------ + tag = { + WCfg.KEY: {}, + Fnt.KEY: {}, + Bind.KEY: {} + } + + for k1 in (WCfg.KEY, Fnt.KEY, Bind.KEY): + for k2 in DEFAULT_STACK[k1]: + tag[k1][k2] = self.stack[k1][k2][-1][1] + + self._w_tags[self._w.index("end-1c")] = tag + + def _stack_get_main_key(self, key): + # ------------------------------------------------------------------------------------------ + if key in WCfg.__dict__.values(): + main_key = WCfg.KEY + elif key in Fnt.__dict__.values(): + main_key = Fnt.KEY + elif key in Bind.__dict__.values(): + main_key = Bind.KEY + else: + raise ValueError("key %s doesn't exists" % key) + + return main_key + + def _stack_add(self, tag, key, value=None): + # ------------------------------------------------------------------------------------------ + main_key = self._stack_get_main_key(key) + + if value is None: + # if value is none, add the previous value + value = self.stack[main_key][key][-1][1] + + self.stack[main_key][key].append((tag, value)) + + def _stack_index(self, tag, key): + # ------------------------------------------------------------------------------------------ + main_key = self._stack_get_main_key(key) + index = None + for i, v in enumerate(self.stack[main_key][key]): + if v[0] == tag: + index = i + + return index + + def _stack_pop(self, tag, key): + # ------------------------------------------------------------------------------------------ + main_key = self._stack_get_main_key(key) + + index = None + if len(self.stack[main_key][key]) > 1: + index = self._stack_index(tag, key) + + if index is not None: + return self.stack[main_key][key].pop(index)[1] + + def _parse_styles(self, tag, attrs): + # ------------------------------------------------------------------------------------------ + # -------------------------------------------------------------------------------- [ COLOR ] + if HTML.Style.COLOR in attrs[HTML.Attrs.STYLE].keys(): + self._stack_add(tag, WCfg.FOREGROUND, attrs[HTML.Attrs.STYLE][HTML.Style.COLOR]) + elif tag == HTML.Tag.A and attrs[HTML.Attrs.HREF]: + self._stack_add(tag, WCfg.FOREGROUND, "blue") + else: + self._stack_add(tag, WCfg.FOREGROUND) + + # ---------------------------------------------------------------------- [ BACKGROUD_COLOR ] + if HTML.Style.BACKGROUD_COLOR in attrs[HTML.Attrs.STYLE].keys(): + self._stack_add(tag, WCfg.BACKGROUND, attrs[HTML.Attrs.STYLE][HTML.Style.BACKGROUD_COLOR]) + elif tag == HTML.Tag.MARK: + self._stack_add(tag, WCfg.BACKGROUND, "yellow") + else: + self._stack_add(tag, WCfg.BACKGROUND) + + # -------------------------------------------------------------------------- [ FONT_FAMILY ] + # font family + if HTML.Style.FONT_FAMILY in attrs[HTML.Attrs.STYLE].keys(): + font_family = Defs.DEFAULT_TEXT_FONT_FAMILY + for f in attrs[HTML.Attrs.STYLE][HTML.Style.FONT_FAMILY].split(","): + f = f.strip() + if f in map(lambda f: f.lower(), font.families()): + font_family = f + break + self._stack_add(tag, Fnt.FAMILY, font_family) + elif tag in (HTML.Tag.PRE, HTML.Tag.CODE): + self._stack_add(tag, Fnt.FAMILY, self.PREFORMATTED_FONT_FAMILY) + else: + self._stack_add(tag, Fnt.FAMILY) + + # ---------------------------------------------------------------------------- [ FONT_SIZE ] + if HTML.Style.FONT_SIZE in attrs[HTML.Attrs.STYLE].keys(): + size = Defs.FONT_SIZE + if attrs[HTML.Attrs.STYLE][HTML.Style.FONT_SIZE].endswith("px"): + if attrs[HTML.Attrs.STYLE][HTML.Style.FONT_SIZE][:-2].isdigit(): + size = int(attrs[HTML.Attrs.STYLE][HTML.Style.FONT_SIZE][:-2]) + elif attrs[HTML.Attrs.STYLE][HTML.Style.FONT_SIZE].endswith(r"%"): + if attrs[HTML.Attrs.STYLE][HTML.Style.FONT_SIZE][:-1].isdigit(): + size = int((int(attrs[HTML.Attrs.STYLE][HTML.Style.FONT_SIZE][:-1]) * Defs.FONT_SIZE) / 100) + self._stack_add(tag, Fnt.SIZE, size) + elif tag.startswith('h') and len(tag) == 2: + self._stack_add(tag, Fnt.SIZE, Defs.HEADINGS_FONT_SIZE[tag]) + else: + self._stack_add(tag, Fnt.SIZE) + + # --------------------------------------------------------------------------- [ TEXT_ALIGN ] + if HTML.Style.TEXT_ALIGN in attrs[HTML.Attrs.STYLE].keys() and tag in HTML.TEXT_ALIGN_TAGS: + self._stack_add(tag, WCfg.JUSTIFY, attrs[HTML.Attrs.STYLE][HTML.Style.TEXT_ALIGN]) + else: + self._stack_add(tag, WCfg.JUSTIFY) + + # ---------------------------------------------------------------------- [ TEXT_DECORATION ] + if HTML.Style.TEXT_DECORATION in attrs[HTML.Attrs.STYLE].keys(): + if tag == HTML.Tag.STRONG: + self._stack_add(tag, Fnt.UNDERLINE, False) + self._stack_add(tag, Fnt.OVERSTRIKE, False) + elif HTML.StyleTextDecoration.UNDERLINE in attrs[HTML.Attrs.STYLE][HTML.Style.TEXT_DECORATION]: + self._stack_add(tag, Fnt.UNDERLINE, True) + self._stack_add(tag, Fnt.OVERSTRIKE, False) + elif HTML.StyleTextDecoration.LINE_THROUGH in attrs[HTML.Attrs.STYLE][HTML.Style.TEXT_DECORATION]: + self._stack_add(tag, Fnt.UNDERLINE, False) + self._stack_add(tag, Fnt.OVERSTRIKE, True) + else: + self._stack_add(tag, Fnt.UNDERLINE) + self._stack_add(tag, Fnt.OVERSTRIKE) + else: + if tag == HTML.Tag.A and attrs[HTML.Attrs.HREF]: + self._stack_add(tag, Fnt.UNDERLINE, True) + self._stack_add(tag, Fnt.OVERSTRIKE, False) + elif tag == HTML.Tag.U: + self._stack_add(tag, Fnt.UNDERLINE, True) + self._stack_add(tag, Fnt.OVERSTRIKE, False) + else: + self._stack_add(tag, Fnt.UNDERLINE) + self._stack_add(tag, Fnt.OVERSTRIKE) + + def handle_starttag(self, tag, attrs): + # ------------------------------------------------------------------------------------------ + tag = tag.lower() + attrs = self._parse_attrs(attrs) + + if tag in HTML.STYLE_TAGS: + # ---------------------------------------------------------------------- [ STYLED_TAGS ] + self._parse_styles(tag, attrs) + + if tag == HTML.Tag.B or tag == HTML.Tag.STRONG or tag in HTML.HEADING_TAGS: + self._stack_add(tag, Fnt.WEIGHT, "bold") + + elif tag == HTML.Tag.I or tag == HTML.Tag.EM: + self._stack_add(tag, Fnt.SLANT, "italic") + + elif tag == HTML.Tag.A: + self._stack_add(tag, Bind.LINK, attrs[HTML.Attrs.HREF]) + + elif tag == HTML.Tag.OL: + # ---------------------------------------------------------------- [ ORDERED_LISTS ] + if attrs[HTML.Attrs.TYPE] and attrs[HTML.Attrs.TYPE] in HTML.TypeOrderedList.__dict__.values(): + list_type = attrs[HTML.Attrs.TYPE] + else: + list_type = HTML.TypeOrderedList._1 + self.list_tags.append(ListTag(ordered=True, list_type=list_type)) + + tabs = [] + for i in range(len(self.list_tags)): + offset = 30 * (i + 1) + tabs += [offset, tk.RIGHT, offset + 5, tk.LEFT] + self._stack_add(tag, WCfg.TABS, tabs) + + elif tag == HTML.Tag.UL: + # -------------------------------------------------------------- [ UNORDERED_LISTS ] + self.list_tags.append(ListTag(ordered=False)) + + tabs = [] + for i in range(len(self.list_tags)): + offset = 30 * (i + 1) + tabs += [offset, tk.RIGHT, offset + 5, tk.LEFT] + self._stack_add(tag, WCfg.TABS, tabs) + + elif tag == HTML.Tag.LI: + # ------------------------------------------------------------------ [ LISTS_LINES ] + level = len(self.list_tags) + if level: + self.list_tags[-1].add() + + if self.strip: + self._insert_new_line() + + line_index = self.list_tags[-1].line_index() + if self.list_tags[-1].ordered: + line_index = "\t" + "\t\t" * (level - 1) + line_index + ".\t" + else: + line_index = "\t" + "\t\t" * (level - 1) + line_index + "\t" + + self._stack_add(tag, Fnt.UNDERLINE, False) + self._stack_add(tag, Fnt.OVERSTRIKE, False) + self._w_tags_add() + self._w.insert(tk.INSERT, line_index) + self._stack_pop(tag, Fnt.UNDERLINE) + self._stack_pop(tag, Fnt.OVERSTRIKE) + + elif tag == HTML.Tag.IMG and attrs[HTML.Attrs.SRC]: + # -------------------------------------------------------------------- [ UNSTYLED_TAGS ] + image = None + print(attrs[HTML.Attrs.SRC], self.cached_images) + if attrs[HTML.Attrs.SRC].startswith(("https://", "ftp://", "http://")): + if attrs[HTML.Attrs.SRC] in self.cached_images.keys(): + image = deepcopy(self.cached_images[attrs[HTML.Attrs.SRC]]) + else: + try: + image = Image.open(BytesIO(requests.get(attrs[HTML.Attrs.SRC]).content)) + self.cached_images[attrs[HTML.Attrs.SRC]] = deepcopy(image) + except: + pass + + if attrs[HTML.Attrs.SRC] in self.cached_images.keys(): + image = deepcopy(self.cached_images[attrs[HTML.Attrs.SRC]]) + elif os.path.exists(attrs[HTML.Attrs.SRC]): + image = Image.open(attrs[HTML.Attrs.SRC]) + self.cached_images[attrs[HTML.Attrs.SRC]] = deepcopy(image) + if image: + width = image.size[0] + height = image.size[1] + resize = False + if str(attrs[HTML.Attrs.WIDTH]).isdigit(): + width = int(attrs[HTML.Attrs.WIDTH]) + resize = True + if str(attrs[HTML.Attrs.HEIGHT]).isdigit(): + height = int(attrs[HTML.Attrs.HEIGHT]) + resize = True + if resize: + image = image.resize((width, height), Image.ANTIALIAS) + self.images.append(ImageTk.PhotoImage(image)) + self._w.image_create(tk.INSERT, image=self.images[-1]) + + if self.strip: + # ------------------------------------------------------------------------ [ NEW_LINES ] + if tag == HTML.Tag.BR: + self._insert_new_line() + else: + self.html_tags.append(tag) + + if tag in HTML.NEW_LINE_TAGS and self.strip and self._w.index("end-1c") != "1.0": + if tag in (HTML.Tag.DIV,): + self._insert_new_line() + elif tag in (HTML.Tag.UL, HTML.Tag.OL): + if len(self.list_tags) == 1: + self._insert_new_line(double=True) + else: + self._insert_new_line(double=False) + else: + self._insert_new_line(double=True) + + self._w_tags_add() + + def handle_charref(self, data): + # ------------------------------------------------------------------------------------------ + try: + char = chr(int(data)) + self._w.insert(tk.INSERT, char) + except: + pass + + def _insert_new_line(self, double=False): + # ------------------------------------------------------------------------------------------ + self._remove_last_space() + if self._w.get("end-3c", "end-1c") == "\n\n": + pass + elif self._w.get("end-2c", "end-1c") == "\n": + if double: + self._w.insert(tk.INSERT, "\n") + else: + if double: + self._w.insert(tk.INSERT, "\n\n") + else: + self._w.insert(tk.INSERT, "\n") + + def _text_rstrip(self): + # ------------------------------------------------------------------------------------------ + for _ in range(3): + if self._w.get("end-2c", "end-1c") in (" ", "\n"): + self._w.delete("end-2c", "end-1c") + + def _remove_last_space(self): + # ------------------------------------------------------------------------------------------ + if self._w.get("end-2c", "end-1c") == " ": + self._w.delete("end-2c", "end-1c") + + def _remove_multi_spaces(self, data): + # ------------------------------------------------------------------------------------------ + data = data.replace(" ", " ") + if " " in data: + data = self._remove_multi_spaces(data) + return data + + def handle_data(self, data): + # ------------------------------------------------------------------------------------------ + if self.strip: + if len(self.html_tags) and self.html_tags[-1] in (HTML.Tag.PRE, HTML.Tag.CODE): + pass + elif not data.strip(): + data = "" + else: + # left strip + if self._w.index("end-1c").endswith(".0"): + data = data.lstrip() + elif self._w.get("end-2c", "end-1c") == " ": + data = data.lstrip() + + data = data.replace("\n", " ").replace("\t", " ") + data = data + " " + data = self._remove_multi_spaces(data) + if len(self.html_tags): + level = len(self.list_tags) + if self.html_tags[-1] in (HTML.Tag.UL, HTML.Tag.OL): + self._w.insert(tk.INSERT, "\t" * 2 * level) + + self._w.insert(tk.INSERT, data) + + def handle_endtag(self, tag): + # ------------------------------------------------------------------------------------------ + tag = tag.lower() + + try: + index = len(self.html_tags) - self.html_tags[::-1].index(tag) - 1 + self.html_tags.pop(index) + except: + pass + + if tag in HTML.STYLE_TAGS: + + self._stack_pop(tag, WCfg.FOREGROUND) + self._stack_pop(tag, WCfg.BACKGROUND) + self._stack_pop(tag, WCfg.JUSTIFY) + self._stack_pop(tag, Fnt.FAMILY) + self._stack_pop(tag, Fnt.SIZE) + self._stack_pop(tag, Fnt.UNDERLINE) + self._stack_pop(tag, Fnt.OVERSTRIKE) + + if tag == HTML.Tag.B or tag == HTML.Tag.STRONG or tag in HTML.HEADING_TAGS: + self._stack_pop(tag, Fnt.WEIGHT) + + elif tag == HTML.Tag.I or tag == HTML.Tag.EM: + self._stack_pop(tag, Fnt.SLANT) + + elif tag == HTML.Tag.A: + self._stack_pop(tag, Bind.LINK) + + elif tag == HTML.Tag.OL or tag == HTML.Tag.UL: + if len(self.list_tags): + self.list_tags = self.list_tags[:-1] + + self._stack_pop(tag, WCfg.TABS) + + if tag in HTML.NEW_LINE_TAGS and self.strip: + self._insert_new_line() + + self._w_tags_add() + + if tag in HTML.NEW_LINE_TAGS and self.strip: + if tag in (HTML.Tag.DIV, HTML.Tag.UL, HTML.Tag.OL): + if not len(self.list_tags): + self._insert_new_line(double=True) + else: + self._insert_new_line(double=True) + + def _w_tags_apply_all(self): + # ------------------------------------------------------------------------------------------ + # update indexes + if self.strip: + self._text_rstrip() + end_index = tk.END + for key, tag in reversed(tuple(self._w_tags.items())): + tag[WTag.START_INDEX] = key + tag[WTag.END_INDEX] = end_index + end_index = key + + # add tags + self.hlink_slots = [] + for key, tag in self._w_tags.items(): + self._w.tag_add(key, tag[WTag.START_INDEX], tag[WTag.END_INDEX]) + self._w.tag_config(key, font=font.Font(**tag[Fnt.KEY]), **tag[WCfg.KEY]) + if tag[Bind.KEY][Bind.LINK]: + self.hlink_slots.append(HLinkSlot(self._w, key, tag[Bind.KEY][Bind.LINK])) + self._w.tag_bind(key, "", self.hlink_slots[-1].call) + self._w.tag_bind(key, "", self.hlink_slots[-1].leave) + self._w.tag_bind(key, "", self.hlink_slots[-1].enter) + + def w_set_html(self, w, html, strip): + # ------------------------------------------------------------------------------------------ + self._w = w + self.stack = deepcopy(DEFAULT_STACK) + self.stack[WCfg.KEY][WCfg.BACKGROUND].append(("__DEFAULT__", self._w.cget("background"))) + self.stack[Fnt.KEY][Fnt.FAMILY].append(("__DEFAULT__", self.DEFAULT_TEXT_FONT_FAMILY)) + self._w_tags = OrderedDict() + self.html_tags = [] + self.images = [] + self.list_tags = [] + self.strip = strip + self._w_tags_add() + self.feed(html) + self._w_tags_apply_all() + del self._w diff --git a/requirements.txt b/requirements.txt index 4dfd550..9061f46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,5 +10,3 @@ pyautogui requests beautifulsoup4 whatsmyip -pypng -tkhtmlview diff --git a/test.ps1 b/test.ps1 index 2968834..5a876d7 100644 --- a/test.ps1 +++ b/test.ps1 @@ -1,6 +1,6 @@ cd temp\test Remove-Item venv -Recurse -& conda create --prefix venv python=3.7 -y +& conda create --prefix venv python=3.7.3 -y conda activate ./venv cd ../../dist pip install ((dir).Name | grep whl)