Check DOCX templates for accessibility, as warnings by default - #82
Merged
Conversation
Adds a static accessibility checker for the DOCX templates users actually receive. It reads the OOXML package directly (stdlib zipfile + ElementTree, no new dependency) and reports missing alt text, empty or ambiguous link text, missing language metadata, heading structure, table header and merged-cell risks, explicitly low-contrast text, and floating objects or text boxes that disturb reading order. Findings go through messages.py like every other checker, so they get stable diagnostic codes (EA540-IA567 in the accessibility range) and work with --suppress, --format github and --max-warnings for free. A DOCX has no line numbers, so findings name the package part they came from and are de-duplicated per document: a rule matching every table in a file reports once. Every finding is capped at warning severity by default, so adopting the check annotates a build rather than breaking it -- across the 190 templates in the local AL checkouts, no document reports an error under the default. --docx-accessibility-severity error restores each rule's own severity for repos ready to enforce. The ceiling only demotes; it never promotes a rule past its natural severity. Rule severities are calibrated against that corpus rather than guessed: document-title-missing fires on 76% of documents and heading-none on 32%, so the first is informational and the second only applies to documents with enough prose (1500 characters) that headings are a reasonable expectation.
Workflow commands are consumed by the runner, so a --format github run left the job log empty -- everything only showed up in the annotations panel. Echo the same one-line summary the text format prints.
collect_docx_files resolved every path to absolute, so DOCX annotations carried /home/runner/work/... while YAML ones were workspace-relative. GitHub only attaches an annotation to a file in the PR diff when the path is relative to the workspace, so the DOCX ones silently detached. Dedupe on the resolved path but report the path as it was given. Also lowercases the message fragments that are interpolated mid-sentence, so they no longer read "Table has many empty cells ... in word/document.xml".
…first A real CI run surfaced two problems with emitting only annotations. GitHub keeps just the first 10 annotations per level per check run. A run with 188 findings rendered exactly 10 failures, 10 warnings and 10 notices, and because --format github printed nothing else, the other 158 findings were not visible anywhere. Print the full text report alongside the annotations so the log is always complete. Emit annotations severity-first as well, so when the cap does truncate, the ones that survive are the errors rather than whatever was collected first.
"a table has no header row marker" is not actionable in a forty-page template. A DOCX has no line number to point at, so each rule now quotes up to 80 characters of text near the problem: tables quote their first text, images and text boxes quote the paragraph beside them, and empty-paragraph runs quote what precedes them and say how long the longest run is. Locating context also stops distinct problems collapsing into one finding. Findings are de-duplicated on their rendered message, so two tables with the same defect used to report once; they are now two findings, which is the number of fixes the author has to make. Over the 190 templates in the local AL checkouts this takes the total from roughly 646 findings to 761.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a static accessibility checker for the DOCX templates users receive, plus fixes to the checks drafted on the old
document-accessibilitybranch (which was 83 commits behind main and duplicated the ruamel change that had already landed).Findings go through
messages.pylike every other checker, so they get stable diagnostic codes (EA540–IA567in the accessibility range) and work with--suppress,--format githuband--max-warningsfor free.Gradual adoption
Every finding is capped at warning severity by default. Across the 190 templates in the AL repos, no document reports an error under the default, so turning this on annotates a build rather than breaking it.
--docx-accessibility-severity errorrestores each rule's own severity for repos ready to enforce. The ceiling only demotes; it never promotes a rule past its natural severity.Findings say where to look
A DOCX has no line number to point at, so each rule quotes up to 80 characters of nearby text:
Tables quote their first text, images and text boxes quote the paragraph beside them, empty-paragraph runs quote what precedes them. This also stops distinct problems collapsing: findings de-duplicate on their rendered message, so two tables with the same defect are two findings — the number of fixes actually required.
Correctness fixes, each with a regression test
w:outlineLvl9 means body text, not heading level 10 — a body paragraph was reported as an H10 and trippedheading-skipped-level.fldCharbegin/instrText/separate/end) used the whole paragraph as their visible text, so two links in a paragraph produced a boguslink-ambiguousand genuinely bad link text inside a sentence was never caught.<w:b w:val="0"/>is bold off, but was read as on, pushing 14pt text into the large-text bucket and lowering the contrast bar from 4.5 to 3.0.w:rchildren, so runs insidew:hyperlinkwere skipped — link text is a common failure.w:pict) alt text was never read; barew:objectembeds were never examined; SmartArt was matched on an element name that does not exist.Calibration
Severities are measured against the real corpus, not guessed.
document-title-missingfires on 76% of documents so it is informational;heading-nonetriggered at 400 characters and flagged one-page court forms, so it now needs 1500.Fix found by running it in real CI
--format githubprinted only annotations. GitHub keeps just the first 10 per level per check run, so a real run with 188 findings left the other 158 visible nowhere at all. It now prints the full text report alongside, severity-first so the cap truncates the least important.Verification
Exercised end to end in GitHub Actions against a real package: LemmaLegalConsulting/docassemble-MOHUDEvictionProject#669 — 14 templates, 35 findings, 0 errors, annotations attached to the right paths, build failure entirely pre-existing YAML.
310 tests pass; mypy and black clean.
SuffolkLITLab/ALActions#94needs 1.5.0 published before it can merge.