OCaml HTML5 parser/serialiser based on Python's JustHTML
1(** Reusable context/ancestor tracking for checkers.
2
3 Many checkers need to track element ancestors, depth, or maintain
4 context stacks during DOM traversal. This module provides common
5 utilities to reduce duplication. *)
6
7(** Generic stack-based context tracker. *)
8module Stack : sig
9 type 'a t
10
11 (** Create an empty context stack. *)
12 val create : unit -> 'a t
13
14 (** Reset the stack to empty. *)
15 val reset : 'a t -> unit
16
17 (** Push a context onto the stack. *)
18 val push : 'a t -> 'a -> unit
19
20 (** Pop a context from the stack. Returns None if empty. *)
21 val pop : 'a t -> 'a option
22
23 (** Get the current (top) context without removing it. *)
24 val current : 'a t -> 'a option
25
26 (** Get current depth (number of items on stack). *)
27 val depth : 'a t -> int
28
29 (** Check if stack is empty. *)
30 val is_empty : 'a t -> bool
31
32 (** Get all ancestors (bottom to top). *)
33 val to_list : 'a t -> 'a list
34
35 (** Check if any ancestor satisfies predicate. *)
36 val exists : 'a t -> ('a -> bool) -> bool
37
38 (** Find first ancestor satisfying predicate (top to bottom). *)
39 val find : 'a t -> ('a -> bool) -> 'a option
40
41 (** Iterate over all contexts (top to bottom). *)
42 val iter : 'a t -> ('a -> unit) -> unit
43end = struct
44 type 'a t = { mutable stack : 'a list; mutable len : int }
45
46 let create () = { stack = []; len = 0 }
47 let reset t = t.stack <- []; t.len <- 0
48 let push t x = t.stack <- x :: t.stack; t.len <- t.len + 1
49 let pop t = match t.stack with
50 | [] -> None
51 | x :: rest -> t.stack <- rest; t.len <- t.len - 1; Some x
52 let current t = match t.stack with
53 | [] -> None
54 | x :: _ -> Some x
55 let depth t = t.len (* O(1) instead of O(n) *)
56 let is_empty t = t.len = 0
57 let to_list t = List.rev t.stack
58 let exists t f = List.exists f t.stack
59 let find t f = List.find_opt f t.stack
60 let iter t f = List.iter f t.stack
61end
62
63(** Simple depth counter for tracking nesting level. *)
64module Depth : sig
65 type t
66
67 (** Create a depth counter starting at 0. *)
68 val create : unit -> t
69
70 (** Reset depth to 0. *)
71 val reset : t -> unit
72
73 (** Increment depth (entering element). *)
74 val enter : t -> unit
75
76 (** Decrement depth (leaving element). Returns false if was already 0. *)
77 val leave : t -> bool
78
79 (** Get current depth. *)
80 val get : t -> int
81
82 (** Check if inside (depth > 0). *)
83 val is_inside : t -> bool
84end = struct
85 type t = { mutable depth : int }
86
87 let create () = { depth = 0 }
88 let reset t = t.depth <- 0
89 let enter t = t.depth <- t.depth + 1
90 let leave t =
91 if t.depth > 0 then begin
92 t.depth <- t.depth - 1;
93 true
94 end else false
95 let get t = t.depth
96 let is_inside t = t.depth > 0
97end
98
99(** Element name stack for tracking ancestors by name. *)
100module Ancestors : sig
101 type t
102
103 (** Create an empty ancestor tracker. *)
104 val create : unit -> t
105
106 (** Reset to empty. *)
107 val reset : t -> unit
108
109 (** Push an element name onto the ancestor stack. *)
110 val push : t -> string -> unit
111
112 (** Pop an element from the ancestor stack. *)
113 val pop : t -> unit
114
115 (** Get the immediate parent element name. *)
116 val parent : t -> string option
117
118 (** Check if an element name is an ancestor. *)
119 val has_ancestor : t -> string -> bool
120
121 (** Get depth (number of ancestors). *)
122 val depth : t -> int
123
124 (** Get all ancestor names (outermost first). *)
125 val to_list : t -> string list
126end = struct
127 type t = { mutable stack : string list; mutable len : int }
128
129 let create () = { stack = []; len = 0 }
130 let reset t = t.stack <- []; t.len <- 0
131 let push t name = t.stack <- name :: t.stack; t.len <- t.len + 1
132 let pop t = match t.stack with
133 | _ :: rest -> t.stack <- rest; t.len <- t.len - 1
134 | [] -> ()
135 let parent t = match t.stack with
136 | x :: _ -> Some x
137 | [] -> None
138 let has_ancestor t name = List.mem name t.stack
139 let depth t = t.len (* O(1) instead of O(n) *)
140 let to_list t = List.rev t.stack
141end