code wiki / _hdl_build / nx_cms_a11y.nx

nx_cms_a11y.nx source

↩ module page · 113 lines · 4356 B

1// nx_cms_a11y.nx -- CMS ACCESSIBILITY (WCAG) auditor (the wp-accessibility class, built honestly). Static 2// HTML checks against a focused, citable WCAG 2.x rule set -- the kind of automated a11y gate WordPress 3// themes routinely fail: 4// R1 WCAG 3.1.1 : the document declares a language (<html ... lang=...>) 5// R2 WCAG 1.1.1 : every <img> carries a non-empty alt= (text alternative) 6// R3 WCAG 1.3.1 : heading levels are not SKIPPED (h1 -> h3 with no h2 = fail) 7// R4 WCAG 4.1.2 : every non-hidden <input> has an accessible name (aria-label=) 8// Pure functions over an HTML buffer (defensive at the boundary, rule 12). license_tier: ORIGINAL 9// module: nishi-core.cms.a11y 10// depends: nishi-core.io.syscalls 11// capability: CMS 12import "nx_syscalls.nx" 13 14func a11_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 15 16// first offset of NUL-terminated needle in buf[from..n); -1 if absent 17func a11_find(buf: *u8, from: i64, n: i64, needle: *u8) -> i64 { 18 let nl: i64 = a11_slen(needle) 19 var i: i64 = from 20 while i + nl <= n { 21 var q: i64 = 0 22 var ok: i64 = 1 23 while q < nl { if (buf[i+q] as i64) != (needle[q] as i64) { ok = 0; q = nl } q = q + 1 } 24 if ok == 1 { return i } 25 i = i + 1 26 } 27 return 0 - 1 28} 29 30// offset of the next '>' at or after `from` (tag end), or n 31func a11_tag_end(buf: *u8, from: i64, n: i64) -> i64 { 32 var i: i64 = from 33 while i < n { if (buf[i] as i64) == 62 { return i } i = i + 1 } 34 return n 35} 36 37// R1 WCAG 3.1.1: document declares a language. find "<html", require "lang=" before that tag closes. 38func a11y_has_lang(buf: *u8, n: i64) -> i64 { 39 let h: i64 = a11_find(buf, 0, n, "<html" as *u8) 40 if h < 0 { return 0 } 41 let te: i64 = a11_tag_end(buf, h, n) 42 if a11_find(buf, h, te, "lang=" as *u8) >= 0 { return 1 } 43 return 0 44} 45 46// helper: does the attribute attr (e.g. "alt=") appear with a NON-EMPTY quoted value inside [from,end)? 47func a11_attr_nonempty(buf: *u8, from: i64, end: i64, attr: *u8) -> i64 { 48 let a: i64 = a11_find(buf, from, end, attr) 49 if a < 0 { return 0 } 50 let al: i64 = a11_slen(attr) 51 let vq: i64 = a + al // expected opening quote position 52 if vq + 1 >= end { return 0 } 53 if (buf[vq] as i64) != 34 { return 0 } // must be a double-quote 54 if (buf[vq+1] as i64) == 34 { return 0 } // empty value ("") -> fail 55 return 1 56} 57 58// R2 WCAG 1.1.1: every <img> has a non-empty alt=. returns 1 if all OK (or no imgs), 0 if any missing/empty. 59func a11y_imgs_alt(buf: *u8, n: i64) -> i64 { 60 var i: i64 = 0 61 while i >= 0 { 62 let p: i64 = a11_find(buf, i, n, "<img" as *u8) 63 if p < 0 { return 1 } 64 let te: i64 = a11_tag_end(buf, p, n) 65 if a11_attr_nonempty(buf, p, te, "alt=" as *u8) == 0 { return 0 } 66 i = te + 1 67 } 68 return 1 69} 70 71// R4 WCAG 4.1.2: every non-hidden <input> has aria-label=. returns 1 if all OK, 0 otherwise. 72func a11y_inputs_labeled(buf: *u8, n: i64) -> i64 { 73 var i: i64 = 0 74 while i >= 0 { 75 let p: i64 = a11_find(buf, i, n, "<input" as *u8) 76 if p < 0 { return 1 } 77 let te: i64 = a11_tag_end(buf, p, n) 78 var hidden: i64 = 0 79 if a11_find(buf, p, te, "type=\"hidden\"" as *u8) >= 0 { hidden = 1 } 80 if hidden == 0 { if a11_attr_nonempty(buf, p, te, "aria-label=" as *u8) == 0 { return 0 } } 81 i = te + 1 82 } 83 return 1 84} 85 86// R3 WCAG 1.3.1: heading levels not skipped (a jump of >1 going deeper = fail). 87func a11y_heading_order(buf: *u8, n: i64) -> i64 { 88 var prev: i64 = 0 89 var ok: i64 = 1 90 var i: i64 = 0 91 while i + 2 < n { 92 if (buf[i] as i64) == 60 { if (buf[i+1] as i64) == 104 { // "<h" 93 let d: i64 = buf[i+2] as i64 94 if d >= 49 { if d <= 54 { // '1'..'6' 95 let lvl: i64 = d - 48 96 if prev > 0 { if lvl > prev + 1 { ok = 0 } } 97 prev = lvl 98 } } 99 } } 100 i = i + 1 101 } 102 return ok 103} 104 105// full audit: number of the 4 rules satisfied (0..4) 106func a11y_audit(buf: *u8, n: i64) -> i64 { 107 var c: i64 = 0 108 if a11y_has_lang(buf, n) == 1 { c = c + 1 } 109 if a11y_imgs_alt(buf, n) == 1 { c = c + 1 } 110 if a11y_heading_order(buf, n) == 1 { c = c + 1 } 111 if a11y_inputs_labeled(buf, n) == 1 { c = c + 1 } 112 return c 113}