package cascade

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file selector_summary.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
module String_set = Set.Make (String)

type attr_key = Selector.ns option * Selector.attr_name

type attr_fact = {
  key : attr_key;
  matcher : Selector.attribute_match;
  flag : Selector.attr_flag option;
}

type nth_fact = Index of int | Odd | Even

type t = {
  ids : String_set.t;
  classes : String_set.t;
  element : string option;
  attrs : attr_fact list;
  nth : nth_fact list;
  not_ids : String_set.t;
  not_classes : String_set.t;
  not_elements : String_set.t;
  not_attrs : attr_fact list;
  not_nth : nth_fact list;
  pseudo_element : Selector.t option;
  parent : t option;
  complex : bool;
}

let empty =
  {
    ids = String_set.empty;
    classes = String_set.empty;
    element = None;
    attrs = [];
    nth = [];
    not_ids = String_set.empty;
    not_classes = String_set.empty;
    not_elements = String_set.empty;
    not_attrs = [];
    not_nth = [];
    pseudo_element = None;
    parent = None;
    complex = false;
  }

let mark_complex s = if s.complex then s else { s with complex = true }
let add_nth nth s = { s with nth = nth :: s.nth; complex = true }

let nth_of_selector = function
  | Selector_intf.First_child | Selector_intf.Only_child -> Some (Index 1)
  | Selector_intf.Nth_child (Selector_intf.Odd, None) -> Some Odd
  | Selector_intf.Nth_child (Selector_intf.Even, None) -> Some Even
  | Selector_intf.Nth_child (Selector_intf.Index n, None) when n >= 1 ->
      Some (Index n)
  | Selector_intf.Nth_child (Selector_intf.An_plus_b (2, 1), None) -> Some Odd
  | Selector_intf.Nth_child (Selector_intf.An_plus_b (2, 0), None)
  | Selector_intf.Nth_child (Selector_intf.An_plus_b (2, 2), None) ->
      Some Even
  | _ -> None

let add_not_simple s (sel : Selector.t) =
  match sel with
  | Selector_intf.Class c ->
      { s with not_classes = String_set.add c s.not_classes }
  | Selector_intf.Id i -> { s with not_ids = String_set.add i s.not_ids }
  | Selector_intf.Element (_, name) ->
      { s with not_elements = String_set.add name s.not_elements }
  | Selector_intf.Attribute (ns, attr_name, matcher, flag) ->
      {
        s with
        not_attrs = { key = (ns, attr_name); matcher; flag } :: s.not_attrs;
      }
  | sel -> (
      match nth_of_selector sel with
      | Some nth -> { s with not_nth = nth :: s.not_nth }
      | None -> s)

let add_not_selector s = function
  | ( Selector_intf.Class _ | Selector_intf.Id _ | Selector_intf.Element _
    | Selector_intf.Attribute _ ) as sel ->
      add_not_simple s sel
  | sel -> (
      match nth_of_selector sel with
      | Some _ -> add_not_simple s sel
      | None -> s)

let rec fold_simple s (sel : Selector.t) =
  match sel with
  | Class c -> { s with classes = String_set.add c s.classes }
  | Id i -> { s with ids = String_set.add i s.ids }
  | Element (_, name) -> (
      match s.element with
      | None -> { s with element = Some name }
      | Some prev when prev = name -> s
      | Some _ -> mark_complex s)
  | Universal _ -> s
  | Compound xs -> List.fold_left fold_simple s xs
  (* A pseudo-element is a subject of its own, so it replaces the originating
     element rather than narrowing it. Same predicate as the reader's, so the
     summary calls a compound a pseudo-element exactly when the reader does. *)
  | sel when Selector.is_pseudo_element sel -> (
      (* Normalize legacy single-colon spellings ([:before] vs [::before]) so
         the two parses of the same pseudo-element compare equal. *)
      let canonical : Selector.t =
        match sel with
        | Before _ -> Before Double
        | After _ -> After Double
        | First_letter _ -> First_letter Double
        | First_line _ -> First_line Double
        | other -> other
      in
      match s.pseudo_element with
      | None -> { s with pseudo_element = Some canonical }
      | Some prev when Selector.equal prev canonical -> s
      | Some _ -> mark_complex s)
  | Attribute (ns, attr_name, matcher, flag) ->
      {
        s with
        attrs = { key = (ns, attr_name); matcher; flag } :: s.attrs;
        complex = true;
      }
  | Not xs -> { (List.fold_left add_not_selector s xs) with complex = true }
  | sel -> (
      match nth_of_selector sel with
      | Some nth -> add_nth nth s
      | None -> mark_complex s)

let rec of_selector (sel : Selector.t) : t =
  match sel with
  | Combined (left, Child, right) ->
      let s = of_selector right in
      { s with parent = Some (of_selector left) }
  | Combined (_, _, right) -> of_selector right
  | Relative (_, right) -> of_selector right
  | List [ single ] -> of_selector single
  | List _ -> { empty with complex = true }
  | _ -> fold_simple empty sel

let exact_value = function
  | Selector.Exact s | Exact_quoted (s, _) -> Some s
  | _ -> None

let same_attr_key a b = a.key = b.key

let attr_flag_is_case_insensitive = function
  | Some Selector_intf.Insensitive -> true
  | _ -> false

let exact_attr_disjoint a b =
  match (exact_value a.matcher, exact_value b.matcher) with
  | Some x, Some y -> (
      match (a.flag, b.flag) with
      | Some Selector_intf.Sensitive, Some Selector_intf.Sensitive -> x <> y
      | _ -> String.lowercase_ascii x <> String.lowercase_ascii y)
  | _ -> false

let attrs_disjoint a b =
  List.exists
    (fun attr_a ->
      List.exists
        (fun attr_b ->
          same_attr_key attr_a attr_b && exact_attr_disjoint attr_a attr_b)
        b.attrs)
    a.attrs

let exact_attr_implies a b =
  same_attr_key a b
  &&
  match (exact_value a.matcher, exact_value b.matcher) with
  | Some x, Some y when attr_flag_is_case_insensitive b.flag ->
      String.lowercase_ascii x = String.lowercase_ascii y
  | Some x, Some y -> String.equal x y
  | _ -> false

let attr_negation_disjoint attrs not_attrs =
  List.exists
    (fun attr ->
      List.exists (fun not_attr -> exact_attr_implies attr not_attr) not_attrs)
    attrs

let nth_disjoint a b =
  match (a, b) with
  | Index x, Index y -> x <> y
  | Index x, Odd | Odd, Index x -> x mod 2 = 0
  | Index x, Even | Even, Index x -> x mod 2 <> 0
  | Odd, Even | Even, Odd -> true
  | Odd, Odd | Even, Even -> false

let nths_disjoint a b =
  List.exists (fun nth_a -> List.exists (nth_disjoint nth_a) b.nth) a.nth

let nth_implies a b =
  match (a, b) with
  | Index x, Index y -> x = y
  | Index x, Odd -> x mod 2 <> 0
  | Index x, Even -> x mod 2 = 0
  | Odd, Odd | Even, Even -> true
  | Odd, (Index _ | Even) | Even, (Index _ | Odd) -> false

let nth_negation_disjoint nth not_nth =
  List.exists
    (fun fact ->
      List.exists (fun not_fact -> nth_implies fact not_fact) not_nth)
    nth

let set_negation_disjoint required forbidden =
  not (String_set.disjoint required forbidden)

let element_negation_disjoint element forbidden =
  match element with Some el -> String_set.mem el forbidden | None -> false

let positive_vs_negative_disjoint positive negative =
  set_negation_disjoint positive.ids negative.not_ids
  || set_negation_disjoint positive.classes negative.not_classes
  || element_negation_disjoint positive.element negative.not_elements
  || attr_negation_disjoint positive.attrs negative.not_attrs
  || nth_negation_disjoint positive.nth negative.not_nth

let negation_disjoint a b =
  positive_vs_negative_disjoint a b || positive_vs_negative_disjoint b a

let direct_facts_disjoint a b =
  let pe_disjoint =
    match (a.pseudo_element, b.pseudo_element) with
    | None, None -> false
    | Some _, None | None, Some _ -> true
    | Some x, Some y -> not (Selector.equal x y)
  in
  if pe_disjoint then true
  else
    let elem_disjoint =
      match (a.element, b.element) with Some x, Some y -> x <> y | _ -> false
    in
    if elem_disjoint then true
    else
      let id_disjoint =
        (not (String_set.is_empty a.ids))
        && (not (String_set.is_empty b.ids))
        && String_set.disjoint a.ids b.ids
      in
      id_disjoint || attrs_disjoint a b || nths_disjoint a b
      || negation_disjoint a b

let rec may_overlap a b =
  if direct_facts_disjoint a b then false
  else
    match (a.parent, b.parent) with
    | Some pa, Some pb when not (may_overlap pa pb) -> false
    | _ -> true

let ids s = String_set.elements s.ids
let classes s = String_set.elements s.classes
let element s = s.element
let is_complex s = s.complex