A script to convert a WCAG-EM report from JSON to reStructuredText for a VPAT
At work, I've had to produce VPAT documents for the software I'm responsible for.
The VPAT template asks you to list, for each of the WCAG criteria, whether you support it or not, or if it doesn't apply.
The W3C have made a WCAG-EM Report Tool which helps you to work through the WCAG criteria and make notes about whether they're satisfied.
At the end, you can download a copy of the report in either summarised HTML format, or a JSON file with all the data you entered.
The first time I did a VPAT, I mostly manually converted the information from the WCAG-EM report to a reStructuredText table, to go in our Sphinx documentation.
Now I'm doing it a second time, I know I don't want to waste my time doing that!
So I've written a Python script which takes in the JSON file from the report tool and prints out the tables for the VPAT template, in reStructuredText format.
Here's my script:
2024/06/wcag-em-report-to-rst.py (Source)
#!/usr/bin/env python # coding: utf-8 # Copyright 2024 Christian Lawson-Perfect # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Convert a WCAG-EM report tool JSON file to reStructuredText # # This script converts the JSON file you get from the [WCAG-EM Report Tool](https://www.w3.org/WAI/eval/report-tool/) into reStructuredText markup. # # It needs the following files: # # * https://raw.githubusercontent.com/w3c/wai-wcag-em-report-tool/main/src/data/wcag.json - The data on WCAG criteria used by the report tool. # * https://raw.githubusercontent.com/w3c/wai-wcag-em-report-tool/main/src/locales/en/WCAG.json - The English readable names for WCAG criteria. Save as ``WCAG_text.json``. # # Usage: # # python wcag-em-report-to-rst.py product-report.json from collections import defaultdict from itertools import groupby import json import sys # Load the report data, and the WCAG criterion data report_json_filename = sys.argv[1] with open(report_json_filename) as f: data = json.load(f) with open('wcag.json') as f: wcag = json.load(f) with open('WCAG_text.json') as f: wcag_text = json.load(f) wcag_ids = {v['id']: v for v in wcag['2.2'].values()} criteria_names = {k: v['TITLE'] for k,v in wcag_text['SUCCESS_CRITERION'].items()} criteria = defaultdict(list) for item in data['auditSample']: id = item['test']['id'][len('WCAG22:'):] criteria[id].append(item) # For each criterion, gather info from the report report = [] def indent_lines(text, indent): lines = text.split('\n') return lines[0]+'\n'.join(indent+line for line in lines[1:]) for key, criterion in criteria.items(): description = '' result = '' for item in criterion: if 'description' in item['result']: description = item['result']['description'] if 'Website' not in item['subject']['type']: continue result = item['result']['outcome']['title'] result_map = { 'Passed': 'Supports', 'Not present': 'Not Applicable', } result = result_map.get(result, result) info = wcag_ids[key] num = info['num'] conformanceLevel = info['conformanceLevel'] name = criteria_names[num] report.append({ 'key': key, 'description': indent_lines(description, ' '*6), 'result': result, 'num': num, 'conformanceLevel': conformanceLevel, 'name': name, }) # Criteria not assessed # # The following criteria were not assessed: print("The following criteria were not assessed:\n") not_assessed = [n for n in wcag_ids.values() if n['id'] not in criteria] for n in not_assessed: print(f'''* {n['num']} {n['id']} ({n['conformanceLevel']}) https://www.w3.org/WAI/WCAG22/quickref/#{n['id']}''') print('\n') # Produce ReStructuredText of report for level, items in groupby(sorted(report, key=lambda x: (x['conformanceLevel'], x['num'])), key=lambda x: x['conformanceLevel']): header = f'''Table 1: Success Criteria, Level {level}''' print(header) print('*'*len(header)) print(''' .. list-table:: :header-rows: 1 - - Criteria - Conformance Level - Remarks and Explanations''') for item in items: print(''' - - .. _vpat-{key}: `{num}: {name} <https://www.w3.org/WAI/WCAG22/quickref/#{key}>`__ (Level {conformanceLevel}) - {result} - {description}'''.format(**item)) print('')
And it ends up looking something like this:
Table 1: Success Criteria, Level A
Criteria
Conformance Level
Remarks and Explanations
1.1.1: Non-text Content (Level A)
Supports
Text is always the primary method of conveying information.
Not Applicable
Not Applicable
1.2.3: Audio Description or Media Alternative (Prerecorded) (Level A)
Not Applicable
1.3.1: Info and Relationships (Level A)
Supports
1.3.2: Meaningful Sequence (Level A)
Supports
1.3.3: Sensory Characteristics (Level A)
Supports
All interactive elements are clearly labelled in text.
1.4.1: Use of Color (Level A)
Supports
and so on.
As ever, I'm putting this here both so I can find it again later, and in case anyone else finds it useful.