OCaml HTML5 parser/serialiser based on Python's JustHTML
1(** Typed error codes for HTML5 validation messages.
2
3 This module defines a comprehensive hierarchy of validation errors using
4 polymorphic variants, organized by error category. Each error type is
5 documented with the specific HTML5 conformance requirement it represents.
6
7 The error hierarchy is:
8 - {!t} is the top-level type containing all errors wrapped by category
9 - Each category (e.g., {!attr_error}, {!aria_error}) groups related errors
10 - Inline descriptors like [[\`Attr of string]] provide self-documenting parameters
11
12 {2 Example Usage}
13
14 {[
15 (* Category-level matching *)
16 let is_accessibility_error = function
17 | `Aria _ | `Li_role _ -> true
18 | _ -> false
19
20 (* Fine-grained matching *)
21 match err with
22 | `Attr (`Duplicate_id (`Id id)) -> handle_duplicate id
23 | `Img `Missing_alt -> suggest_alt_text ()
24 | _ -> default_handler err
25 ]}
26*)
27
28(** {1 Severity} *)
29
30(** Severity level of a validation message.
31 - [Error]: Conformance error that must be fixed
32 - [Warning]: Likely problem that should be reviewed
33 - [Info]: Suggestion for best practices *)
34type severity = Error | Warning | Info
35
36(** {1 Attribute Errors}
37
38 Errors related to HTML attributes: disallowed attributes, missing required
39 attributes, invalid attribute values, and duplicate IDs. *)
40
41(** Attribute-related validation errors.
42
43 These errors occur when attributes violate HTML5 content model rules:
44 - Attributes used on elements where they're not allowed
45 - Required attributes that are missing
46 - Attribute values that don't match expected formats
47 - Duplicate ID attributes within a document *)
48type attr_error = [
49 | `Not_allowed of [`Attr of string] * [`Elem of string]
50 (** Attribute is not in the set of allowed attributes for this element.
51 Per HTML5 spec, each element has a defined set of content attributes;
52 using attributes outside this set is a conformance error.
53 Example: [type] attribute on a [<div>] element. *)
54
55 | `Not_allowed_here of [`Attr of string]
56 (** Attribute is valid on this element type but not in this context.
57 Some attributes are only allowed under specific conditions, such as
58 the [download] attribute which requires specific ancestor elements. *)
59
60 | `Not_allowed_when of [`Attr of string] * [`Elem of string] * [`Condition of string]
61 (** Attribute conflicts with another attribute or element state.
62 Example: [readonly] and [disabled] together, or [multiple] on
63 certain input types where it's not supported. *)
64
65 | `Missing of [`Elem of string] * [`Attr of string]
66 (** Element is missing a required attribute.
67 Per HTML5, certain elements have required attributes for conformance.
68 Example: [<img>] requires [src] or [srcset], [<input>] requires [type]. *)
69
70 | `Missing_one_of of [`Elem of string] * [`Attrs of string list]
71 (** Element must have at least one of the listed attributes.
72 Some elements require at least one from a set of attributes.
73 Example: [<base>] needs [href] or [target] (or both). *)
74
75 | `Bad_value of [`Elem of string] * [`Attr of string] * [`Value of string] * [`Reason of string]
76 (** Attribute value doesn't match the expected format or enumeration.
77 HTML5 defines specific value spaces for many attributes (enumerations,
78 URLs, integers, etc.). This error indicates the value is malformed. *)
79
80 | `Bad_value_generic of [`Message of string]
81 (** Generic bad attribute value with custom message.
82 Used when the specific validation failure requires a custom explanation
83 that doesn't fit the standard bad value template. *)
84
85 | `Duplicate_id of [`Id of string]
86 (** Document contains multiple elements with the same ID.
87 Per HTML5, the [id] attribute must be unique within a document.
88 Duplicate IDs cause problems with fragment navigation, label
89 association, and JavaScript DOM queries. *)
90
91 | `Data_invalid_name of [`Reason of string]
92 (** Custom data attribute name violates naming rules.
93 [data-*] attribute names must be valid XML NCNames (no colons,
94 must start with letter or underscore). The reason explains
95 the specific naming violation. *)
96
97 | `Data_uppercase
98 (** Custom data attribute name contains uppercase letters.
99 [data-*] attribute names must not contain ASCII uppercase letters
100 (A-Z) per HTML5. Use lowercase with hyphens instead. *)
101]
102
103(** {1 Element Structure Errors}
104
105 Errors related to element usage, nesting, and content models. *)
106
107(** Element structure validation errors.
108
109 These errors occur when elements violate HTML5 content model rules:
110 - Obsolete elements that should be replaced
111 - Elements used in wrong contexts (invalid parent/child relationships)
112 - Missing required child elements
113 - Empty elements that must have content *)
114type element_error = [
115 | `Obsolete of [`Elem of string] * [`Suggestion of string]
116 (** Element is obsolete and should not be used.
117 HTML5 obsoletes certain elements from HTML4 (e.g., [<font>], [<center>]).
118 The suggestion provides the recommended modern alternative. *)
119
120 | `Obsolete_attr of [`Elem of string] * [`Attr of string] * [`Suggestion of string option]
121 (** Attribute on this element is obsolete.
122 Some attributes are obsolete on specific elements but may be valid
123 elsewhere. Example: [align] on [<table>] (use CSS instead). *)
124
125 | `Obsolete_global_attr of [`Attr of string] * [`Suggestion of string]
126 (** Global attribute is obsolete on all elements.
127 Attributes like [bgcolor] are obsolete everywhere in HTML5. *)
128
129 | `Not_allowed_as_child of [`Child of string] * [`Parent of string]
130 (** Element cannot be a child of the specified parent.
131 HTML5 defines content models for each element specifying which
132 children are allowed. Example: [<div>] inside [<p>] is invalid. *)
133
134 | `Unknown of [`Elem of string]
135 (** Element name is not recognized.
136 The element is not defined in HTML5, SVG, or MathML specs.
137 May be a typo or a custom element without hyphen. *)
138
139 | `Must_not_descend of [`Elem of string] * [`Attr of string option] * [`Ancestor of string]
140 (** Element must not appear as descendant of the specified ancestor.
141 Some elements have restrictions on their ancestry regardless of
142 direct parent. Example: [<form>] cannot be nested inside [<form>].
143 The optional attribute indicates a conditional restriction. *)
144
145 | `Missing_child of [`Parent of string] * [`Child of string]
146 (** Parent element is missing a required child element.
147 Some elements must contain specific children for conformance.
148 Example: [<dl>] requires [<dt>] and [<dd>] children. *)
149
150 | `Missing_child_one_of of [`Parent of string] * [`Children of string list]
151 (** Parent must contain at least one of the listed child elements.
152 Example: [<ruby>] must contain [<rt>] or [<rp>]. *)
153
154 | `Missing_child_generic of [`Parent of string]
155 (** Parent is missing an unspecified required child.
156 Used when the required child depends on context. *)
157
158 | `Must_not_be_empty of [`Elem of string]
159 (** Element must have content and cannot be empty.
160 Some elements require text or child element content.
161 Example: [<title>] must not be empty. *)
162
163 | `Text_not_allowed of [`Parent of string]
164 (** Text content is not allowed in this element.
165 Some elements only allow element children, not text.
166 Example: [<table>] cannot contain direct text children. *)
167]
168
169(** {1 Tag and Parse Errors}
170
171 Errors from the parsing phase related to tags and document structure. *)
172
173(** Tag-level parse errors.
174
175 These errors occur during HTML parsing when the parser encounters
176 problematic tag structures or reaches end-of-file unexpectedly. *)
177type tag_error = [
178 | `Stray_start of [`Tag of string]
179 (** Start tag appears in a position where it's not allowed.
180 The parser encountered an opening tag that cannot appear in
181 the current insertion mode. Example: [<tr>] outside [<table>]. *)
182
183 | `Stray_end of [`Tag of string]
184 (** End tag appears without a matching start tag.
185 The parser encountered a closing tag with no corresponding
186 open element in scope. *)
187
188 | `End_for_void of [`Tag of string]
189 (** End tag for a void element that cannot have one.
190 Void elements ([<br>], [<img>], etc.) cannot have end tags
191 in HTML5. Example: [</br>] is invalid. *)
192
193 | `Self_closing_non_void
194 (** Self-closing syntax [/>] used on non-void HTML element.
195 In HTML5, [/>] is only meaningful on void elements and
196 foreign (SVG/MathML) elements. On other elements it's ignored. *)
197
198 | `Not_in_scope of [`Tag of string]
199 (** End tag seen but no matching element in scope.
200 The parser found a closing tag but the element isn't in the
201 current scope (may be blocked by formatting elements). *)
202
203 | `End_implied_open of [`Tag of string]
204 (** End tag implied closing of other open elements.
205 The parser had to implicitly close elements to process this
206 end tag, indicating mismatched nesting. *)
207
208 | `Start_in_table of [`Tag of string]
209 (** Start tag appeared inside table where it's foster-parented.
210 When certain tags appear in table context, they're moved
211 outside the table (foster parenting), indicating malformed markup. *)
212
213 | `Bad_start_in of [`Tag of string] * [`Context of string]
214 (** Start tag appeared in invalid context.
215 Generic error for tags in wrong parsing contexts. *)
216
217 | `Eof_with_open
218 (** End of file reached with unclosed elements.
219 The document ended with elements still open on the stack,
220 indicating missing closing tags. *)
221]
222
223(** Character reference errors.
224
225 These errors occur when character references (like [&] or [A])
226 expand to problematic Unicode code points. *)
227type char_ref_error = [
228 | `Forbidden_codepoint of [`Codepoint of int]
229 (** Character reference expands to a forbidden code point.
230 Certain code points are forbidden in HTML documents (e.g.,
231 NULL U+0000, noncharacters). These cannot appear even via
232 character references. *)
233
234 | `Control_char of [`Codepoint of int]
235 (** Character reference expands to a control character.
236 C0 and C1 control characters (except tab, newline, etc.)
237 are problematic and trigger this warning. *)
238
239 | `Non_char of [`Codepoint of int] * [`Astral of bool]
240 (** Character reference expands to a Unicode noncharacter.
241 Noncharacters (like U+FFFE, U+FFFF) are reserved and
242 should not appear in documents. Astral flag indicates
243 if it's in the supplementary planes. *)
244
245 | `Unassigned
246 (** Character reference expands to permanently unassigned code point.
247 The referenced code point will never be assigned a character. *)
248
249 | `Zero
250 (** Character reference expands to U+0000 (NULL).
251 NULL is replaced with U+FFFD (replacement character) per HTML5. *)
252
253 | `Out_of_range
254 (** Character reference value exceeds Unicode maximum.
255 Numeric character references must be <= U+10FFFF. *)
256
257 | `Carriage_return
258 (** Numeric character reference expanded to carriage return.
259 CR (U+000D) via numeric reference is replaced with LF. *)
260]
261
262(** {1 ARIA and Accessibility Errors}
263
264 Errors related to WAI-ARIA attributes and accessibility conformance. *)
265
266(** ARIA and role validation errors.
267
268 These errors ensure proper usage of WAI-ARIA attributes and roles
269 for accessibility. Incorrect ARIA can make content less accessible
270 than having no ARIA at all. *)
271type aria_error = [
272 | `Unnecessary_role of [`Role of string] * [`Elem of string] * [`Reason of string]
273 (** Role is redundant because element has implicit role.
274 Many HTML elements have implicit ARIA roles; explicitly setting
275 the same role is unnecessary. Example: [role="button"] on [<button>]. *)
276
277 | `Bad_role of [`Elem of string] * [`Role of string]
278 (** Role value is invalid or not allowed on this element.
279 The role is either not a valid ARIA role token or is not
280 permitted on this particular element type. *)
281
282 | `Must_not_specify of [`Attr of string] * [`Elem of string] * [`Condition of string]
283 (** ARIA attribute must not be specified in this situation.
284 Some ARIA attributes are prohibited on certain elements unless
285 specific conditions are met. *)
286
287 | `Must_not_use of [`Attr of string] * [`Elem of string] * [`Condition of string]
288 (** ARIA attribute must not be used with this element configuration.
289 The attribute conflicts with another attribute or state of the element. *)
290
291 | `Should_not_use of [`Attr of string] * [`Role of string]
292 (** ARIA attribute should not be used with this role (warning).
293 While not strictly invalid, the attribute is discouraged
294 with this role as it may cause confusion. *)
295
296 | `Hidden_on_body
297 (** [aria-hidden="true"] used on body element.
298 Hiding the entire document from assistive technology is
299 almost certainly an error. *)
300
301 | `Unrecognized_role of [`Token of string]
302 (** Unrecognized role token was discarded.
303 The role attribute contained a token that isn't a valid
304 ARIA role. Browsers ignore unknown role tokens. *)
305
306 | `Tab_without_tabpanel
307 (** Tab element has no corresponding tabpanel.
308 Each [role="tab"] should control a [role="tabpanel"].
309 Missing tabpanels indicate incomplete tab interface. *)
310
311 | `Multiple_main
312 (** Document has multiple visible main landmarks.
313 Only one visible [role="main"] or [<main>] should exist
314 per document for proper landmark navigation. *)
315
316 | `Accessible_name_prohibited of [`Attr of string] * [`Elem of string]
317 (** Accessible name attribute not allowed on element with generic role.
318 Elements with implicit [role="generic"] (or no role) cannot have
319 [aria-label], [aria-labelledby], or [aria-braillelabel] unless
320 they have an explicit role that supports accessible names. *)
321]
322
323(** List item role constraint errors.
324
325 Special ARIA role restrictions on [<li>] elements and [<div>]
326 children of [<dl>] elements. *)
327type li_role_error = [
328 | `Div_in_dl_bad_role
329 (** [<div>] child of [<dl>] has invalid role.
330 When [<div>] is used to group [<dt>]/[<dd>] pairs in a [<dl>],
331 it may only have [role="presentation"] or [role="none"]. *)
332
333 | `Li_bad_role_in_menu
334 (** [<li>] in menu/menubar has invalid role.
335 [<li>] descendants of [role="menu"] or [role="menubar"] must
336 have roles like [menuitem], [menuitemcheckbox], etc. *)
337
338 | `Li_bad_role_in_tablist
339 (** [<li>] in tablist has invalid role.
340 [<li>] descendants of [role="tablist"] must have [role="tab"]. *)
341
342 | `Li_bad_role_in_list
343 (** [<li>] in list context has invalid role.
344 [<li>] in [<ul>], [<ol>], [<menu>], or [role="list"] must
345 have [role="listitem"] or no explicit role. *)
346]
347
348(** {1 Table Errors}
349
350 Errors in HTML table structure and cell spanning. *)
351
352(** Table structure validation errors.
353
354 These errors indicate problems with table structure that may
355 cause incorrect rendering or accessibility issues. *)
356type table_error = [
357 | `Row_no_cells of [`Row of int]
358 (** Table row has no cells starting on it.
359 The specified row number (1-indexed) in an implicit row group
360 has no cells beginning on that row, possibly due to rowspan. *)
361
362 | `Cell_overlap
363 (** Table cells overlap due to spanning.
364 A cell's rowspan/colspan causes it to overlap with another cell,
365 making the table structure ambiguous. *)
366
367 | `Cell_spans_rowgroup
368 (** Cell's rowspan extends past its row group.
369 A cell's rowspan would extend beyond the [<tbody>], [<thead>],
370 or [<tfoot>] containing it; the span is clipped. *)
371
372 | `Column_no_cells of [`Column of int] * [`Elem of string]
373 (** Table column has no cells.
374 A column established by [<col>] or [<colgroup>] has no cells
375 beginning in it, indicating mismatched column definitions. *)
376]
377
378(** {1 Internationalization Errors}
379
380 Errors related to language declaration and text direction. *)
381
382(** Language and internationalization validation errors.
383
384 These errors help ensure documents properly declare their language
385 and text direction for accessibility and correct rendering. *)
386type i18n_error = [
387 | `Missing_lang
388 (** Document has no language declaration.
389 The [<html>] element should have a [lang] attribute declaring
390 the document's primary language for accessibility. *)
391
392 | `Wrong_lang of [`Detected of string] * [`Declared of string] * [`Suggested of string]
393 (** Declared language doesn't match detected content language.
394 Automatic language detection suggests the [lang] attribute
395 value is incorrect for the actual content. *)
396
397 | `Missing_dir_rtl of [`Language of string]
398 (** RTL language content lacks [dir="rtl"].
399 Content detected as a right-to-left language should have
400 explicit direction declaration. *)
401
402 | `Wrong_dir of [`Language of string] * [`Declared of string]
403 (** Text direction doesn't match detected language direction.
404 The [dir] attribute value conflicts with the detected
405 language's natural direction. *)
406
407 | `Xml_lang_without_lang
408 (** [xml:lang] present but [lang] is missing.
409 When [xml:lang] is specified (for XHTML compatibility),
410 the [lang] attribute must also be present with the same value. *)
411
412 | `Xml_lang_mismatch
413 (** [xml:lang] and [lang] attribute values don't match.
414 Both attributes must have identical values when present. *)
415
416 | `Not_nfc of [`Replacement of string]
417 (** Text is not in Unicode Normalization Form C.
418 HTML5 requires NFC normalization. The replacement string
419 shows the correctly normalized form. *)
420]
421
422(** {1 Import Map Errors}
423
424 Errors in [<script type="importmap">] JSON content. *)
425
426(** Import map validation errors.
427
428 These errors occur when validating the JSON content of
429 [<script type="importmap">] elements per the Import Maps spec. *)
430type importmap_error = [
431 | `Invalid_json
432 (** Import map content is not valid JSON.
433 The script content must be parseable as JSON. *)
434
435 | `Invalid_root
436 (** Import map root is not a valid object.
437 The JSON must be an object with only [imports], [scopes],
438 and [integrity] properties. *)
439
440 | `Imports_not_object
441 (** The [imports] property is not a JSON object.
442 [imports] must be an object mapping specifiers to URLs. *)
443
444 | `Empty_key
445 (** Specifier map contains an empty string key.
446 Module specifier keys must be non-empty strings. *)
447
448 | `Non_string_value
449 (** Specifier map contains a non-string value.
450 All values in the specifier map must be strings (URLs). *)
451
452 | `Key_trailing_slash
453 (** Specifier with trailing [/] maps to URL without trailing [/].
454 When a specifier key ends with [/], its value must also
455 end with [/] for proper prefix matching. *)
456
457 | `Scopes_not_object
458 (** The [scopes] property is not a JSON object.
459 [scopes] must be an object with URL keys. *)
460
461 | `Scopes_values_not_object
462 (** A [scopes] entry value is not a JSON object.
463 Each scope must map to a specifier map object. *)
464
465 | `Scopes_invalid_url
466 (** A [scopes] key is not a valid URL.
467 Scope keys must be valid URL strings. *)
468
469 | `Scopes_value_invalid_url
470 (** A specifier value in [scopes] is not a valid URL.
471 URL values in scope specifier maps must be valid. *)
472]
473
474(** {1 Element-Specific Errors}
475
476 Validation errors specific to particular HTML elements. *)
477
478(** Image element ([<img>]) validation errors. *)
479type img_error = [
480 | `Missing_alt
481 (** Image lacks [alt] attribute for accessibility.
482 Per WCAG and HTML5, images must have [alt] text describing
483 their content, or [alt=""] for decorative images. *)
484
485 | `Missing_src_or_srcset
486 (** Image has neither [src] nor [srcset].
487 An [<img>] must have at least one image source specified. *)
488
489 | `Empty_alt_with_role
490 (** Image with [alt=""] has a [role] attribute.
491 Decorative images (empty [alt]) must not have [role] because
492 they should be hidden from assistive technology. *)
493
494 | `Ismap_needs_href
495 (** Image with [ismap] lacks [<a href>] ancestor.
496 Server-side image maps require a link wrapper to function. *)
497]
498
499(** Link element ([<link>]) validation errors. *)
500type link_error = [
501 | `Missing_href
502 (** [<link>] has no [href] or [imagesrcset].
503 A link element must have a resource to link to. *)
504
505 | `As_requires_preload
506 (** [<link as="...">] used without [rel="preload"].
507 The [as] attribute is only meaningful for preload/modulepreload. *)
508
509 | `Imagesrcset_requires_as_image
510 (** [<link imagesrcset>] used without [as="image"].
511 Image srcset preloading requires [as="image"]. *)
512]
513
514(** Label element ([<label>]) validation errors. *)
515type label_error = [
516 | `Too_many_labelable
517 (** Label contains multiple labelable descendants.
518 A [<label>] should associate with exactly one form control. *)
519
520 | `For_id_mismatch
521 (** Label's [for] doesn't match descendant input's [id].
522 When a [<label>] has both [for] and a descendant input,
523 the input's [id] must match the [for] value. *)
524
525 | `Role_on_ancestor
526 (** [<label>] with role is ancestor of labelable element.
527 Adding [role] to a label that wraps a form control
528 breaks the implicit label association. *)
529
530 | `Aria_label_on_ancestor
531 (** [<label>] with [aria-label] is ancestor of labelable element.
532 [aria-label] on a label that wraps a form control creates
533 conflicting accessible names. *)
534
535 | `Role_on_for
536 (** [<label>] with role uses [for] association.
537 Labels with explicit [for] association must not have [role]. *)
538
539 | `Aria_label_on_for
540 (** [<label>] with [aria-label] uses [for] association.
541 [aria-label] on a label associated via [for] creates
542 conflicting accessible names. *)
543]
544
545(** Input element ([<input>]) validation errors. *)
546type input_error = [
547 | `Checkbox_needs_aria_pressed
548 (** Checkbox with [role="button"] lacks [aria-pressed].
549 When a checkbox is styled as a toggle button, it needs
550 [aria-pressed] to convey the toggle state. *)
551
552 | `Value_constraint of [`Constraint of string]
553 (** Input [value] doesn't meet type-specific constraints.
554 Different input types have different value format requirements
555 (dates, numbers, emails, etc.). *)
556
557 | `List_not_allowed
558 (** [list] attribute used on incompatible input type.
559 The [list] attribute for datalist binding is only valid
560 on certain input types (text, search, url, etc.). *)
561
562 | `List_requires_datalist
563 (** [list] attribute doesn't reference a [<datalist>].
564 The [list] attribute must contain the ID of a datalist element. *)
565]
566
567(** Responsive image ([srcset]/[sizes]) validation errors. *)
568type srcset_error = [
569 | `Sizes_without_srcset
570 (** [sizes] used without [srcset].
571 The [sizes] attribute is meaningless without [srcset]. *)
572
573 | `Imagesizes_without_imagesrcset
574 (** [imagesizes] used without [imagesrcset].
575 On [<link>], [imagesizes] requires [imagesrcset]. *)
576
577 | `W_without_sizes
578 (** [srcset] with width descriptors lacks [sizes].
579 When using width descriptors ([w]) in [srcset], the [sizes]
580 attribute must specify the rendered size. *)
581
582 | `Source_missing_srcset
583 (** [<source>] in [<picture>] lacks [srcset].
584 Picture source elements must have a srcset. *)
585
586 | `Source_needs_media_or_type
587 (** [<source>] needs [media] or [type] to differentiate.
588 When multiple sources exist, each must have selection criteria. *)
589
590 | `Picture_missing_img
591 (** [<picture>] lacks required [<img>] child.
592 A picture element must contain an img as the fallback. *)
593]
594
595(** SVG element validation errors. *)
596type svg_error = [
597 | `Deprecated_attr of [`Attr of string] * [`Elem of string]
598 (** SVG attribute is deprecated.
599 Certain SVG presentation attributes are deprecated in
600 favor of CSS properties. *)
601
602 | `Missing_attr of [`Elem of string] * [`Attr of string]
603 (** SVG element missing required attribute.
604 Some SVG elements have required attributes for valid rendering. *)
605]
606
607(** Miscellaneous element-specific errors.
608
609 These errors are specific to individual elements that don't
610 warrant their own category. *)
611type misc_error = [
612 | `Option_empty_without_label
613 (** [<option>] without [label] attribute is empty.
614 Options need either text content or a label attribute. *)
615
616 | `Bdo_missing_dir
617 (** [<bdo>] element lacks required [dir] attribute.
618 The bidirectional override element must specify direction. *)
619
620 | `Bdo_dir_auto
621 (** [<bdo>] has [dir="auto"] which is invalid.
622 BDO requires explicit [ltr] or [rtl], not auto-detection. *)
623
624 | `Base_missing_href_or_target
625 (** [<base>] has neither [href] nor [target].
626 A base element must specify at least one of these. *)
627
628 | `Base_after_link_script
629 (** [<base>] appears after [<link>] or [<script>].
630 The base URL must be established before other URL resolution. *)
631
632 | `Map_id_name_mismatch
633 (** [<map>] [id] and [name] attributes don't match.
634 For image maps, both attributes must have the same value. *)
635
636 | `Summary_missing_role
637 (** Non-default [<summary>] lacks [role] attribute.
638 Custom summary content outside details needs explicit role. *)
639
640 | `Summary_missing_attrs
641 (** Non-default [<summary>] missing required ARIA attributes.
642 Custom summary implementations need proper ARIA. *)
643
644 | `Summary_role_not_allowed
645 (** [<summary>] for its parent [<details>] has [role].
646 Default summary for details must not override its role. *)
647
648 | `Autocomplete_webauthn_on_select
649 (** [<select>] has [autocomplete] containing [webauthn].
650 WebAuthn autocomplete tokens are not valid for select elements. *)
651
652 | `Commandfor_invalid_target
653 (** [commandfor] doesn't reference a valid element ID.
654 The invoker must reference an element in the same tree. *)
655
656 | `Style_type_invalid
657 (** [<style type>] has value other than [text/css].
658 HTML5 only supports CSS in style elements. *)
659
660 | `Headingoffset_invalid
661 (** [headingoffset] value is out of range.
662 Must be an integer between 0 and 8. *)
663
664 | `Media_empty
665 (** [media] attribute is empty string.
666 Media queries must be non-empty if the attribute is present. *)
667
668 | `Media_all
669 (** [media] attribute is just ["all"].
670 Using [media="all"] is pointless; omit the attribute instead. *)
671
672 | `Multiple_h1
673 (** Document contains multiple [<h1>] elements.
674 Best practice is one [<h1>] per document unless using
675 [headingoffset] to indicate sectioning. *)
676
677 | `Multiple_autofocus
678 (** Multiple elements have [autofocus] in same scope.
679 Only one element should have autofocus per scoping root. *)
680]
681
682(** {1 Top-Level Error Type} *)
683
684(** All HTML5 validation errors, organized by category.
685
686 Pattern match on the outer constructor to handle error categories,
687 or match through to specific errors as needed.
688
689 {[
690 let severity_of_category = function
691 | `Aria _ -> may_be_warning
692 | `I18n _ -> usually_info_or_warning
693 | _ -> usually_error
694 ]} *)
695type t = [
696 | `Attr of attr_error
697 (** Attribute validation errors *)
698 | `Element of element_error
699 (** Element structure errors *)
700 | `Tag of tag_error
701 (** Tag-level parse errors *)
702 | `Char_ref of char_ref_error
703 (** Character reference errors *)
704 | `Aria of aria_error
705 (** ARIA and accessibility errors *)
706 | `Li_role of li_role_error
707 (** List item role constraints *)
708 | `Table of table_error
709 (** Table structure errors *)
710 | `I18n of i18n_error
711 (** Language and direction errors *)
712 | `Importmap of importmap_error
713 (** Import map JSON errors *)
714 | `Img of img_error
715 (** Image element errors *)
716 | `Link of link_error
717 (** Link element errors *)
718 | `Label of label_error
719 (** Label element errors *)
720 | `Input of input_error
721 (** Input element errors *)
722 | `Srcset of srcset_error
723 (** Responsive image errors *)
724 | `Svg of svg_error
725 (** SVG-specific errors *)
726 | `Misc of misc_error
727 (** Miscellaneous element errors *)
728 | `Generic of string
729 (** Fallback for messages without specific error codes *)
730]
731
732(** {1 Functions} *)
733
734(** Get the severity level for an error.
735 Most errors are [Error]; some ARIA and i18n issues are [Warning] or [Info]. *)
736val severity : t -> severity
737
738(** Get a short categorization code string.
739 Useful for filtering, grouping, or machine-readable output.
740 Example: ["disallowed-attribute"], ["missing-alt"], ["aria-not-allowed"]. *)
741val code_string : t -> string
742
743(** Convert error to human-readable message.
744 Produces messages matching the Nu HTML Validator format with
745 proper Unicode curly quotes around identifiers. *)
746val to_message : t -> string
747
748(** Format a string with Unicode curly quotes.
749 Wraps the string in U+201C and U+201D ("..."). *)
750val q : string -> string
751
752(** {1 Error Construction Helpers}
753
754 These functions simplify creating common error types. *)
755
756(** Create a bad attribute value error.
757 Example: [bad_value ~element:"img" ~attr:"src" ~value:"" ~reason:"URL cannot be empty"] *)
758val bad_value : element:string -> attr:string -> value:string -> reason:string -> t
759
760(** Create a bad attribute value error with just a message.
761 Example: [bad_value_msg "The value must be a valid URL"] *)
762val bad_value_msg : string -> t
763
764(** Create a missing required attribute error.
765 Example: [missing_attr ~element:"img" ~attr:"alt"] *)
766val missing_attr : element:string -> attr:string -> t
767
768(** Create an attribute not allowed error.
769 Example: [attr_not_allowed ~element:"span" ~attr:"href"] *)
770val attr_not_allowed : element:string -> attr:string -> t
771
772(** Create an element not allowed as child error.
773 Example: [not_allowed_as_child ~child:"div" ~parent:"p"] *)
774val not_allowed_as_child : child:string -> parent:string -> t
775
776(** Create a must not be empty error.
777 Example: [must_not_be_empty ~element:"title"] *)
778val must_not_be_empty : element:string -> t