skip to main content

@tonyfast s notebooks

site navigation
notebook summary
title
generating higher resolution exceptions for the vnu validator
description
the vnu validator is now available on conda which means it is easier to test your html
cells
7 total
4 code
state
executed out of order
kernel
Python [conda env:test-nbconvert-a11y]
language
python
name
conda-env-test-nbconvert-a11y-py
lines of code
50
outputs
2
table of contents
{"kernelspec": {"display_name": "Python [conda env:test-nbconvert-a11y]", "language": "python", "name": "conda-env-test-nbconvert-a11y-py"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.6"}, "widgets": {"application/vnd.jupyter.widget-state+json": {"state": {}, "version_major": 2, "version_minor": 0}}, "title": "generating higher resolution exceptions for the vnu validator", "description": "the vnu validator is now available on conda which means it is easier to test your\nhtml"}
notebook toolbar
Activate
cell ordering
1

generating higher resolution exceptions for the vnu validator

the vnu validator is now available on conda which means it is easier to test your html

2
    import pandas, pathlib, json, collections, exceptiongroup, re, dataclasses
    file = "../../../nbconvert-a11y/tests/exports/validator/lorenz-executed-default.json"    
3
    class Violation(Exception):
        map = {}
        def __class_getitem__(cls, id):
            bases = None
            if isinstance(id, tuple): id, bases = id
            if id in cls.map: return cls.map[id]
            return cls.map.setdefault(id, type(id, bases or (Violation,), {}))
        
        def __repr__(self):
            return super().__repr__()[:150]
4

exceptions are facetted by their type and info level.

5
    def messages_to_violations(messages):
        ct = collections.defaultdict(int)
        CSS_START = re.compile("""^“\S+”:""")
        exceptions = []
        for message in messages:
            t = message["type"],
            ct[t[0]] += 1
            if message.get("subType"): t += message.get("subType"),
            msg = message["message"]
            if msg.startswith("CSS:"):
                msg = msg[5:]
                t += "css",         
                if CSS_START.match(msg):
                    prop, _, msg = msg.partition(": ")
                    t += prop[1:-1],
                    id = F"""{message["type"]}-{prop[1:-1]}"""
                else:
                    id =F"""{message["type"]}-{msg.strip()}"""
            else:
                id = F"""{message["type"]}-{msg.strip()}"""
                msg = message["extract"]
            exceptions.append(Violation[id](msg.strip()))
        if exceptions:
            return exceptiongroup.ExceptionGroup(
                F"""{sum(ct.values())} violations: """ + ", ".join(
                    F"""{v} {k}"""  for k, v in ct.items()
                ),
                exceptions)
    exc = messages_to_violations(
        messages := json.loads(pathlib.Path(file).read_text())["messages"]
    ); exc
1 outputs.
ExceptionGroup('14 violations: 9 info, 5 error',
               [('n">\n<head><meta charset="utf-8"/>\n<meta'),
                ('"utf-8"/>\n<meta content="width=device-width, initial-scale=1.0" name="viewport"/>\n<titl'),
                ('</script>\n<style type="text/css">\n    p'),
                ('</style>\n<style type="text/css">\n/*---'),
                error-Unknown pseudo-element or pseudo-class “:horizontal”('Unknown pseudo-element or pseudo-class “:horizontal”'),
                error-Unknown pseudo-element or pseudo-class “:vertical”('Unknown pseudo-element or pseudo-class “:vertical”'),
                error-box-shadow("“'0px 4px 4px rgba(0, 0, 0, 0.25)'” is not a “box-shadow” value."),
                error-overflow('“overlay” is not a “overflow” value.'),
                ('</style>\n<style type="text/css">\n/*---'),
                ('</style>\n<style type="text/css">\n/* Fo'),
                ('ndex="0">\n<script type="text/javascript">\nvar e'),
                ('ndex="0">\n<img alt="No description has been provided for this image" class="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAhYAAAGzCAYAAABzfl,
                ('ndex="0">\n<img alt="No description has been provided for this image" class="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAhYAAAGzCAYAAABzfl,
                ('>\n</body>\n<script type="application/vnd.jupyter.widget-state+json">\n{"sta')])
6

messages_to_violations to violations dynamically generates Exception types for specific responses from the vnu validator.

7
    
    print(
        F"we created {len(Violation.map)} exceptions.",
        *Violation.map.values(), sep="\n")
1 outputs.
we created 8 exceptions.
<class '__main__.info-Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.'>
<class '__main__.info-The “type” attribute for the “style” element is not needed and should be omitted.'>
<class '__main__.error-Unknown pseudo-element or pseudo-class “:horizontal”'>
<class '__main__.error-Unknown pseudo-element or pseudo-class “:vertical”'>
<class '__main__.error-box-shadow'>
<class '__main__.error-overflow'>
<class '__main__.info-The “type” attribute is unnecessary for JavaScript resources.'>
<class '__main__.error-Stray start tag “script”.'>