OCaml HTML5 parser/serialiser based on Python's JustHTML
1(** Typed error codes for HTML5 validation messages. *)
2
3type severity = Error | Warning | Info
4
5type attr_error = [
6 | `Not_allowed of [`Attr of string] * [`Elem of string]
7 | `Not_allowed_here of [`Attr of string]
8 | `Not_allowed_when of [`Attr of string] * [`Elem of string] * [`Condition of string]
9 | `Missing of [`Elem of string] * [`Attr of string]
10 | `Missing_one_of of [`Elem of string] * [`Attrs of string list]
11 | `Bad_value of [`Elem of string] * [`Attr of string] * [`Value of string] * [`Reason of string]
12 | `Bad_value_generic of [`Message of string]
13 | `Duplicate_id of [`Id of string]
14 | `Data_invalid_name of [`Reason of string]
15 | `Data_uppercase
16]
17
18type element_error = [
19 | `Obsolete of [`Elem of string] * [`Suggestion of string]
20 | `Obsolete_attr of [`Elem of string] * [`Attr of string] * [`Suggestion of string option]
21 | `Obsolete_global_attr of [`Attr of string] * [`Suggestion of string]
22 | `Not_allowed_as_child of [`Child of string] * [`Parent of string]
23 | `Unknown of [`Elem of string]
24 | `Must_not_descend of [`Elem of string] * [`Attr of string option] * [`Ancestor of string]
25 | `Missing_child of [`Parent of string] * [`Child of string]
26 | `Missing_child_one_of of [`Parent of string] * [`Children of string list]
27 | `Missing_child_generic of [`Parent of string]
28 | `Must_not_be_empty of [`Elem of string]
29 | `Text_not_allowed of [`Parent of string]
30]
31
32type tag_error = [
33 | `Stray_start of [`Tag of string]
34 | `Stray_end of [`Tag of string]
35 | `End_for_void of [`Tag of string]
36 | `Self_closing_non_void
37 | `Not_in_scope of [`Tag of string]
38 | `End_implied_open of [`Tag of string]
39 | `Start_in_table of [`Tag of string]
40 | `Bad_start_in of [`Tag of string] * [`Context of string]
41 | `Eof_with_open
42]
43
44type char_ref_error = [
45 | `Forbidden_codepoint of [`Codepoint of int]
46 | `Control_char of [`Codepoint of int]
47 | `Non_char of [`Codepoint of int] * [`Astral of bool]
48 | `Unassigned
49 | `Zero
50 | `Out_of_range
51 | `Carriage_return
52]
53
54type aria_error = [
55 | `Unnecessary_role of [`Role of string] * [`Elem of string] * [`Reason of string]
56 | `Bad_role of [`Elem of string] * [`Role of string]
57 | `Must_not_specify of [`Attr of string] * [`Elem of string] * [`Condition of string]
58 | `Must_not_use of [`Attr of string] * [`Elem of string] * [`Condition of string]
59 | `Should_not_use of [`Attr of string] * [`Role of string]
60 | `Hidden_on_body
61 | `Unrecognized_role of [`Token of string]
62 | `Tab_without_tabpanel
63 | `Multiple_main
64 | `Accessible_name_prohibited of [`Attr of string] * [`Elem of string]
65]
66
67type li_role_error = [
68 | `Div_in_dl_bad_role
69 | `Li_bad_role_in_menu
70 | `Li_bad_role_in_tablist
71 | `Li_bad_role_in_list
72]
73
74type table_error = [
75 | `Row_no_cells of [`Row of int]
76 | `Cell_overlap
77 | `Cell_spans_rowgroup
78 | `Column_no_cells of [`Column of int] * [`Elem of string]
79]
80
81type i18n_error = [
82 | `Missing_lang
83 | `Wrong_lang of [`Detected of string] * [`Declared of string] * [`Suggested of string]
84 | `Missing_dir_rtl of [`Language of string]
85 | `Wrong_dir of [`Language of string] * [`Declared of string]
86 | `Xml_lang_without_lang
87 | `Xml_lang_mismatch
88 | `Not_nfc of [`Replacement of string]
89]
90
91type importmap_error = [
92 | `Invalid_json
93 | `Invalid_root
94 | `Imports_not_object
95 | `Empty_key
96 | `Non_string_value
97 | `Key_trailing_slash
98 | `Scopes_not_object
99 | `Scopes_values_not_object
100 | `Scopes_invalid_url
101 | `Scopes_value_invalid_url
102]
103
104type img_error = [
105 | `Missing_alt
106 | `Missing_src_or_srcset
107 | `Empty_alt_with_role
108 | `Ismap_needs_href
109]
110
111type link_error = [
112 | `Missing_href
113 | `As_requires_preload
114 | `Imagesrcset_requires_as_image
115]
116
117type label_error = [
118 | `Too_many_labelable
119 | `For_id_mismatch
120 | `Role_on_ancestor
121 | `Role_on_for
122 | `Aria_label_on_for
123]
124
125type input_error = [
126 | `Checkbox_needs_aria_pressed
127 | `Value_constraint of [`Constraint of string]
128 | `List_not_allowed
129 | `List_requires_datalist
130]
131
132type srcset_error = [
133 | `Sizes_without_srcset
134 | `Imagesizes_without_imagesrcset
135 | `W_without_sizes
136 | `Source_missing_srcset
137 | `Source_needs_media_or_type
138 | `Picture_missing_img
139]
140
141type svg_error = [
142 | `Deprecated_attr of [`Attr of string] * [`Elem of string]
143 | `Missing_attr of [`Elem of string] * [`Attr of string]
144]
145
146type misc_error = [
147 | `Option_empty_without_label
148 | `Bdo_missing_dir
149 | `Bdo_dir_auto
150 | `Base_missing_href_or_target
151 | `Base_after_link_script
152 | `Map_id_name_mismatch
153 | `Summary_missing_role
154 | `Summary_missing_attrs
155 | `Summary_role_not_allowed
156 | `Autocomplete_webauthn_on_select
157 | `Commandfor_invalid_target
158 | `Style_type_invalid
159 | `Headingoffset_invalid
160 | `Media_empty
161 | `Media_all
162 | `Multiple_h1
163 | `Multiple_autofocus
164]
165
166type t = [
167 | `Attr of attr_error
168 | `Element of element_error
169 | `Tag of tag_error
170 | `Char_ref of char_ref_error
171 | `Aria of aria_error
172 | `Li_role of li_role_error
173 | `Table of table_error
174 | `I18n of i18n_error
175 | `Importmap of importmap_error
176 | `Img of img_error
177 | `Link of link_error
178 | `Label of label_error
179 | `Input of input_error
180 | `Srcset of srcset_error
181 | `Svg of svg_error
182 | `Misc of misc_error
183 | `Generic of string
184]
185
186(** Get the severity level for an error code *)
187let severity : t -> severity = function
188 (* Info level *)
189 | `I18n `Missing_lang -> Info
190 | `Misc `Multiple_h1 -> Info
191
192 (* Warning level *)
193 | `I18n (`Wrong_lang _) -> Warning
194 | `I18n (`Missing_dir_rtl _) -> Warning
195 | `I18n (`Wrong_dir _) -> Warning
196 | `I18n (`Not_nfc _) -> Warning
197 | `Aria (`Unnecessary_role _) -> Warning
198 | `Aria (`Should_not_use _) -> Warning
199 | `Element (`Unknown _) -> Warning
200
201 (* Everything else is Error *)
202 | _ -> Error
203
204(** Get a short code string for categorization *)
205let code_string : t -> string = function
206 (* Attribute errors *)
207 | `Attr (`Not_allowed _) -> "disallowed-attribute"
208 | `Attr (`Not_allowed_here _) -> "disallowed-attribute"
209 | `Attr (`Not_allowed_when _) -> "disallowed-attribute"
210 | `Attr (`Missing _) -> "missing-required-attribute"
211 | `Attr (`Missing_one_of _) -> "missing-required-attribute"
212 | `Attr (`Bad_value _) -> "bad-attribute-value"
213 | `Attr (`Bad_value_generic _) -> "bad-attribute-value"
214 | `Attr (`Duplicate_id _) -> "duplicate-id"
215 | `Attr (`Data_invalid_name _) -> "bad-attribute-name"
216 | `Attr `Data_uppercase -> "bad-attribute-name"
217
218 (* Element errors *)
219 | `Element (`Obsolete _) -> "obsolete-element"
220 | `Element (`Obsolete_attr _) -> "obsolete-attribute"
221 | `Element (`Obsolete_global_attr _) -> "obsolete-attribute"
222 | `Element (`Not_allowed_as_child _) -> "disallowed-child"
223 | `Element (`Unknown _) -> "unknown-element"
224 | `Element (`Must_not_descend _) -> "prohibited-ancestor"
225 | `Element (`Missing_child _) -> "missing-required-child"
226 | `Element (`Missing_child_one_of _) -> "missing-required-child"
227 | `Element (`Missing_child_generic _) -> "missing-required-child"
228 | `Element (`Must_not_be_empty _) -> "empty-element"
229 | `Element (`Text_not_allowed _) -> "text-not-allowed"
230
231 (* Tag errors *)
232 | `Tag (`Stray_start _) -> "stray-tag"
233 | `Tag (`Stray_end _) -> "stray-tag"
234 | `Tag (`End_for_void _) -> "end-tag-void"
235 | `Tag `Self_closing_non_void -> "self-closing-non-void"
236 | `Tag (`Not_in_scope _) -> "no-element-in-scope"
237 | `Tag (`End_implied_open _) -> "end-tag-implied"
238 | `Tag (`Start_in_table _) -> "start-tag-in-table"
239 | `Tag (`Bad_start_in _) -> "bad-start-tag"
240 | `Tag `Eof_with_open -> "eof-open-elements"
241
242 (* Character reference errors *)
243 | `Char_ref (`Forbidden_codepoint _) -> "forbidden-codepoint"
244 | `Char_ref (`Control_char _) -> "char-ref-control"
245 | `Char_ref (`Non_char _) -> "char-ref-non-char"
246 | `Char_ref `Unassigned -> "char-ref-unassigned"
247 | `Char_ref `Zero -> "char-ref-zero"
248 | `Char_ref `Out_of_range -> "char-ref-range"
249 | `Char_ref `Carriage_return -> "numeric-char-ref"
250
251 (* ARIA errors *)
252 | `Aria (`Unnecessary_role _) -> "unnecessary-role"
253 | `Aria (`Bad_role _) -> "bad-role"
254 | `Aria (`Must_not_specify _) -> "aria-not-allowed"
255 | `Aria (`Must_not_use _) -> "aria-not-allowed"
256 | `Aria (`Should_not_use _) -> "aria-not-allowed"
257 | `Aria `Hidden_on_body -> "aria-not-allowed"
258 | `Aria (`Unrecognized_role _) -> "unrecognized-role"
259 | `Aria `Tab_without_tabpanel -> "tab-without-tabpanel"
260 | `Aria `Multiple_main -> "multiple-main"
261 | `Aria (`Accessible_name_prohibited _) -> "aria-not-allowed"
262
263 (* List item role errors *)
264 | `Li_role `Div_in_dl_bad_role -> "invalid-role"
265 | `Li_role `Li_bad_role_in_menu -> "invalid-role"
266 | `Li_role `Li_bad_role_in_tablist -> "invalid-role"
267 | `Li_role `Li_bad_role_in_list -> "invalid-role"
268
269 (* Table errors *)
270 | `Table (`Row_no_cells _) -> "table-row"
271 | `Table `Cell_overlap -> "table-overlap"
272 | `Table `Cell_spans_rowgroup -> "table-span"
273 | `Table (`Column_no_cells _) -> "table-column"
274
275 (* I18n errors *)
276 | `I18n `Missing_lang -> "missing-lang"
277 | `I18n (`Wrong_lang _) -> "wrong-lang"
278 | `I18n (`Missing_dir_rtl _) -> "missing-dir"
279 | `I18n (`Wrong_dir _) -> "wrong-dir"
280 | `I18n `Xml_lang_without_lang -> "xml-lang"
281 | `I18n `Xml_lang_mismatch -> "xml-lang-mismatch"
282 | `I18n (`Not_nfc _) -> "unicode-normalization"
283
284 (* Import map errors *)
285 | `Importmap `Invalid_json -> "importmap"
286 | `Importmap `Invalid_root -> "importmap"
287 | `Importmap `Imports_not_object -> "importmap"
288 | `Importmap `Empty_key -> "importmap"
289 | `Importmap `Non_string_value -> "importmap"
290 | `Importmap `Key_trailing_slash -> "importmap"
291 | `Importmap `Scopes_not_object -> "importmap"
292 | `Importmap `Scopes_values_not_object -> "importmap"
293 | `Importmap `Scopes_invalid_url -> "importmap"
294 | `Importmap `Scopes_value_invalid_url -> "importmap"
295
296 (* Image errors *)
297 | `Img `Missing_alt -> "missing-alt"
298 | `Img `Missing_src_or_srcset -> "missing-src"
299 | `Img `Empty_alt_with_role -> "img-alt-role"
300 | `Img `Ismap_needs_href -> "ismap-needs-href"
301
302 (* Link errors *)
303 | `Link `Missing_href -> "missing-href"
304 | `Link `As_requires_preload -> "link-as-preload"
305 | `Link `Imagesrcset_requires_as_image -> "link-imagesrcset"
306
307 (* Label errors *)
308 | `Label `Too_many_labelable -> "label-multiple"
309 | `Label `For_id_mismatch -> "label-for-mismatch"
310 | `Label `Role_on_ancestor -> "role-on-label"
311 | `Label `Role_on_for -> "role-on-label"
312 | `Label `Aria_label_on_for -> "aria-label-on-label"
313
314 (* Input errors *)
315 | `Input `Checkbox_needs_aria_pressed -> "missing-aria-pressed"
316 | `Input (`Value_constraint _) -> "input-value"
317 | `Input `List_not_allowed -> "list-not-allowed"
318 | `Input `List_requires_datalist -> "list-datalist"
319
320 (* Srcset errors *)
321 | `Srcset `Sizes_without_srcset -> "sizes-without-srcset"
322 | `Srcset `Imagesizes_without_imagesrcset -> "imagesizes-without-srcset"
323 | `Srcset `W_without_sizes -> "srcset-needs-sizes"
324 | `Srcset `Source_missing_srcset -> "missing-srcset"
325 | `Srcset `Source_needs_media_or_type -> "source-needs-media"
326 | `Srcset `Picture_missing_img -> "picture-missing-img"
327
328 (* SVG errors *)
329 | `Svg (`Deprecated_attr _) -> "svg-deprecated"
330 | `Svg (`Missing_attr _) -> "missing-required-attribute"
331
332 (* Misc errors *)
333 | `Misc `Option_empty_without_label -> "empty-option"
334 | `Misc `Bdo_missing_dir -> "missing-dir"
335 | `Misc `Bdo_dir_auto -> "bdo-dir-auto"
336 | `Misc `Base_missing_href_or_target -> "missing-required-attribute"
337 | `Misc `Base_after_link_script -> "base-position"
338 | `Misc `Map_id_name_mismatch -> "map-id-name"
339 | `Misc `Summary_missing_role -> "summary-role"
340 | `Misc `Summary_missing_attrs -> "summary-attrs"
341 | `Misc `Summary_role_not_allowed -> "summary-role"
342 | `Misc `Autocomplete_webauthn_on_select -> "autocomplete"
343 | `Misc `Commandfor_invalid_target -> "commandfor"
344 | `Misc `Style_type_invalid -> "style-type"
345 | `Misc `Headingoffset_invalid -> "headingoffset"
346 | `Misc `Media_empty -> "media-empty"
347 | `Misc `Media_all -> "media-all"
348 | `Misc `Multiple_h1 -> "multiple-h1"
349 | `Misc `Multiple_autofocus -> "multiple-autofocus"
350
351 (* Generic *)
352 | `Generic _ -> "generic"
353
354(** Format using curly quotes (Unicode) *)
355let q s = "\xe2\x80\x9c" ^ s ^ "\xe2\x80\x9d"
356
357(** Convert error code to exact Nu validator message string *)
358let to_message : t -> string = function
359 (* Attribute errors *)
360 | `Attr (`Not_allowed (`Attr attr, `Elem element)) ->
361 Printf.sprintf "Attribute %s not allowed on element %s at this point."
362 (q attr) (q element)
363 | `Attr (`Not_allowed_here (`Attr attr)) ->
364 Printf.sprintf "Attribute %s not allowed here." (q attr)
365 | `Attr (`Not_allowed_when (`Attr attr, `Elem _, `Condition condition)) ->
366 Printf.sprintf "The %s attribute must not be used on any element which has %s." (q attr) condition
367 | `Attr (`Missing (`Elem element, `Attr attr)) ->
368 Printf.sprintf "Element %s is missing required attribute %s."
369 (q element) (q attr)
370 | `Attr (`Missing_one_of (`Elem element, `Attrs attrs)) ->
371 let attrs_str = String.concat ", " attrs in
372 Printf.sprintf "Element %s is missing one or more of the following attributes: [%s]."
373 (q element) attrs_str
374 | `Attr (`Bad_value (`Elem element, `Attr attr, `Value value, `Reason reason)) ->
375 Printf.sprintf "Bad value %s for attribute %s on element %s: %s"
376 (q value) (q attr) (q element) reason
377 | `Attr (`Bad_value_generic (`Message message)) -> message
378 | `Attr (`Duplicate_id (`Id id)) ->
379 Printf.sprintf "Duplicate ID %s." (q id)
380 | `Attr (`Data_invalid_name (`Reason reason)) ->
381 Printf.sprintf "%s attribute names %s." (q "data-*") reason
382 | `Attr `Data_uppercase ->
383 Printf.sprintf "%s attributes must not have characters from the range %s\xe2\x80\xa6%s in the name."
384 (q "data-*") (q "A") (q "Z")
385
386 (* Element errors *)
387 | `Element (`Obsolete (`Elem element, `Suggestion suggestion)) ->
388 if suggestion = "" then
389 Printf.sprintf "The %s element is obsolete." (q element)
390 else
391 Printf.sprintf "The %s element is obsolete. %s" (q element) suggestion
392 | `Element (`Obsolete_attr (`Elem element, `Attr attr, `Suggestion suggestion)) ->
393 let base = Printf.sprintf "The %s attribute on the %s element is obsolete."
394 (q attr) (q element) in
395 (match suggestion with Some s -> base ^ " " ^ s | None -> base)
396 | `Element (`Obsolete_global_attr (`Attr attr, `Suggestion suggestion)) ->
397 Printf.sprintf "The %s attribute is obsolete. %s" (q attr) suggestion
398 | `Element (`Not_allowed_as_child (`Child child, `Parent parent)) ->
399 Printf.sprintf "Element %s not allowed as child of element %s in this context. (Suppressing further errors from this subtree.)"
400 (q child) (q parent)
401 | `Element (`Unknown (`Elem name)) ->
402 Printf.sprintf "Unknown element %s." (q name)
403 | `Element (`Must_not_descend (`Elem element, `Attr attr, `Ancestor ancestor)) ->
404 (match attr with
405 | Some a ->
406 Printf.sprintf "The element %s with the attribute %s must not appear as a descendant of the %s element."
407 (q element) (q a) (q ancestor)
408 | None ->
409 Printf.sprintf "The element %s must not appear as a descendant of the %s element."
410 (q element) (q ancestor))
411 | `Element (`Missing_child (`Parent parent, `Child child)) ->
412 Printf.sprintf "Element %s is missing required child element %s."
413 (q parent) (q child)
414 | `Element (`Missing_child_one_of (`Parent parent, `Children children)) ->
415 let children_str = String.concat ", " children in
416 Printf.sprintf "Element %s is missing one or more of the following child elements: [%s]."
417 (q parent) children_str
418 | `Element (`Missing_child_generic (`Parent parent)) ->
419 Printf.sprintf "Element %s is missing a required child element." (q parent)
420 | `Element (`Must_not_be_empty (`Elem element)) ->
421 Printf.sprintf "Element %s must not be empty." (q element)
422 | `Element (`Text_not_allowed (`Parent parent)) ->
423 Printf.sprintf "Text not allowed in element %s in this context." (q parent)
424
425 (* Tag errors *)
426 | `Tag (`Stray_start (`Tag tag)) ->
427 Printf.sprintf "Stray start tag %s." (q tag)
428 | `Tag (`Stray_end (`Tag tag)) ->
429 Printf.sprintf "Stray end tag %s." (q tag)
430 | `Tag (`End_for_void (`Tag tag)) ->
431 Printf.sprintf "End tag %s." (q tag)
432 | `Tag `Self_closing_non_void ->
433 Printf.sprintf "Self-closing syntax (%s) used on a non-void HTML element. Ignoring the slash and treating as a start tag."
434 (q "/>")
435 | `Tag (`Not_in_scope (`Tag tag)) ->
436 Printf.sprintf "No %s element in scope but a %s end tag seen."
437 (q tag) (q tag)
438 | `Tag (`End_implied_open (`Tag tag)) ->
439 Printf.sprintf "End tag %s implied, but there were open elements."
440 (q tag)
441 | `Tag (`Start_in_table (`Tag tag)) ->
442 Printf.sprintf "Start tag %s seen in %s." (q tag) (q "table")
443 | `Tag (`Bad_start_in (`Tag tag, `Context _)) ->
444 Printf.sprintf "Bad start tag in %s in %s in %s."
445 (q tag) (q "noscript") (q "head")
446 | `Tag `Eof_with_open ->
447 "End of file seen and there were open elements."
448
449 (* Character reference errors *)
450 | `Char_ref (`Forbidden_codepoint (`Codepoint codepoint)) ->
451 Printf.sprintf "Forbidden code point U+%04x." codepoint
452 | `Char_ref (`Control_char (`Codepoint codepoint)) ->
453 Printf.sprintf "Character reference expands to a control character (U+%04x)." codepoint
454 | `Char_ref (`Non_char (`Codepoint codepoint, `Astral astral)) ->
455 if astral then
456 Printf.sprintf "Character reference expands to an astral non-character (U+%05x)." codepoint
457 else
458 Printf.sprintf "Character reference expands to a non-character (U+%04x)." codepoint
459 | `Char_ref `Unassigned ->
460 "Character reference expands to a permanently unassigned code point."
461 | `Char_ref `Zero ->
462 "Character reference expands to zero."
463 | `Char_ref `Out_of_range ->
464 "Character reference outside the permissible Unicode range."
465 | `Char_ref `Carriage_return ->
466 "A numeric character reference expanded to carriage return."
467
468 (* ARIA errors *)
469 | `Aria (`Unnecessary_role (`Role role, `Elem _, `Reason reason)) ->
470 Printf.sprintf "The %s role is unnecessary %s."
471 (q role) reason
472 | `Aria (`Bad_role (`Elem element, `Role role)) ->
473 Printf.sprintf "Bad value %s for attribute %s on element %s."
474 (q role) (q "role") (q element)
475 | `Aria (`Must_not_specify (`Attr attr, `Elem element, `Condition condition)) ->
476 Printf.sprintf "The %s attribute must not be specified on any %s element unless %s."
477 (q attr) (q element) condition
478 | `Aria (`Must_not_use (`Attr attr, `Elem element, `Condition condition)) ->
479 Printf.sprintf "The %s attribute must not be used on an %s element which has %s."
480 (q attr) (q element) condition
481 | `Aria (`Should_not_use (`Attr attr, `Role role)) ->
482 Printf.sprintf "The %s attribute should not be used on any element which has %s."
483 (q attr) (q ("role=" ^ role))
484 | `Aria `Hidden_on_body ->
485 Printf.sprintf "%s must not be used on the %s element."
486 (q "aria-hidden=true") (q "body")
487 | `Aria (`Unrecognized_role (`Token token)) ->
488 Printf.sprintf "Discarding unrecognized token %s from value of attribute %s. Browsers ignore any token that is not a defined ARIA non-abstract role."
489 (q token) (q "role")
490 | `Aria `Tab_without_tabpanel ->
491 Printf.sprintf "Every active %s element must have a corresponding %s element."
492 (q "role=tab") (q "role=tabpanel")
493 | `Aria `Multiple_main ->
494 Printf.sprintf "A document should not include more than one visible element with %s."
495 (q "role=main")
496 | `Aria (`Accessible_name_prohibited (`Attr attr, `Elem element)) ->
497 (* Roles that prohibit accessible names - defined by ARIA spec *)
498 let prohibited_roles = [
499 "caption"; "code"; "deletion"; "emphasis"; "generic"; "insertion";
500 "paragraph"; "presentation"; "strong"; "subscript"; "superscript"
501 ] in
502 let roles_str = String.concat ", " (List.map q (List.rev (List.tl (List.rev prohibited_roles)))) ^
503 ", or " ^ q (List.hd (List.rev prohibited_roles)) in
504 Printf.sprintf "The %s attribute must not be specified on any %s element unless the element has a %s value other than %s."
505 (q attr) (q element) (q "role") roles_str
506
507 (* List item role errors *)
508 | `Li_role `Div_in_dl_bad_role ->
509 Printf.sprintf "A %s child of a %s element must not have any %s value other than %s or %s."
510 (q "div") (q "dl") (q "role") (q "presentation") (q "none")
511 | `Li_role `Li_bad_role_in_menu ->
512 Printf.sprintf "An %s element that is a descendant of a %s element or %s element must not have any %s value other than %s, %s, %s, %s, or %s."
513 (q "li") (q "role=menu") (q "role=menubar") (q "role")
514 (q "group") (q "menuitem") (q "menuitemcheckbox") (q "menuitemradio") (q "separator")
515 | `Li_role `Li_bad_role_in_tablist ->
516 Printf.sprintf "An %s element that is a descendant of a %s element must not have any %s value other than %s."
517 (q "li") (q "role=tablist") (q "role") (q "tab")
518 | `Li_role `Li_bad_role_in_list ->
519 Printf.sprintf "An %s element that is a descendant of a %s, %s, or %s element with no explicit %s value, or a descendant of a %s element, must not have any %s value other than %s."
520 (q "li") (q "ul") (q "ol") (q "menu") (q "role") (q "role=list") (q "role") (q "listitem")
521
522 (* Table errors *)
523 | `Table (`Row_no_cells (`Row row)) ->
524 Printf.sprintf "Row %d of an implicit row group has no cells beginning on it." row
525 | `Table `Cell_overlap ->
526 "Table cell is overlapped by later table cell."
527 | `Table `Cell_spans_rowgroup ->
528 Printf.sprintf "Table cell spans past the end of its row group established by a %s element; clipped to the end of the row group."
529 (q "tbody")
530 | `Table (`Column_no_cells (`Column column, `Elem element)) ->
531 Printf.sprintf "Table column %d established by element %s has no cells beginning in it."
532 column (q element)
533
534 (* I18n errors *)
535 | `I18n `Missing_lang ->
536 Printf.sprintf "Consider adding a %s attribute to the %s start tag to declare the language of this document."
537 (q "lang") (q "html")
538 | `I18n (`Wrong_lang (`Detected detected, `Declared declared, `Suggested suggested)) ->
539 Printf.sprintf "This document appears to be written in %s but the %s start tag has %s. Consider using %s (or variant) instead."
540 detected (q "html") (q ("lang=\"" ^ declared ^ "\"")) (q ("lang=\"" ^ suggested ^ "\""))
541 | `I18n (`Missing_dir_rtl (`Language language)) ->
542 Printf.sprintf "This document appears to be written in %s. Consider adding %s to the %s start tag."
543 language (q "dir=\"rtl\"") (q "html")
544 | `I18n (`Wrong_dir (`Language language, `Declared declared)) ->
545 Printf.sprintf "This document appears to be written in %s but the %s start tag has %s. Consider using %s instead."
546 language (q "html") (q ("dir=\"" ^ declared ^ "\"")) (q "dir=\"rtl\"")
547 | `I18n `Xml_lang_without_lang ->
548 Printf.sprintf "When the attribute %s in no namespace is specified, the element must also have the attribute %s present with the same value."
549 (q "xml:lang") (q "lang")
550 | `I18n `Xml_lang_mismatch ->
551 Printf.sprintf "The %s and %s attributes must have the same value."
552 (q "xml:lang") (q "lang")
553 | `I18n (`Not_nfc (`Replacement replacement)) ->
554 Printf.sprintf "Text run is not in Unicode Normalization Form C. Should instead be %s. (Copy and paste that into your source document to replace the un-normalized text.)"
555 (q replacement)
556
557 (* Import map errors *)
558 | `Importmap `Invalid_json ->
559 Printf.sprintf "A script %s with a %s attribute whose value is %s must have valid JSON content."
560 (q "script") (q "type") (q "importmap")
561 | `Importmap `Invalid_root ->
562 Printf.sprintf "A %s element with a %s attribute whose value is %s must contain a JSON object with no properties other than %s, %s, and %s."
563 (q "script") (q "type") (q "importmap") (q "imports") (q "scopes") (q "integrity")
564 | `Importmap `Imports_not_object ->
565 Printf.sprintf "The value of the %s property within the content of a %s element with a %s attribute whose value is %s must be a JSON object."
566 (q "imports") (q "script") (q "type") (q "importmap")
567 | `Importmap `Empty_key ->
568 Printf.sprintf "A specifier map defined in a %s property within the content of a %s element with a %s attribute whose value is %s must only contain non-empty keys."
569 (q "imports") (q "script") (q "type") (q "importmap")
570 | `Importmap `Non_string_value ->
571 Printf.sprintf "A specifier map defined in a %s property within the content of a %s element with a %s attribute whose value is %s must only contain string values."
572 (q "imports") (q "script") (q "type") (q "importmap")
573 | `Importmap `Key_trailing_slash ->
574 Printf.sprintf "A specifier map defined in a %s property within the content of a %s element with a %s attribute whose value is %s must have values that end with %s when its corresponding key ends with %s."
575 (q "imports") (q "script") (q "type") (q "importmap") (q "/") (q "/")
576 | `Importmap `Scopes_not_object ->
577 Printf.sprintf "The value of the %s property within the content of a %s element with a %s attribute whose value is %s must be a JSON object whose keys are valid URL strings."
578 (q "scopes") (q "script") (q "type") (q "importmap")
579 | `Importmap `Scopes_values_not_object ->
580 Printf.sprintf "The value of the %s property within the content of a %s element with a %s attribute whose value is %s must be a JSON object whose values are also JSON objects."
581 (q "scopes") (q "script") (q "type") (q "importmap")
582 | `Importmap `Scopes_invalid_url ->
583 Printf.sprintf "The value of the %s property within the content of a %s element with a %s attribute whose value is %s must be a JSON object whose keys are valid URL strings."
584 (q "scopes") (q "script") (q "type") (q "importmap")
585 | `Importmap `Scopes_value_invalid_url ->
586 Printf.sprintf "A specifier map defined in a %s property within the content of a %s element with a %s attribute whose value is %s must only contain valid URL values."
587 (q "scopes") (q "script") (q "type") (q "importmap")
588
589 (* Image errors *)
590 | `Img `Missing_alt ->
591 Printf.sprintf "An %s element must have an %s attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images."
592 (q "img") (q "alt")
593 | `Img `Missing_src_or_srcset ->
594 Printf.sprintf "Element %s is missing one or more of the following attributes: [src, srcset]."
595 (q "img")
596 | `Img `Empty_alt_with_role ->
597 Printf.sprintf "An %s element which has an %s attribute whose value is the empty string must not have a %s attribute."
598 (q "img") (q "alt") (q "role")
599 | `Img `Ismap_needs_href ->
600 Printf.sprintf "The %s element with the %s attribute set must have an %s ancestor with the %s attribute."
601 (q "img") (q "ismap") (q "a") (q "href")
602
603 (* Link errors *)
604 | `Link `Missing_href ->
605 Printf.sprintf "A %s element must have an %s or %s attribute, or both."
606 (q "link") (q "href") (q "imagesrcset")
607 | `Link `As_requires_preload ->
608 Printf.sprintf "A %s element with an %s attribute must have a %s attribute that contains the value %s or the value %s."
609 (q "link") (q "as") (q "rel") (q "preload") (q "modulepreload")
610 | `Link `Imagesrcset_requires_as_image ->
611 Printf.sprintf "A %s element with an %s attribute must have an %s attribute with value %s."
612 (q "link") (q "imagesrcset") (q "as") (q "image")
613
614 (* Label errors *)
615 | `Label `Too_many_labelable ->
616 Printf.sprintf "The %s element may contain at most one %s, %s, %s, %s, %s, %s, or %s descendant."
617 (q "label") (q "button") (q "input") (q "meter") (q "output") (q "progress") (q "select") (q "textarea")
618 | `Label `For_id_mismatch ->
619 Printf.sprintf "Any %s descendant of a %s element with a %s attribute must have an ID value that matches that %s attribute."
620 (q "input") (q "label") (q "for") (q "for")
621 | `Label `Role_on_ancestor ->
622 Printf.sprintf "The %s attribute must not be used on any %s element that is an ancestor of a labelable element."
623 (q "role") (q "label")
624 | `Label `Role_on_for ->
625 Printf.sprintf "The %s attribute must not be used on any %s element that is associated with a labelable element."
626 (q "role") (q "label")
627 | `Label `Aria_label_on_for ->
628 Printf.sprintf "The %s attribute must not be used on any %s element that is associated with a labelable element."
629 (q "aria-label") (q "label")
630
631 (* Input errors *)
632 | `Input `Checkbox_needs_aria_pressed ->
633 Printf.sprintf "An %s element with a %s attribute whose value is %s and with a %s attribute whose value is %s must have an %s attribute."
634 (q "input") (q "type") (q "checkbox") (q "role") (q "button") (q "aria-pressed")
635 | `Input (`Value_constraint (`Constraint constraint_type)) -> constraint_type
636 | `Input `List_not_allowed ->
637 Printf.sprintf "Attribute %s is only allowed when the input type is %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, or %s."
638 (q "list") (q "color") (q "date") (q "datetime-local") (q "email") (q "month")
639 (q "number") (q "range") (q "search") (q "tel") (q "text") (q "time") (q "url") (q "week")
640 | `Input `List_requires_datalist ->
641 Printf.sprintf "The %s attribute of the %s element must refer to a %s element."
642 (q "list") (q "input") (q "datalist")
643
644 (* Srcset errors *)
645 | `Srcset `Sizes_without_srcset ->
646 Printf.sprintf "The %s attribute must only be specified if the %s attribute is also specified."
647 (q "sizes") (q "srcset")
648 | `Srcset `Imagesizes_without_imagesrcset ->
649 Printf.sprintf "The %s attribute must only be specified if the %s attribute is also specified."
650 (q "imagesizes") (q "imagesrcset")
651 | `Srcset `W_without_sizes ->
652 Printf.sprintf "When the %s attribute has any image candidate string with a width descriptor, the %s attribute must also be specified."
653 (q "srcset") (q "sizes")
654 | `Srcset `Source_missing_srcset ->
655 Printf.sprintf "Element %s is missing required attribute %s."
656 (q "source") (q "srcset")
657 | `Srcset `Source_needs_media_or_type ->
658 Printf.sprintf "A %s element that has a following sibling %s element or %s element with a %s attribute must have a %s attribute and/or %s attribute."
659 (q "source") (q "source") (q "img") (q "srcset") (q "media") (q "type")
660 | `Srcset `Picture_missing_img ->
661 Printf.sprintf "Element %s is missing required child element %s."
662 (q "picture") (q "img")
663
664 (* SVG errors *)
665 | `Svg (`Deprecated_attr (`Attr attr, `Elem element)) ->
666 Printf.sprintf "Attribute %s not allowed on element %s at this point."
667 (q attr) (q element)
668 | `Svg (`Missing_attr (`Elem element, `Attr attr)) ->
669 Printf.sprintf "Element %s is missing required attribute %s."
670 (q element) (q attr)
671
672 (* Misc errors *)
673 | `Misc `Option_empty_without_label ->
674 Printf.sprintf "Element %s without attribute %s must not be empty."
675 (q "option") (q "label")
676 | `Misc `Bdo_missing_dir ->
677 Printf.sprintf "Element %s must have attribute %s." (q "bdo") (q "dir")
678 | `Misc `Bdo_dir_auto ->
679 Printf.sprintf "The value of %s attribute for the %s element must not be %s."
680 (q "dir") (q "bdo") (q "auto")
681 | `Misc `Base_missing_href_or_target ->
682 Printf.sprintf "Element %s is missing one or more of the following attributes: [href, target]."
683 (q "base")
684 | `Misc `Base_after_link_script ->
685 Printf.sprintf "The %s element must come before any %s or %s elements in the document."
686 (q "base") (q "link") (q "script")
687 | `Misc `Map_id_name_mismatch ->
688 Printf.sprintf "The %s attribute on a %s element must have an the same value as the %s attribute."
689 (q "id") (q "map") (q "name")
690 | `Misc `Summary_missing_role ->
691 Printf.sprintf "Element %s is missing required attribute %s."
692 (q "summary") (q "role")
693 | `Misc `Summary_missing_attrs ->
694 Printf.sprintf "Element %s is missing one or more of the following attributes: [aria-checked, aria-level, role]."
695 (q "summary")
696 | `Misc `Summary_role_not_allowed ->
697 Printf.sprintf "The %s attribute must not be used on any %s element that is a summary for its parent %s element."
698 (q "role") (q "summary") (q "details")
699 | `Misc `Autocomplete_webauthn_on_select ->
700 Printf.sprintf "The value of the %s attribute for the %s element must not contain %s."
701 (q "autocomplete") (q "select") (q "webauthn")
702 | `Misc `Commandfor_invalid_target ->
703 Printf.sprintf "The value of the %s attribute of the %s element must be the ID of an element in the same tree as the %s with the %s attribute."
704 (q "commandfor") (q "button") (q "button") (q "commandfor")
705 | `Misc `Style_type_invalid ->
706 Printf.sprintf "The only allowed value for the %s attribute for the %s element is %s (with no parameters). (But the attribute is not needed and should be omitted altogether.)"
707 (q "type") (q "style") (q "text/css")
708 | `Misc `Headingoffset_invalid ->
709 Printf.sprintf "The value of the %s attribute must be a number between %s and %s."
710 (q "headingoffset") (q "0") (q "8")
711 | `Misc `Media_empty ->
712 Printf.sprintf "Value of %s attribute here must not be empty." (q "media")
713 | `Misc `Media_all ->
714 Printf.sprintf "Value of %s attribute here must not be %s." (q "media") (q "all")
715 | `Misc `Multiple_h1 ->
716 Printf.sprintf "Consider using only one %s element per document (or, if using %s elements multiple times is required, consider using the %s attribute to indicate that these %s elements are not all top-level headings)."
717 (q "h1") (q "h1") (q "headingoffset") (q "h1")
718 | `Misc `Multiple_autofocus ->
719 Printf.sprintf "There must not be two elements with the same %s that both have the %s attribute specified."
720 (q "nearest ancestor autofocus scoping root element") (q "autofocus")
721
722 (* Generic *)
723 | `Generic message -> message