php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
selectors.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2021-2024 Alexander Borisov
3 *
4 * Author: Alexander Borisov <borisov@lexbor.com>
5 * Adapted for PHP + libxml2 by: Niels Dossche <nielsdos@php.net>
6 * Based on Lexbor (upstream commit b347aa4e4da4e82b1cae18989ceea1aa0278daf1)
7 */
8
9#include <libxml/xmlstring.h>
10#include <libxml/dict.h>
11#include <Zend/zend.h>
12#include <Zend/zend_operators.h>
13#include <Zend/zend_API.h>
14#include <php.h>
15
19#include "../../../php_dom.h"
20
21#include <math.h>
22
23/* Note: casting and then comparing is a bit faster on my i7-4790 */
24#define CMP_NODE_TYPE(node, ty) ((unsigned char) (node)->type == ty)
25
30
31static void dom_lxb_str_wrapper_release(dom_lxb_str_wrapper *wrapper)
32{
33 if (wrapper->should_free) {
34 xmlFree(wrapper->str.data);
35 }
36}
37
38static zend_always_inline bool lxb_selectors_adapted_is_matchable_child(const xmlNode *node)
39{
40 return CMP_NODE_TYPE(node, XML_ELEMENT_NODE);
41}
42
43static zend_always_inline bool lxb_selectors_adapted_cmp_local_name_literal(const xmlNode *node, const char *name)
44{
45 return strcmp((const char *) node->name, name) == 0;
46}
47
48static zend_always_inline bool lxb_selectors_adapted_cmp_ns(const xmlNode *a, const xmlNode *b)
49{
50 /* Namespace URIs are not interned, hence a->href != b->href. */
51 return a->ns == b->ns || (a->ns != NULL && b->ns != NULL && xmlStrEqual(a->ns->href, b->ns->href));
52}
53
54static zend_always_inline bool lxb_selectors_adapted_cmp_local_name_id(const xmlNode *node, const lxb_selectors_adapted_id *id)
55{
56 uintptr_t ptr = (uintptr_t) node->name;
57 if (id->interned && (ptr & (ZEND_MM_ALIGNMENT - 1)) != 0) {
58 /* It cannot be a heap-allocated string because the pointer is not properly aligned for a heap allocation.
59 * Therefore, it must be interned into the dictionary pool. */
60 return node->name == id->name;
61 }
62
63 return strcmp((const char *) node->name, (const char *) id->name) == 0;
64}
65
66static zend_always_inline const xmlAttr *lxb_selectors_adapted_attr(const xmlNode *node, const lxb_char_t *name)
67{
68 const xmlAttr *attr = NULL;
69 ZEND_ASSERT(node->doc != NULL);
71 /* No need to handle DTD entities as we're in HTML. */
72 size_t name_bound = strlen((const char *) name) + 1;
73 for (const xmlAttr *cur = node->properties; cur != NULL; cur = cur->next) {
74 if (lexbor_str_data_nlocmp_right(cur->name, name, name_bound)) {
75 attr = cur;
76 break;
77 }
78 }
79 } else {
80 attr = xmlHasProp(node, (const xmlChar *) name);
81 }
82
83 if (attr != NULL && attr->ns != NULL) {
84 return NULL;
85 }
86 return attr;
87}
88
89static zend_always_inline bool lxb_selectors_adapted_has_attr(const xmlNode *node, const char *name)
90{
91 return lxb_selectors_adapted_attr(node, (const lxb_char_t *) name) != NULL;
92}
93
94static zend_always_inline dom_lxb_str_wrapper lxb_selectors_adapted_attr_value(const xmlAttr *attr)
95{
97 ret.str.data = (lxb_char_t *) php_libxml_attr_value(attr, &ret.should_free);
98 ret.str.length = strlen((const char *) ret.str.data);
99 return ret;
100}
101
102static bool lxb_selectors_attrib_name_cmp(const lxb_css_selector_t *selector, const char *name, size_t len)
103{
104 return selector->name.length == len && lexbor_str_data_nlocmp_right((const lxb_char_t *) name, selector->name.data, len);
105}
106
107/* From https://html.spec.whatwg.org/#case-sensitivity-of-selectors
108 * "Attribute selectors on an HTML element in an HTML document must treat the values of attributes with the following names as ASCII case-insensitive:" */
109static bool lxb_selectors_is_lowercased_html_attrib_name(const lxb_css_selector_t *selector)
110{
111 return lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("accept"))
112 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("accept-charset"))
113 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("align"))
114 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("alink"))
115 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("axis"))
116 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("bgcolor"))
117 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("charset"))
118 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("checked"))
119 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("clear"))
120 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("codetype"))
121 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("color"))
122 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("compact"))
123 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("declare"))
124 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("defer"))
125 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("dir"))
126 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("direction"))
127 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("disabled"))
128 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("enctype"))
129 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("face"))
130 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("frame"))
131 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("hreflang"))
132 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("http-equiv"))
133 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("lang"))
134 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("language"))
135 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("link"))
136 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("media"))
137 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("method"))
138 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("multiple"))
139 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("nohref"))
140 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("noresize"))
141 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("noshade"))
142 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("nowrap"))
143 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("readonly"))
144 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("rel"))
145 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("rev"))
146 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("rules"))
147 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("scope"))
148 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("scrolling"))
149 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("selected"))
150 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("shape"))
151 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("target"))
152 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("text"))
153 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("type"))
154 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("valign"))
155 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("valuetype"))
156 || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("vlink"));
157}
158
159static void lxb_selectors_adapted_set_entry_id_ex(lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *node)
160{
161 entry->id.attr_case_insensitive = lxb_selectors_is_lowercased_html_attrib_name(selector);
162
163 if (node->doc != NULL && node->doc->dict != NULL) {
164 const xmlChar *interned = xmlDictExists(node->doc->dict, selector->name.data, selector->name.length);
165 if (interned != NULL) {
166 entry->id.name = interned;
167 entry->id.interned = true;
168 return;
169 }
170 }
171
172 entry->id.name = selector->name.data;
173 entry->id.interned = false;
174}
175
176static zend_always_inline void lxb_selectors_adapted_set_entry_id(lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *node)
177{
178 if (entry->id.name == NULL) {
179 lxb_selectors_adapted_set_entry_id_ex(entry, selector, node);
180 }
181}
182
183static lxb_status_t
184lxb_selectors_state_tree(lxb_selectors_t *selectors, const xmlNode *root,
185 const lxb_css_selector_list_t *list);
186
187static lxb_status_t
188lxb_selectors_state_run(lxb_selectors_t *selectors, const xmlNode *node,
189 const lxb_css_selector_list_t *list);
190
192lxb_selectors_state_find(lxb_selectors_t *selectors,
193 lxb_selectors_entry_t *entry);
194
196lxb_selectors_state_find_check(lxb_selectors_t *selectors, const xmlNode *node,
197 const lxb_css_selector_t *selector,
198 lxb_selectors_entry_t *entry);
199
201lxb_selectors_state_pseudo_class_function(lxb_selectors_t *selectors,
202 lxb_selectors_entry_t *entry);
203
204static const xmlNode *
205lxb_selectors_next_node(lxb_selectors_nested_t *main);
206
207static const xmlNode *
208lxb_selectors_state_has_relative(const xmlNode *node,
209 const lxb_css_selector_t *selector);
210
212lxb_selectors_state_after_find_has(lxb_selectors_t *selectors,
213 lxb_selectors_entry_t *entry);
214
216lxb_selectors_state_after_find(lxb_selectors_t *selectors,
217 lxb_selectors_entry_t *entry);
218
220lxb_selectors_state_after_nth_child(lxb_selectors_t *selectors,
221 lxb_selectors_entry_t *entry);
222
223static bool
224lxb_selectors_match(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry,
225 const lxb_css_selector_t *selector, const xmlNode *node);
226
227static bool
228lxb_selectors_match_element(const lxb_css_selector_t *selector,
229 const xmlNode *node, lxb_selectors_entry_t *entry);
230
231static bool
232lxb_selectors_match_id(const lxb_css_selector_t *selector, const xmlNode *node, bool quirks);
233
234static bool
235lxb_selectors_match_class(const lexbor_str_t *target, const lexbor_str_t *src,
236 bool quirks);
237
238static bool
239lxb_selectors_match_attribute(const lxb_css_selector_t *selector,
240 const xmlNode *node, lxb_selectors_entry_t *entry);
241
242static bool
243lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
244 const xmlNode *node);
245
246static bool
247lxb_selectors_pseudo_class_function(const lxb_css_selector_t *selector,
248 const xmlNode *node);
249
250static bool
251lxb_selectors_pseudo_element(const lxb_css_selector_t *selector,
252 const xmlNode *node);
253
254static bool
255lxb_selectors_pseudo_class_disabled(const xmlNode *node);
256
257static bool
258lxb_selectors_pseudo_class_first_child(const xmlNode *node);
259
260static bool
261lxb_selectors_pseudo_class_first_of_type(const xmlNode *node);
262
263static bool
264lxb_selectors_pseudo_class_last_child(const xmlNode *node);
265
266static bool
267lxb_selectors_pseudo_class_last_of_type(const xmlNode *node);
268
269static bool
270lxb_selectors_pseudo_class_read_write(const xmlNode *node);
271
272static bool
273lxb_selectors_anb_calc(const lxb_css_selector_anb_of_t *anb, size_t index);
274
275static lxb_status_t
276lxb_selectors_cb_ok(const xmlNode *node,
277 lxb_css_selector_specificity_t spec, void *ctx);
278
279static lxb_status_t
280lxb_selectors_cb_not(const xmlNode *node,
281 lxb_css_selector_specificity_t spec, void *ctx);
282
283
286{
288
289 selectors->objs = lexbor_dobject_create();
290 status = lexbor_dobject_init(selectors->objs,
291 128, sizeof(lxb_selectors_entry_t));
292 if (status != LXB_STATUS_OK) {
293 return status;
294 }
295
296 selectors->nested = lexbor_dobject_create();
297 status = lexbor_dobject_init(selectors->nested,
298 64, sizeof(lxb_selectors_nested_t));
299 if (status != LXB_STATUS_OK) {
300 return status;
301 }
302
304
305 return LXB_STATUS_OK;
306}
307
308void
310{
311 lexbor_dobject_clean(selectors->objs);
312 lexbor_dobject_clean(selectors->nested);
313}
314
315void
317{
318 selectors->objs = lexbor_dobject_destroy(selectors->objs, true);
319 selectors->nested = lexbor_dobject_destroy(selectors->nested, true);
320}
321
322lxb_inline const xmlNode *
325 const lxb_css_selector_t *selector,
326 const xmlNode *node)
327{
328 node = node->parent;
329
330 while (node != NULL) {
332 && lxb_selectors_match(selectors, entry, selector, node))
333 {
334 return node;
335 }
336
337 node = node->parent;
338 }
339
340 return NULL;
341}
342
343lxb_inline const xmlNode *
345 const lxb_css_selector_t *selector, const xmlNode *node)
346{
347 if (lxb_selectors_match(selectors, entry, selector, node)) {
348 return node;
349 }
350
351 return NULL;
352}
353
354lxb_inline const xmlNode *
356 const lxb_css_selector_t *selector, const xmlNode *root)
357{
358 root = root->parent;
359
360 if (root != NULL && CMP_NODE_TYPE(root, XML_ELEMENT_NODE)
361 && lxb_selectors_match(selectors, entry, selector, root))
362 {
363 return root;
364 }
365
366 return NULL;
367}
368
369lxb_inline const xmlNode *
371 const lxb_css_selector_t *selector, const xmlNode *node)
372{
373 node = node->prev;
374
375 while (node != NULL) {
376 if (CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
377 if (lxb_selectors_match(selectors, entry, selector, node)) {
378 return node;
379 }
380
381 return NULL;
382 }
383
384 node = node->prev;
385 }
386
387 return NULL;
388}
389
390lxb_inline const xmlNode *
392 const lxb_css_selector_t *selector, const xmlNode *node)
393{
394 node = node->prev;
395
396 while (node != NULL) {
397 if (CMP_NODE_TYPE(node, XML_ELEMENT_NODE) &&
398 lxb_selectors_match(selectors, entry, selector, node))
399 {
400 return node;
401 }
402
403 node = node->prev;
404 }
405
406 return NULL;
407}
408
410lxb_selectors_find(lxb_selectors_t *selectors, const xmlNode *root,
411 const lxb_css_selector_list_t *list,
412 lxb_selectors_cb_f cb, void *ctx)
413{
416
417 entry = lexbor_dobject_calloc(selectors->objs);
418
420 entry->selector = list->last;
421
422 nested.parent = NULL;
423 nested.entry = entry;
424 nested.cb = cb;
425 nested.ctx = ctx;
426
427 selectors->current = &nested;
428 selectors->status = LXB_STATUS_OK;
429
430 return lxb_selectors_state_tree(selectors, root, list);
431}
432
434lxb_selectors_match_node(lxb_selectors_t *selectors, const xmlNode *node,
435 const lxb_css_selector_list_t *list,
436 lxb_selectors_cb_f cb, void *ctx)
437{
441
442 if (!CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
443 return LXB_STATUS_OK;
444 }
445
446 entry = lexbor_dobject_calloc(selectors->objs);
447
449 entry->selector = list->last;
450
451 nested.parent = NULL;
452 nested.entry = entry;
453 nested.cb = cb;
454 nested.ctx = ctx;
455
456 selectors->current = &nested;
457 selectors->status = LXB_STATUS_OK;
458
459 status = lxb_selectors_state_run(selectors, node, list);
460
461 lxb_selectors_clean(selectors);
462
463 return status;
464}
465
466static lxb_status_t
467lxb_selectors_state_tree(lxb_selectors_t *selectors, const xmlNode *root,
468 const lxb_css_selector_list_t *list)
469{
471 const xmlNode *node;
472
473#if 0
474 if (selectors->options & LXB_SELECTORS_OPT_MATCH_ROOT) {
475 node = root;
476
479 node = root->children;
480 }
481 }
482 else
483#endif
484 {
485 node = root->children;
486 }
487
488 if (node == NULL) {
489 goto out;
490 }
491
492 do {
493 if (!CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
494 goto next;
495 }
496
497 status = lxb_selectors_state_run(selectors, node, list);
498 if (status != LXB_STATUS_OK) {
499 if (status == LXB_STATUS_STOP) {
500 break;
501 }
502
503 lxb_selectors_clean(selectors);
504
505 return status;
506 }
507
508 if (node->children != NULL) {
509 node = node->children;
510 }
511 else {
512
513 next:
514
515 while (node != root && node->next == NULL) {
516 node = node->parent;
517 }
518
519 if (node == root) {
520 break;
521 }
522
523 node = node->next;
524 }
525 }
526 while (true);
527
528out:
529 lxb_selectors_clean(selectors);
530
531 return LXB_STATUS_OK;
532}
533
534static lxb_status_t
535lxb_selectors_state_run(lxb_selectors_t *selectors, const xmlNode *node,
536 const lxb_css_selector_list_t *list)
537{
539
540 entry = selectors->current->entry;
541
542 entry->node = node;
543 selectors->state = lxb_selectors_state_find;
544 selectors->first = entry;
545
546again:
547
548 do {
549 entry = selectors->state(selectors, entry);
550 }
551 while (entry != NULL);
552
553 if (selectors->current->parent != NULL
554 && selectors->status == LXB_STATUS_OK)
555 {
556 entry = selectors->current->entry;
557 selectors->state = selectors->current->return_state;
558
559 goto again;
560 }
561
562 return selectors->status;
563}
564
566lxb_selectors_state_find(lxb_selectors_t *selectors,
568{
569 const xmlNode *node;
571 const lxb_css_selector_t *selector;
572 const lxb_css_selector_anb_of_t *anb;
573 const lxb_css_selector_pseudo_t *pseudo;
574
575 selector = entry->selector;
576
578 pseudo = &selector->u.pseudo;
579
580 /* Optimizing. */
581
582 switch (pseudo->type) {
585 anb = pseudo->data;
586
587 if (anb->of != NULL) {
588 break;
589 }
590
591 goto godoit;
592
595 goto godoit;
596
597 default:
598 break;
599 }
600
601 if (entry->nested == NULL) {
602 next = lexbor_dobject_calloc(selectors->objs);
603
605
606 entry->nested = lexbor_dobject_calloc(selectors->nested);
607
608 entry->nested->entry = next;
609 entry->nested->parent = selectors->current;
610 }
611
612 selectors->state = lxb_selectors_state_pseudo_class_function;
613 selectors->current->last = entry;
614 selectors->current = entry->nested;
615
616 next = entry->nested->entry;
617 next->node = entry->node;
618
619 return next;
620 }
621
622godoit:
623
624 switch (entry->combinator) {
626 node = lxb_selectors_descendant(selectors, entry,
627 selector, entry->node);
628 break;
629
631 node = lxb_selectors_close(selectors, entry,
632 selector, entry->node);
633 break;
634
636 node = lxb_selectors_child(selectors, entry,
637 selector, entry->node);
638 break;
639
641 node = lxb_selectors_sibling(selectors, entry,
642 selector, entry->node);
643 break;
644
646 node = lxb_selectors_following(selectors, entry,
647 selector, entry->node);
648 break;
649
651 default:
652 selectors->status = LXB_STATUS_ERROR;
653 return NULL;
654 }
655
656 return lxb_selectors_state_find_check(selectors, node, selector, entry);
657}
658
660lxb_selectors_state_find_check(lxb_selectors_t *selectors, const xmlNode *node,
661 const lxb_css_selector_t *selector,
663{
666
667 if (node == NULL) {
668
669 try_next:
670
671 if (entry->next == NULL) {
672
673 try_next_list:
674
675 if (selector->list->next == NULL) {
676 return NULL;
677 }
678
679 /*
680 * Try the following selectors from the selector list.
681 */
682
683 if (entry->following != NULL) {
684 entry->following->node = entry->node;
685
686 if (selectors->current->parent == NULL) {
687 selectors->first = entry->following;
688 }
689
690 return entry->following;
691 }
692
693 next = lexbor_dobject_calloc(selectors->objs);
694
696 next->selector = selector->list->next->last;
697 next->node = entry->node;
698
699 entry->following = next;
700
701 if (selectors->current->parent == NULL) {
702 selectors->first = next;
703 }
704
705 return next;
706 }
707
708 do {
709 entry = entry->next;
710
712 if (entry->next == NULL) {
713 selector = entry->selector;
714 goto try_next;
715 }
716
717 entry = entry->next;
718 }
719
720 switch (entry->combinator) {
722 node = entry->node->parent;
723
724 if (node == NULL
726 {
727 node = NULL;
728 }
729
730 break;
731
733 node = entry->node->prev;
734 break;
735
739 node = NULL;
740 break;
741
743 default:
744 selectors->status = LXB_STATUS_ERROR;
745 return NULL;
746 }
747 }
748 while (node == NULL);
749
750 entry->node = node;
751
752 return entry;
753 }
754
755 if (selector->prev == NULL) {
756 current = selectors->current;
757
758 selectors->status = current->cb(current->entry->node,
759 selector->list->specificity,
760 current->ctx);
761
762 if ((selectors->options & LXB_SELECTORS_OPT_MATCH_FIRST) == 0
763 && current->parent == NULL)
764 {
765 if (selectors->status == LXB_STATUS_OK) {
766 entry = selectors->first;
767 goto try_next_list;
768 }
769 }
770
771 return NULL;
772 }
773
774 if (entry->prev == NULL) {
775 next = lexbor_dobject_calloc(selectors->objs);
776
777 next->combinator = selector->combinator;
778 next->selector = selector->prev;
779 next->node = node;
780
781 next->next = entry;
782 entry->prev = next;
783
784 return next;
785 }
786
787 entry->prev->node = node;
788
789 return entry->prev;
790}
791
793lxb_selectors_state_pseudo_class_function(lxb_selectors_t *selectors,
795{
796 const xmlNode *node, *base;
798 const lxb_css_selector_list_t *list;
800 const lxb_css_selector_pseudo_t *pseudo;
801
802 current = selectors->current;
803
804 base = lxb_selectors_next_node(current);
805 if (base == NULL) {
806 goto not_found;
807 }
808
809 pseudo = &current->parent->last->selector->u.pseudo;
810
811 switch (pseudo->type) {
813 list = (lxb_css_selector_list_t *) pseudo->data;
814 node = lxb_selectors_state_has_relative(base, list->first);
815
816 if (node == NULL) {
817 selectors->current = selectors->current->parent;
818 entry = selectors->current->last;
819
820 selectors->state = lxb_selectors_state_find;
821
822 return lxb_selectors_state_find_check(selectors, NULL,
823 entry->selector, entry);
824 }
825
826 current->root = base;
827
828 current->entry->selector = list->last;
829 current->entry->node = node;
830 current->return_state = lxb_selectors_state_after_find_has;
831 current->cb = lxb_selectors_cb_ok;
832 current->ctx = &current->found;
833 current->found = false;
834
835 selectors->state = lxb_selectors_state_find;
836
837 return entry;
838
842 current->entry->selector = ((lxb_css_selector_list_t *) pseudo->data)->last;
843 current->entry->node = base;
844 current->return_state = lxb_selectors_state_after_find;
845 current->cb = lxb_selectors_cb_ok;
846 current->ctx = &current->found;
847 current->found = false;
848
849 selectors->state = lxb_selectors_state_find;
850
851 return entry;
852
854 current->entry->selector = ((lxb_css_selector_list_t *) pseudo->data)->last;
855 current->entry->node = base;
856 current->return_state = lxb_selectors_state_after_find;
857 current->cb = lxb_selectors_cb_not;
858 current->ctx = &current->found;
859 current->found = true;
860
861 selectors->state = lxb_selectors_state_find;
862
863 return entry;
864
867 anb = pseudo->data;
868
869 current->entry->selector = anb->of->last;
870 current->entry->node = base;
871 current->return_state = lxb_selectors_state_after_nth_child;
872 current->cb = lxb_selectors_cb_ok;
873 current->ctx = &current->found;
874 current->root = base;
875 current->index = 0;
876 current->found = false;
877
878 selectors->state = lxb_selectors_state_find;
879
880 return entry;
881
882 /*
883 * This one can only happen if the user has somehow messed up the
884 * selector.
885 */
886
893 default:
894 break;
895 }
896
897not_found:
898
899 selectors->current = selectors->current->parent;
900 entry = selectors->current->last;
901
902 selectors->state = lxb_selectors_state_find;
903
904 return lxb_selectors_state_find_check(selectors, NULL,
905 entry->selector, entry);
906}
907
908static const xmlNode *
909lxb_selectors_next_node(lxb_selectors_nested_t *main)
910{
911 const xmlNode *node = main->entry->node;
912
913 switch (main->parent->last->combinator) {
916 if (node->parent == NULL
917 || !CMP_NODE_TYPE(node->parent, XML_ELEMENT_NODE))
918 {
919 return NULL;
920 }
921
922 return node->parent;
923
925 return node;
926
929 node = node->prev;
930 break;
931
932 default:
933 return NULL;
934 }
935
936 while (node != NULL) {
937 if (CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
938 break;
939 }
940
941 node = node->prev;
942 }
943
944 return node;
945}
946
947static const xmlNode *
948lxb_selectors_state_has_relative(const xmlNode *node,
949 const lxb_css_selector_t *selector)
950{
951 const xmlNode *root = node;
952
953 switch (selector->combinator) {
956 node = node->children;
957 break;
958
961 node = node->next;
962 break;
963
964 default:
965 return NULL;
966 }
967
968 while (node != NULL) {
969 if (CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
970 break;
971 }
972
973 while (node != root && node->next == NULL && node->parent != NULL) {
974 node = node->parent;
975 }
976
977 if (node == root) {
978 return NULL;
979 }
980
981 node = node->next;
982 }
983
984 return node;
985}
986
988lxb_selectors_state_after_find_has(lxb_selectors_t *selectors,
990{
991 const xmlNode *node;
992 lxb_selectors_entry_t *parent;
994
995 if (selectors->current->found) {
996 node = selectors->current->root;
997
998 selectors->current = selectors->current->parent;
999 parent = selectors->current->last;
1000
1001 selectors->state = lxb_selectors_state_find;
1002
1003 return lxb_selectors_state_find_check(selectors, node,
1004 parent->selector, parent);
1005 }
1006
1007 current = selectors->current;
1008 node = entry->node;
1009
1010 switch (entry->selector->list->first->combinator) {
1012 if (node->children != NULL) {
1013 node = node->children;
1014 }
1015 else {
1016
1017 next:
1018
1019 while (node != current->root && node->next == NULL) {
1020 node = node->parent;
1021 }
1022
1023 if (node == current->root) {
1024 goto failed;
1025 }
1026
1027 node = node->next;
1028 }
1029
1030 if (!CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
1031 goto next;
1032 }
1033
1034 break;
1035
1038 node = node->next;
1039
1040 while (node != NULL && !CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
1041 node = node->next;
1042 }
1043
1044 if (node == NULL) {
1045 goto failed;
1046 }
1047
1048 break;
1049
1051 goto failed;
1052
1055 default:
1056 selectors->status = LXB_STATUS_ERROR;
1057 return NULL;
1058 }
1059
1060 entry->node = node;
1061 selectors->state = lxb_selectors_state_find;
1062
1063 return entry;
1064
1065failed:
1066
1067 selectors->current = selectors->current->parent;
1068 parent = selectors->current->last;
1069
1070 selectors->state = lxb_selectors_state_find;
1071
1072 return lxb_selectors_state_find_check(selectors, NULL,
1073 parent->selector, parent);
1074}
1075
1076
1077static lxb_selectors_entry_t *
1078lxb_selectors_state_after_find(lxb_selectors_t *selectors,
1079 lxb_selectors_entry_t *entry)
1080{
1081 const xmlNode *node;
1082 lxb_selectors_entry_t *parent;
1084
1085 current = selectors->current;
1086
1087 if (current->found) {
1088 node = entry->node;
1089
1090 selectors->current = current->parent;
1091 parent = selectors->current->last;
1092
1093 selectors->state = lxb_selectors_state_find;
1094
1095 return lxb_selectors_state_find_check(selectors, node,
1096 parent->selector, parent);
1097 }
1098
1099 node = entry->node;
1100
1101 switch (current->parent->last->combinator) {
1103 if (node->parent != NULL
1104 && CMP_NODE_TYPE(node->parent, XML_ELEMENT_NODE))
1105 {
1106 node = node->parent;
1107 }
1108 else {
1109 node = NULL;
1110 }
1111
1112 break;
1113
1115 node = node->prev;
1116
1117 while (node != NULL && !CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
1118 node = node->prev;
1119 }
1120
1121 break;
1122
1126 node = NULL;
1127 break;
1128
1130 default:
1131 selectors->status = LXB_STATUS_ERROR;
1132 return NULL;
1133 }
1134
1135 if (node == NULL) {
1136 selectors->current = current->parent;
1137 parent = selectors->current->last;
1138
1139 selectors->state = lxb_selectors_state_find;
1140
1141 return lxb_selectors_state_find_check(selectors, node,
1142 parent->selector, parent);
1143 }
1144
1145 entry->node = node;
1146 selectors->state = lxb_selectors_state_find;
1147
1148 return entry;
1149}
1150
1151static lxb_selectors_entry_t *
1152lxb_selectors_state_after_nth_child(lxb_selectors_t *selectors,
1153 lxb_selectors_entry_t *entry)
1154{
1155 bool found;
1156 const xmlNode *node;
1157 lxb_selectors_entry_t *parent;
1159 const lxb_css_selector_t *selector;
1160 const lxb_css_selector_pseudo_t *pseudo;
1161
1162 current = selectors->current;
1163 selector = current->parent->last->selector;
1164 pseudo = &selector->u.pseudo;
1165
1166 node = entry->node;
1167
1168 if (current->found) {
1169 current->index += 1;
1170 }
1171 else if (current->root == node) {
1172 node = NULL;
1173 goto done;
1174 }
1175
1177 node = node->prev;
1178
1179 while (node != NULL) {
1180 if (CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
1181 break;
1182 }
1183
1184 node = node->prev;
1185 }
1186 }
1187 else {
1188 node = node->next;
1189
1190 while (node != NULL) {
1191 if (CMP_NODE_TYPE(node, XML_ELEMENT_NODE)) {
1192 break;
1193 }
1194
1195 node = node->next;
1196 }
1197 }
1198
1199 if (node == NULL) {
1200 goto done;
1201 }
1202
1203 entry->node = node;
1204 current->found = false;
1205 selectors->state = lxb_selectors_state_find;
1206
1207 return entry;
1208
1209done:
1210
1211 if (current->index > 0) {
1212 found = lxb_selectors_anb_calc(pseudo->data, current->index);
1213
1214 node = (found) ? current->root : NULL;
1215 }
1216
1217 selectors->state = lxb_selectors_state_find;
1218 selectors->current = selectors->current->parent;
1219
1220 parent = selectors->current->last;
1221
1222 return lxb_selectors_state_find_check(selectors, node,
1223 parent->selector, parent);
1224}
1225
1226static bool
1227lxb_selectors_match(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry,
1228 const lxb_css_selector_t *selector, const xmlNode *node)
1229{
1230 switch (selector->type) {
1232 return true;
1233
1235 return lxb_selectors_match_element(selector, node, entry);
1236
1238 return lxb_selectors_match_id(selector, node, selectors->options & LXB_SELECTORS_OPT_QUIRKS_MODE);
1239
1241 const xmlAttr *dom_attr = lxb_selectors_adapted_attr(node, (const lxb_char_t *) "class");
1242 if (dom_attr == NULL) {
1243 return false;
1244 }
1245
1246 dom_lxb_str_wrapper trg = lxb_selectors_adapted_attr_value(dom_attr);
1247
1248 if (trg.str.length == 0) {
1249 dom_lxb_str_wrapper_release(&trg);
1250 return false;
1251 }
1252
1253 bool ret = lxb_selectors_match_class(&trg.str,
1254 &selector->name, selectors->options & LXB_SELECTORS_OPT_QUIRKS_MODE);
1255 dom_lxb_str_wrapper_release(&trg);
1256 return ret;
1257 }
1258
1260 return lxb_selectors_match_attribute(selector, node, entry);
1261
1263 return lxb_selectors_pseudo_class(selector, node);
1264
1266 return lxb_selectors_pseudo_class_function(selector, node);
1267
1269 return lxb_selectors_pseudo_element(selector, node);
1270
1272 return false;
1273
1275 }
1276
1277 return false;
1278}
1279
1280static bool
1281lxb_selectors_match_element(const lxb_css_selector_t *selector,
1282 const xmlNode *node, lxb_selectors_entry_t *entry)
1283{
1284 lxb_selectors_adapted_set_entry_id(entry, selector, node);
1285 return lxb_selectors_adapted_cmp_local_name_id(node, &entry->id);
1286}
1287
1288static bool
1289lxb_selectors_match_id(const lxb_css_selector_t *selector, const xmlNode *node, bool quirks)
1290{
1291 const xmlAttr *dom_attr = lxb_selectors_adapted_attr(node, (const lxb_char_t *) "id");
1292 if (dom_attr == NULL) {
1293 return false;
1294 }
1295
1296 const lexbor_str_t *src = &selector->name;
1297 dom_lxb_str_wrapper trg = lxb_selectors_adapted_attr_value(dom_attr);
1298 bool ret = false;
1299 if (trg.str.length == src->length) {
1300 if (quirks) {
1301 ret = lexbor_str_data_ncasecmp(trg.str.data, src->data, src->length);
1302 } else {
1303 ret = lexbor_str_data_ncmp(trg.str.data, src->data, src->length);
1304 }
1305 }
1306 dom_lxb_str_wrapper_release(&trg);
1307
1308 return ret;
1309}
1310
1311static bool
1312lxb_selectors_match_class(const lexbor_str_t *target, const lexbor_str_t *src,
1313 bool quirks)
1314{
1316
1317 if (target->length < src->length) {
1318 return false;
1319 }
1320
1321 bool is_it = false;
1322
1323 const lxb_char_t *data = target->data;
1324 const lxb_char_t *pos = data;
1325 const lxb_char_t *end = data + target->length;
1326
1327 for (; data < end; data++) {
1328 chr = *data;
1329
1330 if (lexbor_utils_whitespace(chr, ==, ||)) {
1331
1332 if ((size_t) (data - pos) == src->length) {
1333 if (quirks) {
1334 is_it = lexbor_str_data_ncasecmp(pos, src->data, src->length);
1335 }
1336 else {
1337 is_it = lexbor_str_data_ncmp(pos, src->data, src->length);
1338 }
1339
1340 if (is_it) {
1341 return true;
1342 }
1343 }
1344
1345 if ((size_t) (end - data) < src->length) {
1346 return false;
1347 }
1348
1349 pos = data + 1;
1350 }
1351 }
1352
1353 if ((size_t) (end - pos) == src->length && src->length != 0) {
1354 if (quirks) {
1355 is_it = lexbor_str_data_ncasecmp(pos, src->data, src->length);
1356 }
1357 else {
1358 is_it = lexbor_str_data_ncmp(pos, src->data, src->length);
1359 }
1360 }
1361
1362 return is_it;
1363}
1364
1365static bool
1366lxb_selectors_match_attribute_value(const lxb_css_selector_attribute_t *attr, bool force_modifier_i, const lexbor_str_t *trg, const lexbor_str_t *src)
1367{
1368 bool res;
1369 bool ins = attr->modifier == LXB_CSS_SELECTOR_MODIFIER_I || force_modifier_i;
1370
1371 switch (attr->match) {
1372 case LXB_CSS_SELECTOR_MATCH_EQUAL: /* = */
1373 if (trg->length == src->length) {
1374 if (ins) {
1375 return lexbor_str_data_ncasecmp(trg->data, src->data,
1376 src->length);
1377 }
1378
1379 return lexbor_str_data_ncmp(trg->data, src->data,
1380 src->length);
1381 }
1382
1383 return false;
1384
1385 case LXB_CSS_SELECTOR_MATCH_INCLUDE: /* ~= */
1386 return lxb_selectors_match_class(trg, src, ins);
1387
1388 case LXB_CSS_SELECTOR_MATCH_DASH: /* |= */
1389 if (trg->length == src->length) {
1390 if (ins) {
1391 return lexbor_str_data_ncasecmp(trg->data, src->data,
1392 src->length);
1393 }
1394
1395 return lexbor_str_data_ncmp(trg->data, src->data,
1396 src->length);
1397 }
1398
1399 if (trg->length > src->length) {
1400 if (ins) {
1402 src->data, src->length);
1403 }
1404 else {
1406 src->data, src->length);
1407 }
1408
1409 if (res && trg->data[src->length] == '-') {
1410 return true;
1411 }
1412 }
1413
1414 return false;
1415
1416 case LXB_CSS_SELECTOR_MATCH_PREFIX: /* ^= */
1417 if (src->length != 0 && trg->length >= src->length) {
1418 if (ins) {
1419 return lexbor_str_data_ncasecmp(trg->data, src->data,
1420 src->length);
1421 }
1422
1423 return lexbor_str_data_ncmp(trg->data, src->data,
1424 src->length);
1425 }
1426
1427 return false;
1428
1429 case LXB_CSS_SELECTOR_MATCH_SUFFIX: /* $= */
1430 if (src->length != 0 && trg->length >= src->length) {
1431 size_t dif = trg->length - src->length;
1432
1433 if (ins) {
1434 return lexbor_str_data_ncasecmp(trg->data + dif,
1435 src->data, src->length);
1436 }
1437
1438 return lexbor_str_data_ncmp(trg->data + dif, src->data,
1439 src->length);
1440 }
1441
1442 return false;
1443
1445 if (src->length == 0) {
1446 return false;
1447 }
1448
1449 if (ins) {
1451 src->data, src->length);
1452 }
1453
1454 return lexbor_str_data_ncmp_contain(trg->data, trg->length,
1455 src->data, src->length);
1457 }
1458
1459 return false;
1460}
1461
1462static bool
1463lxb_selectors_match_attribute(const lxb_css_selector_t *selector,
1464 const xmlNode *node, lxb_selectors_entry_t *entry)
1465{
1466 const lxb_css_selector_attribute_t *attr = &selector->u.attribute;
1467
1468 lxb_selectors_adapted_set_entry_id(entry, selector, node);
1469
1470 const xmlAttr *dom_attr = lxb_selectors_adapted_attr(node, entry->id.name);
1471 if (dom_attr == NULL) {
1472 return false;
1473 }
1474
1475 const lexbor_str_t *src = &attr->value;
1476 if (src->data == NULL) {
1477 return true;
1478 }
1479
1480 dom_lxb_str_wrapper trg = lxb_selectors_adapted_attr_value(dom_attr);
1481 ZEND_ASSERT(node->doc != NULL);
1482 bool res = lxb_selectors_match_attribute_value(
1483 attr,
1485 &trg.str,
1486 src
1487 );
1488 dom_lxb_str_wrapper_release(&trg);
1489 return res;
1490}
1491
1492static bool
1493lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
1494 const xmlNode *node)
1495{
1496 const lxb_css_selector_pseudo_t *pseudo = &selector->u.pseudo;
1497
1498 static const lxb_char_t checkbox[] = "checkbox";
1499 static const size_t checkbox_length = sizeof(checkbox) / sizeof(lxb_char_t) - 1;
1500
1501 static const lxb_char_t radio[] = "radio";
1502 static const size_t radio_length = sizeof(radio) / sizeof(lxb_char_t) - 1;
1503
1504 switch (pseudo->type) {
1506 return false;
1507
1509 /* https://drafts.csswg.org/selectors/#the-any-link-pseudo */
1511 && (lxb_selectors_adapted_cmp_local_name_literal(node, "a")
1512 || lxb_selectors_adapted_cmp_local_name_literal(node, "area")))
1513 {
1514 return lxb_selectors_adapted_has_attr(node, "href");
1515 }
1516
1517 return false;
1518
1520 if (!EG(exception)) {
1521 php_dom_throw_error_with_message(NOT_SUPPORTED_ERR, ":blank selector is not implemented because CSSWG has not yet decided its semantics (https://github.com/w3c/csswg-drafts/issues/1967)", true);
1522 }
1523 return false;
1524
1526 /* https://drafts.csswg.org/selectors/#checked */
1528 return false;
1529 }
1530 if (lxb_selectors_adapted_cmp_local_name_literal(node, "input")) {
1531 const xmlAttr *dom_attr = lxb_selectors_adapted_attr(node, (const lxb_char_t *) "type");
1532 if (dom_attr == NULL) {
1533 return false;
1534 }
1535
1536 dom_lxb_str_wrapper str = lxb_selectors_adapted_attr_value(dom_attr);
1537 bool res = false;
1538
1539 if (str.str.length == 8) {
1540 if (lexbor_str_data_ncasecmp(checkbox, str.str.data, checkbox_length)) {
1541 res = lxb_selectors_adapted_has_attr(node, "checked");
1542 }
1543 }
1544 else if (str.str.length == 5) {
1545 if (lexbor_str_data_ncasecmp(radio, str.str.data, radio_length)) {
1546 res = lxb_selectors_adapted_has_attr(node, "checked");
1547 }
1548 }
1549
1550 dom_lxb_str_wrapper_release(&str);
1551
1552 return res;
1553 }
1554 else if(lxb_selectors_adapted_cmp_local_name_literal(node, "option")) {
1555 return lxb_selectors_adapted_has_attr(node, "selected");
1556 }
1557
1558 return false;
1559
1562 return false;
1563
1565 return lxb_selectors_pseudo_class_disabled(node);
1566
1568 node = node->children;
1569
1570 while (node != NULL) {
1571 /* Following https://developer.mozilla.org/en-US/docs/Web/CSS/:empty, i.e. what currently happens in browsers,
1572 * not the CSS Selectors Level 4 Draft that no one implements yet. */
1574 return false;
1575 }
1576
1577 node = node->next;
1578 }
1579
1580 return true;
1581
1583 return !lxb_selectors_pseudo_class_disabled(node);
1584
1586 return lxb_selectors_pseudo_class_first_child(node);
1587
1589 return lxb_selectors_pseudo_class_first_of_type(node);
1590
1592 break;
1593
1595 break;
1596
1598 break;
1599
1601 break;
1602
1604 break;
1605
1607 break;
1608
1610 break;
1611
1613 break;
1614
1616 break;
1617
1619 return lxb_selectors_pseudo_class_last_child(node);
1620
1622 return lxb_selectors_pseudo_class_last_of_type(node);
1623
1625 /* https://html.spec.whatwg.org/multipage/semantics-other.html#selector-link */
1627 && (lxb_selectors_adapted_cmp_local_name_literal(node, "a")
1628 || lxb_selectors_adapted_cmp_local_name_literal(node, "area")))
1629 {
1630 return lxb_selectors_adapted_has_attr(node, "href");
1631 }
1632
1633 return false;
1634
1636 break;
1637
1639 return lxb_selectors_pseudo_class_first_child(node)
1640 && lxb_selectors_pseudo_class_last_child(node);
1641
1643 return lxb_selectors_pseudo_class_first_of_type(node)
1644 && lxb_selectors_pseudo_class_last_of_type(node);
1645
1648 && (lxb_selectors_adapted_cmp_local_name_literal(node, "input")
1649 || lxb_selectors_adapted_cmp_local_name_literal(node, "select")
1650 || lxb_selectors_adapted_cmp_local_name_literal(node, "textarea")))
1651 {
1652 return !lxb_selectors_adapted_has_attr(node, "required");
1653 }
1654
1655 return false;
1656
1658 break;
1659
1661 break;
1662
1665 && (lxb_selectors_adapted_cmp_local_name_literal(node, "input")
1666 || lxb_selectors_adapted_cmp_local_name_literal(node, "textarea")))
1667 {
1668 return lxb_selectors_adapted_has_attr(node, "placeholder");
1669 }
1670
1671 return false;
1672
1674 return !lxb_selectors_pseudo_class_read_write(node);
1675
1677 return lxb_selectors_pseudo_class_read_write(node);
1678
1681 && (lxb_selectors_adapted_cmp_local_name_literal(node, "input")
1682 || lxb_selectors_adapted_cmp_local_name_literal(node, "select")
1683 || lxb_selectors_adapted_cmp_local_name_literal(node, "textarea")))
1684 {
1685 return lxb_selectors_adapted_has_attr(node, "required");
1686 }
1687
1688 return false;
1689
1691 return node->parent != NULL
1692 && (node->parent->type == XML_DOCUMENT_FRAG_NODE || node->parent->type == XML_DOCUMENT_NODE
1693 || node->parent->type == XML_HTML_DOCUMENT_NODE);
1694
1696 break;
1697
1699 break;
1700
1702 break;
1703
1705 break;
1706
1708 break;
1709
1711 break;
1712
1714 break;
1715 }
1716
1717 return false;
1718}
1719
1720static bool
1721lxb_selectors_pseudo_class_function(const lxb_css_selector_t *selector,
1722 const xmlNode *node)
1723{
1724 size_t index;
1725 const xmlNode *base;
1726 const lxb_css_selector_pseudo_t *pseudo;
1727
1728 pseudo = &selector->u.pseudo;
1729
1730 switch (pseudo->type) {
1733 index = 0;
1734
1736 while (node != NULL) {
1737 if (lxb_selectors_adapted_is_matchable_child(node))
1738 {
1739 index++;
1740 }
1741
1742 node = node->prev;
1743 }
1744 }
1745 else {
1746 while (node != NULL) {
1747 if (lxb_selectors_adapted_is_matchable_child(node))
1748 {
1749 index++;
1750 }
1751
1752 node = node->next;
1753 }
1754 }
1755
1756 return lxb_selectors_anb_calc(pseudo->data, index);
1757
1760 index = 0;
1761 base = node;
1762
1764 while (node != NULL) {
1765 if(lxb_selectors_adapted_is_matchable_child(node)
1766 && xmlStrEqual(node->name, base->name)
1767 && lxb_selectors_adapted_cmp_ns(node, base))
1768 {
1769 index++;
1770 }
1771
1772 node = node->prev;
1773 }
1774 }
1775 else {
1776 while (node != NULL) {
1777 if(lxb_selectors_adapted_is_matchable_child(node)
1778 && xmlStrEqual(node->name, base->name)
1779 && lxb_selectors_adapted_cmp_ns(node, base))
1780 {
1781 index++;
1782 }
1783
1784 node = node->next;
1785 }
1786 }
1787
1788 return lxb_selectors_anb_calc(pseudo->data, index);
1789
1794 default:
1795 break;
1796 }
1797
1798 return false;
1799}
1800
1801static bool
1802lxb_selectors_pseudo_element(const lxb_css_selector_t *selector,
1803 const xmlNode *node)
1804{
1805 const lxb_css_selector_pseudo_t *pseudo = &selector->u.pseudo;
1806
1807 switch (pseudo->type) {
1820 break;
1821 }
1822
1823 return false;
1824}
1825
1826/* https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled */
1827static bool
1828lxb_selectors_pseudo_class_disabled(const xmlNode *node)
1829{
1831 return false;
1832 }
1833
1834 if (lxb_selectors_adapted_has_attr(node, "disabled")
1835 && (lxb_selectors_adapted_cmp_local_name_literal(node, "button")
1836 || lxb_selectors_adapted_cmp_local_name_literal(node, "input")
1837 || lxb_selectors_adapted_cmp_local_name_literal(node, "select")
1838 || lxb_selectors_adapted_cmp_local_name_literal(node, "textarea")
1839 || lxb_selectors_adapted_cmp_local_name_literal(node, "optgroup")
1840 || lxb_selectors_adapted_cmp_local_name_literal(node, "fieldset")))
1841 {
1842 return true;
1843 }
1844
1845 if (lxb_selectors_adapted_cmp_local_name_literal(node, "fieldset")) {
1846 const xmlNode *fieldset = node;
1847 node = node->parent;
1848
1849 while (node != NULL && lxb_selectors_adapted_is_matchable_child(node)) {
1850 /* node is a disabled fieldset that is an ancestor of fieldset */
1852 && lxb_selectors_adapted_cmp_local_name_literal(node, "fieldset")
1853 && lxb_selectors_adapted_has_attr(node, "disabled"))
1854 {
1855 /* Search first legend child and figure out if fieldset is a descendent from that. */
1856 const xmlNode *search_current = node->children;
1857 do {
1858 if (search_current->type == XML_ELEMENT_NODE
1860 && lxb_selectors_adapted_cmp_local_name_literal(search_current, "legend")) {
1861 /* search_current is a legend element. */
1862 const xmlNode *inner_search_current = fieldset;
1863
1864 /* Disabled does not apply if fieldset is a descendant from search_current */
1865 do {
1866 if (inner_search_current == search_current) {
1867 return false;
1868 }
1869
1870 inner_search_current = inner_search_current->parent;
1871 } while (inner_search_current != NULL);
1872
1873 return true;
1874 }
1875
1876 search_current = search_current->next;
1877 } while (search_current != NULL);
1878 }
1879
1880 node = node->parent;
1881 }
1882 }
1883
1884 return false;
1885}
1886
1887static bool
1888lxb_selectors_pseudo_class_first_child(const xmlNode *node)
1889{
1890 node = node->prev;
1891
1892 while (node != NULL) {
1893 if (lxb_selectors_adapted_is_matchable_child(node))
1894 {
1895 return false;
1896 }
1897
1898 node = node->prev;
1899 }
1900
1901 return true;
1902}
1903
1904static bool
1905lxb_selectors_pseudo_class_first_of_type(const xmlNode *node)
1906{
1907 const xmlNode *root = node;
1908 node = node->prev;
1909
1910 while (node) {
1911 if (lxb_selectors_adapted_is_matchable_child(node)
1912 && xmlStrEqual(node->name, root->name)
1913 && lxb_selectors_adapted_cmp_ns(node, root))
1914 {
1915 return false;
1916 }
1917
1918 node = node->prev;
1919 }
1920
1921 return true;
1922}
1923
1924static bool
1925lxb_selectors_pseudo_class_last_child(const xmlNode *node)
1926{
1927 node = node->next;
1928
1929 while (node != NULL) {
1930 if (lxb_selectors_adapted_is_matchable_child(node))
1931 {
1932 return false;
1933 }
1934
1935 node = node->next;
1936 }
1937
1938 return true;
1939}
1940
1941static bool
1942lxb_selectors_pseudo_class_last_of_type(const xmlNode *node)
1943{
1944 const xmlNode *root = node;
1945 node = node->next;
1946
1947 while (node) {
1948 if (lxb_selectors_adapted_is_matchable_child(node)
1949 && xmlStrEqual(node->name, root->name)
1950 && lxb_selectors_adapted_cmp_ns(node, root))
1951 {
1952 return false;
1953 }
1954
1955 node = node->next;
1956 }
1957
1958 return true;
1959}
1960
1961/* https://drafts.csswg.org/selectors/#rw-pseudos */
1962static bool
1963lxb_selectors_pseudo_class_read_write(const xmlNode *node)
1964{
1966 if (lxb_selectors_adapted_cmp_local_name_literal(node, "input")
1967 || lxb_selectors_adapted_cmp_local_name_literal(node, "textarea")) {
1968 return !lxb_selectors_adapted_has_attr(node, "readonly") && !lxb_selectors_adapted_has_attr(node, "disabled");
1969 } else {
1970 const xmlAttr *attr = lxb_selectors_adapted_attr(node, (const lxb_char_t *) "contenteditable");
1971 return attr && !dom_compare_value(attr, BAD_CAST "false");
1972 }
1973 }
1974
1975 return false;
1976}
1977
1978static bool
1979lxb_selectors_anb_calc(const lxb_css_selector_anb_of_t *anb, size_t index)
1980{
1981 double num;
1982
1983 if (anb->anb.a == 0) {
1984 if (anb->anb.b >= 0 && (size_t) anb->anb.b == index) {
1985 return true;
1986 }
1987 }
1988 else {
1989 num = ((double) index - (double) anb->anb.b) / (double) anb->anb.a;
1990
1991 if (num >= 0.0f && (num - trunc(num)) == 0.0f) {
1992 return true;
1993 }
1994 }
1995
1996 return false;
1997}
1998
1999static lxb_status_t
2000lxb_selectors_cb_ok(const xmlNode *node,
2001 lxb_css_selector_specificity_t spec, void *ctx)
2002{
2003 *((bool *) ctx) = true;
2004 return LXB_STATUS_OK;
2005}
2006
2007static lxb_status_t
2008lxb_selectors_cb_not(const xmlNode *node,
2009 lxb_css_selector_specificity_t spec, void *ctx)
2010{
2011 *((bool *) ctx) = false;
2012 return LXB_STATUS_OK;
2013}
size_t len
Definition apprentice.c:174
char * cb
Definition assert.c:26
bool exception
Definition assert.c:30
chr(int $codepoint)
@ LXB_STATUS_STOP
Definition base.h:68
@ LXB_STATUS_OK
Definition base.h:49
@ LXB_STATUS_ERROR
Definition base.h:50
struct lxb_css_selector lxb_css_selector_t
Definition base.h:39
struct lxb_css_selector_list lxb_css_selector_list_t
Definition base.h:40
DNS_STATUS status
Definition dns_win32.c:49
void * lexbor_dobject_calloc(lexbor_dobject_t *dobject)
Definition dobject.c:123
lxb_status_t lexbor_dobject_init(lexbor_dobject_t *dobject, size_t chunk_size, size_t struct_size)
Definition dobject.c:22
void lexbor_dobject_clean(lexbor_dobject_t *dobject)
Definition dobject.c:64
lexbor_dobject_t * lexbor_dobject_destroy(lexbor_dobject_t *dobject, bool destroy_self)
Definition dobject.c:75
lexbor_dobject_t * lexbor_dobject_create(void)
Definition dobject.c:16
void php_dom_throw_error_with_message(dom_exception_code error_code, const char *error_message, bool strict_error)
@ NOT_SUPPORTED_ERR
zend_string * res
Definition ffi.c:4692
void * ptr
Definition ffi.c:3814
new_type attr
Definition ffi.c:4364
int main(int argc, char **argv)
Definition gd2copypal.c:12
#define NULL
Definition gdcache.h:45
#define next(ls)
Definition minilua.c:2661
PHP_DOM_EXPORT const php_dom_ns_magic_token * php_dom_ns_is_html_magic_token
PHP_DOM_EXPORT bool php_dom_ns_is_fast(const xmlNode *nodep, const php_dom_ns_magic_token *magic_token)
PHP_DOM_EXPORT bool php_dom_ns_is_html_and_document_is_html(const xmlNode *nodep)
bool dom_compare_value(const xmlAttr *attr, const xmlChar *value)
const XML_HTML_DOCUMENT_NODE
const XML_PI_NODE
const XML_ELEMENT_NODE
const XML_DOCUMENT_NODE
const XML_COMMENT_NODE
const XML_DOCUMENT_FRAG_NODE
unsigned const char * end
Definition php_ffi.h:51
unsigned const char * pos
Definition php_ffi.h:52
zend_constant * data
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_IN_RANGE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_ACTIVE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_PAST
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_ENABLED
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_ANY_LINK
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_WARNING
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_TARGET_WITHIN
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_LOCAL_LINK
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FULLSCREEN
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_CURRENT
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_ROOT
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_LINK
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_VISITED
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_READ_ONLY
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FOCUS_WITHIN
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FOCUS
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_INDETERMINATE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUTURE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FIRST_CHILD
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FOCUS_VISIBLE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_LAST_OF_TYPE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_USER_INVALID
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_VALID
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_OPTIONAL
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_SCOPE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_DISABLED
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_EMPTY
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_DEFAULT
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_READ_WRITE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_PLACEHOLDER_SHOWN
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_ONLY_OF_TYPE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FIRST_OF_TYPE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_INVALID
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_CHECKED
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_BLANK
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_REQUIRED
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_ONLY_CHILD
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_HOVER
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_TARGET
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_OUT_OF_RANGE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_LAST_CHILD
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_LAST_COL
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_LAST_OF_TYPE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_IS
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_DIR
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_HAS
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_COL
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_CURRENT
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_LANG
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_WHERE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_CHILD
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_OF_TYPE
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NOT
@ LXB_CSS_SELECTOR_PSEUDO_CLASS_FUNCTION_NTH_LAST_CHILD
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_SELECTION
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_BACKDROP
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_AFTER
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_GRAMMAR_ERROR
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_TARGET_TEXT
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_PLACEHOLDER
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_FIRST_LINE
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_BEFORE
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_INACTIVE_SELECTION
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_SPELLING_ERROR
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_MARKER
@ LXB_CSS_SELECTOR_PSEUDO_ELEMENT_FIRST_LETTER
uint32_t lxb_css_selector_specificity_t
Definition selector.h:107
@ LXB_CSS_SELECTOR_COMBINATOR_CELL
Definition selector.h:40
@ LXB_CSS_SELECTOR_COMBINATOR_CLOSE
Definition selector.h:36
@ LXB_CSS_SELECTOR_COMBINATOR_DESCENDANT
Definition selector.h:35
@ LXB_CSS_SELECTOR_COMBINATOR_SIBLING
Definition selector.h:38
@ LXB_CSS_SELECTOR_COMBINATOR_CHILD
Definition selector.h:37
@ LXB_CSS_SELECTOR_COMBINATOR_FOLLOWING
Definition selector.h:39
@ LXB_CSS_SELECTOR_TYPE_ELEMENT
Definition selector.h:22
@ LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS_FUNCTION
Definition selector.h:27
@ LXB_CSS_SELECTOR_TYPE_PSEUDO_CLASS
Definition selector.h:26
@ LXB_CSS_SELECTOR_TYPE_ATTRIBUTE
Definition selector.h:25
@ LXB_CSS_SELECTOR_TYPE_PSEUDO_ELEMENT_FUNCTION
Definition selector.h:29
@ LXB_CSS_SELECTOR_TYPE_ID
Definition selector.h:23
@ LXB_CSS_SELECTOR_TYPE_CLASS
Definition selector.h:24
@ LXB_CSS_SELECTOR_TYPE_PSEUDO_ELEMENT
Definition selector.h:28
@ LXB_CSS_SELECTOR_TYPE_ANY
Definition selector.h:21
@ LXB_CSS_SELECTOR_MODIFIER_I
Definition selector.h:58
@ LXB_CSS_SELECTOR_MATCH_PREFIX
Definition selector.h:49
@ LXB_CSS_SELECTOR_MATCH_SUBSTRING
Definition selector.h:51
@ LXB_CSS_SELECTOR_MATCH_DASH
Definition selector.h:48
@ LXB_CSS_SELECTOR_MATCH_SUFFIX
Definition selector.h:50
@ LXB_CSS_SELECTOR_MATCH_INCLUDE
Definition selector.h:47
@ LXB_CSS_SELECTOR_MATCH_EQUAL
Definition selector.h:46
lxb_status_t lxb_selectors_find(lxb_selectors_t *selectors, const xmlNode *root, const lxb_css_selector_list_t *list, lxb_selectors_cb_f cb, void *ctx)
Definition selectors.c:410
void lxb_selectors_destroy(lxb_selectors_t *selectors)
Definition selectors.c:316
#define CMP_NODE_TYPE(node, ty)
Definition selectors.c:24
lxb_inline const xmlNode * lxb_selectors_close(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *node)
Definition selectors.c:344
lxb_status_t lxb_selectors_init(lxb_selectors_t *selectors)
Definition selectors.c:285
lxb_inline const xmlNode * lxb_selectors_sibling(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *node)
Definition selectors.c:370
lxb_status_t lxb_selectors_match_node(lxb_selectors_t *selectors, const xmlNode *node, const lxb_css_selector_list_t *list, lxb_selectors_cb_f cb, void *ctx)
Definition selectors.c:434
void lxb_selectors_clean(lxb_selectors_t *selectors)
Definition selectors.c:309
lxb_inline const xmlNode * lxb_selectors_child(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *root)
Definition selectors.c:355
lxb_inline const xmlNode * lxb_selectors_following(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *node)
Definition selectors.c:391
lxb_inline const xmlNode * lxb_selectors_descendant(lxb_selectors_t *selectors, lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *node)
Definition selectors.c:323
struct lxb_selectors_entry lxb_selectors_entry_t
Definition selectors.h:67
struct lxb_selectors lxb_selectors_t
Definition selectors.h:66
struct lxb_selectors_nested lxb_selectors_nested_t
Definition selectors.h:68
@ LXB_SELECTORS_OPT_DEFAULT
Definition selectors.h:24
@ LXB_SELECTORS_OPT_QUIRKS_MODE
Definition selectors.h:62
@ LXB_SELECTORS_OPT_MATCH_FIRST
Definition selectors.h:59
@ LXB_SELECTORS_OPT_MATCH_ROOT
Definition selectors.h:38
lxb_status_t(* lxb_selectors_cb_f)(const xmlNode *node, lxb_css_selector_specificity_t spec, void *ctx)
Definition selectors.h:71
zval * current
Definition session.c:1024
bool lexbor_str_data_ncmp_contain(const lxb_char_t *where, size_t where_size, const lxb_char_t *what, size_t what_size)
Definition str.c:510
bool lexbor_str_data_ncasecmp(const lxb_char_t *first, const lxb_char_t *sec, size_t size)
Definition str.c:435
bool lexbor_str_data_ncmp(const lxb_char_t *first, const lxb_char_t *sec, size_t size)
Definition str.c:523
bool lexbor_str_data_nlocmp_right(const lxb_char_t *first, const lxb_char_t *sec, size_t size)
Definition str.c:450
bool lexbor_str_data_ncasecmp_contain(const lxb_char_t *where, size_t where_size, const lxb_char_t *what, size_t what_size)
Definition str.c:422
lexbor_str_t str
Definition selectors.c:27
lxb_char_t * data
Definition str.h:47
size_t length
Definition str.h:48
lxb_css_selector_list_t * of
Definition selector.h:79
lxb_css_syntax_anb_t anb
Definition selector.h:78
lxb_css_selector_list_t * next
Definition selector.h:171
lxb_css_selector_t * last
Definition selector.h:167
lxb_css_selector_specificity_t specificity
Definition selector.h:176
lxb_css_selector_t * first
Definition selector.h:166
lxb_css_selector_type_t type
Definition selector.h:84
lxb_css_selector_t * prev
Definition selector.h:97
lxb_css_selector_list_t * list
Definition selector.h:99
union lxb_css_selector::lxb_css_selector_u u
lxb_css_selector_combinator_t combinator
Definition selector.h:85
lexbor_str_t name
Definition selector.h:87
const xmlChar * name
Definition selectors.h:79
lxb_selectors_entry_t * following
Definition selectors.h:91
lxb_selectors_entry_t * prev
Definition selectors.h:90
lxb_selectors_nested_t * nested
Definition selectors.h:92
const xmlNode * node
Definition selectors.h:88
lxb_selectors_adapted_id id
Definition selectors.h:85
const lxb_css_selector_t * selector
Definition selectors.h:87
lxb_selectors_entry_t * next
Definition selectors.h:89
lxb_css_selector_combinator_t combinator
Definition selectors.h:86
lxb_selectors_cb_f cb
Definition selectors.h:99
const xmlNode * root
Definition selectors.h:102
lxb_selectors_state_cb_f return_state
Definition selectors.h:97
lxb_selectors_nested_t * parent
Definition selectors.h:104
lxb_selectors_entry_t * entry
Definition selectors.h:96
lxb_selectors_entry_t * last
Definition selectors.h:103
lxb_selectors_state_cb_f state
Definition selectors.h:111
lxb_selectors_opt_t options
Definition selectors.h:118
lexbor_dobject_t * nested
Definition selectors.h:113
lxb_selectors_entry_t * first
Definition selectors.h:116
lxb_selectors_nested_t * current
Definition selectors.h:115
lexbor_dobject_t * objs
Definition selectors.h:112
lxb_status_t status
Definition selectors.h:119
$obj a
Definition test.php:84
unsigned int lxb_status_t
Definition types.h:28
#define lxb_inline
Definition types.h:21
unsigned char lxb_char_t
Definition types.h:27
lxb_css_selector_attribute_t attribute
Definition selector.h:91
lxb_css_selector_pseudo_t pseudo
Definition selector.h:92
#define lexbor_utils_whitespace(onechar, action, logic)
Definition utils.h:17
strlen(string $string)
strcmp(string $string1, string $string2)
#define EG(v)
#define zend_always_inline
#define ZEND_ASSERT(c)
#define ZEND_STRL(str)
#define EMPTY_SWITCH_DEFAULT_CASE()
zend_string * name
zval * ret
out($f, $s)