mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
0.3.4 removed tkhtmlview from deps
This commit is contained in:
parent
3e96fbed2c
commit
168e902e36
@ -1,2 +1,2 @@
|
||||
from fishy.__main__ import main
|
||||
__version__ = "0.3.3"
|
||||
__version__ = "0.3.4"
|
||||
|
@ -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
|
||||
|
||||
|
1
fishy/libs/__init__.py
Normal file
1
fishy/libs/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import tkhtmlview
|
129
fishy/libs/tkhtmlview/__init__.py
Normal file
129
fishy/libs/tkhtmlview/__init__.py
Normal file
@ -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)
|
688
fishy/libs/tkhtmlview/html_parser.py
Normal file
688
fishy/libs/tkhtmlview/html_parser.py
Normal file
@ -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, "<Button-1>", self.hlink_slots[-1].call)
|
||||
self._w.tag_bind(key, "<Leave>", self.hlink_slots[-1].leave)
|
||||
self._w.tag_bind(key, "<Enter>", 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
|
@ -10,5 +10,3 @@ pyautogui
|
||||
requests
|
||||
beautifulsoup4
|
||||
whatsmyip
|
||||
pypng
|
||||
tkhtmlview
|
||||
|
Loading…
Reference in New Issue
Block a user