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