webcode-tk 1.2.4__tar.gz → 1.2.7__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webcode-tk
3
- Version: 1.2.4
4
- Summary:
3
+ Version: 1.2.7
4
+ Summary: A set of tools for inspecting HTML and CSS code
5
5
  License: MIT
6
6
  License-File: LICENSE.rst
7
7
  Keywords: html,validation,css
@@ -23,6 +23,7 @@ Requires-Dist: lxml (>=6.0.0,<7.0.0)
23
23
  Requires-Dist: mechanicalsoup (>=1.4.0,<2.0.0)
24
24
  Requires-Dist: nltk (>=3.8.1,<4.0.0)
25
25
  Requires-Dist: py-readability-metrics (>=1.4.5,<2.0.0)
26
+ Requires-Dist: pytest-mock (>=3.15.1,<4.0.0)
26
27
  Requires-Dist: requests (>=2.28.1,<3.0.0)
27
28
  Requires-Dist: textatistic (>=0.0.1,<0.0.2)
28
29
  Requires-Dist: tinycss2 (>=1.4.0,<2.0.0)
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "webcode-tk"
3
- version = "1.2.4"
4
- description = ""
3
+ version = "1.2.7"
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"
@@ -26,6 +26,7 @@ nltk = "^3.8.1"
26
26
  tinycss2 = "^1.4.0"
27
27
  lxml = "^6.0.0"
28
28
  mechanicalsoup = "^1.4.0"
29
+ pytest-mock = "^3.15.1"
29
30
 
30
31
  [tool.poetry.dev-dependencies]
31
32
  pytest = "*"
@@ -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.4"
43
+ __version__ = "1.2.7"
@@ -409,7 +409,7 @@ def hex_to_decimal(c: str) -> int:
409
409
  msg += "2 digits"
410
410
  raise ValueError(msg)
411
411
  if c[0].lower() not in hex_map.keys():
412
- raise ValueError(f"The value `{c}` is not a valid hex code.")
412
+ raise ValueError(f"The value {repr(c)} is not a valid hex code.")
413
413
  c = c.lower()
414
414
  ones = hex_map[c[1]]
415
415
  sixteens = hex_map[c[0]] * 16
@@ -158,7 +158,8 @@ def generate_contrast_report(
158
158
  msg = f"{result}: in {current_file}, the {element} failed WCAG "
159
159
  msg += f"{wcag_level} contrast requirements (ratio: "
160
160
  msg += f"{actual_ratio}, required: {target_ratio})."
161
- project_files[current_file].append(msg)
161
+ if msg not in project_files[current_file]:
162
+ project_files[current_file].append(msg)
162
163
 
163
164
  # Check for html docs with no fails.
164
165
  for file in project_files:
@@ -169,9 +170,21 @@ def generate_contrast_report(
169
170
  else:
170
171
  report.extend(errors)
171
172
  else:
172
- msg = f"pass: {file} passes color contrast."
173
- report.append(msg)
173
+ # Check for global selectors (html, *, or body) and calculate
174
+ # contrast
175
+ global_color_result = css_tools.get_global_color_report(
176
+ project_path
177
+ )
178
+ if isinstance(global_color_result, list):
179
+ for result in global_color_result:
180
+ if "fail" in result:
181
+ if file in result:
182
+ error_msg = result.split(file)[1]
183
+ msg = f"fail: in {file}, {error_msg.strip()}"
184
+ else:
185
+ msg = f"pass: {file} passes color contrast."
174
186
 
187
+ report.append(msg)
175
188
  return report
176
189
 
177
190
 
@@ -194,7 +207,7 @@ def analyze_contrast(project_folder: str) -> list[dict]:
194
207
 
195
208
  # Analyze CSS Files
196
209
  for html_doc in parsed_html_docs:
197
- doc_results = analyze_css(html_doc, css_files)
210
+ doc_results = analyze_css(html_doc, css_files, project_folder)
198
211
  project_contrast_results.extend(doc_results)
199
212
  return project_contrast_results
200
213
 
@@ -368,7 +381,9 @@ def append_style_data(
368
381
  css_source[html_file].append(data)
369
382
 
370
383
 
371
- def analyze_css(html_doc: dict, css_files: list[dict]) -> list[dict]:
384
+ def analyze_css(
385
+ html_doc: dict, css_files: list[dict], project_path=""
386
+ ) -> list[dict]:
372
387
  """
373
388
  Analyzes CSS application and color contrast for a single HTML document.
374
389
 
@@ -389,6 +404,9 @@ def analyze_css(html_doc: dict, css_files: list[dict]) -> list[dict]:
389
404
  filename = html_doc["filename"]
390
405
  soup = html_doc["soup"]
391
406
 
407
+ # Check to see if background color was set globally
408
+ global_bg = detect_document_background(project_path, filename)
409
+
392
410
  # Check for CSS sources and create warnings if needed
393
411
  warnings = []
394
412
 
@@ -404,7 +422,7 @@ def analyze_css(html_doc: dict, css_files: list[dict]) -> list[dict]:
404
422
  )
405
423
 
406
424
  # Step 1: Apply default styles to all elements
407
- default_styles = apply_browser_defaults(soup)
425
+ default_styles = apply_browser_defaults(soup, default_bg=global_bg)
408
426
 
409
427
  # Find CSS rules specific to this HTML document
410
428
  doc_css_rules = get_css_rules_for_document(filename, css_files)
@@ -452,10 +470,50 @@ def analyze_css(html_doc: dict, css_files: list[dict]) -> list[dict]:
452
470
  "warning_impact": warning["impact"],
453
471
  }
454
472
  doc_results.append(warning_result)
455
-
456
473
  return doc_results
457
474
 
458
475
 
476
+ def detect_document_background(project_path: str, filename: str) -> str:
477
+ """
478
+ Detect global background color from CSS rules for this document.
479
+
480
+ Args:
481
+ project_path: Path to project root
482
+ filename: Name of current HTML file
483
+
484
+ Returns:
485
+ str: Background color hex value, or DEFAULT_GLOBAL_BACKGROUND if
486
+ none found
487
+ """
488
+ # Get global colors for entire project
489
+ global_colors = css_tools.get_project_global_colors(project_path)
490
+
491
+ # Find entry for this specific HTML file
492
+ for file_path, color_data_list in global_colors.items():
493
+ # Match filename (handle both full path and just filename)
494
+ if filename in file_path:
495
+ # color_data_list is a list of dicts with global selector info
496
+ if isinstance(color_data_list, dict):
497
+ color_data_list = [
498
+ color_data_list,
499
+ ]
500
+ for color_data in color_data_list:
501
+ # Look for body selector specifically
502
+ if color_data.get("selector") == "body":
503
+ bg_color = color_data.get("background-color")
504
+ if bg_color:
505
+ return bg_color
506
+
507
+ # Also check for html or * selectors
508
+ if color_data.get("selector") in ["html", "*"]:
509
+ bg_color = color_data.get("background-color")
510
+ if bg_color:
511
+ return bg_color
512
+
513
+ # No global background found, use default white
514
+ return DEFAULT_GLOBAL_BACKGROUND
515
+
516
+
459
517
  def has_any_css_sources(soup: BeautifulSoup) -> bool:
460
518
  """
461
519
  Check if HTML document has any CSS sources (external or internal).
@@ -483,7 +541,9 @@ def has_any_css_sources(soup: BeautifulSoup) -> bool:
483
541
  return False
484
542
 
485
543
 
486
- def apply_browser_defaults(soup: BeautifulSoup) -> dict:
544
+ def apply_browser_defaults(
545
+ soup: BeautifulSoup, default_bg: str = DEFAULT_GLOBAL_BACKGROUND
546
+ ) -> dict:
487
547
  """
488
548
  Apply browser default styles to all elements with text content.
489
549
 
@@ -504,7 +564,7 @@ def apply_browser_defaults(soup: BeautifulSoup) -> dict:
504
564
  "is_default": True,
505
565
  },
506
566
  "background-color": {
507
- "value": DEFAULT_GLOBAL_BACKGROUND,
567
+ "value": default_bg,
508
568
  "specificity": default_specificity,
509
569
  "source": "default",
510
570
  "is_default": True,
@@ -1613,5 +1673,9 @@ def element_name_or_default(property_data: dict) -> str:
1613
1673
 
1614
1674
  if __name__ == "__main__":
1615
1675
  project_path = "tests/test_files/large_project/"
1616
- contrast_results = generate_contrast_report(project_path, "AA")
1676
+ project_path = "tests/test_files/contrast_tool_test/"
1677
+ contrast_results = generate_contrast_report(project_path, "AAA")
1678
+ project_path = "tests/test_files/contrast_tool_test/"
1679
+ global_colors = css_tools.get_project_global_colors(project_path)
1680
+ print(global_colors)
1617
1681
  print(contrast_results)
@@ -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
- passes.append(f"pass: {file} {result}")
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:
@@ -2877,3 +2880,7 @@ if __name__ == "__main__":
2877
2880
  rulesets = get_element_rulesets(
2878
2881
  "tests/test_files/cascade_complexities", "figure"
2879
2882
  )
2883
+ passing_global_colors = get_global_color_report(project_folder)
2884
+ project_path = "tests/test_files/contrast_tool_test/"
2885
+ global_colors = get_global_color_report(project_path)
2886
+ print(global_colors)
@@ -96,11 +96,16 @@ def get_markup_validity(file_path: str) -> list:
96
96
  headers = {
97
97
  "content-type": "text/html; charset=utf-8",
98
98
  "Accept-Charset": "UTF-8",
99
+ "User-Agent": "python-requests/2.32.5",
99
100
  }
100
101
 
101
102
  r = requests.post(w3cURL, data=payload, headers=headers)
102
- errors = r.json()
103
- errors = errors.get("messages")
103
+ try:
104
+ errors = r.json()
105
+ errors = errors.get("messages")
106
+ except Exception:
107
+ # We need to use the web browser
108
+ errors = get_validation_by_browser(file_path)
104
109
 
105
110
  # raise the alarm if the response code is not 200
106
111
  if r.status_code != 200:
@@ -220,6 +225,45 @@ def is_css_valid(validator_results):
220
225
  return bool(soup.find(id="congrats"))
221
226
 
222
227
 
228
+ def get_validation_by_browser(file_path: str) -> list:
229
+ """Validates HTML using the browser.
230
+
231
+ This function will get the HTML code by file and use the mechanical
232
+ soup browser to get validator results as a ResultSet.
233
+
234
+ Args:
235
+ css_code: CSS code in the form of a string.
236
+
237
+ Returns:
238
+ results: A ResultSet of Tag objects.
239
+ """
240
+ # Get HTML code from file path
241
+ html_code = clerk.file_to_string(file_path)
242
+ try:
243
+ response = browser.open("https://validator.w3.org/")
244
+ if not response.ok:
245
+ response = browser.open("https://validator.w3.org/nu/#textarea")
246
+ browser.select_form("form")
247
+ browser["doc"] = html_code
248
+ browser.submit_selected()
249
+ results = browser.get_current_page().select("div#results")
250
+ else:
251
+ browser.select_form("#validate-by-input form")
252
+ browser["fragment"] = html_code
253
+ browser.submit_selected()
254
+ results = browser.get_current_page().select("div#results")
255
+ except Exception:
256
+ # Convert the file "no_css_connection.html" into a soup tag object
257
+ no_connection_code = (
258
+ "<h1>Sorry, but we could not make a connection</h1>"
259
+ )
260
+ no_connection_code += "<h2>Please try later</h2>"
261
+ soup = BeautifulSoup(no_connection_code, "lxml")
262
+ # Convert string to result set
263
+ results = soup.contents
264
+ return results
265
+
266
+
223
267
  def validate_css(css_code: str) -> bs4.ResultSet:
224
268
  """Validates CSS and returns the results from the css-validator.
225
269
 
File without changes
File without changes