···737737val has_attr : node -> string -> bool
738738(** [has_attr node name] returns [true] if the node has attribute [name]. *)
739739740740+(** {1 Space-Separated Attribute Values}
741741+742742+ Many HTML attributes contain space-separated lists of values. For example,
743743+ the [class] attribute contains CSS class names: [class="header main active"].
744744+ These functions parse such attributes into OCaml lists.
745745+746746+ Per the HTML5 spec, "ASCII whitespace" (space, tab, newline, carriage return,
747747+ form feed) is used as the separator.
748748+*)
749749+750750+val split_on_whitespace : string -> string list
751751+(** [split_on_whitespace s] splits a string on ASCII whitespace.
752752+753753+ This implements the HTML5 "split on ASCII whitespace" algorithm.
754754+755755+ {b Example:}
756756+ {[
757757+ split_on_whitespace "foo bar\tbaz"
758758+ (* Returns: ["foo"; "bar"; "baz"] *)
759759+ ]}
760760+*)
761761+762762+val get_attr_list : node -> string -> string list
763763+(** [get_attr_list node name] returns a space-separated attribute as a list.
764764+765765+ Returns an empty list if the attribute doesn't exist.
766766+*)
767767+768768+val get_class_list : node -> string list
769769+(** [get_class_list node] returns the class attribute as a list of class names. *)
770770+771771+val get_rel_list : node -> string list
772772+(** [get_rel_list node] returns the rel attribute as a list of link types
773773+ (lowercased since they are case-insensitive). *)
774774+775775+val get_headers_list : node -> string list
776776+(** [get_headers_list node] returns the headers attribute as a list of IDs. *)
777777+778778+val get_itemref_list : node -> string list
779779+(** [get_itemref_list node] returns the itemref attribute as a list of IDs. *)
780780+781781+val get_itemprop_list : node -> string list
782782+(** [get_itemprop_list node] returns the itemprop attribute as a list. *)
783783+784784+val get_itemtype_list : node -> string list
785785+(** [get_itemtype_list node] returns the itemtype attribute as a list of URLs. *)
786786+740787(** {1 Location Helpers} *)
741788742789val make_location : line:int -> column:int -> ?end_line:int -> ?end_column:int ->
+33
lib/html5rw/dom/dom_node.ml
···147147148148let has_attr node name = List.mem_assoc name node.attrs
149149150150+(* Whitespace splitting for space-separated attribute values per HTML5 spec.
151151+ Handles ASCII whitespace: space, tab, newline, carriage return, form feed *)
152152+let split_on_whitespace s =
153153+ let is_whitespace = function
154154+ | ' ' | '\t' | '\n' | '\r' | '\x0c' -> true
155155+ | _ -> false
156156+ in
157157+ let len = String.length s in
158158+ let rec find_start acc i =
159159+ if i >= len then List.rev acc
160160+ else if is_whitespace s.[i] then find_start acc (i + 1)
161161+ else find_end acc i (i + 1)
162162+ and find_end acc start i =
163163+ if i >= len then List.rev (String.sub s start (i - start) :: acc)
164164+ else if is_whitespace s.[i] then find_start (String.sub s start (i - start) :: acc) (i + 1)
165165+ else find_end acc start (i + 1)
166166+ in
167167+ find_start [] 0
168168+169169+(* Get space-separated attribute as list *)
170170+let get_attr_list node name =
171171+ match get_attr node name with
172172+ | Some s -> split_on_whitespace s
173173+ | None -> []
174174+175175+(* Common space-separated attribute accessors *)
176176+let get_class_list node = get_attr_list node "class"
177177+let get_rel_list node = List.map String.lowercase_ascii (get_attr_list node "rel")
178178+let get_headers_list node = get_attr_list node "headers"
179179+let get_itemref_list node = get_attr_list node "itemref"
180180+let get_itemprop_list node = get_attr_list node "itemprop"
181181+let get_itemtype_list node = get_attr_list node "itemtype"
182182+150183(* Tree traversal *)
151184let rec descendants node =
152185 List.concat_map (fun n -> n :: descendants n) node.children
+86
lib/html5rw/dom/dom_node.mli
···740740val has_attr : node -> string -> bool
741741(** [has_attr node name] returns [true] if the node has attribute [name]. *)
742742743743+(** {1 Space-Separated Attribute Values}
744744+745745+ Many HTML attributes contain space-separated lists of values. For example,
746746+ the [class] attribute contains CSS class names: [class="header main active"].
747747+ These functions parse such attributes into OCaml lists.
748748+749749+ Per the HTML5 spec, "ASCII whitespace" (space, tab, newline, carriage return,
750750+ form feed) is used as the separator.
751751+*)
752752+753753+val split_on_whitespace : string -> string list
754754+(** [split_on_whitespace s] splits a string on ASCII whitespace.
755755+756756+ This implements the HTML5 "split on ASCII whitespace" algorithm used
757757+ for parsing space-separated attribute values.
758758+759759+ {b Example:}
760760+ {[
761761+ split_on_whitespace "foo bar\tbaz"
762762+ (* Returns: ["foo"; "bar"; "baz"] *)
763763+ ]}
764764+*)
765765+766766+val get_attr_list : node -> string -> string list
767767+(** [get_attr_list node name] returns a space-separated attribute as a list.
768768+769769+ Returns an empty list if the attribute doesn't exist.
770770+771771+ {b Example:}
772772+ {[
773773+ (* For <div class="foo bar baz"> *)
774774+ get_attr_list div "class"
775775+ (* Returns: ["foo"; "bar"; "baz"] *)
776776+ ]}
777777+*)
778778+779779+val get_class_list : node -> string list
780780+(** [get_class_list node] returns the class attribute as a list of class names.
781781+782782+ Equivalent to [get_attr_list node "class"].
783783+784784+ {b Example:}
785785+ {[
786786+ (* For <div class="container main"> *)
787787+ get_class_list div
788788+ (* Returns: ["container"; "main"] *)
789789+ ]}
790790+*)
791791+792792+val get_rel_list : node -> string list
793793+(** [get_rel_list node] returns the rel attribute as a list of link types.
794794+795795+ Link types are lowercased since they are case-insensitive per HTML5 spec.
796796+797797+ {b Example:}
798798+ {[
799799+ (* For <link rel="stylesheet preload"> *)
800800+ get_rel_list link
801801+ (* Returns: ["stylesheet"; "preload"] *)
802802+ ]}
803803+*)
804804+805805+val get_headers_list : node -> string list
806806+(** [get_headers_list node] returns the headers attribute as a list of IDs.
807807+808808+ Used on [<td>] and [<th>] elements to associate cells with headers.
809809+*)
810810+811811+val get_itemref_list : node -> string list
812812+(** [get_itemref_list node] returns the itemref attribute as a list of IDs.
813813+814814+ Used for microdata to reference elements by ID.
815815+*)
816816+817817+val get_itemprop_list : node -> string list
818818+(** [get_itemprop_list node] returns the itemprop attribute as a list.
819819+820820+ Used for microdata property names.
821821+*)
822822+823823+val get_itemtype_list : node -> string list
824824+(** [get_itemtype_list node] returns the itemtype attribute as a list of URLs.
825825+826826+ Used for microdata type URLs.
827827+*)
828828+743829(** {1 Location Helpers}
744830745831 Functions to manage source location information for nodes.
+10-6
lib/html5rw/dom/dom_serialize.ml
···88open Bytesrw
99open Dom_node
10101111-(* Void elements that don't have end tags *)
1212-let void_elements = [
1313- "area"; "base"; "br"; "col"; "embed"; "hr"; "img"; "input";
1414- "link"; "meta"; "source"; "track"; "wbr"
1515-]
1111+(* Void elements that don't have end tags - O(1) hashtable lookup *)
1212+let void_elements_tbl =
1313+ let elements = [
1414+ "area"; "base"; "br"; "col"; "embed"; "hr"; "img"; "input";
1515+ "link"; "meta"; "source"; "track"; "wbr"
1616+ ] in
1717+ let tbl = Hashtbl.create (List.length elements) in
1818+ List.iter (fun e -> Hashtbl.add tbl e ()) elements;
1919+ tbl
16201717-let is_void name = List.mem name void_elements
2121+let is_void name = Hashtbl.mem void_elements_tbl name
18221923(* Foreign attribute adjustments for test output *)
2024let foreign_attr_adjustments = [
+32-8
lib/html5rw/parser/parser_constants.ml
···8080let mathml_text_integration = ["mi"; "mo"; "mn"; "ms"; "mtext"]
8181let mathml_text_integration_tbl = make_set mathml_text_integration
82828383-(* MathML attribute adjustments *)
8484-let mathml_attr_adjustments = [
8585- ("definitionurl", "definitionURL")
8686-]
8383+(* MathML attribute adjustments - O(1) hashtable lookup *)
8484+let mathml_attr_adjustments_tbl =
8585+ let adjustments = [("definitionurl", "definitionURL")] in
8686+ let tbl = Hashtbl.create 4 in
8787+ List.iter (fun (k, v) -> Hashtbl.add tbl k v) adjustments;
8888+ tbl
87898890let adjust_mathml_attrs attrs =
8991 List.map (fun (k, v) ->
9090- match List.assoc_opt (lowercase k) mathml_attr_adjustments with
9292+ match Hashtbl.find_opt mathml_attr_adjustments_tbl (lowercase k) with
9193 | Some adjusted_k -> (adjusted_k, v)
9294 | None -> (k, v)
9395 ) attrs
···9597(* SVG HTML integration points *)
9698let svg_html_integration = ["foreignObject"; "desc"; "title"]
9799let svg_html_integration_tbl = make_set (List.map lowercase svg_html_integration)
100100+101101+(* Helper to create hashtable from association list for O(1) lookup *)
102102+let make_assoc_tbl pairs =
103103+ let tbl = Hashtbl.create (List.length pairs) in
104104+ List.iter (fun (k, v) -> Hashtbl.add tbl k v) pairs;
105105+ tbl
9810699107(* SVG tag name adjustments *)
100108let svg_tag_adjustments = [
···136144 ("radialgradient", "radialGradient");
137145 ("textpath", "textPath");
138146]
147147+let svg_tag_adjustments_tbl = make_assoc_tbl svg_tag_adjustments
139148140149(* SVG attribute adjustments *)
141150let svg_attr_adjustments = [
···198207 ("ychannelselector", "yChannelSelector");
199208 ("zoomandpan", "zoomAndPan");
200209]
210210+let svg_attr_adjustments_tbl = make_assoc_tbl svg_attr_adjustments
201211202212(* Foreign attribute adjustments *)
203213let foreign_attr_adjustments = [
···213223 ("xmlns", ("", "xmlns", "http://www.w3.org/2000/xmlns/"));
214224 ("xmlns:xlink", ("xmlns", "xlink", "http://www.w3.org/2000/xmlns/"));
215225]
226226+let foreign_attr_adjustments_tbl = make_assoc_tbl foreign_attr_adjustments
216227217228(* Quirks mode detection *)
218229let quirky_public_matches = [
···293304 "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"
294305]
295306307307+(* Table-related element sets for tree builder O(1) lookups *)
308308+let table_section_elements = ["tbody"; "thead"; "tfoot"]
309309+let table_section_elements_tbl = make_set table_section_elements
310310+311311+let table_cell_elements = ["td"; "th"]
312312+let table_cell_elements_tbl = make_set table_cell_elements
313313+314314+let foster_parenting_elements = ["table"; "tbody"; "tfoot"; "thead"; "tr"]
315315+let foster_parenting_elements_tbl = make_set foster_parenting_elements
316316+296317(* Helper functions - O(1) hashtable lookups *)
297318let is_void_element name = Hashtbl.mem void_elements_tbl name
298319let is_formatting_element name = Hashtbl.mem formatting_elements_tbl name
···303324let is_mathml_text_integration name = Hashtbl.mem mathml_text_integration_tbl name
304325let is_svg_html_integration name = Hashtbl.mem svg_html_integration_tbl (lowercase name)
305326let is_select_scope_exclude name = Hashtbl.mem select_scope_exclude_tbl name
327327+let is_table_section_element name = Hashtbl.mem table_section_elements_tbl name
328328+let is_table_cell_element name = Hashtbl.mem table_cell_elements_tbl name
329329+let is_foster_parenting_element name = Hashtbl.mem foster_parenting_elements_tbl name
306330307331(* Backwards compatibility aliases *)
308332let is_void = List.mem
···311335let is_heading = List.mem
312336313337let adjust_svg_tag_name name =
314314- match List.assoc_opt (lowercase name) svg_tag_adjustments with
338338+ match Hashtbl.find_opt svg_tag_adjustments_tbl (lowercase name) with
315339 | Some adjusted -> adjusted
316340 | None -> name
317341318342let adjust_svg_attrs attrs =
319343 List.map (fun (name, value) ->
320344 let adjusted_name =
321321- match List.assoc_opt (lowercase name) svg_attr_adjustments with
345345+ match Hashtbl.find_opt svg_attr_adjustments_tbl (lowercase name) with
322346 | Some n -> n
323347 | None -> name
324348 in
···327351328352let adjust_foreign_attrs attrs =
329353 List.map (fun (name, value) ->
330330- match List.assoc_opt (lowercase name) foreign_attr_adjustments with
354354+ match Hashtbl.find_opt foreign_attr_adjustments_tbl (lowercase name) with
331355 | Some (prefix, local, _ns) ->
332356 if prefix = "" then (local, value)
333357 else (prefix ^ ":" ^ local, value)
+11-11
lib/html5rw/parser/parser_tree_builder.ml
···9191 (* Set initial mode based on context *)
9292 t.mode <- (
9393 if name = "html" then Parser_insertion_mode.Before_head
9494- else if List.mem name ["tbody"; "thead"; "tfoot"] && (ns = None || ns = Some "html") then
9494+ else if Parser_constants.is_table_section_element name && (ns = None || ns = Some "html") then
9595 Parser_insertion_mode.In_table_body
9696 else if name = "tr" && (ns = None || ns = Some "html") then
9797 Parser_insertion_mode.In_row
9898- else if List.mem name ["td"; "th"] && (ns = None || ns = Some "html") then
9898+ else if Parser_constants.is_table_cell_element name && (ns = None || ns = Some "html") then
9999 Parser_insertion_mode.In_cell
100100 else if name = "caption" && (ns = None || ns = Some "html") then
101101 Parser_insertion_mode.In_caption
···160160 match current_node t with
161161 | None -> (t.document, None)
162162 | Some target ->
163163- if t.foster_parenting && List.mem target.Dom.name ["table"; "tbody"; "tfoot"; "thead"; "tr"] then begin
163163+ if t.foster_parenting && Parser_constants.is_foster_parenting_element target.Dom.name then begin
164164 (* Foster parenting per WHATWG spec *)
165165 (* Step 1: Find last (most recent) template and table in stack *)
166166 (* Note: index 0 = top of stack = most recently added *)
···599599 | Some p -> Dom.remove_child p !last_node
600600 | None -> ());
601601 (* Check if we need foster parenting *)
602602- if t.foster_parenting && List.mem ca.Dom.name ["table"; "tbody"; "tfoot"; "thead"; "tr"] then begin
602602+ if t.foster_parenting && Parser_constants.is_foster_parenting_element ca.Dom.name then begin
603603 (* Find table and insert before it *)
604604 let rec find_table = function
605605 | [] -> None
···698698 end;
699699 if t.mode <> Parser_insertion_mode.In_select_in_table then
700700 t.mode <- Parser_insertion_mode.In_select
701701- end else if List.mem name ["td"; "th"] && not is_last then
701701+ end else if Parser_constants.is_table_cell_element name && not is_last then
702702 t.mode <- Parser_insertion_mode.In_cell
703703 else if name = "tr" then
704704 t.mode <- Parser_insertion_mode.In_row
705705- else if List.mem name ["tbody"; "thead"; "tfoot"] then
705705+ else if Parser_constants.is_table_section_element name then
706706 t.mode <- Parser_insertion_mode.In_table_body
707707 else if name = "caption" then
708708 t.mode <- Parser_insertion_mode.In_caption
···1473147314741474and process_in_table t token =
14751475 match token with
14761476- | Token.Character _ when (match current_node t with Some n -> List.mem n.Dom.name ["table"; "tbody"; "tfoot"; "thead"; "tr"] | None -> false) ->
14761476+ | Token.Character _ when (match current_node t with Some n -> Parser_constants.is_foster_parenting_element n.Dom.name | None -> false) ->
14771477 t.pending_table_chars <- [];
14781478 t.original_mode <- Some t.mode;
14791479 t.mode <- Parser_insertion_mode.In_table_text;
···1798179817991799and process_in_cell t token =
18001800 match token with
18011801- | Token.Tag { kind = Token.End; name; _ } when List.mem name ["td"; "th"] ->
18011801+ | Token.Tag { kind = Token.End; name; _ } when Parser_constants.is_table_cell_element name ->
18021802 if not (has_element_in_table_scope t name) then
18031803 parse_error t "unexpected-end-tag"
18041804 else begin
···18221822 when List.mem name ["body"; "caption"; "col"; "colgroup"; "html"] ->
18231823 parse_error t "unexpected-end-tag"
18241824 | Token.Tag { kind = Token.End; name; _ }
18251825- when List.mem name ["table"; "tbody"; "tfoot"; "thead"; "tr"] ->
18251825+ when Parser_constants.is_foster_parenting_element name ->
18261826 if not (has_element_in_table_scope t name) then
18271827 parse_error t "unexpected-end-tag"
18281828 else begin
···18351835and close_cell t =
18361836 generate_implied_end_tags t ();
18371837 (match current_node t with
18381838- | Some n when not (List.mem n.Dom.name ["td"; "th"] && is_in_html_namespace n) -> parse_error t "end-tag-too-early"
18381838+ | Some n when not (Parser_constants.is_table_cell_element n.Dom.name && is_in_html_namespace n) -> parse_error t "end-tag-too-early"
18391839 | _ -> ());
18401840 pop_until_html_one_of t ["td"; "th"];
18411841 clear_active_formatting_to_marker t;
···20502050 t.template_modes <- Parser_insertion_mode.In_table_body :: t.template_modes;
20512051 t.mode <- Parser_insertion_mode.In_table_body;
20522052 process_token t token
20532053- | Token.Tag { kind = Token.Start; name; _ } when List.mem name ["td"; "th"] ->
20532053+ | Token.Tag { kind = Token.Start; name; _ } when Parser_constants.is_table_cell_element name ->
20542054 t.template_modes <- (match t.template_modes with _ :: rest -> rest | [] -> []);
20552055 t.template_modes <- Parser_insertion_mode.In_row :: t.template_modes;
20562056 t.mode <- Parser_insertion_mode.In_row;
+3-6
lib/html5rw/tokenizer/tokenizer_impl.ml
···19431943 error t (Printf.sprintf "surrogate-character-reference:%04x" code);
19441944 replacement_char
19451945 end else if (code >= 0xFDD0 && code <= 0xFDEF) ||
19461946- List.mem code [0xFFFE; 0xFFFF; 0x1FFFE; 0x1FFFF; 0x2FFFE; 0x2FFFF;
19471947- 0x3FFFE; 0x3FFFF; 0x4FFFE; 0x4FFFF; 0x5FFFE; 0x5FFFF;
19481948- 0x6FFFE; 0x6FFFF; 0x7FFFE; 0x7FFFF; 0x8FFFE; 0x8FFFF;
19491949- 0x9FFFE; 0x9FFFF; 0xAFFFE; 0xAFFFF; 0xBFFFE; 0xBFFFF;
19501950- 0xCFFFE; 0xCFFFF; 0xDFFFE; 0xDFFFF; 0xEFFFE; 0xEFFFF;
19511951- 0xFFFFE; 0xFFFFF; 0x10FFFE; 0x10FFFF] then begin
19461946+ (* Noncharacters end in 0xFFFE or 0xFFFF in each plane (0-16).
19471947+ O(1) bitwise check instead of O(n) list membership. *)
19481948+ (let low16 = code land 0xFFFF in low16 = 0xFFFE || low16 = 0xFFFF) then begin
19521949 error t (Printf.sprintf "noncharacter-character-reference:%05x" code);
19531950 Entities.Numeric_ref.codepoint_to_utf8 code
19541951 end else if (code >= 0x01 && code <= 0x08) || code = 0x0B ||
+3-21
lib/htmlrw_check/datatype/datatype.ml
···4242 else String.sub s start (end_pos - start + 1)
43434444(** Split string on HTML whitespace characters (space, tab, LF, FF, CR).
4545- Filters out empty tokens. Used for space-separated attribute values. *)
4646-let split_on_whitespace s =
4747- let len = String.length s in
4848- let rec split acc start i =
4949- if i >= len then
5050- if i > start then
5151- List.rev ((String.sub s start (i - start)) :: acc)
5252- else
5353- List.rev acc
5454- else if is_whitespace s.[i] then
5555- let acc' =
5656- if i > start then
5757- (String.sub s start (i - start)) :: acc
5858- else
5959- acc
6060- in
6161- split acc' (i + 1) (i + 1)
6262- else
6363- split acc start (i + 1)
6464- in
6565- split [] 0 0
4545+ Filters out empty tokens. Used for space-separated attribute values.
4646+ Delegates to the core library implementation. *)
4747+let split_on_whitespace = Html5rw.Dom.split_on_whitespace
66486749(** Factory for creating enum-based validators.
6850 Many HTML attributes accept a fixed set of keyword values.
+2-2
lib/htmlrw_check/element/attr.ml
···872872let get_rel attrs =
873873 List.find_map (function `Rel s -> Some s | _ -> None) attrs
874874875875-(** Get rel attribute as list of link types (space-separated) *)
875875+(** Get rel attribute as list of link types (space-separated, lowercased per HTML5 spec) *)
876876let get_rel_list attrs =
877877 match get_rel attrs with
878878- | Some s -> Datatype.split_on_whitespace s
878878+ | Some s -> List.map String.lowercase_ascii (Datatype.split_on_whitespace s)
879879 | None -> []
880880881881(** Get headers attribute as raw string *)
+9
lib/htmlrw_check/element/element.ml
···104104let get_all_aria elem = Attr.get_all_aria elem.attrs
105105let get_all_data elem = Attr.get_all_data elem.attrs
106106107107+(** Space-separated list getters *)
108108+let get_class_list elem = Attr.get_class_list elem.attrs
109109+let get_rel_list elem = Attr.get_rel_list elem.attrs
110110+let get_headers_list elem = Attr.get_headers_list elem.attrs
111111+let get_itemref_list elem = Attr.get_itemref_list elem.attrs
112112+let get_itemprop_list elem = Attr.get_itemprop_list elem.attrs
113113+let get_itemtype_list elem = Attr.get_itemtype_list elem.attrs
114114+let get_aria_list name elem = Attr.get_aria_list name elem.attrs
115115+107116(** {1 Category Checks} *)
108117109118(** Check if this is a void element *)
+26
lib/htmlrw_check/element/element.mli
···162162val get_all_data : t -> (string * string) list
163163(** [get_all_data elem] extracts all data-* attributes. *)
164164165165+(** {1 Space-Separated List Accessors}
166166+167167+ These functions return attribute values as parsed lists, splitting on
168168+ whitespace per HTML5 spec. *)
169169+170170+val get_class_list : t -> string list
171171+(** [get_class_list elem] returns class names as a list. *)
172172+173173+val get_rel_list : t -> string list
174174+(** [get_rel_list elem] returns link types as a list. *)
175175+176176+val get_headers_list : t -> string list
177177+(** [get_headers_list elem] returns header IDs as a list (for td/th). *)
178178+179179+val get_itemref_list : t -> string list
180180+(** [get_itemref_list elem] returns itemref IDs as a list. *)
181181+182182+val get_itemprop_list : t -> string list
183183+(** [get_itemprop_list elem] returns itemprop names as a list. *)
184184+185185+val get_itemtype_list : t -> string list
186186+(** [get_itemtype_list elem] returns itemtype URLs as a list. *)
187187+188188+val get_aria_list : string -> t -> string list
189189+(** [get_aria_list name elem] returns space-separated ARIA values as a list. *)
190190+165191(** {1 Raw Attribute Fallback} *)
166192167193val get_raw_attr : string -> t -> string option
+15-4
lib/htmlrw_check/message_collector.ml
···33type t = {
44 mutable messages : Message.t list;
55 mutable current_location : Message.location option;
66+ mutable cached_reversed : Message.t list option; (* Cache for O(1) repeated access *)
67}
7888-let create () = { messages = []; current_location = None }
99+let create () = { messages = []; current_location = None; cached_reversed = None }
9101011let set_current_location t location = t.current_location <- location
1112let clear_current_location t = t.current_location <- None
1213let get_current_location t = t.current_location
13141414-let add t msg = t.messages <- msg :: t.messages
1515+let add t msg =
1616+ t.messages <- msg :: t.messages;
1717+ t.cached_reversed <- None (* Invalidate cache *)
15181619(** Add a message from a typed conformance error code *)
1720let add_typed t ?location ?element ?attribute ?extract error_code =
···2326 let msg = Message.of_conformance_error ?location:loc ?element ?attribute ?extract error_code in
2427 add t msg
25282626-let messages t = List.rev t.messages
2929+let messages t =
3030+ match t.cached_reversed with
3131+ | Some cached -> cached
3232+ | None ->
3333+ let reversed = List.rev t.messages in
3434+ t.cached_reversed <- Some reversed;
3535+ reversed
27362837let errors t =
2938 List.filter (fun msg -> msg.Message.severity = Message.Error) (messages t)
···4554 if msg.Message.severity = Message.Error then acc + 1 else acc)
4655 0 t.messages
47564848-let clear t = t.messages <- []
5757+let clear t =
5858+ t.messages <- [];
5959+ t.cached_reversed <- None
+4-8
lib/htmlrw_check/semantic/id_checker.ml
···55555656(** Attributes that reference a single ID - O(1) lookup. *)
5757let single_id_ref_attrs =
5858- let tbl = Hashtbl.create 8 in
5959- List.iter (fun a -> Hashtbl.add tbl a ()) [
5858+ Attr_utils.hashtbl_of_list [
6059 "for"; (* label *)
6160 "form"; (* form-associated elements *)
6261 "list"; (* input *)
···6463 "popovertarget"; (* button - references popover element *)
6564 "commandfor"; (* button - references element to control *)
6665 "anchor"; (* popover - references anchor element *)
6767- ];
6868- tbl
6666+ ]
69677068let is_single_id_ref_attr name = Hashtbl.mem single_id_ref_attrs name
71697270(** Attributes that reference multiple IDs (space-separated) - O(1) lookup. *)
7371let multi_id_ref_attrs =
7474- let tbl = Hashtbl.create 8 in
7575- List.iter (fun a -> Hashtbl.add tbl a ()) [
7272+ Attr_utils.hashtbl_of_list [
7673 "headers"; (* td, th *)
7774 "aria-labelledby";
7875 "aria-describedby";
···8077 "aria-flowto";
8178 "aria-owns";
8279 "itemref";
8383- ];
8484- tbl
8080+ ]
85818682let is_multi_id_ref_attr name = Hashtbl.mem multi_id_ref_attrs name
8783
···135135 if name_lower = "link" then begin
136136 let has_imagesizes = Attr_utils.has_attr "imagesizes" attrs in
137137 let has_imagesrcset = Attr_utils.has_attr "imagesrcset" attrs in
138138- let rel_value = Attr_utils.get_attr "rel" attrs in
139138 let as_value = Attr_utils.get_attr "as" attrs in
140139141140 (* imagesizes requires imagesrcset *)
···155154 (* as attribute requires rel="preload" or rel="modulepreload" *)
156155 (match as_value with
157156 | Some _ ->
158158- let rel_is_preload = match rel_value with
159159- | Some v ->
160160- let rel_lower = String.lowercase_ascii (String.trim v) in
161161- String.length rel_lower > 0 &&
162162- (List.mem "preload" (String.split_on_char ' ' rel_lower) ||
163163- List.mem "modulepreload" (String.split_on_char ' ' rel_lower))
164164- | None -> false
165165- in
157157+ let rel_types = Element.get_rel_list element in
158158+ let rel_is_preload = List.mem "preload" rel_types || List.mem "modulepreload" rel_types in
166159 if not rel_is_preload then
167160 Message_collector.add_typed collector (`Link `As_requires_preload)
168161 | None -> ())
+1-3
lib/htmlrw_check/specialized/label_checker.ml
···4455(** Labelable elements that label can reference - O(1) hashtable lookup *)
66let labelable_elements =
77- let tbl = Hashtbl.create 8 in
88- List.iter (fun e -> Hashtbl.add tbl e ()) ["button"; "input"; "meter"; "output"; "progress"; "select"; "textarea"];
99- tbl
77+ Attr_utils.hashtbl_of_list ["button"; "input"; "meter"; "output"; "progress"; "select"; "textarea"]
108119let is_labelable name = Hashtbl.mem labelable_elements name
1210