1 /*
2  * Copyright (C) 2021-2024 Alexander Borisov
3  *
4  * Author: Alexander Borisov <borisov@lexbor.com>
5  */
6 
7 
8 #ifndef LEXBOR_SELECTORS_H
9 #define LEXBOR_SELECTORS_H
10 
11 #ifdef __cplusplus
12 extern "C" {
13 #endif
14 
15 #include "lexbor/selectors/base.h"
16 #include "lexbor/dom/dom.h"
17 #include "lexbor/css/selectors/selectors.h"
18 #include "lexbor/core/array_obj.h"
19 
20 
21 typedef enum {
22     LXB_SELECTORS_OPT_DEFAULT = 0x00,
23 
24     /*
25      * Includes the passed (root) node in the search.
26      *
27      * By default, the root node does not participate in selector searches,
28      * only its children.
29      *
30      * This behavior is logical, if you have found a node and then you want to
31      * search for other nodes in it, you don't need to check it again.
32      *
33      * But there are cases when it is necessary for root node to participate
34      * in the search.  That's what this option is for.
35      */
36     LXB_SELECTORS_OPT_MATCH_ROOT = 1 << 1,
37 
38     /*
39      * Stop searching after the first match with any of the selectors
40      * in the list.
41      *
42      * By default, the callback will be triggered for each selector list.
43      * That is, if your node matches different selector lists, it will be
44      * returned multiple times in the callback.
45      *
46      * For example:
47      *    HTML: <div id="ok"><span>test</span></div>
48      *    Selectors: div, div[id="ok"], div:has(:not(a))
49      *
50      * The default behavior will cause three callbacks with the same node (div).
51      * Because it will be found by every selector in the list.
52      *
53      * This option allows you to end the element check after the first match on
54      * any of the selectors.  That is, the callback will be called only once
55      * for example above.  This way we get rid of duplicates in the search.
56      */
57     LXB_SELECTORS_OPT_MATCH_FIRST = 1 << 2
58 }
59 lxb_selectors_opt_t;
60 
61 typedef struct lxb_selectors lxb_selectors_t;
62 typedef struct lxb_selectors_entry lxb_selectors_entry_t;
63 typedef struct lxb_selectors_nested lxb_selectors_nested_t;
64 
65 typedef lxb_status_t
66 (*lxb_selectors_cb_f)(lxb_dom_node_t *node,
67                       lxb_css_selector_specificity_t spec, void *ctx);
68 
69 typedef lxb_selectors_entry_t *
70 (*lxb_selectors_state_cb_f)(lxb_selectors_t *selectors,
71                             lxb_selectors_entry_t *entry);
72 
73 struct lxb_selectors_entry {
74     uintptr_t                     id;
75     lxb_css_selector_combinator_t combinator;
76     const lxb_css_selector_t      *selector;
77     lxb_dom_node_t                *node;
78     lxb_selectors_entry_t         *next;
79     lxb_selectors_entry_t         *prev;
80     lxb_selectors_entry_t         *following;
81     lxb_selectors_nested_t        *nested;
82 };
83 
84 struct lxb_selectors_nested {
85     lxb_selectors_entry_t    *entry;
86     lxb_selectors_state_cb_f return_state;
87 
88     lxb_selectors_cb_f       cb;
89     void                     *ctx;
90 
91     lxb_dom_node_t           *root;
92     lxb_selectors_entry_t    *last;
93     lxb_selectors_nested_t   *parent;
94 
95     size_t                   index;
96     bool                     found;
97 };
98 
99 struct lxb_selectors {
100     lxb_selectors_state_cb_f state;
101     lexbor_dobject_t         *objs;
102     lexbor_dobject_t         *nested;
103 
104     lxb_selectors_nested_t   *current;
105     lxb_selectors_entry_t    *first;
106 
107     lxb_selectors_opt_t      options;
108     lxb_status_t             status;
109 };
110 
111 
112 /*
113  * Create lxb_selectors_t object.
114  *
115  * @return lxb_selectors_t * if successful, otherwise NULL.
116  */
117 LXB_API lxb_selectors_t *
118 lxb_selectors_create(void);
119 
120 /*
121  * Initialization of lxb_selectors_t object.
122  *
123  * Caches are initialized in this function.
124  *
125  * @param[in] lxb_selectors_t *
126  *
127  * @return LXB_STATUS_OK if successful, otherwise an error status value.
128  */
129 LXB_API lxb_status_t
130 lxb_selectors_init(lxb_selectors_t *selectors);
131 
132 /*
133  * Clears the object. Returns object to states as after initialization.
134  *
135  * After each call to lxb_selectors_find() and lxb_selectors_find_for_node(),
136  * the lxb_selectors_t object is cleared. That is, you don't need to call this
137  * function every time after searching by a selector.
138  *
139  * @param[in] lxb_url_parser_t *
140  */
141 LXB_API void
142 lxb_selectors_clean(lxb_selectors_t *selectors);
143 
144 /*
145  * Destroy lxb_selectors_t object.
146  *
147  * Destroying all caches.
148  *
149  * @param[in] lxb_selectors_t *. Can be NULL.
150  * @param[in] if false: only destroys internal caches.
151  * if true: destroys the lxb_selectors_t object and all internal caches.
152  *
153  * @return lxb_selectors_t * if self_destroy = false, otherwise NULL.
154  */
155 LXB_API lxb_selectors_t *
156 lxb_selectors_destroy(lxb_selectors_t *selectors, bool self_destroy);
157 
158 /*
159  * Search for nodes by selector list.
160  *
161  * Default Behavior:
162  *    1. The root node does not participate in the search, only its child nodes.
163  *    2. If a node matches multiple selector lists, a callback with that node
164  *       will be called on each list.
165  *       For example:
166  *           HTML: <div id="ok"><span></span></div>
167  *           Selectors: div, div[id="ok"], div:has(:not(a))
168  *       For each selector list, a callback with a "div" node will be called.
169  *
170  * To change the search behavior, see lxb_selectors_opt_set().
171  *
172  * @param[in] lxb_selectors_t *.
173  * @param[in] lxb_dom_node_t *.  The node from which the search will begin.
174  * @param[in] const lxb_css_selector_list_t *.  Selectors List.
175  * @param[in] lxb_selectors_cb_f.  Callback for a found node.
176  * @param[in] void *.  Context for the callback.
177  * if true: destroys the lxb_selectors_t object and all internal caches.
178  *
179  * @return LXB_STATUS_OK if successful, otherwise an error status value.
180  */
181 LXB_API lxb_status_t
182 lxb_selectors_find(lxb_selectors_t *selectors, lxb_dom_node_t *root,
183                    const lxb_css_selector_list_t *list,
184                    lxb_selectors_cb_f cb, void *ctx);
185 
186 /*
187  * Match a node to a Selectors List.
188  *
189  * In other words, the function checks which selector lists will find the
190  * specified node.
191  *
192  * Default Behavior:
193  *    1. If a node matches multiple selector lists, a callback with that node
194  *       will be called on each list.
195  *       For example:
196  *           HTML: <div id="ok"><span></span></div>
197  *           Node: div
198  *           Selectors: div, div[id="ok"], div:has(:not(a))
199  *       For each selector list, a callback with a "div" node will be called.
200  *
201  * To change the search behavior, see lxb_selectors_opt_set().
202  *
203  * @param[in] lxb_selectors_t *.
204  * @param[in] lxb_dom_node_t *.  The node from which the search will begin.
205  * @param[in] const lxb_css_selector_list_t *.  Selectors List.
206  * @param[in] lxb_selectors_cb_f.  Callback for a found node.
207  * @param[in] void *.  Context for the callback.
208  * if true: destroys the lxb_selectors_t object and all internal caches.
209  *
210  * @return LXB_STATUS_OK if successful, otherwise an error status value.
211  */
212 LXB_API lxb_status_t
213 lxb_selectors_match_node(lxb_selectors_t *selectors, lxb_dom_node_t *node,
214                          const lxb_css_selector_list_t *list,
215                          lxb_selectors_cb_f cb, void *ctx);
216 
217 /*
218  * Deprecated!
219  * This function does exactly the same thing as lxb_selectors_match_node().
220  */
221 LXB_API LXB_DEPRECATED(lxb_status_t
222 lxb_selectors_find_reverse(lxb_selectors_t *selectors, lxb_dom_node_t *root,
223                            lxb_css_selector_list_t *list,
224                            lxb_selectors_cb_f cb, void *ctx));
225 
226 /*
227  * Inline functions.
228  */
229 
230 /*
231  * The function sets the node search options.
232  *
233  * For more information, see lxb_selectors_opt_t.
234  *
235  * @param[in] lxb_selectors_t *.
236  * @param[in] lxb_selectors_opt_t.
237  */
238 lxb_inline void
lxb_selectors_opt_set(lxb_selectors_t * selectors,lxb_selectors_opt_t opt)239 lxb_selectors_opt_set(lxb_selectors_t *selectors, lxb_selectors_opt_t opt)
240 {
241     selectors->options = opt;
242 }
243 
244 /*
245  * Get the current selector.
246  *
247  * Function to get the selector by which the node was found.
248  * Use context (void *ctx) to pass the lxb_selectors_t object to the callback.
249  *
250  * @param[in] const lxb_selectors_t *.
251  *
252  * @return const lxb_css_selector_list_t *.
253  */
254 lxb_inline const lxb_css_selector_list_t *
lxb_selectors_selector(const lxb_selectors_t * selectors)255 lxb_selectors_selector(const lxb_selectors_t *selectors)
256 {
257     return selectors->current->entry->selector->list;
258 }
259 
260 /*
261  * Not inline for inline.
262  */
263 
264 /*
265  * Same as lxb_selectors_opt_set() function, but not inline.
266  */
267 LXB_API void
268 lxb_selectors_opt_set_noi(lxb_selectors_t *selectors, lxb_selectors_opt_t opt);
269 
270 /*
271  * Same as lxb_selectors_selector() function, but not inline.
272  */
273 LXB_API const lxb_css_selector_list_t *
274 lxb_selectors_selector_noi(const lxb_selectors_t *selectors);
275 
276 
277 #ifdef __cplusplus
278 } /* extern "C" */
279 #endif
280 
281 #endif /* LEXBOR_SELECTORS_H */
282