webcode-tk 0.7.11__tar.gz → 1.0.0__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-0.7.11 → webcode_tk-1.0.0}/PKG-INFO +1 -1
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/pyproject.toml +1 -1
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/__init__.py +1 -1
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/cascade_tools.py +138 -4
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/color_tools.py +48 -2
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/LICENSE.rst +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/README.rst +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/color_keywords.py +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/css_tools.py +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/font_tools.py +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/html_tools.py +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/sample_report.json +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/ux_tools.py +0 -0
- {webcode_tk-0.7.11 → webcode_tk-1.0.0}/webcode_tk/validator_tools.py +0 -0
|
@@ -5,12 +5,13 @@ inheritance and the cascade.
|
|
|
5
5
|
The goal is to use the DOM, inheritance, and the cascade, to determine
|
|
6
6
|
all styles applied to every element on the page.
|
|
7
7
|
|
|
8
|
-
With that, it scans each stylesheet and applies
|
|
8
|
+
With that, it scans each stylesheet and applies each style one at a time
|
|
9
9
|
to all elements and their children (if applicable) of a page.
|
|
10
10
|
"""
|
|
11
11
|
import ctypes
|
|
12
12
|
import re
|
|
13
13
|
from collections import abc
|
|
14
|
+
from typing import Union
|
|
14
15
|
|
|
15
16
|
from file_clerk import clerk
|
|
16
17
|
|
|
@@ -73,12 +74,14 @@ class Element(object):
|
|
|
73
74
|
self.attributes = attributes
|
|
74
75
|
self.background_color = {
|
|
75
76
|
"value": "#ffffff",
|
|
77
|
+
"has_alpha": False,
|
|
78
|
+
"computed_value": "",
|
|
76
79
|
"sheet": "user-agent",
|
|
77
80
|
"selector": "",
|
|
78
81
|
"specificity": "000",
|
|
79
82
|
"applied_by": "context",
|
|
80
83
|
"is_gradient": False,
|
|
81
|
-
"
|
|
84
|
+
"gradient_colors": [],
|
|
82
85
|
}
|
|
83
86
|
self.color = {
|
|
84
87
|
"value": "#000000",
|
|
@@ -129,12 +132,14 @@ class Element(object):
|
|
|
129
132
|
"normal_aaa": True,
|
|
130
133
|
"normal_aa": True,
|
|
131
134
|
"large_aaa": True,
|
|
135
|
+
"large_aa": True,
|
|
132
136
|
}
|
|
133
137
|
self.visited_contrast_data = {
|
|
134
138
|
"ratio": 0.0,
|
|
135
139
|
"normal_aaa": False,
|
|
136
140
|
"normal_aa": False,
|
|
137
141
|
"large_aaa": False,
|
|
142
|
+
"large_aa": False,
|
|
138
143
|
}
|
|
139
144
|
self.__set_link_color()
|
|
140
145
|
self.get_contrast_data("default")
|
|
@@ -166,6 +171,7 @@ class Element(object):
|
|
|
166
171
|
# get relevant specificity
|
|
167
172
|
new_specificity = new_styles.get("specificity")
|
|
168
173
|
col_specificity = self.color.get("specificity")
|
|
174
|
+
|
|
169
175
|
# Check for overrides
|
|
170
176
|
color_has_override = new_specificity >= col_specificity
|
|
171
177
|
|
|
@@ -263,7 +269,12 @@ class Element(object):
|
|
|
263
269
|
col, bg, self.ancestors
|
|
264
270
|
)
|
|
265
271
|
# Set bg to composite color with lowest contrast ratio
|
|
266
|
-
bg = results[0][-
|
|
272
|
+
bg = results[0][-2]
|
|
273
|
+
has_alpha = results[0][-1]
|
|
274
|
+
if has_alpha:
|
|
275
|
+
self.background_color["has_alpha"] = True
|
|
276
|
+
self.background_color["computed_value"] = bg
|
|
277
|
+
|
|
267
278
|
hexc = color.get_hex(col)
|
|
268
279
|
hexbg = color.get_hex(bg)
|
|
269
280
|
self.__build_contrast_report(hexc, hexbg)
|
|
@@ -327,6 +338,9 @@ class Element(object):
|
|
|
327
338
|
self.visited_contrast_data["large_aaa"] = bool(
|
|
328
339
|
link_contrast.get("Large AAA") == "Pass"
|
|
329
340
|
)
|
|
341
|
+
self.visited_contrast_data["large_aa"] = bool(
|
|
342
|
+
link_contrast.get("Large AA") == "Pass"
|
|
343
|
+
)
|
|
330
344
|
|
|
331
345
|
def change_styles(
|
|
332
346
|
self,
|
|
@@ -554,6 +568,7 @@ class Element(object):
|
|
|
554
568
|
directly_targeted = self.directly_targets(selector)
|
|
555
569
|
previously_set = self.background_color.get("applied_by") == "directly"
|
|
556
570
|
bg_gradient = color.is_gradient(val)
|
|
571
|
+
|
|
557
572
|
# If directly, check specificity
|
|
558
573
|
if directly_targeted:
|
|
559
574
|
if previously_set:
|
|
@@ -562,13 +577,49 @@ class Element(object):
|
|
|
562
577
|
colors = []
|
|
563
578
|
if bg_gradient:
|
|
564
579
|
self.background_color["is_gradient"] = True
|
|
580
|
+
c = self.color.get("value")
|
|
581
|
+
details = color.get_color_contrast_with_gradients(
|
|
582
|
+
c, val, self.ancestors
|
|
583
|
+
)
|
|
565
584
|
colors = color.get_gradient_colors(val)
|
|
585
|
+
|
|
586
|
+
# Set gradient colors with highest and lowest contrast
|
|
587
|
+
self.background_color["best_contrast"] = {}
|
|
588
|
+
self.background_color["worst_contrast"] = {}
|
|
589
|
+
|
|
590
|
+
def process_gradient_data(details, bg_dict):
|
|
591
|
+
contrast_ratio = details[0]
|
|
592
|
+
size = self.font_size
|
|
593
|
+
is_bold = self.is_bold
|
|
594
|
+
results = color.get_contrast_results_from_ratio(
|
|
595
|
+
contrast_ratio, size, is_bold
|
|
596
|
+
)
|
|
597
|
+
has_alpha = details[-1]
|
|
598
|
+
if has_alpha:
|
|
599
|
+
bg_dict["has_alpha"] = has_alpha
|
|
600
|
+
self.background_color["has_alpha"] = True
|
|
601
|
+
self.background_color["computed_value"] = details[3]
|
|
602
|
+
bg_dict["computed_value"] = details[-2]
|
|
603
|
+
else:
|
|
604
|
+
bg_dict["value"] = details[2]
|
|
605
|
+
bg_dict["contrast_ratio"] = details[0]
|
|
606
|
+
bg_dict["results"] = results
|
|
607
|
+
|
|
608
|
+
best = details[-1]
|
|
609
|
+
worst = details[0]
|
|
610
|
+
process_gradient_data(
|
|
611
|
+
best, self.background_color["best_contrast"]
|
|
612
|
+
)
|
|
613
|
+
process_gradient_data(
|
|
614
|
+
worst, self.background_color["worst_contrast"]
|
|
615
|
+
)
|
|
566
616
|
self.background_color["gradient_colors"] = colors
|
|
567
617
|
# set color, specificity, and directly applied
|
|
568
618
|
self.background_color["value"] = val
|
|
569
619
|
self.background_color["sheet"] = filename
|
|
570
620
|
self.background_color["specificity"] = new_specificity
|
|
571
621
|
self.background_color["applied_by"] = "directly"
|
|
622
|
+
self.background_color["selector"] = selector
|
|
572
623
|
else:
|
|
573
624
|
if not previously_set:
|
|
574
625
|
self.background_color["value"] = val
|
|
@@ -1441,6 +1492,89 @@ def update_contrast_results(
|
|
|
1441
1492
|
return results
|
|
1442
1493
|
|
|
1443
1494
|
|
|
1495
|
+
def get_color_contrast_details(tree: CSSAppliedTree, rating="AAA") -> list:
|
|
1496
|
+
"""returns a list of all all failures or a pass based on rating
|
|
1497
|
+
|
|
1498
|
+
We'll create a recursive inner nested function to traverse the DOM. We will
|
|
1499
|
+
target only the styles that are applied directly so as to not overwhelm the
|
|
1500
|
+
user and allow fixing the error more easily.
|
|
1501
|
+
|
|
1502
|
+
Args:
|
|
1503
|
+
tree: the cascade tree for a single file.
|
|
1504
|
+
rating: the rating of contrast we check for (AAA or AA). The default
|
|
1505
|
+
is AAA.
|
|
1506
|
+
Returns:
|
|
1507
|
+
results"""
|
|
1508
|
+
results = []
|
|
1509
|
+
filename = tree.filename
|
|
1510
|
+
# Loop recursively through the tree
|
|
1511
|
+
elements = tree.children
|
|
1512
|
+
|
|
1513
|
+
def check_element(
|
|
1514
|
+
el: Element, rating: str, filename: str
|
|
1515
|
+
) -> Union[list, None]:
|
|
1516
|
+
children = el.children
|
|
1517
|
+
msg = ""
|
|
1518
|
+
applied_directly = (
|
|
1519
|
+
el.background_color.get("applied_by") == "directly"
|
|
1520
|
+
or el.color.get("applied_by") == "directly"
|
|
1521
|
+
)
|
|
1522
|
+
if applied_directly or el.name == "body":
|
|
1523
|
+
is_gradient = el.background_color.get("is_gradient")
|
|
1524
|
+
if is_gradient:
|
|
1525
|
+
# get worst case
|
|
1526
|
+
worst_case = el.background_color.get("worst_contrast")
|
|
1527
|
+
worst_results = worst_case.get("results")
|
|
1528
|
+
fails = "fail" in worst_results[:5]
|
|
1529
|
+
if fails:
|
|
1530
|
+
selector = el.background_color.get("selector")
|
|
1531
|
+
if el.is_large:
|
|
1532
|
+
size = "Large"
|
|
1533
|
+
else:
|
|
1534
|
+
size = "Normal"
|
|
1535
|
+
msg = f"{el.name} tag (selector: {selector}) fails color "
|
|
1536
|
+
msg += f"contrast at {size} {rating}."
|
|
1537
|
+
results.append(msg)
|
|
1538
|
+
return
|
|
1539
|
+
contrast_data = el.contrast_data
|
|
1540
|
+
if el.is_large:
|
|
1541
|
+
if rating == "AAA":
|
|
1542
|
+
passes = contrast_data.get("large_aaa")
|
|
1543
|
+
else:
|
|
1544
|
+
passes = contrast_data.get("large_aa")
|
|
1545
|
+
else:
|
|
1546
|
+
if rating == "AAA":
|
|
1547
|
+
passes = contrast_data.get("normal_aaa")
|
|
1548
|
+
else:
|
|
1549
|
+
passes = contrast_data.get("normal_aa")
|
|
1550
|
+
# only add results if failure
|
|
1551
|
+
if not passes:
|
|
1552
|
+
if el.is_large:
|
|
1553
|
+
size = "Large"
|
|
1554
|
+
else:
|
|
1555
|
+
size = "Normal"
|
|
1556
|
+
selector = el.background_color.get("selector")
|
|
1557
|
+
if not selector:
|
|
1558
|
+
selector = el.color.get("selector")
|
|
1559
|
+
msg = f"{el.name} tag (selector: {selector}) fails color "
|
|
1560
|
+
msg += f"contrast at {size} {rating}."
|
|
1561
|
+
results.append(msg)
|
|
1562
|
+
if not children:
|
|
1563
|
+
return
|
|
1564
|
+
else:
|
|
1565
|
+
for kid in children:
|
|
1566
|
+
check_element(kid, rating, filename)
|
|
1567
|
+
|
|
1568
|
+
for element in elements:
|
|
1569
|
+
check_element(element, rating, filename)
|
|
1570
|
+
if not results:
|
|
1571
|
+
# It's a success! Let's let them know
|
|
1572
|
+
msg = f"success: {filename} passes color contrast for {rating} Normal"
|
|
1573
|
+
msg += " and Large."
|
|
1574
|
+
results.append(msg)
|
|
1575
|
+
return results
|
|
1576
|
+
|
|
1577
|
+
|
|
1444
1578
|
if __name__ == "__main__":
|
|
1445
1579
|
project_path = "tests/test_files/attribute_selector_file"
|
|
1446
1580
|
project_path = "tests/test_files/large_project/"
|
|
@@ -1453,6 +1587,7 @@ if __name__ == "__main__":
|
|
|
1453
1587
|
sheet,
|
|
1454
1588
|
],
|
|
1455
1589
|
)
|
|
1590
|
+
results = get_color_contrast_details(gradient_tree, "AAA")
|
|
1456
1591
|
styles_by_html_files = css.get_styles_by_html_files(project_path)
|
|
1457
1592
|
for file in styles_by_html_files:
|
|
1458
1593
|
filepath = file.get("file")
|
|
@@ -1461,4 +1596,3 @@ if __name__ == "__main__":
|
|
|
1461
1596
|
results = {}
|
|
1462
1597
|
root = css_tree.children[0]
|
|
1463
1598
|
results = get_color_contrast_results(root, results)
|
|
1464
|
-
print(results)
|
|
@@ -78,6 +78,44 @@ def passes_color_contrast(level: str, hex1: str, hex2: str) -> bool:
|
|
|
78
78
|
return passes
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def get_contrast_results_from_ratio(
|
|
82
|
+
ratio: float, size: float, is_bold: bool
|
|
83
|
+
) -> str:
|
|
84
|
+
"""takes contrast, compares to map and returns results as a string
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
ratio: the contrast ratio.
|
|
88
|
+
size: calculated font size.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
str: whether it passes or not with details.
|
|
92
|
+
"""
|
|
93
|
+
# is the text large or not?
|
|
94
|
+
is_large = size >= 24 or is_bold and size >= 16.66
|
|
95
|
+
if is_large:
|
|
96
|
+
aaa_goal = contrast_ratio_map.get("Large AAA")
|
|
97
|
+
passes = ratio >= aaa_goal
|
|
98
|
+
if passes:
|
|
99
|
+
return "pass: Large AAA contrast rating"
|
|
100
|
+
aa_goal = contrast_ratio_map.get("Large AA")
|
|
101
|
+
conditionally_passes = ratio >= aa_goal
|
|
102
|
+
if conditionally_passes:
|
|
103
|
+
return "conditional pass: Large AA contrast rating"
|
|
104
|
+
else:
|
|
105
|
+
return "fail: fails all contrast ratings"
|
|
106
|
+
else:
|
|
107
|
+
aaa_goal = contrast_ratio_map.get("Normal AAA")
|
|
108
|
+
passes = ratio >= aaa_goal
|
|
109
|
+
if passes:
|
|
110
|
+
return "pass: Normal AAA contrast rating"
|
|
111
|
+
aa_goal = contrast_ratio_map.get("Normal AA")
|
|
112
|
+
conditionally_passes = ratio >= aa_goal
|
|
113
|
+
if conditionally_passes:
|
|
114
|
+
return "conditional pass: Normal AA contrast rating"
|
|
115
|
+
else:
|
|
116
|
+
return "fail: fails normal sized text contrast"
|
|
117
|
+
|
|
118
|
+
|
|
81
119
|
def get_color_contrast_report(hex1: str, hex2: str) -> dict:
|
|
82
120
|
"""creates a report on how the two hex colors rate on the color
|
|
83
121
|
contrast chart
|
|
@@ -794,10 +832,18 @@ def get_color_contrast_with_gradients(
|
|
|
794
832
|
# append to results list
|
|
795
833
|
if composite_color:
|
|
796
834
|
results.append(
|
|
797
|
-
(
|
|
835
|
+
(
|
|
836
|
+
contrast,
|
|
837
|
+
foreground,
|
|
838
|
+
background,
|
|
839
|
+
composite_color,
|
|
840
|
+
bg_has_alpha,
|
|
841
|
+
)
|
|
798
842
|
)
|
|
799
843
|
else:
|
|
800
|
-
results.append(
|
|
844
|
+
results.append(
|
|
845
|
+
(contrast, foreground, background, background, False)
|
|
846
|
+
)
|
|
801
847
|
results.sort()
|
|
802
848
|
return results
|
|
803
849
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|