#!/usr/bin/env python
#
# css_parser.py
#
# Copyright (c) 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
# generated by wxGlade 0.9.2 on Thu Jan 16 16:34:51 2020
#
# stdlib
from typing import Dict
# 3rd party
import tinycss # type: ignore
import webcolors
import wx # type: ignore
from domdf_python_tools.typing import PathLike
# this package
from domdf_wxpython_tools.panel_listctrl.constants import sys_colour_lookup, text_defaults
__all__ = ["parse_css_file", "parse_css"]
# Setup tinycss
parser = tinycss.make_parser("page3")
[docs]def parse_css_file(filename: PathLike) -> Dict:
"""
Parse the stylesheet in the given file.
:param filename: The filename of the stylesheet to parse.
:return: Parsed CSS stylesheet.
"""
stylesheet = parser.parse_stylesheet_file(css_file=str(filename))
return _parse_css(stylesheet)
[docs]def parse_css(css_data: str) -> Dict:
"""
Parse the stylesheet from the given string
:param css_data: A string representing a CSS stylesheel
:return: Parsed CSS stylesheet
"""
stylesheet = parser.parse_stylesheet(css_unicode=css_data)
return _parse_css(stylesheet)
def _parse_css(stylesheet: tinycss.css21.Stylesheet) -> Dict:
"""
Internal function for actual parsing of css.
:param stylesheet: A tinycss parsed stylesheet.
:return: Parsed CSS stylesheet.
"""
if stylesheet.errors:
raise ValueError(stylesheet.errors[0])
styles = {} # type: ignore
# Remove declarations for other platforms and make
for rule in stylesheet.rules:
styles[rule.selector.as_css()] = {}
for declaration in rule.declarations:
name = declaration.name
value = declaration.value.as_css()
if "color" in name:
value.lstrip("wx.")
if value.startswith("SYS_COLOUR"):
# wx.SystemColour
if value in sys_colour_lookup:
value = wx.SystemSettings.GetColour(sys_colour_lookup[value])
else:
raise ValueError(
f"""wx.SystemColour 'value' not recognised.
See https://wxpython.org/Phoenix/docs/html/wx.SystemColour.enumeration.html for the list of valid values"""
)
elif value.startswith('#'):
# Hex value, pass to wx.Colour directly
pass
elif value.startswith("rgb("):
# RGB value, convert to hex
rgb_triplet = tuple(int(x) for x in value.lstrip("rgb(").rstrip(')').split(','))
value = webcolors.rgb_to_hex(rgb_triplet) # type: ignore
else:
# Named colour, convert to hex
value = webcolors.name_to_hex(value)
if name == "font-family":
if value == "decorative":
value = wx.FONTFAMILY_DECORATIVE
elif value == "roman":
value = wx.FONTFAMILY_ROMAN
elif value == "script":
value = wx.FONTFAMILY_SCRIPT
elif value == "swiss":
value = wx.FONTFAMILY_SWISS
elif value == "modern":
value = wx.FONTFAMILY_MODERN
else:
value = wx.FONTFAMILY_DEFAULT
elif name == "font-style":
if value == "slant":
value = wx.FONTSTYLE_SLANT
elif value == "italic":
value = wx.FONTSTYLE_ITALIC
else:
value = wx.FONTSTYLE_NORMAL
elif name == "font-weight":
if value == "light":
value = wx.FONTWEIGHT_LIGHT
elif value == "bolt":
value = wx.FONTWEIGHT_BOLD
else:
value = wx.FONTWEIGHT_NORMAL
styles[rule.selector.as_css()][name] = value
# if li p is not styled, use the default values
if "li p" not in styles:
styles["li p"] = text_defaults.copy()
# If li p is missing any parameters, add them from the default values
styles["li p"] = {**text_defaults, **styles["li p"]}
# if li p::selection is not styled, use the values from p
if "li p::selection" not in styles:
styles["li p::selection"] = styles["li p"].copy()
# If li p is missing any parameters, add them from p
styles["li p::selection"] = {**styles["li p"], **styles["li p::selection"]}
# For each li p class, add undefined values from p
for style in styles:
if style.split('.')[0] == "li p":
if style == "li p":
continue
if "::selection" in style:
continue
styles[style] = {**styles["li p"], **styles[style]}
# For each p::selection class, add undefined values from p::selection
for style in styles:
if style.split('.')[0] == "li p":
if style == "li p::selection":
continue
if "::selection" not in style:
continue
styles[style] = {**styles["li p::selection"], **styles[style]}
return styles