webcode-tk 1.1.3__tar.gz → 1.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/PKG-INFO +2 -3
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/pyproject.toml +2 -2
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/__init__.py +2 -1
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/cascade_tools.py +234 -101
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/color_tools.py +11 -3
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/css_tools.py +91 -6
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/font_tools.py +8 -7
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/LICENSE.rst +0 -0
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/README.rst +0 -0
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/color_keywords.py +0 -0
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/html_tools.py +0 -0
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/sample_report.json +0 -0
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/ux_tools.py +0 -0
- {webcode_tk-1.1.3 → webcode_tk-1.1.5}/webcode_tk/validator_tools.py +0 -0
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: webcode-tk
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.5
|
|
4
4
|
Summary:
|
|
5
5
|
Home-page: https://github.com/HundredVisionsGuy/webcode-tk
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: html,validation,css
|
|
8
8
|
Author: hundredvisionsguy
|
|
9
9
|
Author-email: cwinikka@gmail.com
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.9.0,<4.0
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "webcode-tk"
|
|
3
|
-
version = "1.1.
|
|
3
|
+
version = "1.1.5"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = ["hundredvisionsguy <cwinikka@gmail.com>"]
|
|
6
6
|
keywords = ["html", "validation", "css", ]
|
|
@@ -16,7 +16,7 @@ classifiers = [
|
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
[tool.poetry.dependencies]
|
|
19
|
-
python = "
|
|
19
|
+
python = ">=3.9.0,<4.0"
|
|
20
20
|
bs4 = "^0.0.1"
|
|
21
21
|
file-clerk = "^1.0.9"
|
|
22
22
|
lxml = "^4.9.1"
|
|
@@ -9,6 +9,7 @@ Modules exported by this package:
|
|
|
9
9
|
properties and hex and rgb values.
|
|
10
10
|
- `color_tools`: processes CSS color related properties and values.
|
|
11
11
|
- `css_tools`: creates Stylesheet objects that store CSS information.
|
|
12
|
+
- `font_tools`: A collection of functions used to process font-related styles.
|
|
12
13
|
- `html_tools`: gets html files from a project folder, gets the HTML code
|
|
13
14
|
from files, gets number of a particular element in a file
|
|
14
15
|
or folder, gets elements as tags, and much more.
|
|
@@ -18,4 +19,4 @@ Modules exported by this package:
|
|
|
18
19
|
- `validator_tools`: sends HTML or CSS code to the W3C Validator to check
|
|
19
20
|
for errors.
|
|
20
21
|
"""
|
|
21
|
-
__version__ = "1.1.
|
|
22
|
+
__version__ = "1.1.5"
|
|
@@ -13,6 +13,7 @@ import re
|
|
|
13
13
|
from collections import abc
|
|
14
14
|
from typing import Union
|
|
15
15
|
|
|
16
|
+
from bs4 import NavigableString
|
|
16
17
|
from file_clerk import clerk
|
|
17
18
|
|
|
18
19
|
from webcode_tk import color_tools as color
|
|
@@ -69,6 +70,7 @@ class Element(object):
|
|
|
69
70
|
parent=None,
|
|
70
71
|
parent_size=root_font_size,
|
|
71
72
|
ancestors=None,
|
|
73
|
+
tag_contents=None,
|
|
72
74
|
) -> None:
|
|
73
75
|
self.name = name
|
|
74
76
|
self.attributes = attributes
|
|
@@ -104,6 +106,10 @@ class Element(object):
|
|
|
104
106
|
value, unit, parent_size, name
|
|
105
107
|
)
|
|
106
108
|
self.is_bold = True
|
|
109
|
+
self.direct_text = ""
|
|
110
|
+
if tag_contents:
|
|
111
|
+
self.__set_direct_text(tag_contents)
|
|
112
|
+
self.has_direct_text = bool(self.direct_text)
|
|
107
113
|
self.visited_color = {
|
|
108
114
|
"value": "",
|
|
109
115
|
"sheet": "",
|
|
@@ -164,6 +170,16 @@ class Element(object):
|
|
|
164
170
|
if parent:
|
|
165
171
|
self.ancestors.append(self.parent)
|
|
166
172
|
|
|
173
|
+
def __set_direct_text(self, contents: list):
|
|
174
|
+
"""get only text not in a tag"""
|
|
175
|
+
text = ""
|
|
176
|
+
for i in contents:
|
|
177
|
+
if isinstance(i, NavigableString):
|
|
178
|
+
new_text = str(i)
|
|
179
|
+
if new_text != "\n":
|
|
180
|
+
text += new_text.strip()
|
|
181
|
+
self.direct_text = text
|
|
182
|
+
|
|
167
183
|
def __set_link_color(self):
|
|
168
184
|
if self.name == "a":
|
|
169
185
|
self.color["value"] = default_link_color
|
|
@@ -239,12 +255,6 @@ class Element(object):
|
|
|
239
255
|
self.visited_background["value"] = new_bg
|
|
240
256
|
self.visited_background["specificity"] = new_specificity
|
|
241
257
|
|
|
242
|
-
# Do we have a link selector?
|
|
243
|
-
is_link_selector = css.is_link_selector(selector)
|
|
244
|
-
if is_link_selector:
|
|
245
|
-
print("We have a link selector in ammend_color_styles")
|
|
246
|
-
print("I suppose we should check specificity")
|
|
247
|
-
|
|
248
258
|
def ammend_bg_color(
|
|
249
259
|
self,
|
|
250
260
|
bg_color: str,
|
|
@@ -335,11 +345,6 @@ class Element(object):
|
|
|
335
345
|
hexc: the hex code for the color value.
|
|
336
346
|
hexbg: the hex code for the background color.
|
|
337
347
|
"""
|
|
338
|
-
bg_gradient = color.is_gradient(hexbg)
|
|
339
|
-
c_gradient = color.is_gradient(hexc)
|
|
340
|
-
if bg_gradient or c_gradient:
|
|
341
|
-
# worst contrast ratio takes the hit
|
|
342
|
-
print("let's process this")
|
|
343
348
|
link_contrast = color.get_color_contrast_report(hexc, hexbg)
|
|
344
349
|
link_ratio = color.contrast_ratio(hexc, hexbg)
|
|
345
350
|
|
|
@@ -364,9 +369,6 @@ class Element(object):
|
|
|
364
369
|
hexc: the hex code for the color value.
|
|
365
370
|
hexbg: the hex code for the background color.
|
|
366
371
|
"""
|
|
367
|
-
has_gradient = color.is_gradient(hexc) or color.is_gradient(hexbg)
|
|
368
|
-
if has_gradient:
|
|
369
|
-
print()
|
|
370
372
|
link_contrast = color.get_color_contrast_report(hexc, hexbg)
|
|
371
373
|
link_ratio = color.contrast_ratio(hexc, hexbg)
|
|
372
374
|
|
|
@@ -565,8 +567,13 @@ class Element(object):
|
|
|
565
567
|
targets = True
|
|
566
568
|
break
|
|
567
569
|
if selector_type == "id_selector":
|
|
570
|
+
# NOTE: testing has not uncovered this state
|
|
571
|
+
# we're keeping it here just in case
|
|
568
572
|
if "id" in self.attributes.keys():
|
|
569
|
-
|
|
573
|
+
for val in self.attributes.get("id"):
|
|
574
|
+
if val == selector[1:]:
|
|
575
|
+
targets = True
|
|
576
|
+
break
|
|
570
577
|
return targets
|
|
571
578
|
|
|
572
579
|
def been_applied(self, property: str) -> bool:
|
|
@@ -673,6 +680,7 @@ class Element(object):
|
|
|
673
680
|
self.background_color["value"] = val
|
|
674
681
|
self.background_color["sheet"] = filename
|
|
675
682
|
self.background_color["applied_by"] = "context"
|
|
683
|
+
self.background_color["selector"] = selector
|
|
676
684
|
|
|
677
685
|
|
|
678
686
|
class CSSAppliedTree:
|
|
@@ -758,7 +766,7 @@ class CSSAppliedTree:
|
|
|
758
766
|
parent_bg = root.background_color.get("value")
|
|
759
767
|
body = Element(
|
|
760
768
|
"body",
|
|
761
|
-
parent=
|
|
769
|
+
parent=["html", root_id, parent_bg],
|
|
762
770
|
parent_size=root_font_size,
|
|
763
771
|
)
|
|
764
772
|
body.parent_size = body.font_size
|
|
@@ -792,40 +800,58 @@ class CSSAppliedTree:
|
|
|
792
800
|
continue
|
|
793
801
|
tag_children = tag.contents
|
|
794
802
|
tag_attributes = tag.attrs
|
|
795
|
-
parent =
|
|
803
|
+
parent = [element.name, id(element)]
|
|
796
804
|
new_element = Element(
|
|
797
805
|
tag_name,
|
|
798
806
|
attributes=tag_attributes,
|
|
799
807
|
parent=parent,
|
|
800
808
|
parent_size=element.font_size,
|
|
801
809
|
ancestors=element.ancestors,
|
|
810
|
+
tag_contents=tag.contents,
|
|
802
811
|
)
|
|
803
812
|
self.__adjust_font_size(new_element)
|
|
804
813
|
element.children.append(new_element)
|
|
805
|
-
for
|
|
806
|
-
if
|
|
807
|
-
their_kids =
|
|
808
|
-
kids_attributes =
|
|
814
|
+
for item in tag_children:
|
|
815
|
+
if item and not isinstance(item, str):
|
|
816
|
+
their_kids = item.contents
|
|
817
|
+
kids_attributes = item.attrs
|
|
809
818
|
if kids_attributes:
|
|
810
819
|
kid_element = Element(
|
|
811
|
-
|
|
820
|
+
item.name,
|
|
812
821
|
attributes=kids_attributes,
|
|
813
|
-
parent=
|
|
822
|
+
parent=[new_element.name, id(new_element)],
|
|
814
823
|
parent_size=new_element.font_size,
|
|
815
824
|
ancestors=new_element.ancestors,
|
|
825
|
+
tag_contents=item.contents,
|
|
816
826
|
)
|
|
817
827
|
else:
|
|
818
828
|
kid_element = Element(
|
|
819
|
-
|
|
820
|
-
parent=
|
|
829
|
+
item.name,
|
|
830
|
+
parent=[new_element.name, id(new_element)],
|
|
821
831
|
parent_size=new_element.font_size,
|
|
822
832
|
ancestors=new_element.ancestors,
|
|
833
|
+
tag_contents=item.contents,
|
|
823
834
|
)
|
|
824
835
|
self.__adjust_font_size(kid_element)
|
|
825
836
|
new_element.children.append(kid_element)
|
|
826
837
|
self.__get_children(kid_element, their_kids)
|
|
827
838
|
return
|
|
828
839
|
|
|
840
|
+
def extract_selectors(self, element):
|
|
841
|
+
"""Get background color and color selectors"""
|
|
842
|
+
bg_data = ""
|
|
843
|
+
col_data = ""
|
|
844
|
+
if isinstance(element, Element):
|
|
845
|
+
if element.background_color.get("selector"):
|
|
846
|
+
bg_color = element.background_color.get("value")
|
|
847
|
+
bg_selector = element.background_color.get("selector")
|
|
848
|
+
bg_data = f"bg: {bg_color} selector: {bg_selector}"
|
|
849
|
+
if element.color.get("selector"):
|
|
850
|
+
color = element.color.get("value")
|
|
851
|
+
col_selector = element.color.get("selector")
|
|
852
|
+
col_data = f"color: {color} selector: {col_selector}"
|
|
853
|
+
return bg_data, col_data
|
|
854
|
+
|
|
829
855
|
def __adjust_largeness(self) -> None:
|
|
830
856
|
"""sets the is_large property based on whether it's bold or not
|
|
831
857
|
and the computed font size.
|
|
@@ -936,6 +962,7 @@ class CSSAppliedTree:
|
|
|
936
962
|
filename: str,
|
|
937
963
|
applied_by="default",
|
|
938
964
|
selector_src="",
|
|
965
|
+
parent=None,
|
|
939
966
|
) -> None:
|
|
940
967
|
"""recursively adjust color where necessary to all elements that
|
|
941
968
|
apply.
|
|
@@ -952,6 +979,22 @@ class CSSAppliedTree:
|
|
|
952
979
|
element: the element in question.
|
|
953
980
|
ruleset: the ruleset we want to apply.
|
|
954
981
|
filename: the file where the styles came from."""
|
|
982
|
+
# If there is a parent, it's a good time to adjust the direct ancestor
|
|
983
|
+
if parent:
|
|
984
|
+
bg_sel, col_sel = self.extract_selectors(parent)
|
|
985
|
+
direct_ancestor = element.ancestors[-1]
|
|
986
|
+
if len(direct_ancestor) == 2:
|
|
987
|
+
direct_ancestor.append("")
|
|
988
|
+
if len(direct_ancestor) == 3:
|
|
989
|
+
direct_ancestor.append(bg_sel)
|
|
990
|
+
direct_ancestor.append(col_sel)
|
|
991
|
+
elif len(direct_ancestor) == 4:
|
|
992
|
+
direct_ancestor[-1] = bg_sel
|
|
993
|
+
direct_ancestor.append(col_sel)
|
|
994
|
+
elif len(direct_ancestor) == 5:
|
|
995
|
+
direct_ancestor[-2] = bg_sel
|
|
996
|
+
direct_ancestor[-1] = col_sel
|
|
997
|
+
|
|
955
998
|
# Adjust colors if necessary - if there was a change,
|
|
956
999
|
# adjust colors for children
|
|
957
1000
|
selector = list(ruleset.keys())[0]
|
|
@@ -1000,32 +1043,6 @@ class CSSAppliedTree:
|
|
|
1000
1043
|
self.__adjust_child_colors(
|
|
1001
1044
|
child, ruleset, filename, element_name
|
|
1002
1045
|
)
|
|
1003
|
-
elif is_background_prop:
|
|
1004
|
-
# background - if not set directly - apply by context
|
|
1005
|
-
value = declaration.get("background-color")
|
|
1006
|
-
if not value:
|
|
1007
|
-
value = declaration.get("background")
|
|
1008
|
-
if property == "background-image":
|
|
1009
|
-
value = declaration.get("background-image")
|
|
1010
|
-
|
|
1011
|
-
# adjust if value is not the same as current value
|
|
1012
|
-
applied_by = element.background_color.get("applied_by")
|
|
1013
|
-
current_value = element.background_color.get("value")
|
|
1014
|
-
time_to_adjust = (
|
|
1015
|
-
applied_by == "default"
|
|
1016
|
-
or applied_by == "context"
|
|
1017
|
-
and value != current_value
|
|
1018
|
-
)
|
|
1019
|
-
# But only if the value was applied to an ancestor
|
|
1020
|
-
# For example, a th is not a parent of a td
|
|
1021
|
-
applied_to_ancestor = False
|
|
1022
|
-
for ancestor in element.ancestors:
|
|
1023
|
-
ancestor_tag = ancestor[0]
|
|
1024
|
-
if ancestor_tag == selector_src:
|
|
1025
|
-
applied_to_ancestor = True
|
|
1026
|
-
time_to_adjust = time_to_adjust and applied_to_ancestor
|
|
1027
|
-
if time_to_adjust:
|
|
1028
|
-
print("Wait")
|
|
1029
1046
|
can_inherit = self.can_inherit_styles(element, selector, ruleset)
|
|
1030
1047
|
if can_inherit and applies:
|
|
1031
1048
|
declaration = ruleset.get(selector)
|
|
@@ -1034,7 +1051,7 @@ class CSSAppliedTree:
|
|
|
1034
1051
|
selector, declaration.copy(), new_specificity, filename
|
|
1035
1052
|
)
|
|
1036
1053
|
for child in element.children:
|
|
1037
|
-
self.__adjust_colors(child, ruleset, filename)
|
|
1054
|
+
self.__adjust_colors(child, ruleset, filename, parent=element)
|
|
1038
1055
|
|
|
1039
1056
|
def __adjust_child_colors(
|
|
1040
1057
|
self,
|
|
@@ -1060,6 +1077,8 @@ class CSSAppliedTree:
|
|
|
1060
1077
|
filename: name of the file where the styles came from
|
|
1061
1078
|
applied_from: the name of the element that was targetted
|
|
1062
1079
|
"""
|
|
1080
|
+
# First adjust direct ancestor
|
|
1081
|
+
|
|
1063
1082
|
# Adjust colors if necessary - if there was a change,
|
|
1064
1083
|
# adjust colors for children
|
|
1065
1084
|
selector = list(ruleset.keys())[0]
|
|
@@ -1607,9 +1626,10 @@ def update_contrast_results(
|
|
|
1607
1626
|
def get_color_contrast_details(tree: CSSAppliedTree, rating="AAA") -> list:
|
|
1608
1627
|
"""returns a list of all failures or a pass based on rating
|
|
1609
1628
|
|
|
1610
|
-
We'll create a recursive inner nested function to traverse the DOM. We
|
|
1611
|
-
target
|
|
1612
|
-
|
|
1629
|
+
We'll create a recursive inner nested function to traverse the DOM. We
|
|
1630
|
+
will target all elements, and then report on number of errors triggered
|
|
1631
|
+
by each selector to help avoid overwhelming the user with too many
|
|
1632
|
+
errors.
|
|
1613
1633
|
|
|
1614
1634
|
Args:
|
|
1615
1635
|
tree: the cascade tree for a single file.
|
|
@@ -1624,68 +1644,183 @@ def get_color_contrast_details(tree: CSSAppliedTree, rating="AAA") -> list:
|
|
|
1624
1644
|
elements = tree.children
|
|
1625
1645
|
|
|
1626
1646
|
def check_element(
|
|
1627
|
-
el: Element, rating: str, filename: str
|
|
1647
|
+
el: Element, rating: str, filename: str, parent_sel=None
|
|
1628
1648
|
) -> Union[list, None]:
|
|
1629
1649
|
children = el.children
|
|
1630
|
-
msg = ""
|
|
1631
|
-
applied_directly = (
|
|
1632
|
-
el.background_color.get("applied_by") == "directly"
|
|
1633
|
-
or el.color.get("applied_by") == "directly"
|
|
1634
|
-
)
|
|
1635
|
-
if applied_directly or el.name == "body":
|
|
1636
|
-
is_gradient = el.background_color.get("is_gradient")
|
|
1637
|
-
if is_gradient:
|
|
1638
|
-
# get worst case
|
|
1639
|
-
worst_case = el.background_color.get("worst_contrast")
|
|
1640
|
-
worst_results = worst_case.get("results")
|
|
1641
|
-
fails = "fail" in worst_results[:5]
|
|
1642
|
-
if fails:
|
|
1643
|
-
selector = el.background_color.get("selector")
|
|
1644
|
-
if el.is_large:
|
|
1645
|
-
size = "Large"
|
|
1646
|
-
else:
|
|
1647
|
-
size = "Normal"
|
|
1648
|
-
msg = f"{el.name} tag (selector: {selector}) fails color "
|
|
1649
|
-
msg += f"contrast at {size} {rating}."
|
|
1650
|
-
results.append(msg)
|
|
1651
|
-
return
|
|
1652
|
-
contrast_data = el.contrast_data
|
|
1653
|
-
if el.is_large:
|
|
1654
|
-
if rating == "AAA":
|
|
1655
|
-
passes = contrast_data.get("large_aaa")
|
|
1656
|
-
else:
|
|
1657
|
-
passes = contrast_data.get("large_aa")
|
|
1658
|
-
else:
|
|
1659
|
-
if rating == "AAA":
|
|
1660
|
-
passes = contrast_data.get("normal_aaa")
|
|
1661
|
-
else:
|
|
1662
|
-
passes = contrast_data.get("normal_aa")
|
|
1663
1650
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1651
|
+
is_gradient = el.background_color.get("is_gradient")
|
|
1652
|
+
|
|
1653
|
+
# if the gradient was not directly applied, we won't get is_gradient
|
|
1654
|
+
# since it might be applied contextually, we should double check
|
|
1655
|
+
if not is_gradient:
|
|
1656
|
+
bg_value = el.background_color.get("value")
|
|
1657
|
+
is_gradient = "gradient" in bg_value
|
|
1658
|
+
|
|
1659
|
+
# we should recalculate contrast if it didn't register
|
|
1660
|
+
if is_gradient:
|
|
1661
|
+
fg_color = el.color.get("value")
|
|
1662
|
+
bg_color = el.background_color.get("value")
|
|
1663
|
+
contrast_details = color.get_color_contrast_with_gradients(
|
|
1664
|
+
fg_color, bg_color, el.ancestors
|
|
1665
|
+
)
|
|
1666
|
+
worst_case = {
|
|
1667
|
+
"has_alpha": False,
|
|
1668
|
+
"computed_value": "",
|
|
1669
|
+
"contrast_ratio": 0.0,
|
|
1670
|
+
"results": "",
|
|
1671
|
+
}
|
|
1672
|
+
contrast, fg_color = contrast_details[0][:2]
|
|
1673
|
+
composite_bg, bg_has_alpha = contrast_details[0][3:]
|
|
1674
|
+
worst_case["has_alpha"] = bg_has_alpha
|
|
1675
|
+
worst_case["computed_value"] = composite_bg
|
|
1676
|
+
worst_case["contrast_ratio"] = contrast
|
|
1677
|
+
hex1 = color.get_hex(fg_color)
|
|
1678
|
+
hex2 = color.get_hex(composite_bg)
|
|
1679
|
+
passes_contrast = color.get_contrast_results_from_ratio(
|
|
1680
|
+
contrast, el.font_size, el.is_bold
|
|
1681
|
+
)
|
|
1682
|
+
worst_case["results"] = passes_contrast
|
|
1683
|
+
el.background_color["worst_contrast"] = worst_case
|
|
1684
|
+
if is_gradient:
|
|
1685
|
+
# get worst case
|
|
1686
|
+
worst_case = el.background_color.get("worst_contrast")
|
|
1687
|
+
worst_results = worst_case.get("results")
|
|
1688
|
+
fails = "fail" in worst_results[:5]
|
|
1689
|
+
if "conditional pass" in worst_results:
|
|
1690
|
+
if rating not in worst_results:
|
|
1691
|
+
fails = True
|
|
1692
|
+
if fails:
|
|
1693
|
+
selector = el.background_color.get("selector")
|
|
1666
1694
|
if el.is_large:
|
|
1667
1695
|
size = "Large"
|
|
1668
1696
|
else:
|
|
1669
1697
|
size = "Normal"
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1698
|
+
if el.has_direct_text:
|
|
1699
|
+
if not selector and parent_sel:
|
|
1700
|
+
selector = parent_sel
|
|
1701
|
+
results.append((filename, selector, size, rating))
|
|
1702
|
+
|
|
1703
|
+
# NOTE: it's possible we won't be able to get a selector
|
|
1704
|
+
# in the future. If so, add an else block and deal with it
|
|
1705
|
+
|
|
1706
|
+
else:
|
|
1707
|
+
# We have a failure, but without direct text
|
|
1708
|
+
# We need to verify whether the failure is passed
|
|
1709
|
+
# on to the children or not
|
|
1710
|
+
parent_bg = el.background_color
|
|
1711
|
+
parent_bg_selector = parent_bg.get("selector")
|
|
1712
|
+
parent_col = el.color
|
|
1713
|
+
parent_col_selector = parent_col.get("selector")
|
|
1714
|
+
for child in children:
|
|
1715
|
+
child_bg_val = child.background_color.get("value")
|
|
1716
|
+
child_col_val = child.color.get("value")
|
|
1717
|
+
|
|
1718
|
+
# Check to see if the values in the child are the
|
|
1719
|
+
# same as with the parent, in which case, if parent
|
|
1720
|
+
# fails, then child should too
|
|
1721
|
+
if child_bg_val == parent_bg.get(
|
|
1722
|
+
"value"
|
|
1723
|
+
) and child_col_val == parent_col.get("value"):
|
|
1724
|
+
selector = parent_bg_selector
|
|
1725
|
+
if not selector:
|
|
1726
|
+
selector = parent_col_selector
|
|
1727
|
+
|
|
1728
|
+
# double check to make sure child is same size or
|
|
1729
|
+
# smaller than parent (size is parent's size)
|
|
1730
|
+
if size == "Normal" and child.is_large:
|
|
1731
|
+
hex1 = color.get_hex(child_bg_val)
|
|
1732
|
+
|
|
1733
|
+
# In case the bg color is a gradient, we
|
|
1734
|
+
# need to pull from parent's bg_data
|
|
1735
|
+
if not hex1:
|
|
1736
|
+
css_color = parent_bg.get("computed_value")
|
|
1737
|
+
if css_color:
|
|
1738
|
+
hex1 = color.get_hex(css_color)
|
|
1739
|
+
else:
|
|
1740
|
+
parent_bg_val = worst_case.get("value")
|
|
1741
|
+
hex1 = color.get_hex(parent_bg_val)
|
|
1742
|
+
hex2 = color.get_hex(child_col_val)
|
|
1743
|
+
child_contrast = (
|
|
1744
|
+
color.get_color_contrast_report(hex1, hex2)
|
|
1745
|
+
)
|
|
1746
|
+
child_sizing = f"Large {rating}"
|
|
1747
|
+
passes = "Fail" != child_contrast.get(
|
|
1748
|
+
child_sizing
|
|
1749
|
+
)
|
|
1750
|
+
if not passes:
|
|
1751
|
+
results.append(
|
|
1752
|
+
(filename, selector, size, rating)
|
|
1753
|
+
)
|
|
1754
|
+
else:
|
|
1755
|
+
results.append(
|
|
1756
|
+
(filename, selector, size, rating)
|
|
1757
|
+
)
|
|
1758
|
+
|
|
1759
|
+
# TODO: add tests to look for a parent with normal
|
|
1760
|
+
# size and a child that's large - make an else block
|
|
1761
|
+
return
|
|
1762
|
+
contrast_data = el.contrast_data
|
|
1763
|
+
if el.is_large:
|
|
1764
|
+
if rating == "AAA":
|
|
1765
|
+
passes = contrast_data.get("large_aaa")
|
|
1766
|
+
else:
|
|
1767
|
+
passes = contrast_data.get("large_aa")
|
|
1768
|
+
else:
|
|
1769
|
+
if rating == "AAA":
|
|
1770
|
+
passes = contrast_data.get("normal_aaa")
|
|
1771
|
+
else:
|
|
1772
|
+
passes = contrast_data.get("normal_aa")
|
|
1773
|
+
|
|
1774
|
+
# only add results if failure
|
|
1775
|
+
if not passes:
|
|
1776
|
+
if el.is_large:
|
|
1777
|
+
size = "Large"
|
|
1778
|
+
else:
|
|
1779
|
+
size = "Normal"
|
|
1780
|
+
selector = el.background_color.get("selector")
|
|
1781
|
+
if not selector:
|
|
1782
|
+
selector = el.color.get("selector")
|
|
1783
|
+
|
|
1784
|
+
# if there still isn't a selector, get the parent selector
|
|
1785
|
+
if not selector:
|
|
1786
|
+
selector = parent_sel
|
|
1787
|
+
|
|
1788
|
+
# and only if the element has direct text (experimental)
|
|
1789
|
+
if el.has_direct_text:
|
|
1790
|
+
results.append((filename, selector, size, rating))
|
|
1676
1791
|
|
|
1677
1792
|
if not children:
|
|
1678
1793
|
return
|
|
1679
1794
|
else:
|
|
1680
1795
|
for kid in children:
|
|
1681
|
-
|
|
1796
|
+
if el.background_color.get("selector"):
|
|
1797
|
+
parent_selector = el.background_color.get("selector")
|
|
1798
|
+
else:
|
|
1799
|
+
parent_selector = el.color.get("selector")
|
|
1800
|
+
if not parent_selector:
|
|
1801
|
+
parent_selector = kid.ancestors[-1][3]
|
|
1802
|
+
if not parent_selector:
|
|
1803
|
+
parent_selector = kid.ancestors[-1][-1]
|
|
1804
|
+
if parent_selector:
|
|
1805
|
+
parent_selector = parent_selector.split("selector:")[
|
|
1806
|
+
-1
|
|
1807
|
+
]
|
|
1808
|
+
check_element(kid, rating, filename, parent_selector)
|
|
1682
1809
|
|
|
1683
1810
|
for element in elements:
|
|
1684
1811
|
check_element(element, rating, filename)
|
|
1685
1812
|
if results:
|
|
1686
1813
|
adjusted = []
|
|
1687
1814
|
for result in results:
|
|
1688
|
-
|
|
1815
|
+
count = results.count(result)
|
|
1816
|
+
file, selector, size, rating = result
|
|
1817
|
+
msg = f"fail: in {file}, selector: {selector} triggered "
|
|
1818
|
+
if count > 1:
|
|
1819
|
+
msg += f"{count} contrast errors for {size} {rating}."
|
|
1820
|
+
else:
|
|
1821
|
+
msg += f"contrast error for {size} {rating}."
|
|
1822
|
+
if msg not in adjusted:
|
|
1823
|
+
adjusted.append(msg)
|
|
1689
1824
|
results = adjusted
|
|
1690
1825
|
else:
|
|
1691
1826
|
# It's a success! Let's let them know
|
|
@@ -1716,8 +1851,6 @@ def get_color_contrast_report(dir_path: str, rating="AAA") -> list:
|
|
|
1716
1851
|
for item in report:
|
|
1717
1852
|
if item not in results:
|
|
1718
1853
|
results.append(item)
|
|
1719
|
-
# Do one more pass on hyperlinks and double check contrast
|
|
1720
|
-
print(results)
|
|
1721
1854
|
return results
|
|
1722
1855
|
|
|
1723
1856
|
|
|
@@ -808,10 +808,12 @@ def get_color_contrast_with_gradients(
|
|
|
808
808
|
bg_color,
|
|
809
809
|
]
|
|
810
810
|
composite_color = ""
|
|
811
|
+
|
|
811
812
|
# check all permutations of color combinations for contrast results
|
|
812
813
|
for foreground in foreground_colors:
|
|
813
814
|
for background in bg_colors:
|
|
814
815
|
composite_color = ""
|
|
816
|
+
|
|
815
817
|
# check for background alpha transparency
|
|
816
818
|
bg_has_alpha = has_alpha_channel(background)
|
|
817
819
|
if bg_has_alpha:
|
|
@@ -819,11 +821,17 @@ def get_color_contrast_with_gradients(
|
|
|
819
821
|
for i in range(len(ancestors) - 1, -1, -1):
|
|
820
822
|
ancestor = ancestors[i]
|
|
821
823
|
if len(ancestor) > 2:
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
824
|
+
if ancestor[2]:
|
|
825
|
+
base_color = ancestor[2]
|
|
826
|
+
composite_color = blend_alpha(
|
|
827
|
+
base_color, background
|
|
828
|
+
)
|
|
829
|
+
container_bg = ancestor[2]
|
|
830
|
+
elif "background:" in ancestor[-1]:
|
|
831
|
+
input("Get something in here")
|
|
825
832
|
if not composite_color:
|
|
826
833
|
composite_color = blend_alpha(container_bg, background)
|
|
834
|
+
|
|
827
835
|
# convert each to hex
|
|
828
836
|
hex1 = to_hex(foreground)
|
|
829
837
|
if composite_color:
|
|
@@ -7,6 +7,7 @@ from typing import Union
|
|
|
7
7
|
|
|
8
8
|
from file_clerk import clerk
|
|
9
9
|
|
|
10
|
+
from webcode_tk import cascade_tools
|
|
10
11
|
from webcode_tk import color_keywords as keyword
|
|
11
12
|
from webcode_tk import color_tools
|
|
12
13
|
from webcode_tk import html_tools
|
|
@@ -2469,6 +2470,8 @@ def get_heading_color_report(project_dir: str) -> list:
|
|
|
2469
2470
|
report: a list of files and a pass or fail message for each."""
|
|
2470
2471
|
report = []
|
|
2471
2472
|
header_re = regex_patterns.get("header_selector")
|
|
2473
|
+
|
|
2474
|
+
# make sure we have a trailing slash separator
|
|
2472
2475
|
if project_dir[-1] != "/":
|
|
2473
2476
|
project_dir += "/"
|
|
2474
2477
|
all_file_data = get_all_project_stylesheets(project_dir)
|
|
@@ -2477,13 +2480,12 @@ def get_heading_color_report(project_dir: str) -> list:
|
|
|
2477
2480
|
filepath = project_dir + filename
|
|
2478
2481
|
all_color_rules = get_all_color_rules(filepath)
|
|
2479
2482
|
header_selectors = []
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2483
|
+
|
|
2484
|
+
# Look through all selectors and their values
|
|
2485
|
+
# if selector is a header selector, then check color
|
|
2486
|
+
for sel, val in all_color_rules.items():
|
|
2487
|
+
is_header_selector = re.findall(header_re, sel)
|
|
2484
2488
|
if is_header_selector:
|
|
2485
|
-
# we have a header selector
|
|
2486
|
-
# we only need to check color
|
|
2487
2489
|
color_data = val
|
|
2488
2490
|
color_value = color_data.get("color")
|
|
2489
2491
|
bg_value = color_data.get("background-color")
|
|
@@ -2537,7 +2539,90 @@ def get_project_color_contrast_report(project_dir: str, level="AAA") -> list:
|
|
|
2537
2539
|
return report
|
|
2538
2540
|
|
|
2539
2541
|
|
|
2542
|
+
def get_element_rulesets(project_dir: str, element: str) -> list:
|
|
2543
|
+
rulesets = []
|
|
2544
|
+
styles_by_files = get_styles_by_html_files(project_dir)
|
|
2545
|
+
for file in styles_by_files:
|
|
2546
|
+
elements = None
|
|
2547
|
+
filepath = file.get("file")
|
|
2548
|
+
filename = clerk.get_file_name(filepath)
|
|
2549
|
+
sheets = file.get("stylesheets")
|
|
2550
|
+
elements = html_tools.get_elements(element, filepath)
|
|
2551
|
+
for sheet in sheets:
|
|
2552
|
+
for ruleset in sheet.rulesets:
|
|
2553
|
+
sel = ruleset.selector
|
|
2554
|
+
selector_applies = cascade_tools.does_selector_apply(
|
|
2555
|
+
elements[0], sel
|
|
2556
|
+
)
|
|
2557
|
+
if selector_applies:
|
|
2558
|
+
rulesets.append((filename, ruleset))
|
|
2559
|
+
|
|
2560
|
+
return rulesets
|
|
2561
|
+
|
|
2562
|
+
|
|
2563
|
+
def get_properties_applied_report(project_dir: str, goals: dict) -> list:
|
|
2564
|
+
"""returns a report on any elements that fail to have a property applied
|
|
2565
|
+
|
|
2566
|
+
Args:
|
|
2567
|
+
project_dir: the path to the project folder
|
|
2568
|
+
goals: a dictionary of elements and the properties expected to be
|
|
2569
|
+
present
|
|
2570
|
+
|
|
2571
|
+
Returns:
|
|
2572
|
+
report: a list of pass or fail messages that indicates the file, the
|
|
2573
|
+
element, and the missing property.
|
|
2574
|
+
"""
|
|
2575
|
+
report = []
|
|
2576
|
+
elements = list(goals.keys())
|
|
2577
|
+
for element in elements:
|
|
2578
|
+
properties_found = []
|
|
2579
|
+
properties = goals.get(element)
|
|
2580
|
+
element_rulesets = get_element_rulesets(project_dir, element)
|
|
2581
|
+
|
|
2582
|
+
# check all rulesets for a given file to see which rulesets have
|
|
2583
|
+
# one or more properties we are checking for
|
|
2584
|
+
for file, rulesets in element_rulesets:
|
|
2585
|
+
found_properties_remaining = list(properties)
|
|
2586
|
+
for expected_property in properties:
|
|
2587
|
+
declarations = rulesets.declaration_block.declarations
|
|
2588
|
+
|
|
2589
|
+
# check all declarations to see if any properties
|
|
2590
|
+
# are a match and pull it from the list of remaining
|
|
2591
|
+
# properties.
|
|
2592
|
+
for declaration in declarations:
|
|
2593
|
+
element_property = declaration.property
|
|
2594
|
+
if expected_property == element_property:
|
|
2595
|
+
properties_found.append(element_property)
|
|
2596
|
+
found_properties_remaining.remove(element_property)
|
|
2597
|
+
break
|
|
2598
|
+
|
|
2599
|
+
# If there are any properties left, it's a fail
|
|
2600
|
+
if found_properties_remaining:
|
|
2601
|
+
count = len(found_properties_remaining)
|
|
2602
|
+
msg = f"fail: in {file}, the {element} tag does not apply "
|
|
2603
|
+
if count == 1:
|
|
2604
|
+
msg += f"1 property: {found_properties_remaining[0]}."
|
|
2605
|
+
else:
|
|
2606
|
+
msg += f"{count} properties: "
|
|
2607
|
+
msg += f"{found_properties_remaining}."
|
|
2608
|
+
|
|
2609
|
+
# all properties were accounted for (none remaining)
|
|
2610
|
+
else:
|
|
2611
|
+
msg = f"pass: in {file}, the {element} tag applies all "
|
|
2612
|
+
msg += "required properties."
|
|
2613
|
+
report.append(msg)
|
|
2614
|
+
return report
|
|
2615
|
+
|
|
2616
|
+
|
|
2540
2617
|
if __name__ == "__main__":
|
|
2618
|
+
project_folder = "tests/test_files/cascade_complexities"
|
|
2619
|
+
goals = {
|
|
2620
|
+
"figure": ("margin", "padding", "border", "float"),
|
|
2621
|
+
}
|
|
2622
|
+
report = get_properties_applied_report(project_folder, goals)
|
|
2623
|
+
rulesets = get_element_rulesets(
|
|
2624
|
+
"tests/test_files/cascade_complexities", "figure"
|
|
2625
|
+
)
|
|
2541
2626
|
insane_gradient = """
|
|
2542
2627
|
-moz-radial-gradient(0% 200%, ellipse cover,
|
|
2543
2628
|
rgba(143, 193, 242, 0.22) 10%,rgba(240, 205, 247,0) 40%),
|
|
@@ -22,9 +22,10 @@ dependent upon the font-family itself and require information from the
|
|
|
22
22
|
font-file or some other hidden calculation from the browser.
|
|
23
23
|
"""
|
|
24
24
|
import re
|
|
25
|
+
from typing import Any
|
|
25
26
|
from typing import Union
|
|
26
27
|
|
|
27
|
-
from webcode_tk
|
|
28
|
+
from webcode_tk import css_tools
|
|
28
29
|
|
|
29
30
|
absolute_keyword_regex = (
|
|
30
31
|
r"\b(?:xx-small|x-small|small(?!-caps)|medium|large|x-large|"
|
|
@@ -76,7 +77,7 @@ FONT_NON_SIZE_NON_FAMILY_KEYWORDS = (
|
|
|
76
77
|
)
|
|
77
78
|
|
|
78
79
|
|
|
79
|
-
def get_absolute_keyword_values(css_code:
|
|
80
|
+
def get_absolute_keyword_values(css_code: any) -> list:
|
|
80
81
|
"""returns a list of all keyword values in CSS
|
|
81
82
|
|
|
82
83
|
To be safe, we should remove all selectors using the curly brackets
|
|
@@ -85,7 +86,7 @@ def get_absolute_keyword_values(css_code: Union[str, Stylesheet]) -> list:
|
|
|
85
86
|
Args:
|
|
86
87
|
css_code: the CSS styles in either string or Stylesheet format
|
|
87
88
|
"""
|
|
88
|
-
if isinstance(css_code, Stylesheet):
|
|
89
|
+
if isinstance(css_code, css_tools.Stylesheet):
|
|
89
90
|
styles = css_code.text
|
|
90
91
|
else:
|
|
91
92
|
styles = css_code
|
|
@@ -108,7 +109,7 @@ def get_absolute_keyword_values(css_code: Union[str, Stylesheet]) -> list:
|
|
|
108
109
|
return values
|
|
109
110
|
|
|
110
111
|
|
|
111
|
-
def get_numeric_fontsize_values(css_code:
|
|
112
|
+
def get_numeric_fontsize_values(css_code: Any) -> list:
|
|
112
113
|
"""returns a list of all numeric font size values.
|
|
113
114
|
|
|
114
115
|
This should work for any standard size value (em, px, %,
|
|
@@ -117,10 +118,10 @@ def get_numeric_fontsize_values(css_code: Union[str, Stylesheet]) -> list:
|
|
|
117
118
|
Args:
|
|
118
119
|
css_code: the CSS styles in either string or Stylesheet format.
|
|
119
120
|
"""
|
|
120
|
-
if isinstance(css_code,
|
|
121
|
-
code = css_code.text
|
|
122
|
-
else:
|
|
121
|
+
if isinstance(css_code, str):
|
|
123
122
|
code = css_code
|
|
123
|
+
else:
|
|
124
|
+
code = css_code.text
|
|
124
125
|
matches = re.findall(numeric_value_regex, code)
|
|
125
126
|
full_matches = []
|
|
126
127
|
for match in matches:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|