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