webcode-tk 1.2.6__tar.gz → 1.2.8__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.2.6 → webcode_tk-1.2.8}/PKG-INFO +2 -2
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/pyproject.toml +3 -3
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/__init__.py +6 -1
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/contrast_tools.py +237 -140
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/css_tools.py +10 -5
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/LICENSE.rst +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/README.rst +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/animation_tools.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/cascade_tools.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/color_keywords.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/color_tools.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/font_tools.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/html_tools.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/sample_report.json +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/utils.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/ux_tools.py +0 -0
- {webcode_tk-1.2.6 → webcode_tk-1.2.8}/webcode_tk/validator_tools.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "webcode-tk"
|
|
3
|
-
version = "1.2.
|
|
4
|
-
description = ""
|
|
3
|
+
version = "1.2.8"
|
|
4
|
+
description = "A set of tools for inspecting HTML and CSS code"
|
|
5
5
|
authors = ["hundredvisionsguy <cwinikka@gmail.com>"]
|
|
6
6
|
keywords = ["html", "validation", "css", ]
|
|
7
7
|
license = "MIT"
|
|
@@ -28,7 +28,7 @@ lxml = "^6.0.0"
|
|
|
28
28
|
mechanicalsoup = "^1.4.0"
|
|
29
29
|
pytest-mock = "^3.15.1"
|
|
30
30
|
|
|
31
|
-
[tool.poetry.dev
|
|
31
|
+
[tool.poetry.group.dev.dependencies]
|
|
32
32
|
pytest = "*"
|
|
33
33
|
mkdocs = "*"
|
|
34
34
|
mkgendocs = "*"
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
- **`color_tools`**: processes CSS color related properties and values.
|
|
18
18
|
|
|
19
|
+
- **`contrast_tools`**: calculates contrast results taking into account how
|
|
20
|
+
the background is invisible, but still affects contrast on elements
|
|
21
|
+
even if those elements do not have a background color explicitly
|
|
22
|
+
applied. It also only checks elements with text in the innerHTML.
|
|
23
|
+
|
|
19
24
|
- **`css_tools`**: creates Stylesheet objects that store CSS information.
|
|
20
25
|
|
|
21
26
|
- **`font_tools`**: A collection of functions used to process font-related
|
|
@@ -35,4 +40,4 @@
|
|
|
35
40
|
- **`validator_tools`**: sends HTML or CSS code to the W3C Validator to check
|
|
36
41
|
for errors.
|
|
37
42
|
"""
|
|
38
|
-
__version__ = "1.2.
|
|
43
|
+
__version__ = "1.2.8"
|
|
@@ -130,6 +130,7 @@ def generate_contrast_report(
|
|
|
130
130
|
|
|
131
131
|
# Check if fails WCAG level
|
|
132
132
|
result = item.get(wcag_level_key)
|
|
133
|
+
|
|
133
134
|
if result:
|
|
134
135
|
result = result.lower()
|
|
135
136
|
else:
|
|
@@ -154,35 +155,42 @@ def generate_contrast_report(
|
|
|
154
155
|
actual_ratio = item.get("contrast_ratio")
|
|
155
156
|
actual_ratio = round(actual_ratio, 1)
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
msg
|
|
159
|
-
msg += f"{
|
|
160
|
-
|
|
161
|
-
project_files[current_file]
|
|
158
|
+
msg = f"{result}: in {current_file}, the <{element}> element"
|
|
159
|
+
msg += f" WCAG {wcag_level} contrast requirements with a "
|
|
160
|
+
msg += f"ratio of {actual_ratio} (min: {target_ratio})."
|
|
161
|
+
|
|
162
|
+
if msg not in project_files[current_file]:
|
|
163
|
+
project_files[current_file].append(msg)
|
|
162
164
|
|
|
163
165
|
# Check for html docs with no fails.
|
|
164
166
|
for file in project_files:
|
|
165
167
|
errors = project_files.get(file)
|
|
168
|
+
|
|
169
|
+
# Check for global selectors (html, *, or body) and calculate contrast
|
|
170
|
+
global_color_result = css_tools.get_global_color_report(project_path)
|
|
171
|
+
|
|
172
|
+
if isinstance(global_color_result, list):
|
|
173
|
+
for result in global_color_result:
|
|
174
|
+
if file in result:
|
|
175
|
+
# Always add global color failures
|
|
176
|
+
if "fail" in result:
|
|
177
|
+
error_msg = result.split(file)[1]
|
|
178
|
+
msg = f"fail: in {file}, {error_msg.strip()}"
|
|
179
|
+
# Add to errors list if not already there
|
|
180
|
+
if msg not in project_files[file]:
|
|
181
|
+
project_files[file].append(msg)
|
|
182
|
+
|
|
183
|
+
# Now append to report
|
|
166
184
|
if errors:
|
|
167
185
|
if "has no CSS" in errors[0]:
|
|
168
186
|
report.append(errors[0])
|
|
169
187
|
else:
|
|
170
188
|
report.extend(errors)
|
|
171
189
|
else:
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
global_color_result = css_tools.get_global_color_report(
|
|
175
|
-
project_path
|
|
176
|
-
)
|
|
177
|
-
if isinstance(global_color_result, list):
|
|
178
|
-
for result in global_color_result:
|
|
179
|
-
if "fail" in result:
|
|
180
|
-
if file in result:
|
|
181
|
-
error_msg = result.split(file)[1]
|
|
182
|
-
msg = f"fail: in {file}, {error_msg.strip()}"
|
|
183
|
-
else:
|
|
184
|
-
msg = f"pass: {file} passes color contrast."
|
|
190
|
+
# File has no element-level failures
|
|
191
|
+
msg = f"pass: {file} passes color contrast."
|
|
185
192
|
report.append(msg)
|
|
193
|
+
|
|
186
194
|
return report
|
|
187
195
|
|
|
188
196
|
|
|
@@ -402,6 +410,9 @@ def analyze_css(
|
|
|
402
410
|
filename = html_doc["filename"]
|
|
403
411
|
soup = html_doc["soup"]
|
|
404
412
|
|
|
413
|
+
# Check to see if background color was set globally
|
|
414
|
+
global_bg = detect_document_background(project_path, filename)
|
|
415
|
+
|
|
405
416
|
# Check for CSS sources and create warnings if needed
|
|
406
417
|
warnings = []
|
|
407
418
|
|
|
@@ -417,7 +428,7 @@ def analyze_css(
|
|
|
417
428
|
)
|
|
418
429
|
|
|
419
430
|
# Step 1: Apply default styles to all elements
|
|
420
|
-
default_styles = apply_browser_defaults(soup)
|
|
431
|
+
default_styles = apply_browser_defaults(soup, default_bg=global_bg)
|
|
421
432
|
|
|
422
433
|
# Find CSS rules specific to this HTML document
|
|
423
434
|
doc_css_rules = get_css_rules_for_document(filename, css_files)
|
|
@@ -468,6 +479,47 @@ def analyze_css(
|
|
|
468
479
|
return doc_results
|
|
469
480
|
|
|
470
481
|
|
|
482
|
+
def detect_document_background(project_path: str, filename: str) -> str:
|
|
483
|
+
"""
|
|
484
|
+
Detect global background color from CSS rules for this document.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
project_path: Path to project root
|
|
488
|
+
filename: Name of current HTML file
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
str: Background color hex value, or DEFAULT_GLOBAL_BACKGROUND if
|
|
492
|
+
none found
|
|
493
|
+
"""
|
|
494
|
+
# Get global colors for entire project
|
|
495
|
+
global_colors = css_tools.get_project_global_colors(project_path)
|
|
496
|
+
|
|
497
|
+
# Find entry for this specific HTML file
|
|
498
|
+
for file_path, color_data_list in global_colors.items():
|
|
499
|
+
# Match filename (handle both full path and just filename)
|
|
500
|
+
if filename in file_path:
|
|
501
|
+
# color_data_list is a list of dicts with global selector info
|
|
502
|
+
if isinstance(color_data_list, dict):
|
|
503
|
+
color_data_list = [
|
|
504
|
+
color_data_list,
|
|
505
|
+
]
|
|
506
|
+
for color_data in color_data_list:
|
|
507
|
+
# Look for body selector specifically
|
|
508
|
+
if color_data.get("selector") == "body":
|
|
509
|
+
bg_color = color_data.get("background-color")
|
|
510
|
+
if bg_color:
|
|
511
|
+
return bg_color
|
|
512
|
+
|
|
513
|
+
# Also check for html or * selectors
|
|
514
|
+
if color_data.get("selector") in ["html", "*"]:
|
|
515
|
+
bg_color = color_data.get("background-color")
|
|
516
|
+
if bg_color:
|
|
517
|
+
return bg_color
|
|
518
|
+
|
|
519
|
+
# No global background found, use default white
|
|
520
|
+
return DEFAULT_GLOBAL_BACKGROUND
|
|
521
|
+
|
|
522
|
+
|
|
471
523
|
def has_any_css_sources(soup: BeautifulSoup) -> bool:
|
|
472
524
|
"""
|
|
473
525
|
Check if HTML document has any CSS sources (external or internal).
|
|
@@ -495,7 +547,9 @@ def has_any_css_sources(soup: BeautifulSoup) -> bool:
|
|
|
495
547
|
return False
|
|
496
548
|
|
|
497
549
|
|
|
498
|
-
def apply_browser_defaults(
|
|
550
|
+
def apply_browser_defaults(
|
|
551
|
+
soup: BeautifulSoup, default_bg: str = DEFAULT_GLOBAL_BACKGROUND
|
|
552
|
+
) -> dict:
|
|
499
553
|
"""
|
|
500
554
|
Apply browser default styles to all elements with text content.
|
|
501
555
|
|
|
@@ -505,8 +559,18 @@ def apply_browser_defaults(soup: BeautifulSoup) -> dict:
|
|
|
505
559
|
element_styles = {}
|
|
506
560
|
default_specificity = css_tools.get_specificity("")
|
|
507
561
|
|
|
508
|
-
for element in soup.
|
|
509
|
-
|
|
562
|
+
for element in soup.descendants:
|
|
563
|
+
# Skip non-tag elements
|
|
564
|
+
if not isinstance(element, Tag):
|
|
565
|
+
continue
|
|
566
|
+
|
|
567
|
+
# Always include html and body elements for inheritance chain
|
|
568
|
+
is_structural = element.name in ["html", "body"]
|
|
569
|
+
has_text = not element.is_empty_element and element.get_text(
|
|
570
|
+
strip=True
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
if is_structural or has_text:
|
|
510
574
|
# The element object itself becomes the key
|
|
511
575
|
element_styles[element] = {
|
|
512
576
|
"color": {
|
|
@@ -516,7 +580,7 @@ def apply_browser_defaults(soup: BeautifulSoup) -> dict:
|
|
|
516
580
|
"is_default": True,
|
|
517
581
|
},
|
|
518
582
|
"background-color": {
|
|
519
|
-
"value":
|
|
583
|
+
"value": default_bg,
|
|
520
584
|
"specificity": default_specificity,
|
|
521
585
|
"source": "default",
|
|
522
586
|
"is_default": True,
|
|
@@ -535,6 +599,8 @@ def apply_browser_defaults(soup: BeautifulSoup) -> dict:
|
|
|
535
599
|
},
|
|
536
600
|
}
|
|
537
601
|
|
|
602
|
+
if element.name in ["html", "body"]:
|
|
603
|
+
print(f" ✓ Added <{element.name}> to element_styles")
|
|
538
604
|
# Apply element-specific defaults
|
|
539
605
|
# Apply link colors
|
|
540
606
|
if element.name == "a":
|
|
@@ -641,8 +707,12 @@ def compute_element_styles(
|
|
|
641
707
|
dict: Dictionary mapping elements to their final computed styles
|
|
642
708
|
after applying CSS cascade and inheritance.
|
|
643
709
|
"""
|
|
644
|
-
#
|
|
645
|
-
computed_styles =
|
|
710
|
+
# ✅ NEW: Shallow copy of dict, deep copy only the style values
|
|
711
|
+
computed_styles = {}
|
|
712
|
+
for element, styles in default_styles.items():
|
|
713
|
+
# Keep the SAME element object (preserves parent relationship)
|
|
714
|
+
# but deep copy its styles dictionary
|
|
715
|
+
computed_styles[element] = copy.deepcopy(styles)
|
|
646
716
|
|
|
647
717
|
# Step 2: Apply all CSS rules (cascade resolution)
|
|
648
718
|
for rules_list in css_rules:
|
|
@@ -1074,145 +1144,144 @@ def apply_inheritance(soup: BeautifulSoup, computed_styles: dict) -> None:
|
|
|
1074
1144
|
def apply_css_inheritance(computed_styles: dict) -> None:
|
|
1075
1145
|
"""
|
|
1076
1146
|
Apply traditional CSS inheritance for inheritable properties.
|
|
1077
|
-
|
|
1078
|
-
Args:
|
|
1079
|
-
computed_styles (dict): Dictionary mapping elements to their
|
|
1080
|
-
computed styles (modified in place).
|
|
1081
|
-
|
|
1082
|
-
Returns:
|
|
1083
|
-
None: Modifies computed_styles dictionary in place.
|
|
1147
|
+
Only inherit from the closest ancestor with an explicit rule.
|
|
1084
1148
|
"""
|
|
1085
1149
|
inheritable_props = {"color", "font-size", "font-weight"}
|
|
1086
1150
|
|
|
1087
|
-
for
|
|
1088
|
-
|
|
1151
|
+
for child_element in computed_styles:
|
|
1152
|
+
child_styles = computed_styles[child_element]
|
|
1089
1153
|
|
|
1090
|
-
#
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
child for child in children if child in computed_styles
|
|
1094
|
-
]
|
|
1154
|
+
# find closest ancestor with explicit value
|
|
1155
|
+
for prop in inheritable_props:
|
|
1156
|
+
child_prop = child_styles.get(prop)
|
|
1095
1157
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
#
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1158
|
+
# Skip if child has explicit CSS rule (not default)
|
|
1159
|
+
if child_prop and child_prop.get("source") not in [
|
|
1160
|
+
"default",
|
|
1161
|
+
None,
|
|
1162
|
+
]:
|
|
1163
|
+
continue
|
|
1164
|
+
|
|
1165
|
+
# Walk up tree to find first ancestor with explicit value
|
|
1166
|
+
current = child_element.parent
|
|
1167
|
+
inherited_value = None
|
|
1168
|
+
|
|
1169
|
+
while current and not inherited_value:
|
|
1170
|
+
if current in computed_styles:
|
|
1171
|
+
parent_styles = computed_styles[current]
|
|
1172
|
+
parent_prop = parent_styles.get(prop)
|
|
1173
|
+
|
|
1174
|
+
# Found a parent with this property
|
|
1175
|
+
if parent_prop:
|
|
1176
|
+
# Only inherit if parent has explicit rule
|
|
1177
|
+
if parent_prop.get("source") == "rule":
|
|
1178
|
+
inherited_value = parent_prop
|
|
1179
|
+
inherited_from = current
|
|
1180
|
+
break # ← Stop at first explicit ancestor
|
|
1181
|
+
|
|
1182
|
+
current = current.parent
|
|
1183
|
+
|
|
1184
|
+
# Apply inherited value if found
|
|
1185
|
+
if inherited_value:
|
|
1186
|
+
child_styles[prop] = {
|
|
1187
|
+
"value": inherited_value["value"],
|
|
1188
|
+
"specificity": inherited_value["specificity"],
|
|
1189
|
+
"source": "inheritance",
|
|
1190
|
+
"selector": inherited_value.get("selector", "inherited"),
|
|
1191
|
+
"css_file": inherited_value.get("css_file"),
|
|
1192
|
+
"css_source_type": inherited_value.get("css_source_type"),
|
|
1193
|
+
"inherited_from": inherited_from,
|
|
1194
|
+
}
|
|
1131
1195
|
|
|
1132
1196
|
|
|
1133
1197
|
def apply_visual_background_inheritance(computed_styles: dict) -> None:
|
|
1134
1198
|
"""
|
|
1135
|
-
Apply visual background colors to elements without explicit
|
|
1136
|
-
backgrounds.
|
|
1199
|
+
Apply visual background colors to elements without explicit backgrounds.
|
|
1137
1200
|
|
|
1138
1201
|
Args:
|
|
1139
1202
|
computed_styles: a dictionary of elements with computed styles.
|
|
1140
1203
|
Returns:
|
|
1141
1204
|
None
|
|
1142
1205
|
"""
|
|
1143
|
-
# Track which elements got new backgrounds this iteration
|
|
1144
|
-
changes_made = True
|
|
1145
|
-
max_iterations = 10 # Prevent infinite loops
|
|
1146
|
-
iteration = 0
|
|
1147
|
-
|
|
1148
|
-
while changes_made and iteration < max_iterations:
|
|
1149
|
-
changes_made = False
|
|
1150
|
-
iteration += 1
|
|
1151
1206
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
# Skip if element already has background-color
|
|
1156
|
-
if "background-color" in element_styles:
|
|
1157
|
-
continue
|
|
1207
|
+
for element in computed_styles:
|
|
1208
|
+
element_styles = computed_styles[element]
|
|
1158
1209
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1210
|
+
# Check for BOTH background-color AND background
|
|
1211
|
+
current_bg = element_styles.get("background-color")
|
|
1212
|
+
has_background_shorthand = "background" in element_styles
|
|
1162
1213
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
element_styles["background-color"] = {
|
|
1167
|
-
"value": None, # No usable color
|
|
1168
|
-
"specificity": "000",
|
|
1169
|
-
"contrast_analysis": "indeterminate",
|
|
1170
|
-
"reason": "background_image_blocks_color_analysis",
|
|
1171
|
-
"original_background": bg_value,
|
|
1172
|
-
"visual_inheritance": False,
|
|
1173
|
-
}
|
|
1174
|
-
changes_made = True
|
|
1175
|
-
continue
|
|
1176
|
-
else:
|
|
1177
|
-
# Has usable background shorthand - skip inheritance
|
|
1178
|
-
continue
|
|
1214
|
+
# If element has background shorthand, process it for images
|
|
1215
|
+
if has_background_shorthand:
|
|
1216
|
+
bg_value = element_styles["background"]["value"]
|
|
1179
1217
|
|
|
1180
|
-
#
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
if ancestor_bg.get("contrast_analysis") == "indeterminate":
|
|
1184
|
-
# Ancestor is indeterminate - propagate that status
|
|
1218
|
+
# Check if background contains a raster image
|
|
1219
|
+
if contains_raster_image(bg_value):
|
|
1220
|
+
# Mark as contrast-indeterminate - cannot analyze
|
|
1185
1221
|
element_styles["background-color"] = {
|
|
1186
1222
|
"value": None,
|
|
1187
1223
|
"specificity": "000",
|
|
1188
1224
|
"contrast_analysis": "indeterminate",
|
|
1189
|
-
"reason":
|
|
1190
|
-
"
|
|
1191
|
-
"
|
|
1192
|
-
"original_background": ancestor_bg.get(
|
|
1193
|
-
"original_background"
|
|
1194
|
-
),
|
|
1225
|
+
"reason": "background_image_blocks_color_analysis",
|
|
1226
|
+
"original_background": bg_value,
|
|
1227
|
+
"visual_inheritance": False,
|
|
1195
1228
|
}
|
|
1196
|
-
|
|
1229
|
+
continue
|
|
1197
1230
|
else:
|
|
1198
|
-
#
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1231
|
+
# Has usable background shorthand - skip inheritance
|
|
1232
|
+
continue
|
|
1233
|
+
if not current_bg:
|
|
1234
|
+
continue
|
|
1235
|
+
|
|
1236
|
+
bg_source = current_bg.get("source")
|
|
1237
|
+
bg_value = current_bg.get("value")
|
|
1238
|
+
|
|
1239
|
+
# Skip elements with explicit CSS rules
|
|
1240
|
+
if bg_source == "rule":
|
|
1241
|
+
continue
|
|
1242
|
+
|
|
1243
|
+
# Only update elements that still have the default white background
|
|
1244
|
+
# This prevents overwriting elements with existing proper backgrounds
|
|
1245
|
+
if bg_source != "default":
|
|
1246
|
+
continue
|
|
1247
|
+
|
|
1248
|
+
# Element needs background inheritance (lines 1120-1141)
|
|
1249
|
+
ancestor_bg = find_ancestor_background(element, computed_styles)
|
|
1250
|
+
|
|
1251
|
+
if not ancestor_bg or ancestor_bg.get("source") == "default":
|
|
1252
|
+
# No explicit background to inherit - keep browser default
|
|
1253
|
+
continue
|
|
1254
|
+
|
|
1255
|
+
if ancestor_bg.get("contrast_analysis") == "indeterminate":
|
|
1256
|
+
# Ancestor is indeterminate - propagate that status
|
|
1257
|
+
element_styles["background-color"] = {
|
|
1258
|
+
"value": None,
|
|
1259
|
+
"specificity": "000",
|
|
1260
|
+
"contrast_analysis": "indeterminate",
|
|
1261
|
+
"reason": ancestor_bg["reason"],
|
|
1262
|
+
"inherited_from": ancestor_bg["source_element"],
|
|
1263
|
+
"source": "visual_inheritance",
|
|
1264
|
+
"original_background": ancestor_bg.get("original_background"),
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
else:
|
|
1268
|
+
# Normal inheritance - extract usable color
|
|
1269
|
+
contrast_color = extract_contrast_color(ancestor_bg["value"])
|
|
1270
|
+
effective_color = (
|
|
1271
|
+
contrast_color if contrast_color else ancestor_bg["value"]
|
|
1272
|
+
)
|
|
1273
|
+
element_styles["background-color"] = {
|
|
1274
|
+
"value": effective_color,
|
|
1275
|
+
"specificity": "000",
|
|
1276
|
+
"source": "visual_inheritance",
|
|
1277
|
+
"inherited_from": ancestor_bg["source_element"],
|
|
1278
|
+
"contrast_analysis": "determinable",
|
|
1279
|
+
"original_background": (
|
|
1280
|
+
ancestor_bg["value"]
|
|
1281
|
+
if ancestor_bg["value"] != DEFAULT_GLOBAL_BACKGROUND
|
|
1282
|
+
else None
|
|
1283
|
+
),
|
|
1284
|
+
}
|
|
1216
1285
|
|
|
1217
1286
|
|
|
1218
1287
|
def has_usable_background_color(background_prop: dict) -> bool:
|
|
@@ -1367,6 +1436,10 @@ def find_ancestor_background(element: Tag, computed_styles: dict) -> dict:
|
|
|
1367
1436
|
# Accept both "rule" and "default" sources
|
|
1368
1437
|
if bg_source in ["rule", "default"]:
|
|
1369
1438
|
bg_value = current_styles[bg_prop]["value"]
|
|
1439
|
+
if bg_source == "default":
|
|
1440
|
+
# Skip ancestors with only default backgrounds
|
|
1441
|
+
skip_ancestor = True
|
|
1442
|
+
break
|
|
1370
1443
|
|
|
1371
1444
|
# Does this ancestor already is indeterminate status
|
|
1372
1445
|
if (
|
|
@@ -1483,6 +1556,26 @@ def analyze_elements_for_contrast(
|
|
|
1483
1556
|
contrast_analysis = element_data["background-color"].get(
|
|
1484
1557
|
"contrast_analysis"
|
|
1485
1558
|
)
|
|
1559
|
+
|
|
1560
|
+
# Check if this element inherited both text and background colors
|
|
1561
|
+
text_color_source_type = extract_property_source(
|
|
1562
|
+
element_data["color"]
|
|
1563
|
+
).get("source_type")
|
|
1564
|
+
bg_color_source_type = extract_property_source(
|
|
1565
|
+
element_data["background-color"]
|
|
1566
|
+
).get("source_type")
|
|
1567
|
+
|
|
1568
|
+
# Determine if this is a global color failure
|
|
1569
|
+
inherited_text = text_color_source_type in [
|
|
1570
|
+
"inherited",
|
|
1571
|
+
"browser_default",
|
|
1572
|
+
]
|
|
1573
|
+
inherited_bg = bg_color_source_type in [
|
|
1574
|
+
"visual_inheritance",
|
|
1575
|
+
"browser_default",
|
|
1576
|
+
]
|
|
1577
|
+
is_global_failure = inherited_text and inherited_bg
|
|
1578
|
+
|
|
1486
1579
|
if is_large:
|
|
1487
1580
|
aaa_results = contrast_report.get("Large AAA")
|
|
1488
1581
|
aa_results = contrast_report.get("Large AA")
|
|
@@ -1514,6 +1607,7 @@ def analyze_elements_for_contrast(
|
|
|
1514
1607
|
"wcag_aa_pass": aa_results,
|
|
1515
1608
|
"wcag_aaa_pass": aaa_results,
|
|
1516
1609
|
"contrast_analysis": contrast_analysis,
|
|
1610
|
+
"global_color_failure": is_global_failure,
|
|
1517
1611
|
}
|
|
1518
1612
|
|
|
1519
1613
|
# add to results
|
|
@@ -1627,4 +1721,7 @@ if __name__ == "__main__":
|
|
|
1627
1721
|
project_path = "tests/test_files/large_project/"
|
|
1628
1722
|
project_path = "tests/test_files/contrast_tool_test/"
|
|
1629
1723
|
contrast_results = generate_contrast_report(project_path, "AAA")
|
|
1630
|
-
|
|
1724
|
+
project_path = "tests/test_files/contrast_tool_test/"
|
|
1725
|
+
global_colors = css_tools.get_project_global_colors(project_path)
|
|
1726
|
+
print()
|
|
1727
|
+
print()
|
|
@@ -2458,7 +2458,10 @@ def get_global_color_report(project_dir: str, level="aaa") -> list:
|
|
|
2458
2458
|
if global_color_data:
|
|
2459
2459
|
for item in global_color_data:
|
|
2460
2460
|
file, result = get_color_data(filename, item, level)
|
|
2461
|
-
|
|
2461
|
+
if "fail" in result:
|
|
2462
|
+
passes.append(f"fail: {file} {result}")
|
|
2463
|
+
else:
|
|
2464
|
+
passes.append(f"pass: {file} {result}")
|
|
2462
2465
|
if passes:
|
|
2463
2466
|
details = ""
|
|
2464
2467
|
for detail in passes:
|
|
@@ -2485,8 +2488,6 @@ def get_color_data(file: str, color_details: dict, level="aaa") -> tuple:
|
|
|
2485
2488
|
Returns:
|
|
2486
2489
|
color_data: a tuple with filename and results as a string"""
|
|
2487
2490
|
selector = color_details.get("selector")
|
|
2488
|
-
bg_color = color_details.get("background-color")
|
|
2489
|
-
color = color_details.get("color")
|
|
2490
2491
|
contrast_ratio = color_details.get("contrast_ratio")
|
|
2491
2492
|
if level == "aaa":
|
|
2492
2493
|
passes = color_details.get("passes_normal_aaa")
|
|
@@ -2495,8 +2496,8 @@ def get_color_data(file: str, color_details: dict, level="aaa") -> tuple:
|
|
|
2495
2496
|
if passes:
|
|
2496
2497
|
results = "passes global colors"
|
|
2497
2498
|
else:
|
|
2498
|
-
results = f"{selector}
|
|
2499
|
-
results += f" ratio of {contrast_ratio}."
|
|
2499
|
+
results = f"the <{selector}> element fails WCAG {level.upper()}"
|
|
2500
|
+
results += f"contrast with a ratio of {contrast_ratio}."
|
|
2500
2501
|
color_data = (file, results)
|
|
2501
2502
|
return color_data
|
|
2502
2503
|
|
|
@@ -2877,3 +2878,7 @@ if __name__ == "__main__":
|
|
|
2877
2878
|
rulesets = get_element_rulesets(
|
|
2878
2879
|
"tests/test_files/cascade_complexities", "figure"
|
|
2879
2880
|
)
|
|
2881
|
+
passing_global_colors = get_global_color_report(project_folder)
|
|
2882
|
+
project_path = "tests/test_files/contrast_tool_test/"
|
|
2883
|
+
global_colors = get_global_color_report(project_path)
|
|
2884
|
+
print(global_colors)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|