mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00

Splits the purpose of the baseobj_zv: now no longer either is an array or an object. Stores the hash table pointer directly, if used.
284 lines
8.4 KiB
C
284 lines
8.4 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| https://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Niels Dossche <nielsdos@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "php.h"
|
|
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
|
|
#include "../php_dom.h"
|
|
#include "../obj_map.h"
|
|
|
|
#include "ext/lexbor/lexbor/css/parser.h"
|
|
#include "../lexbor/selectors-adapted/selectors.h"
|
|
|
|
// TODO: optimization idea: cache the parsed selectors in an LRU fashion?
|
|
|
|
typedef struct {
|
|
HashTable *list;
|
|
dom_object *intern;
|
|
} dom_query_selector_all_ctx;
|
|
|
|
typedef struct {
|
|
const xmlNode *reference;
|
|
bool result;
|
|
} dom_query_selector_matches_ctx;
|
|
|
|
static lxb_selectors_opt_t dom_quirks_opt(lxb_selectors_opt_t options, const dom_object *intern)
|
|
{
|
|
if (intern->document != NULL && intern->document->quirks_mode) {
|
|
options |= LXB_SELECTORS_OPT_QUIRKS_MODE;
|
|
}
|
|
return options;
|
|
}
|
|
|
|
lxb_status_t dom_query_selector_find_single_callback(const xmlNode *node, lxb_css_selector_specificity_t spec, void *ctx)
|
|
{
|
|
xmlNodePtr *result = (xmlNodePtr *) ctx;
|
|
*result = (xmlNodePtr) node;
|
|
return LXB_STATUS_STOP;
|
|
}
|
|
|
|
lxb_status_t dom_query_selector_find_array_callback(const xmlNode *node, lxb_css_selector_specificity_t spec, void *ctx)
|
|
{
|
|
dom_query_selector_all_ctx *qsa_ctx = (dom_query_selector_all_ctx *) ctx;
|
|
zval object;
|
|
php_dom_create_object((xmlNodePtr) node, &object, qsa_ctx->intern);
|
|
zend_hash_next_index_insert_new(qsa_ctx->list, &object);
|
|
return LXB_STATUS_OK;
|
|
}
|
|
|
|
lxb_status_t dom_query_selector_find_matches_callback(const xmlNode *node, lxb_css_selector_specificity_t spec, void *ctx)
|
|
{
|
|
dom_query_selector_matches_ctx *matches_ctx = (dom_query_selector_matches_ctx *) ctx;
|
|
if (node == matches_ctx->reference) {
|
|
matches_ctx->result = true;
|
|
return LXB_STATUS_STOP;
|
|
}
|
|
return LXB_STATUS_OK;
|
|
}
|
|
|
|
static lxb_css_selector_list_t *dom_parse_selector(
|
|
lxb_css_parser_t *parser,
|
|
lxb_selectors_t *selectors,
|
|
const zend_string *selectors_str,
|
|
lxb_selectors_opt_t options,
|
|
const dom_object *intern
|
|
)
|
|
{
|
|
lxb_status_t status;
|
|
|
|
memset(parser, 0, sizeof(lxb_css_parser_t));
|
|
status = lxb_css_parser_init(parser, NULL);
|
|
ZEND_ASSERT(status == LXB_STATUS_OK);
|
|
|
|
memset(selectors, 0, sizeof(lxb_selectors_t));
|
|
status = lxb_selectors_init(selectors);
|
|
ZEND_ASSERT(status == LXB_STATUS_OK);
|
|
lxb_selectors_opt_set(selectors, dom_quirks_opt(options, intern));
|
|
|
|
lxb_css_selector_list_t *list = lxb_css_selectors_parse(parser, (const lxb_char_t *) ZSTR_VAL(selectors_str), ZSTR_LEN(selectors_str));
|
|
if (UNEXPECTED(list == NULL)) {
|
|
size_t nr_of_messages = lexbor_array_obj_length(&parser->log->messages);
|
|
if (nr_of_messages > 0) {
|
|
lxb_css_log_message_t *msg = lexbor_array_obj_get(&parser->log->messages, 0);
|
|
char *error;
|
|
zend_spprintf(&error, 0, "Invalid selector (%.*s)", (int) msg->text.length, msg->text.data);
|
|
php_dom_throw_error_with_message(SYNTAX_ERR, error, true);
|
|
efree(error);
|
|
} else {
|
|
php_dom_throw_error_with_message(SYNTAX_ERR, "Invalid selector", true);
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
static lxb_status_t dom_check_css_execution_status(lxb_status_t status)
|
|
{
|
|
if (UNEXPECTED(status != LXB_STATUS_OK && status != LXB_STATUS_STOP)) {
|
|
zend_argument_value_error(1, "contains an unsupported selector");
|
|
return status;
|
|
}
|
|
return LXB_STATUS_OK;
|
|
}
|
|
|
|
static void dom_selector_cleanup(lxb_css_parser_t *parser, lxb_selectors_t *selectors, lxb_css_selector_list_t *list)
|
|
{
|
|
lxb_css_selector_list_destroy_memory(list);
|
|
lxb_selectors_destroy(selectors);
|
|
(void) lxb_css_parser_destroy(parser, false);
|
|
}
|
|
|
|
static lxb_status_t dom_query_selector_common(
|
|
const xmlNode *root,
|
|
const dom_object *intern,
|
|
const zend_string *selectors_str,
|
|
lxb_selectors_cb_f cb,
|
|
void *ctx,
|
|
lxb_selectors_opt_t options
|
|
)
|
|
{
|
|
lxb_status_t status;
|
|
|
|
lxb_css_parser_t parser;
|
|
lxb_selectors_t selectors;
|
|
|
|
lxb_css_selector_list_t *list = dom_parse_selector(&parser, &selectors, selectors_str, options, intern);
|
|
if (UNEXPECTED(list == NULL)) {
|
|
status = LXB_STATUS_ERROR;
|
|
} else {
|
|
status = lxb_selectors_find(&selectors, root, list, cb, ctx);
|
|
status = dom_check_css_execution_status(status);
|
|
}
|
|
|
|
dom_selector_cleanup(&parser, &selectors, list);
|
|
|
|
return status;
|
|
}
|
|
|
|
static lxb_status_t dom_query_matches(
|
|
const xmlNode *root,
|
|
const dom_object *intern,
|
|
const zend_string *selectors_str,
|
|
void *ctx
|
|
)
|
|
{
|
|
lxb_status_t status;
|
|
|
|
lxb_css_parser_t parser;
|
|
lxb_selectors_t selectors;
|
|
|
|
lxb_css_selector_list_t *list = dom_parse_selector(&parser, &selectors, selectors_str, LXB_SELECTORS_OPT_MATCH_FIRST, intern);
|
|
if (UNEXPECTED(list == NULL)) {
|
|
status = LXB_STATUS_ERROR;
|
|
} else {
|
|
status = lxb_selectors_match_node(&selectors, root, list, dom_query_selector_find_matches_callback, ctx);
|
|
status = dom_check_css_execution_status(status);
|
|
}
|
|
|
|
dom_selector_cleanup(&parser, &selectors, list);
|
|
|
|
return status;
|
|
}
|
|
|
|
static const xmlNode *dom_query_closest(
|
|
const xmlNode *root,
|
|
const dom_object *intern,
|
|
const zend_string *selectors_str
|
|
)
|
|
{
|
|
const xmlNode *ret = NULL;
|
|
|
|
lxb_css_parser_t parser;
|
|
lxb_selectors_t selectors;
|
|
|
|
lxb_css_selector_list_t *list = dom_parse_selector(&parser, &selectors, selectors_str, LXB_SELECTORS_OPT_MATCH_FIRST, intern);
|
|
if (EXPECTED(list != NULL)) {
|
|
const xmlNode *current = root;
|
|
while (current != NULL) {
|
|
dom_query_selector_matches_ctx ctx = { current, false };
|
|
lxb_status_t status = lxb_selectors_match_node(&selectors, current, list, dom_query_selector_find_matches_callback, &ctx);
|
|
status = dom_check_css_execution_status(status);
|
|
if (UNEXPECTED(status != LXB_STATUS_OK)) {
|
|
break;
|
|
}
|
|
if (ctx.result) {
|
|
ret = current;
|
|
break;
|
|
}
|
|
current = current->parent;
|
|
}
|
|
}
|
|
|
|
dom_selector_cleanup(&parser, &selectors, list);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* https://dom.spec.whatwg.org/#dom-parentnode-queryselector */
|
|
void dom_parent_node_query_selector(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str)
|
|
{
|
|
xmlNodePtr result = NULL;
|
|
|
|
if (dom_query_selector_common(
|
|
thisp,
|
|
intern,
|
|
selectors_str,
|
|
dom_query_selector_find_single_callback,
|
|
&result,
|
|
LXB_SELECTORS_OPT_MATCH_FIRST
|
|
) != LXB_STATUS_OK || result == NULL) {
|
|
RETURN_NULL();
|
|
} else {
|
|
DOM_RET_OBJ(result, intern);
|
|
}
|
|
}
|
|
|
|
/* https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall */
|
|
void dom_parent_node_query_selector_all(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str)
|
|
{
|
|
HashTable *list = zend_new_array(0);
|
|
dom_query_selector_all_ctx ctx = { list, intern };
|
|
|
|
if (dom_query_selector_common(
|
|
thisp,
|
|
intern,
|
|
selectors_str,
|
|
dom_query_selector_find_array_callback,
|
|
&ctx,
|
|
LXB_SELECTORS_OPT_DEFAULT
|
|
) != LXB_STATUS_OK) {
|
|
zend_array_destroy(list);
|
|
RETURN_THROWS();
|
|
} else {
|
|
object_init_ex(return_value, dom_modern_nodelist_class_entry);
|
|
dom_object *ret_obj = Z_DOMOBJ_P(return_value);
|
|
dom_nnodemap_object *mapptr = (dom_nnodemap_object *) ret_obj->ptr;
|
|
mapptr->array = list;
|
|
mapptr->release_array = true;
|
|
mapptr->handler = &php_dom_obj_map_nodeset;
|
|
}
|
|
}
|
|
|
|
/* https://dom.spec.whatwg.org/#dom-element-matches */
|
|
void dom_element_matches(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str)
|
|
{
|
|
dom_query_selector_matches_ctx ctx = { thisp, false };
|
|
|
|
if (dom_query_matches(
|
|
thisp,
|
|
intern,
|
|
selectors_str,
|
|
&ctx
|
|
) != LXB_STATUS_OK) {
|
|
RETURN_THROWS();
|
|
} else {
|
|
RETURN_BOOL(ctx.result);
|
|
}
|
|
}
|
|
|
|
/* https://dom.spec.whatwg.org/#dom-element-closest */
|
|
void dom_element_closest(xmlNodePtr thisp, dom_object *intern, zval *return_value, const zend_string *selectors_str)
|
|
{
|
|
const xmlNode *result = dom_query_closest(thisp, intern, selectors_str);
|
|
if (EXPECTED(result != NULL)) {
|
|
DOM_RET_OBJ((xmlNodePtr) result, intern);
|
|
}
|
|
}
|
|
|
|
#endif
|