mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00
Implement Dom\TokenList (#13664)
Part of RFC: https://wiki.php.net/rfc/dom_additions_84 Closes GH-11688.
This commit is contained in:
parent
768900b180
commit
fc09f4b2bc
42 changed files with 2186 additions and 30 deletions
|
@ -36,6 +36,7 @@ if test "$PHP_DOM" != "no"; then
|
||||||
documenttype.c entity.c \
|
documenttype.c entity.c \
|
||||||
nodelist.c html_collection.c text.c comment.c \
|
nodelist.c html_collection.c text.c comment.c \
|
||||||
entityreference.c \
|
entityreference.c \
|
||||||
|
token_list.c \
|
||||||
notation.c xpath.c dom_iterators.c \
|
notation.c xpath.c dom_iterators.c \
|
||||||
namednodemap.c xpath_callbacks.c \
|
namednodemap.c xpath_callbacks.c \
|
||||||
$LEXBOR_SOURCES],
|
$LEXBOR_SOURCES],
|
||||||
|
|
|
@ -14,6 +14,7 @@ if (PHP_DOM == "yes") {
|
||||||
node.c characterdata.c documenttype.c \
|
node.c characterdata.c documenttype.c \
|
||||||
entity.c nodelist.c html_collection.c text.c comment.c \
|
entity.c nodelist.c html_collection.c text.c comment.c \
|
||||||
entityreference.c \
|
entityreference.c \
|
||||||
|
token_list.c \
|
||||||
notation.c xpath.c dom_iterators.c \
|
notation.c xpath.c dom_iterators.c \
|
||||||
namednodemap.c xpath_callbacks.c", null, "-Iext/dom/lexbor");
|
namednodemap.c xpath_callbacks.c", null, "-Iext/dom/lexbor");
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ extern PHP_DOM_EXPORT zend_class_entry *dom_modern_entityreference_class_entry;
|
||||||
extern PHP_DOM_EXPORT zend_class_entry *dom_processinginstruction_class_entry;
|
extern PHP_DOM_EXPORT zend_class_entry *dom_processinginstruction_class_entry;
|
||||||
extern PHP_DOM_EXPORT zend_class_entry *dom_modern_processinginstruction_class_entry;
|
extern PHP_DOM_EXPORT zend_class_entry *dom_modern_processinginstruction_class_entry;
|
||||||
extern PHP_DOM_EXPORT zend_class_entry *dom_abstract_base_document_class_entry;
|
extern PHP_DOM_EXPORT zend_class_entry *dom_abstract_base_document_class_entry;
|
||||||
|
extern PHP_DOM_EXPORT zend_class_entry *dom_token_list_class_entry;
|
||||||
#ifdef LIBXML_XPATH_ENABLED
|
#ifdef LIBXML_XPATH_ENABLED
|
||||||
extern PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
|
extern PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
|
||||||
extern PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
|
extern PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
|
||||||
|
|
|
@ -85,6 +85,7 @@ zend_result dom_element_id_write(dom_object *obj, zval *newval);
|
||||||
zend_result dom_element_schema_type_info_read(dom_object *obj, zval *retval);
|
zend_result dom_element_schema_type_info_read(dom_object *obj, zval *retval);
|
||||||
zend_result dom_element_inner_html_read(dom_object *obj, zval *retval);
|
zend_result dom_element_inner_html_read(dom_object *obj, zval *retval);
|
||||||
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval);
|
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval);
|
||||||
|
zend_result dom_element_class_list_read(dom_object *obj, zval *retval);
|
||||||
|
|
||||||
/* entity properties */
|
/* entity properties */
|
||||||
zend_result dom_entity_public_id_read(dom_object *obj, zval *retval);
|
zend_result dom_entity_public_id_read(dom_object *obj, zval *retval);
|
||||||
|
@ -148,6 +149,11 @@ zend_result dom_processinginstruction_data_write(dom_object *obj, zval *newval);
|
||||||
/* text properties */
|
/* text properties */
|
||||||
zend_result dom_text_whole_text_read(dom_object *obj, zval *retval);
|
zend_result dom_text_whole_text_read(dom_object *obj, zval *retval);
|
||||||
|
|
||||||
|
/* token_list properties */
|
||||||
|
zend_result dom_token_list_length_read(dom_object *obj, zval *retval);
|
||||||
|
zend_result dom_token_list_value_read(dom_object *obj, zval *retval);
|
||||||
|
zend_result dom_token_list_value_write(dom_object *obj, zval *newval);
|
||||||
|
|
||||||
#ifdef LIBXML_XPATH_ENABLED
|
#ifdef LIBXML_XPATH_ENABLED
|
||||||
/* xpath properties */
|
/* xpath properties */
|
||||||
zend_result dom_xpath_document_read(dom_object *obj, zval *retval);
|
zend_result dom_xpath_document_read(dom_object *obj, zval *retval);
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "namespace_compat.h"
|
#include "namespace_compat.h"
|
||||||
#include "internal_helpers.h"
|
#include "internal_helpers.h"
|
||||||
#include "dom_properties.h"
|
#include "dom_properties.h"
|
||||||
|
#include "token_list.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* class DOMElement extends DOMNode
|
* class DOMElement extends DOMNode
|
||||||
|
@ -175,6 +176,33 @@ zend_result dom_element_class_name_write(dom_object *obj, zval *newval)
|
||||||
}
|
}
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
/* {{{ classList TokenList
|
||||||
|
URL: https://dom.spec.whatwg.org/#dom-element-classlist
|
||||||
|
*/
|
||||||
|
zend_result dom_element_class_list_read(dom_object *obj, zval *retval)
|
||||||
|
{
|
||||||
|
const uint32_t PROP_INDEX = 20;
|
||||||
|
|
||||||
|
#if ZEND_DEBUG
|
||||||
|
zend_string *class_list_str = ZSTR_INIT_LITERAL("classList", false);
|
||||||
|
const zend_property_info *prop_info = zend_get_property_info(dom_modern_element_class_entry, class_list_str, 0);
|
||||||
|
zend_string_release_ex(class_list_str, false);
|
||||||
|
ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == PROP_INDEX);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
zval *cached_token_list = OBJ_PROP_NUM(&obj->std, PROP_INDEX);
|
||||||
|
if (Z_ISUNDEF_P(cached_token_list)) {
|
||||||
|
object_init_ex(cached_token_list, dom_token_list_class_entry);
|
||||||
|
dom_token_list_object *intern = php_dom_token_list_from_obj(Z_OBJ_P(cached_token_list));
|
||||||
|
dom_token_list_ctor(intern, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZVAL_OBJ_COPY(retval, Z_OBJ_P(cached_token_list));
|
||||||
|
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
/* }}} */
|
||||||
|
|
||||||
/* {{{ id string
|
/* {{{ id string
|
||||||
URL: https://dom.spec.whatwg.org/#dom-element-id
|
URL: https://dom.spec.whatwg.org/#dom-element-id
|
||||||
Since:
|
Since:
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "internal_helpers.h"
|
#include "internal_helpers.h"
|
||||||
#include "php_dom_arginfo.h"
|
#include "php_dom_arginfo.h"
|
||||||
#include "dom_properties.h"
|
#include "dom_properties.h"
|
||||||
|
#include "token_list.h"
|
||||||
#include "zend_interfaces.h"
|
#include "zend_interfaces.h"
|
||||||
#include "lexbor/lexbor/core/types.h"
|
#include "lexbor/lexbor/core/types.h"
|
||||||
#include "lexbor/lexbor/core/lexbor.h"
|
#include "lexbor/lexbor/core/lexbor.h"
|
||||||
|
@ -81,6 +82,7 @@ PHP_DOM_EXPORT zend_class_entry *dom_modern_entityreference_class_entry;
|
||||||
PHP_DOM_EXPORT zend_class_entry *dom_processinginstruction_class_entry;
|
PHP_DOM_EXPORT zend_class_entry *dom_processinginstruction_class_entry;
|
||||||
PHP_DOM_EXPORT zend_class_entry *dom_modern_processinginstruction_class_entry;
|
PHP_DOM_EXPORT zend_class_entry *dom_modern_processinginstruction_class_entry;
|
||||||
PHP_DOM_EXPORT zend_class_entry *dom_abstract_base_document_class_entry;
|
PHP_DOM_EXPORT zend_class_entry *dom_abstract_base_document_class_entry;
|
||||||
|
PHP_DOM_EXPORT zend_class_entry *dom_token_list_class_entry;
|
||||||
#ifdef LIBXML_XPATH_ENABLED
|
#ifdef LIBXML_XPATH_ENABLED
|
||||||
PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
|
PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
|
||||||
PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
|
PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
|
||||||
|
@ -97,6 +99,7 @@ static zend_object_handlers dom_modern_nodelist_object_handlers;
|
||||||
static zend_object_handlers dom_html_collection_object_handlers;
|
static zend_object_handlers dom_html_collection_object_handlers;
|
||||||
static zend_object_handlers dom_object_namespace_node_handlers;
|
static zend_object_handlers dom_object_namespace_node_handlers;
|
||||||
static zend_object_handlers dom_modern_domimplementation_object_handlers;
|
static zend_object_handlers dom_modern_domimplementation_object_handlers;
|
||||||
|
static zend_object_handlers dom_token_list_object_handlers;
|
||||||
#ifdef LIBXML_XPATH_ENABLED
|
#ifdef LIBXML_XPATH_ENABLED
|
||||||
zend_object_handlers dom_xpath_object_handlers;
|
zend_object_handlers dom_xpath_object_handlers;
|
||||||
#endif
|
#endif
|
||||||
|
@ -132,6 +135,7 @@ static HashTable dom_modern_entity_prop_handlers;
|
||||||
static HashTable dom_processinginstruction_prop_handlers;
|
static HashTable dom_processinginstruction_prop_handlers;
|
||||||
static HashTable dom_modern_processinginstruction_prop_handlers;
|
static HashTable dom_modern_processinginstruction_prop_handlers;
|
||||||
static HashTable dom_namespace_node_prop_handlers;
|
static HashTable dom_namespace_node_prop_handlers;
|
||||||
|
static HashTable dom_token_list_prop_handlers;
|
||||||
#ifdef LIBXML_XPATH_ENABLED
|
#ifdef LIBXML_XPATH_ENABLED
|
||||||
static HashTable dom_xpath_prop_handlers;
|
static HashTable dom_xpath_prop_handlers;
|
||||||
#endif
|
#endif
|
||||||
|
@ -633,6 +637,18 @@ static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject)
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static zend_object *dom_token_list_new(zend_class_entry *class_type)
|
||||||
|
{
|
||||||
|
dom_token_list_object *intern = zend_object_alloc(sizeof(*intern), class_type);
|
||||||
|
|
||||||
|
intern->dom.prop_handler = &dom_token_list_prop_handlers;
|
||||||
|
|
||||||
|
zend_object_std_init(&intern->dom.std, class_type);
|
||||||
|
object_properties_init(&intern->dom.std, class_type);
|
||||||
|
|
||||||
|
return &intern->dom.std;
|
||||||
|
}
|
||||||
|
|
||||||
static const zend_module_dep dom_deps[] = {
|
static const zend_module_dep dom_deps[] = {
|
||||||
ZEND_MOD_REQUIRED("libxml")
|
ZEND_MOD_REQUIRED("libxml")
|
||||||
ZEND_MOD_CONFLICTS("domxml")
|
ZEND_MOD_CONFLICTS("domxml")
|
||||||
|
@ -658,7 +674,6 @@ zend_module_entry dom_module_entry = { /* {{{ */
|
||||||
ZEND_GET_MODULE(dom)
|
ZEND_GET_MODULE(dom)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void dom_objects_free_storage(zend_object *object);
|
|
||||||
void dom_nnodemap_objects_free_storage(zend_object *object);
|
void dom_nnodemap_objects_free_storage(zend_object *object);
|
||||||
static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
|
static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
|
||||||
static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty);
|
static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty);
|
||||||
|
@ -732,6 +747,16 @@ PHP_MINIT_FUNCTION(dom)
|
||||||
dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage;
|
dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage;
|
||||||
dom_object_namespace_node_handlers.clone_obj = dom_object_namespace_node_clone_obj;
|
dom_object_namespace_node_handlers.clone_obj = dom_object_namespace_node_clone_obj;
|
||||||
|
|
||||||
|
memcpy(&dom_token_list_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
|
||||||
|
dom_token_list_object_handlers.offset = XtOffsetOf(dom_token_list_object, dom.std);
|
||||||
|
dom_token_list_object_handlers.free_obj = dom_token_list_free_obj;
|
||||||
|
/* The Web IDL (Web Interface Description Language - https://webidl.spec.whatwg.org) has the [SameObject] constraint
|
||||||
|
* for this object, which is incompatible with cloning because it imposes that there is only one instance
|
||||||
|
* per parent object. */
|
||||||
|
dom_token_list_object_handlers.clone_obj = NULL;
|
||||||
|
dom_token_list_object_handlers.read_dimension = dom_token_list_read_dimension;
|
||||||
|
dom_token_list_object_handlers.has_dimension = dom_token_list_has_dimension;
|
||||||
|
|
||||||
zend_hash_init(&classes, 0, NULL, NULL, true);
|
zend_hash_init(&classes, 0, NULL, NULL, true);
|
||||||
|
|
||||||
dom_adjacent_position_class_entry = register_class_Dom_AdjacentPosition();
|
dom_adjacent_position_class_entry = register_class_Dom_AdjacentPosition();
|
||||||
|
@ -1033,6 +1058,7 @@ PHP_MINIT_FUNCTION(dom)
|
||||||
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "tagName", dom_element_tag_name_read, NULL);
|
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "tagName", dom_element_tag_name_read, NULL);
|
||||||
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "id", dom_element_id_read, dom_element_id_write);
|
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "id", dom_element_id_read, dom_element_id_write);
|
||||||
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "className", dom_element_class_name_read, dom_element_class_name_write);
|
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "className", dom_element_class_name_read, dom_element_class_name_write);
|
||||||
|
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "classList", dom_element_class_list_read, NULL);
|
||||||
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "attributes", dom_node_attributes_read, NULL);
|
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "attributes", dom_node_attributes_read, NULL);
|
||||||
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
|
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "firstElementChild", dom_parent_node_first_element_child_read, NULL);
|
||||||
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
|
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "lastElementChild", dom_parent_node_last_element_child_read, NULL);
|
||||||
|
@ -1227,6 +1253,16 @@ PHP_MINIT_FUNCTION(dom)
|
||||||
zend_hash_add_new_ptr(&classes, dom_modern_xpath_class_entry->name, &dom_xpath_prop_handlers);
|
zend_hash_add_new_ptr(&classes, dom_modern_xpath_class_entry->name, &dom_xpath_prop_handlers);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
dom_token_list_class_entry = register_class_Dom_TokenList(zend_ce_aggregate, zend_ce_countable);
|
||||||
|
dom_token_list_class_entry->create_object = dom_token_list_new;
|
||||||
|
dom_token_list_class_entry->default_object_handlers = &dom_token_list_object_handlers;
|
||||||
|
dom_token_list_class_entry->get_iterator = dom_token_list_get_iterator;
|
||||||
|
|
||||||
|
zend_hash_init(&dom_token_list_prop_handlers, 0, NULL, NULL, true);
|
||||||
|
DOM_REGISTER_PROP_HANDLER(&dom_token_list_prop_handlers, "length", dom_token_list_length_read, NULL);
|
||||||
|
DOM_REGISTER_PROP_HANDLER(&dom_token_list_prop_handlers, "value", dom_token_list_value_read, dom_token_list_value_write);
|
||||||
|
zend_hash_add_new_ptr(&classes, dom_token_list_class_entry->name, &dom_token_list_prop_handlers);
|
||||||
|
|
||||||
register_php_dom_symbols(module_number);
|
register_php_dom_symbols(module_number);
|
||||||
|
|
||||||
php_libxml_register_export(dom_node_class_entry, php_dom_export_node);
|
php_libxml_register_export(dom_node_class_entry, php_dom_export_node);
|
||||||
|
@ -1292,6 +1328,7 @@ PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */
|
||||||
zend_hash_destroy(&dom_modern_entity_prop_handlers);
|
zend_hash_destroy(&dom_modern_entity_prop_handlers);
|
||||||
zend_hash_destroy(&dom_processinginstruction_prop_handlers);
|
zend_hash_destroy(&dom_processinginstruction_prop_handlers);
|
||||||
zend_hash_destroy(&dom_modern_processinginstruction_prop_handlers);
|
zend_hash_destroy(&dom_modern_processinginstruction_prop_handlers);
|
||||||
|
zend_hash_destroy(&dom_token_list_prop_handlers);
|
||||||
#ifdef LIBXML_XPATH_ENABLED
|
#ifdef LIBXML_XPATH_ENABLED
|
||||||
zend_hash_destroy(&dom_xpath_prop_handlers);
|
zend_hash_destroy(&dom_xpath_prop_handlers);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -122,6 +122,7 @@ static inline dom_object_namespace_node *php_dom_namespace_node_obj_from_obj(zen
|
||||||
|
|
||||||
#define DOM_HTML_NO_DEFAULT_NS (1U << 31)
|
#define DOM_HTML_NO_DEFAULT_NS (1U << 31)
|
||||||
|
|
||||||
|
void dom_objects_free_storage(zend_object *object);
|
||||||
dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document);
|
dom_doc_propsptr dom_get_doc_props(php_libxml_ref_obj *document);
|
||||||
libxml_doc_props const* dom_get_doc_props_read_only(const php_libxml_ref_obj *document);
|
libxml_doc_props const* dom_get_doc_props_read_only(const php_libxml_ref_obj *document);
|
||||||
zend_object *dom_objects_new(zend_class_entry *class_type);
|
zend_object *dom_objects_new(zend_class_entry *class_type);
|
||||||
|
@ -230,14 +231,8 @@ xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node,
|
||||||
|
|
||||||
static zend_always_inline bool php_dom_is_cache_tag_stale_from_doc_ptr(const php_libxml_cache_tag *cache_tag, const php_libxml_ref_obj *doc_ptr)
|
static zend_always_inline bool php_dom_is_cache_tag_stale_from_doc_ptr(const php_libxml_cache_tag *cache_tag, const php_libxml_ref_obj *doc_ptr)
|
||||||
{
|
{
|
||||||
ZEND_ASSERT(cache_tag != NULL);
|
|
||||||
ZEND_ASSERT(doc_ptr != NULL);
|
ZEND_ASSERT(doc_ptr != NULL);
|
||||||
/* See overflow comment in php_libxml_invalidate_node_list_cache(). */
|
return php_libxml_is_cache_tag_stale(cache_tag, &doc_ptr->cache_tag);
|
||||||
#if SIZEOF_SIZE_T == 8
|
|
||||||
return cache_tag->modification_nr != doc_ptr->cache_tag.modification_nr;
|
|
||||||
#else
|
|
||||||
return cache_tag->modification_nr != doc_ptr->cache_tag.modification_nr || UNEXPECTED(doc_ptr->cache_tag.modification_nr == SIZE_MAX);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static zend_always_inline bool php_dom_is_cache_tag_stale_from_node(const php_libxml_cache_tag *cache_tag, const xmlNodePtr node)
|
static zend_always_inline bool php_dom_is_cache_tag_stale_from_node(const php_libxml_cache_tag *cache_tag, const xmlNodePtr node)
|
||||||
|
|
|
@ -1304,6 +1304,8 @@ namespace Dom
|
||||||
|
|
||||||
public string $id;
|
public string $id;
|
||||||
public string $className;
|
public string $className;
|
||||||
|
/** @readonly */
|
||||||
|
public TokenList $classList;
|
||||||
|
|
||||||
/** @implementation-alias DOMNode::hasAttributes */
|
/** @implementation-alias DOMNode::hasAttributes */
|
||||||
public function hasAttributes(): bool {}
|
public function hasAttributes(): bool {}
|
||||||
|
@ -1661,6 +1663,31 @@ namespace Dom
|
||||||
public function saveXmlFile(string $filename, int $options = 0): int|false {}
|
public function saveXmlFile(string $filename, int $options = 0): int|false {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @not-serializable
|
||||||
|
* @strict-properties
|
||||||
|
*/
|
||||||
|
final class TokenList implements IteratorAggregate, Countable
|
||||||
|
{
|
||||||
|
/** @implementation-alias Dom\Node::__construct */
|
||||||
|
private function __construct() {}
|
||||||
|
|
||||||
|
/** @readonly */
|
||||||
|
public int $length;
|
||||||
|
public function item(int $index): ?string {}
|
||||||
|
public function contains(string $token): bool {}
|
||||||
|
public function add(string ...$tokens): void {}
|
||||||
|
public function remove(string ...$tokens): void {}
|
||||||
|
public function toggle(string $token, ?bool $force = null): bool {}
|
||||||
|
public function replace(string $token, string $newToken): bool {}
|
||||||
|
public function supports(string $token): bool {}
|
||||||
|
public string $value;
|
||||||
|
|
||||||
|
public function count(): int {}
|
||||||
|
|
||||||
|
public function getIterator(): \Iterator {}
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef LIBXML_XPATH_ENABLED
|
#ifdef LIBXML_XPATH_ENABLED
|
||||||
/** @not-serializable */
|
/** @not-serializable */
|
||||||
final class XPath
|
final class XPath
|
||||||
|
|
88
ext/dom/php_dom_arginfo.h
generated
88
ext/dom/php_dom_arginfo.h
generated
|
@ -1,5 +1,5 @@
|
||||||
/* This is a generated file, edit the .stub.php file instead.
|
/* This is a generated file, edit the .stub.php file instead.
|
||||||
* Stub hash: 9065d5c713a6fb879f8116821eaabc3a01a4db20 */
|
* Stub hash: 1faa01d0564052dda0dc41f36033a076b8b4c31c */
|
||||||
|
|
||||||
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
|
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
|
||||||
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
|
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
|
||||||
|
@ -1079,6 +1079,38 @@ ZEND_END_ARG_INFO()
|
||||||
|
|
||||||
#define arginfo_class_Dom_XMLDocument_saveXmlFile arginfo_class_Dom_HTMLDocument_saveXmlFile
|
#define arginfo_class_Dom_XMLDocument_saveXmlFile arginfo_class_Dom_HTMLDocument_saveXmlFile
|
||||||
|
|
||||||
|
#define arginfo_class_Dom_TokenList___construct arginfo_class_DOMDocumentFragment___construct
|
||||||
|
|
||||||
|
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_TokenList_item, 0, 1, IS_STRING, 1)
|
||||||
|
ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0)
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
|
||||||
|
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_TokenList_contains, 0, 1, _IS_BOOL, 0)
|
||||||
|
ZEND_ARG_TYPE_INFO(0, token, IS_STRING, 0)
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
|
||||||
|
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_TokenList_add, 0, 0, IS_VOID, 0)
|
||||||
|
ZEND_ARG_VARIADIC_TYPE_INFO(0, tokens, IS_STRING, 0)
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
|
||||||
|
#define arginfo_class_Dom_TokenList_remove arginfo_class_Dom_TokenList_add
|
||||||
|
|
||||||
|
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_TokenList_toggle, 0, 1, _IS_BOOL, 0)
|
||||||
|
ZEND_ARG_TYPE_INFO(0, token, IS_STRING, 0)
|
||||||
|
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, force, _IS_BOOL, 1, "null")
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
|
||||||
|
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_TokenList_replace, 0, 2, _IS_BOOL, 0)
|
||||||
|
ZEND_ARG_TYPE_INFO(0, token, IS_STRING, 0)
|
||||||
|
ZEND_ARG_TYPE_INFO(0, newToken, IS_STRING, 0)
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
|
||||||
|
#define arginfo_class_Dom_TokenList_supports arginfo_class_Dom_TokenList_contains
|
||||||
|
|
||||||
|
#define arginfo_class_Dom_TokenList_count arginfo_class_Dom_Node_getLineNo
|
||||||
|
|
||||||
|
#define arginfo_class_Dom_TokenList_getIterator arginfo_class_DOMNodeList_getIterator
|
||||||
|
|
||||||
#if defined(LIBXML_XPATH_ENABLED)
|
#if defined(LIBXML_XPATH_ENABLED)
|
||||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Dom_XPath___construct, 0, 0, 1)
|
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Dom_XPath___construct, 0, 0, 1)
|
||||||
ZEND_ARG_OBJ_INFO(0, document, Dom\\Document, 0)
|
ZEND_ARG_OBJ_INFO(0, document, Dom\\Document, 0)
|
||||||
|
@ -1323,6 +1355,15 @@ ZEND_METHOD(Dom_XMLDocument, createEmpty);
|
||||||
ZEND_METHOD(Dom_XMLDocument, createFromFile);
|
ZEND_METHOD(Dom_XMLDocument, createFromFile);
|
||||||
ZEND_METHOD(Dom_XMLDocument, createFromString);
|
ZEND_METHOD(Dom_XMLDocument, createFromString);
|
||||||
ZEND_METHOD(Dom_XMLDocument, xinclude);
|
ZEND_METHOD(Dom_XMLDocument, xinclude);
|
||||||
|
ZEND_METHOD(Dom_TokenList, item);
|
||||||
|
ZEND_METHOD(Dom_TokenList, contains);
|
||||||
|
ZEND_METHOD(Dom_TokenList, add);
|
||||||
|
ZEND_METHOD(Dom_TokenList, remove);
|
||||||
|
ZEND_METHOD(Dom_TokenList, toggle);
|
||||||
|
ZEND_METHOD(Dom_TokenList, replace);
|
||||||
|
ZEND_METHOD(Dom_TokenList, supports);
|
||||||
|
ZEND_METHOD(Dom_TokenList, count);
|
||||||
|
ZEND_METHOD(Dom_TokenList, getIterator);
|
||||||
#if defined(LIBXML_XPATH_ENABLED)
|
#if defined(LIBXML_XPATH_ENABLED)
|
||||||
ZEND_METHOD(Dom_XPath, __construct);
|
ZEND_METHOD(Dom_XPath, __construct);
|
||||||
#endif
|
#endif
|
||||||
|
@ -1839,6 +1880,20 @@ static const zend_function_entry class_Dom_XMLDocument_methods[] = {
|
||||||
ZEND_FE_END
|
ZEND_FE_END
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const zend_function_entry class_Dom_TokenList_methods[] = {
|
||||||
|
ZEND_RAW_FENTRY("__construct", zim_Dom_Node___construct, arginfo_class_Dom_TokenList___construct, ZEND_ACC_PRIVATE, NULL, NULL)
|
||||||
|
ZEND_ME(Dom_TokenList, item, arginfo_class_Dom_TokenList_item, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, contains, arginfo_class_Dom_TokenList_contains, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, add, arginfo_class_Dom_TokenList_add, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, remove, arginfo_class_Dom_TokenList_remove, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, toggle, arginfo_class_Dom_TokenList_toggle, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, replace, arginfo_class_Dom_TokenList_replace, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, supports, arginfo_class_Dom_TokenList_supports, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, count, arginfo_class_Dom_TokenList_count, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_ME(Dom_TokenList, getIterator, arginfo_class_Dom_TokenList_getIterator, ZEND_ACC_PUBLIC)
|
||||||
|
ZEND_FE_END
|
||||||
|
};
|
||||||
|
|
||||||
#if defined(LIBXML_XPATH_ENABLED)
|
#if defined(LIBXML_XPATH_ENABLED)
|
||||||
static const zend_function_entry class_Dom_XPath_methods[] = {
|
static const zend_function_entry class_Dom_XPath_methods[] = {
|
||||||
ZEND_ME(Dom_XPath, __construct, arginfo_class_Dom_XPath___construct, ZEND_ACC_PUBLIC)
|
ZEND_ME(Dom_XPath, __construct, arginfo_class_Dom_XPath___construct, ZEND_ACC_PUBLIC)
|
||||||
|
@ -3080,6 +3135,13 @@ static zend_class_entry *register_class_Dom_Element(zend_class_entry *class_entr
|
||||||
zend_declare_typed_property(class_entry, property_className_name, &property_className_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
|
zend_declare_typed_property(class_entry, property_className_name, &property_className_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
|
||||||
zend_string_release(property_className_name);
|
zend_string_release(property_className_name);
|
||||||
|
|
||||||
|
zval property_classList_default_value;
|
||||||
|
ZVAL_UNDEF(&property_classList_default_value);
|
||||||
|
zend_string *property_classList_name = zend_string_init("classList", sizeof("classList") - 1, 1);
|
||||||
|
zend_string *property_classList_class_Dom_TokenList = zend_string_init("Dom\\TokenList", sizeof("Dom\\TokenList")-1, 1);
|
||||||
|
zend_declare_typed_property(class_entry, property_classList_name, &property_classList_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_classList_class_Dom_TokenList, 0, 0));
|
||||||
|
zend_string_release(property_classList_name);
|
||||||
|
|
||||||
zval property_attributes_default_value;
|
zval property_attributes_default_value;
|
||||||
ZVAL_UNDEF(&property_attributes_default_value);
|
ZVAL_UNDEF(&property_attributes_default_value);
|
||||||
zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1);
|
zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1);
|
||||||
|
@ -3571,6 +3633,30 @@ static zend_class_entry *register_class_Dom_XMLDocument(zend_class_entry *class_
|
||||||
return class_entry;
|
return class_entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static zend_class_entry *register_class_Dom_TokenList(zend_class_entry *class_entry_Dom_IteratorAggregate, zend_class_entry *class_entry_Dom_Countable)
|
||||||
|
{
|
||||||
|
zend_class_entry ce, *class_entry;
|
||||||
|
|
||||||
|
INIT_NS_CLASS_ENTRY(ce, "Dom", "TokenList", class_Dom_TokenList_methods);
|
||||||
|
class_entry = zend_register_internal_class_ex(&ce, NULL);
|
||||||
|
class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
|
||||||
|
zend_class_implements(class_entry, 2, class_entry_Dom_IteratorAggregate, class_entry_Dom_Countable);
|
||||||
|
|
||||||
|
zval property_length_default_value;
|
||||||
|
ZVAL_UNDEF(&property_length_default_value);
|
||||||
|
zend_string *property_length_name = zend_string_init("length", sizeof("length") - 1, 1);
|
||||||
|
zend_declare_typed_property(class_entry, property_length_name, &property_length_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
|
||||||
|
zend_string_release(property_length_name);
|
||||||
|
|
||||||
|
zval property_value_default_value;
|
||||||
|
ZVAL_UNDEF(&property_value_default_value);
|
||||||
|
zend_string *property_value_name = zend_string_init("value", sizeof("value") - 1, 1);
|
||||||
|
zend_declare_typed_property(class_entry, property_value_name, &property_value_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
|
||||||
|
zend_string_release(property_value_name);
|
||||||
|
|
||||||
|
return class_entry;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(LIBXML_XPATH_ENABLED)
|
#if defined(LIBXML_XPATH_ENABLED)
|
||||||
static zend_class_entry *register_class_Dom_XPath(void)
|
static zend_class_entry *register_class_Dom_XPath(void)
|
||||||
{
|
{
|
||||||
|
|
32
ext/dom/tests/modern/token_list/add.phpt
Normal file
32
ext/dom/tests/modern/token_list/add.phpt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: add
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
$list->add();
|
||||||
|
$list->add('a', 'b');
|
||||||
|
$list->add('c');
|
||||||
|
|
||||||
|
$str = 'd';
|
||||||
|
$ref =& $str;
|
||||||
|
|
||||||
|
$list->add($ref);
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
$list->value = '';
|
||||||
|
$list->add('e');
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="a b c d"/>
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="e"/>
|
41
ext/dom/tests/modern/token_list/add_errors.phpt
Normal file
41
ext/dom/tests/modern/token_list/add_errors.phpt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: add errors
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString("<root/>");
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->add("");
|
||||||
|
} catch (DOMException $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$list->add(" ");
|
||||||
|
} catch (DOMException $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$list->add("\0");
|
||||||
|
} catch (ValueError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$list->add(0);
|
||||||
|
} catch (TypeError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
The empty string is not a valid token
|
||||||
|
The token must not contain any ASCII whitespace
|
||||||
|
Dom\TokenList::add(): Argument #1 must not contain any null bytes
|
||||||
|
Dom\TokenList::add(): Argument #1 must be of type string, int given
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root/>
|
47
ext/dom/tests/modern/token_list/attlist.phpt
Normal file
47
ext/dom/tests/modern/token_list/attlist.phpt
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: ATTLIST interaction
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString(<<<XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE root [
|
||||||
|
<!ELEMENT root EMPTY>
|
||||||
|
<!ATTLIST child class CDATA "first second">
|
||||||
|
]>
|
||||||
|
<root><child/></root>
|
||||||
|
XML, LIBXML_DTDATTR);
|
||||||
|
$element = $dom->documentElement->firstChild;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
echo 'class: ', $element->getAttribute('class'), "\n";
|
||||||
|
var_dump($list);
|
||||||
|
|
||||||
|
$list->remove('first');
|
||||||
|
|
||||||
|
var_dump($list);
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
class: first second
|
||||||
|
object(Dom\TokenList)#2 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(2)
|
||||||
|
["value"]=>
|
||||||
|
string(12) "first second"
|
||||||
|
}
|
||||||
|
object(Dom\TokenList)#2 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(1)
|
||||||
|
["value"]=>
|
||||||
|
string(6) "second"
|
||||||
|
}
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE root [
|
||||||
|
<!ELEMENT root EMPTY>
|
||||||
|
<!ATTLIST child class CDATA "first second">
|
||||||
|
]>
|
||||||
|
<root><child class="second"/></root>
|
41
ext/dom/tests/modern/token_list/change_attribute.phpt
Normal file
41
ext/dom/tests/modern/token_list/change_attribute.phpt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: change attribute
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="a b c"><child/></root>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
var_dump($list);
|
||||||
|
|
||||||
|
$element->attributes[0]->value = 'd';
|
||||||
|
|
||||||
|
var_dump($list);
|
||||||
|
|
||||||
|
$list->value = 'e f g';
|
||||||
|
|
||||||
|
var_dump($list);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
object(Dom\TokenList)#3 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(3)
|
||||||
|
["value"]=>
|
||||||
|
string(5) "a b c"
|
||||||
|
}
|
||||||
|
object(Dom\TokenList)#3 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(1)
|
||||||
|
["value"]=>
|
||||||
|
string(1) "d"
|
||||||
|
}
|
||||||
|
object(Dom\TokenList)#3 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(3)
|
||||||
|
["value"]=>
|
||||||
|
string(5) "e f g"
|
||||||
|
}
|
17
ext/dom/tests/modern/token_list/clone.phpt
Normal file
17
ext/dom/tests/modern/token_list/clone.phpt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: clone
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="a b c"><child/></root>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
clone $element->classList;
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
Fatal error: Uncaught Error: Trying to clone an uncloneable object of class Dom\TokenList in %s:%d
|
||||||
|
Stack trace:
|
||||||
|
#0 {main}
|
||||||
|
thrown in %s on line %d
|
68
ext/dom/tests/modern/token_list/contains.phpt
Normal file
68
ext/dom/tests/modern/token_list/contains.phpt
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: contains
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
var_dump($list->contains(''));
|
||||||
|
var_dump($list->contains('A'));
|
||||||
|
var_dump($list->contains('B'));
|
||||||
|
var_dump($list->contains('C'));
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
var_dump($list->contains(' A'));
|
||||||
|
var_dump($list->contains('B '));
|
||||||
|
var_dump($list->contains(' C '));
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
var_dump($list->contains('a'));
|
||||||
|
var_dump($list->contains('b'));
|
||||||
|
var_dump($list->contains('c'));
|
||||||
|
|
||||||
|
$element->setAttribute('class', 'D');
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
var_dump($list->contains('A'));
|
||||||
|
var_dump($list->contains('B'));
|
||||||
|
var_dump($list->contains('C'));
|
||||||
|
var_dump($list->contains('D'));
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
$list->value = 'E';
|
||||||
|
var_dump($list->contains('D'));
|
||||||
|
var_dump($list->contains('E'));
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
---
|
||||||
|
bool(false)
|
||||||
|
bool(true)
|
||||||
|
bool(true)
|
||||||
|
bool(true)
|
||||||
|
---
|
||||||
|
bool(false)
|
||||||
|
bool(false)
|
||||||
|
bool(false)
|
||||||
|
---
|
||||||
|
bool(false)
|
||||||
|
bool(false)
|
||||||
|
bool(false)
|
||||||
|
---
|
||||||
|
bool(false)
|
||||||
|
bool(false)
|
||||||
|
bool(false)
|
||||||
|
bool(true)
|
||||||
|
---
|
||||||
|
bool(false)
|
||||||
|
bool(true)
|
16
ext/dom/tests/modern/token_list/contains_empty.phpt
Normal file
16
ext/dom/tests/modern/token_list/contains_empty.phpt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: contains empty
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
var_dump($list->contains('x'));
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
bool(false)
|
20
ext/dom/tests/modern/token_list/contains_error.phpt
Normal file
20
ext/dom/tests/modern/token_list/contains_error.phpt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: contains errors
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->contains("\0");
|
||||||
|
} catch (ValueError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Dom\TokenList::contains(): Argument #1 ($token) must not contain any null bytes
|
16
ext/dom/tests/modern/token_list/count.phpt
Normal file
16
ext/dom/tests/modern/token_list/count.phpt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: count
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="a b c"><child/></root>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
var_dump($element->classList->count(), count($element->classList), $element->classList->length);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(3)
|
||||||
|
int(3)
|
||||||
|
int(3)
|
19
ext/dom/tests/modern/token_list/debug.phpt
Normal file
19
ext/dom/tests/modern/token_list/debug.phpt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: debug
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="a b c"><child/></root>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
var_dump($element->classList);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
object(Dom\TokenList)#3 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(3)
|
||||||
|
["value"]=>
|
||||||
|
string(5) "a b c"
|
||||||
|
}
|
93
ext/dom/tests/modern/token_list/dimensions.phpt
Normal file
93
ext/dom/tests/modern/token_list/dimensions.phpt
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: dimensions
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C 0"/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
foreach (range(-1, 4) as $i) {
|
||||||
|
echo "--- $i ---\n";
|
||||||
|
var_dump($list[$i], isset($list[$i]), empty($list[$i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "--- \"0\" ---\n";
|
||||||
|
var_dump($list["0"], isset($list["0"]), empty($list["0"]));
|
||||||
|
|
||||||
|
echo "--- \"foo\" ---\n";
|
||||||
|
try {
|
||||||
|
var_dump($list["foo"], isset($list["foo"]), empty($list["foo"]));
|
||||||
|
} catch (TypeError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "--- 1.1 ---\n";
|
||||||
|
var_dump($list[1.1], isset($list[1.1]), empty($list[1.1]));
|
||||||
|
|
||||||
|
echo "--- true ---\n";
|
||||||
|
var_dump($list[true], isset($list[true]), empty($list[true]));
|
||||||
|
|
||||||
|
echo "--- false ---\n";
|
||||||
|
var_dump($list[false], isset($list[false]), empty($list[false]));
|
||||||
|
|
||||||
|
echo "--- ref ---\n";
|
||||||
|
$tmp = 2;
|
||||||
|
$ref =& $tmp;
|
||||||
|
var_dump($list[$ref], isset($list[$ref]), empty($list[$ref]));
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
--- -1 ---
|
||||||
|
NULL
|
||||||
|
bool(false)
|
||||||
|
bool(true)
|
||||||
|
--- 0 ---
|
||||||
|
string(1) "A"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
--- 1 ---
|
||||||
|
string(1) "B"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
--- 2 ---
|
||||||
|
string(1) "C"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
--- 3 ---
|
||||||
|
string(1) "0"
|
||||||
|
bool(true)
|
||||||
|
bool(true)
|
||||||
|
--- 4 ---
|
||||||
|
NULL
|
||||||
|
bool(false)
|
||||||
|
bool(true)
|
||||||
|
--- "0" ---
|
||||||
|
string(1) "A"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
--- "foo" ---
|
||||||
|
Cannot access offset of type string on Dom\TokenList
|
||||||
|
--- 1.1 ---
|
||||||
|
|
||||||
|
Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d
|
||||||
|
|
||||||
|
Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d
|
||||||
|
|
||||||
|
Deprecated: Implicit conversion from float 1.1 to int loses precision in %s on line %d
|
||||||
|
string(1) "B"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
--- true ---
|
||||||
|
string(1) "B"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
--- false ---
|
||||||
|
string(1) "A"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
--- ref ---
|
||||||
|
string(1) "C"
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
57
ext/dom/tests/modern/token_list/dimensions_error.phpt
Normal file
57
ext/dom/tests/modern/token_list/dimensions_error.phpt
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: dimensions error
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
$testOffsets = [
|
||||||
|
new stdClass,
|
||||||
|
[],
|
||||||
|
fopen("php://output", "w"),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($testOffsets as $offset) {
|
||||||
|
try {
|
||||||
|
$list[$offset];
|
||||||
|
} catch (TypeError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
isset($list[$offset]);
|
||||||
|
} catch (TypeError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
empty($list[$offset]);
|
||||||
|
} catch (TypeError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list[][0] = 1;
|
||||||
|
} catch (Error $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
Cannot access offset of type stdClass on Dom\TokenList
|
||||||
|
Cannot access offset of type stdClass in isset or empty
|
||||||
|
Cannot access offset of type stdClass in isset or empty
|
||||||
|
Cannot access offset of type array on Dom\TokenList
|
||||||
|
Cannot access offset of type array in isset or empty
|
||||||
|
Cannot access offset of type array in isset or empty
|
||||||
|
|
||||||
|
Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d
|
||||||
|
|
||||||
|
Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d
|
||||||
|
|
||||||
|
Warning: Resource ID#%d used as offset, casting to integer (%d) in %s on line %d
|
||||||
|
Cannot append to Dom\TokenList
|
43
ext/dom/tests/modern/token_list/entities.phpt
Normal file
43
ext/dom/tests/modern/token_list/entities.phpt
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: entities interaction
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString(<<<XML
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE root [
|
||||||
|
<!ENTITY ent "foo">
|
||||||
|
]>
|
||||||
|
<root class="x&ent;x"/>
|
||||||
|
XML);
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
var_dump($list);
|
||||||
|
|
||||||
|
var_dump($list->contains("xfoox"));
|
||||||
|
var_dump($list->contains("xx"));
|
||||||
|
var_dump($list->contains("foo"));
|
||||||
|
|
||||||
|
$list->add("test");
|
||||||
|
|
||||||
|
echo $dom->saveXML();
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
object(Dom\TokenList)#3 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(1)
|
||||||
|
["value"]=>
|
||||||
|
string(5) "xfoox"
|
||||||
|
}
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
||||||
|
bool(false)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE root [
|
||||||
|
<!ENTITY ent "foo">
|
||||||
|
]>
|
||||||
|
<root class="xfoox test"/>
|
23
ext/dom/tests/modern/token_list/equality.phpt
Normal file
23
ext/dom/tests/modern/token_list/equality.phpt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: Test equality
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="a b c"><child/></root>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
|
||||||
|
var_dump($element->classList === $element->classList);
|
||||||
|
$list = $element->classList;
|
||||||
|
var_dump($list === $list);
|
||||||
|
var_dump($list === $element->classList);
|
||||||
|
|
||||||
|
var_dump($list === $element->firstChild->classList);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
bool(true)
|
||||||
|
bool(true)
|
||||||
|
bool(true)
|
||||||
|
bool(false)
|
19
ext/dom/tests/modern/token_list/foreach_by_ref.phpt
Normal file
19
ext/dom/tests/modern/token_list/foreach_by_ref.phpt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: foreach by ref
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($dom->documentElement->classList as &$class) {
|
||||||
|
}
|
||||||
|
} catch (Error $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
An iterator cannot be used with foreach by reference
|
39
ext/dom/tests/modern/token_list/getIterator.phpt
Normal file
39
ext/dom/tests/modern/token_list/getIterator.phpt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: getIterator
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
$it = $list->getIterator();
|
||||||
|
var_dump($it);
|
||||||
|
|
||||||
|
var_dump($it->key(), $it->current());
|
||||||
|
$it->next();
|
||||||
|
var_dump($it->key(), $it->current());
|
||||||
|
$it->next();
|
||||||
|
var_dump($it->key(), $it->current());
|
||||||
|
$it->next();
|
||||||
|
var_dump($it->key(), $it->current());
|
||||||
|
|
||||||
|
$it->rewind();
|
||||||
|
var_dump($it->key(), $it->current());
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
object(InternalIterator)#5 (0) {
|
||||||
|
}
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
int(3)
|
||||||
|
NULL
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
30
ext/dom/tests/modern/token_list/item.phpt
Normal file
30
ext/dom/tests/modern/token_list/item.phpt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: item
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
foreach (range(-1, 3) as $i) {
|
||||||
|
var_dump($list->item($i));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
$list->value = 'D';
|
||||||
|
var_dump($list->item(0));
|
||||||
|
var_dump($list->item(1));
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
NULL
|
||||||
|
string(1) "A"
|
||||||
|
string(1) "B"
|
||||||
|
string(1) "C"
|
||||||
|
NULL
|
||||||
|
---
|
||||||
|
string(1) "D"
|
||||||
|
NULL
|
27
ext/dom/tests/modern/token_list/iteration_01.phpt
Normal file
27
ext/dom/tests/modern/token_list/iteration_01.phpt
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: iteration 01
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C D E F"/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
foreach ($list as $i => $item) {
|
||||||
|
var_dump($i, $item);
|
||||||
|
$list->remove('A', 'D');
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(0)
|
||||||
|
string(1) "B"
|
||||||
|
int(1)
|
||||||
|
string(1) "C"
|
||||||
|
int(2)
|
||||||
|
string(1) "E"
|
||||||
|
int(3)
|
||||||
|
string(1) "F"
|
117
ext/dom/tests/modern/token_list/iteration_02.phpt
Normal file
117
ext/dom/tests/modern/token_list/iteration_02.phpt
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: iteration 02
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C D E F"/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
foreach ($list as $i => $item) {
|
||||||
|
var_dump($i, $item);
|
||||||
|
echo "==========\n";
|
||||||
|
foreach ($list as $i2 => $item2) {
|
||||||
|
var_dump($i2, $item2);
|
||||||
|
}
|
||||||
|
echo "==========\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
==========
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
int(3)
|
||||||
|
string(1) "D"
|
||||||
|
int(4)
|
||||||
|
string(1) "E"
|
||||||
|
int(5)
|
||||||
|
string(1) "F"
|
||||||
|
==========
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
==========
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
int(3)
|
||||||
|
string(1) "D"
|
||||||
|
int(4)
|
||||||
|
string(1) "E"
|
||||||
|
int(5)
|
||||||
|
string(1) "F"
|
||||||
|
==========
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
==========
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
int(3)
|
||||||
|
string(1) "D"
|
||||||
|
int(4)
|
||||||
|
string(1) "E"
|
||||||
|
int(5)
|
||||||
|
string(1) "F"
|
||||||
|
==========
|
||||||
|
int(3)
|
||||||
|
string(1) "D"
|
||||||
|
==========
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
int(3)
|
||||||
|
string(1) "D"
|
||||||
|
int(4)
|
||||||
|
string(1) "E"
|
||||||
|
int(5)
|
||||||
|
string(1) "F"
|
||||||
|
==========
|
||||||
|
int(4)
|
||||||
|
string(1) "E"
|
||||||
|
==========
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
int(3)
|
||||||
|
string(1) "D"
|
||||||
|
int(4)
|
||||||
|
string(1) "E"
|
||||||
|
int(5)
|
||||||
|
string(1) "F"
|
||||||
|
==========
|
||||||
|
int(5)
|
||||||
|
string(1) "F"
|
||||||
|
==========
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "C"
|
||||||
|
int(3)
|
||||||
|
string(1) "D"
|
||||||
|
int(4)
|
||||||
|
string(1) "E"
|
||||||
|
int(5)
|
||||||
|
string(1) "F"
|
||||||
|
==========
|
|
@ -0,0 +1,53 @@
|
||||||
|
--TEST--
|
||||||
|
Test DOMTokenList iterator invalidation after modification
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C D"/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
$counter = 0;
|
||||||
|
foreach ($list as $key => $token) {
|
||||||
|
var_dump($key, $token);
|
||||||
|
if (++$counter === 2) {
|
||||||
|
$list->value = 'E F G';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "---\n";
|
||||||
|
|
||||||
|
$iterator = $list->getIterator();
|
||||||
|
$iterator->next();
|
||||||
|
$list->value = 'X Y Z';
|
||||||
|
var_dump($iterator->key());
|
||||||
|
var_dump($iterator->current());
|
||||||
|
$iterator->rewind();
|
||||||
|
var_dump($iterator->key());
|
||||||
|
var_dump($iterator->current());
|
||||||
|
$list->value = '';
|
||||||
|
var_dump($iterator->key());
|
||||||
|
var_dump($iterator->current());
|
||||||
|
var_dump($iterator->valid());
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
---
|
||||||
|
int(0)
|
||||||
|
string(1) "A"
|
||||||
|
int(1)
|
||||||
|
string(1) "B"
|
||||||
|
int(2)
|
||||||
|
string(1) "G"
|
||||||
|
---
|
||||||
|
int(1)
|
||||||
|
string(1) "Y"
|
||||||
|
int(0)
|
||||||
|
string(1) "X"
|
||||||
|
int(0)
|
||||||
|
NULL
|
||||||
|
bool(false)
|
38
ext/dom/tests/modern/token_list/remove.phpt
Normal file
38
ext/dom/tests/modern/token_list/remove.phpt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: remove
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="test1 test2"/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
$list->remove();
|
||||||
|
$list->remove('test1');
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
$list->remove('nope');
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
$list->remove('test2');
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
$list->value = 'test3 test4';
|
||||||
|
$list->remove('test4');
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="test2"/>
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="test2"/>
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class=""/>
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="test3"/>
|
41
ext/dom/tests/modern/token_list/remove_errors.phpt
Normal file
41
ext/dom/tests/modern/token_list/remove_errors.phpt
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: remove errors
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString("<root/>");
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->remove("");
|
||||||
|
} catch (DOMException $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$list->remove(" ");
|
||||||
|
} catch (DOMException $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$list->remove("\0");
|
||||||
|
} catch (ValueError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$list->remove(0);
|
||||||
|
} catch (TypeError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
The empty string is not a valid token
|
||||||
|
The token must not contain any ASCII whitespace
|
||||||
|
Dom\TokenList::remove(): Argument #1 must not contain any null bytes
|
||||||
|
Dom\TokenList::remove(): Argument #1 must be of type string, int given
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root/>
|
33
ext/dom/tests/modern/token_list/removed_element.phpt
Normal file
33
ext/dom/tests/modern/token_list/removed_element.phpt
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: operate on removed element
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
$element->remove();
|
||||||
|
|
||||||
|
var_dump($list);
|
||||||
|
|
||||||
|
$list->remove('B');
|
||||||
|
|
||||||
|
var_dump($list);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
object(Dom\TokenList)#3 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(3)
|
||||||
|
["value"]=>
|
||||||
|
string(5) "A B C"
|
||||||
|
}
|
||||||
|
object(Dom\TokenList)#3 (2) {
|
||||||
|
["length"]=>
|
||||||
|
int(2)
|
||||||
|
["value"]=>
|
||||||
|
string(3) "A C"
|
||||||
|
}
|
55
ext/dom/tests/modern/token_list/replace.phpt
Normal file
55
ext/dom/tests/modern/token_list/replace.phpt
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: replace
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
var_dump($list->replace('nonexistent', 'X'));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
var_dump($list->replace('B', 'X'));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
var_dump($list->replace('C', 'X'));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
var_dump($list->replace('A', 'B'));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
var_dump($list->replace('X', 'B'));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
$list->value = 'A';
|
||||||
|
$list->replace('A', 'AA');
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
bool(false)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="A B C"/>
|
||||||
|
bool(true)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="A X C"/>
|
||||||
|
bool(true)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="A X"/>
|
||||||
|
bool(true)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="B X"/>
|
||||||
|
bool(true)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="B"/>
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="AA"/>
|
34
ext/dom/tests/modern/token_list/replace_error.phpt
Normal file
34
ext/dom/tests/modern/token_list/replace_error.phpt
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: replace errors
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->replace("\0", "X");
|
||||||
|
} catch (ValueError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->replace("X", "\0");
|
||||||
|
} catch (ValueError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->replace("a b", "X");
|
||||||
|
} catch (DOMException $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Dom\TokenList::replace(): Argument #1 ($token) must not contain any null bytes
|
||||||
|
Dom\TokenList::replace(): Argument #2 ($newToken) must not contain any null bytes
|
||||||
|
The token must not contain any ASCII whitespace
|
18
ext/dom/tests/modern/token_list/supports.phpt
Normal file
18
ext/dom/tests/modern/token_list/supports.phpt
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: supports
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="a b c"><child/></root>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
try {
|
||||||
|
$element->classList->supports('a');
|
||||||
|
} catch (TypeError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Attribute "class" does not define any supported tokens
|
83
ext/dom/tests/modern/token_list/toggle.phpt
Normal file
83
ext/dom/tests/modern/token_list/toggle.phpt
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: toggle
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
echo "--- Toggle A (forced add) ---\n";
|
||||||
|
|
||||||
|
var_dump($list->toggle("A", true));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
echo "--- Toggle A (not forced) ---\n";
|
||||||
|
|
||||||
|
var_dump($list->toggle("A"));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
echo "--- Toggle A (forced remove) ---\n";
|
||||||
|
|
||||||
|
var_dump($list->toggle("A", false));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
echo "--- Toggle B (forced remove) ---\n";
|
||||||
|
|
||||||
|
var_dump($list->toggle("B", false));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
echo "--- Toggle D ---\n";
|
||||||
|
|
||||||
|
var_dump($list->toggle("D"));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
echo "--- Toggle C ---\n";
|
||||||
|
|
||||||
|
var_dump($list->toggle("C"));
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
echo "--- Toggle E ---\n";
|
||||||
|
|
||||||
|
$list->value = 'E';
|
||||||
|
$list->toggle('E');
|
||||||
|
|
||||||
|
echo $dom->saveXML(), "\n";
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
--- Toggle A (forced add) ---
|
||||||
|
bool(true)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="A B C"/>
|
||||||
|
--- Toggle A (not forced) ---
|
||||||
|
bool(false)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="B C"/>
|
||||||
|
--- Toggle A (forced remove) ---
|
||||||
|
bool(false)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="B C"/>
|
||||||
|
--- Toggle B (forced remove) ---
|
||||||
|
bool(false)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="C"/>
|
||||||
|
--- Toggle D ---
|
||||||
|
bool(true)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="C D"/>
|
||||||
|
--- Toggle C ---
|
||||||
|
bool(false)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class="D"/>
|
||||||
|
--- Toggle E ---
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root class=""/>
|
27
ext/dom/tests/modern/token_list/toggle_error.phpt
Normal file
27
ext/dom/tests/modern/token_list/toggle_error.phpt
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: toggle errors
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root class="A B C"/>');
|
||||||
|
$element = $dom->documentElement;
|
||||||
|
$list = $element->classList;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->toggle("\0");
|
||||||
|
} catch (ValueError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->toggle("a b");
|
||||||
|
} catch (DOMException $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Dom\TokenList::toggle(): Argument #1 ($token) must not contain any null bytes
|
||||||
|
The token must not contain any ASCII whitespace
|
25
ext/dom/tests/modern/token_list/value_edge_cases.phpt
Normal file
25
ext/dom/tests/modern/token_list/value_edge_cases.phpt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
--TEST--
|
||||||
|
TokenList: value edge cases
|
||||||
|
--EXTENSIONS--
|
||||||
|
dom
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$dom = DOM\XMLDocument::createFromString('<root/>');
|
||||||
|
$list = $dom->documentElement->classList;
|
||||||
|
|
||||||
|
var_dump($list->value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$list->value = "\0";
|
||||||
|
} catch (ValueError $e) {
|
||||||
|
echo $e->getMessage(), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
var_dump($list->value);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
string(0) ""
|
||||||
|
Value must not contain any null bytes
|
||||||
|
string(0) ""
|
732
ext/dom/token_list.c
Normal file
732
ext/dom/token_list.c
Normal file
|
@ -0,0 +1,732 @@
|
||||||
|
/*
|
||||||
|
+----------------------------------------------------------------------+
|
||||||
|
| 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 "token_list.h"
|
||||||
|
#include "infra.h"
|
||||||
|
#include "zend_interfaces.h"
|
||||||
|
|
||||||
|
#define TOKEN_LIST_GET_INTERNAL() php_dom_token_list_from_obj(Z_OBJ_P(ZEND_THIS))
|
||||||
|
#define TOKEN_LIST_GET_SET(intern) (&(intern)->token_set)
|
||||||
|
#define Z_TOKEN_LIST_P(zv) php_dom_token_list_from_obj(Z_OBJ_P(zv))
|
||||||
|
|
||||||
|
typedef struct dom_token_list_it {
|
||||||
|
zend_object_iterator it;
|
||||||
|
/* Store the hash position here to allow multiple (e.g. nested) iterations of the same token list. */
|
||||||
|
HashPosition pos;
|
||||||
|
php_libxml_cache_tag cache_tag;
|
||||||
|
} dom_token_list_it;
|
||||||
|
|
||||||
|
static zend_always_inline bool dom_contains_ascii_whitespace(const char *data)
|
||||||
|
{
|
||||||
|
return strpbrk(data, ascii_whitespace) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static zend_always_inline void dom_add_token(HashTable *ht, zend_string *token)
|
||||||
|
{
|
||||||
|
/* Key outlives the value's lifetime because as long as the entry is in the table it is kept alive. */
|
||||||
|
zval zv;
|
||||||
|
ZVAL_STR(&zv, token);
|
||||||
|
zend_hash_add(ht, token, &zv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#concept-ordered-set-parser
|
||||||
|
* and https://infra.spec.whatwg.org/#split-on-ascii-whitespace */
|
||||||
|
static void dom_ordered_set_parser(HashTable *token_set, const char *position)
|
||||||
|
{
|
||||||
|
/* Adapted steps from "split on ASCII whitespace" such that that loop directly appends to the token set. */
|
||||||
|
|
||||||
|
/* 1. Let position be a position variable for input, initially pointing at the start of input.
|
||||||
|
* => That's the position pointer. */
|
||||||
|
/* 2. Let tokens be a list of strings, initially empty.
|
||||||
|
* => That's the token set. */
|
||||||
|
|
||||||
|
/* 3. Skip ASCII whitespace within input given position. */
|
||||||
|
position += strspn(position, ascii_whitespace);
|
||||||
|
|
||||||
|
/* 4. While position is not past the end of input: */
|
||||||
|
while (*position != '\0') {
|
||||||
|
/* 4.1. Let token be the result of collecting a sequence of code points that are not ASCII whitespace from input */
|
||||||
|
const char *start = position;
|
||||||
|
position += strcspn(position, ascii_whitespace);
|
||||||
|
size_t length = position - start;
|
||||||
|
|
||||||
|
/* 4.2. Append token to tokens. */
|
||||||
|
zend_string *token = zend_string_init(start, length, false);
|
||||||
|
dom_add_token(token_set, token);
|
||||||
|
zend_string_release_ex(token, false);
|
||||||
|
|
||||||
|
/* 4.3. Skip ASCII whitespace within input given position. */
|
||||||
|
position += strspn(position, ascii_whitespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Return tokens.
|
||||||
|
* => That's the token set. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#concept-ordered-set-serializer */
|
||||||
|
static char *dom_ordered_set_serializer(HashTable *token_set)
|
||||||
|
{
|
||||||
|
size_t length = 0;
|
||||||
|
zend_string *token;
|
||||||
|
ZEND_HASH_MAP_FOREACH_STR_KEY(token_set, token) {
|
||||||
|
size_t needed_size = ZSTR_LEN(token) + 1; /* +1 for the space (or \0 at the end) */
|
||||||
|
if (UNEXPECTED(ZSTR_MAX_LEN - length < needed_size)) {
|
||||||
|
/* Shouldn't really be able to happen in practice. */
|
||||||
|
zend_throw_error(NULL, "Token set too large");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
length += needed_size;
|
||||||
|
} ZEND_HASH_FOREACH_END();
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
char *ret = emalloc(1);
|
||||||
|
*ret = '\0';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ret = emalloc(length);
|
||||||
|
char *ptr = ret;
|
||||||
|
ZEND_HASH_MAP_FOREACH_STR_KEY(token_set, token) {
|
||||||
|
memcpy(ptr, ZSTR_VAL(token), ZSTR_LEN(token));
|
||||||
|
ptr += ZSTR_LEN(token);
|
||||||
|
*ptr++ = ' ';
|
||||||
|
} ZEND_HASH_FOREACH_END();
|
||||||
|
ptr[-1] = '\0'; /* replace last space with \0 */
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static zend_always_inline xmlNode *dom_token_list_get_element(dom_token_list_object *intern)
|
||||||
|
{
|
||||||
|
php_libxml_node_ptr *element_ptr = intern->dom.ptr;
|
||||||
|
return element_ptr->node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static zend_always_inline const xmlAttr *dom_token_list_get_attr(dom_token_list_object *intern)
|
||||||
|
{
|
||||||
|
const xmlNode *element_node = dom_token_list_get_element(intern);
|
||||||
|
return xmlHasNsProp(element_node, BAD_CAST "class", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#concept-dtl-update */
|
||||||
|
static void dom_token_list_update(dom_token_list_object *intern)
|
||||||
|
{
|
||||||
|
const xmlAttr *attr = dom_token_list_get_attr(intern);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
|
||||||
|
php_libxml_invalidate_cache_tag(&intern->cache_tag);
|
||||||
|
|
||||||
|
/* 1. If the associated element does not have an associated attribute and token set is empty, then return. */
|
||||||
|
if (attr == NULL && zend_hash_num_elements(token_set) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Set an attribute value for the associated element using associated attribute’s local name and the result of
|
||||||
|
* running the ordered set serializer for token set. */
|
||||||
|
char *value = dom_ordered_set_serializer(token_set);
|
||||||
|
xmlSetNsProp(dom_token_list_get_element(intern), NULL, BAD_CAST "class", BAD_CAST value);
|
||||||
|
efree(intern->cached_string);
|
||||||
|
intern->cached_string = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static xmlChar *dom_token_list_get_class_value(const xmlAttr *attr, bool *should_free)
|
||||||
|
{
|
||||||
|
if (attr != NULL && attr->children != NULL) {
|
||||||
|
return php_libxml_attr_value(attr, should_free);
|
||||||
|
}
|
||||||
|
*should_free = false;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dom_token_list_update_set(dom_token_list_object *intern, HashTable *token_set)
|
||||||
|
{
|
||||||
|
/* https://dom.spec.whatwg.org/#ref-for-domtokenlist%E2%91%A0%E2%91%A1 */
|
||||||
|
bool should_free;
|
||||||
|
const xmlAttr *attr = dom_token_list_get_attr(intern);
|
||||||
|
/* 1. If the data is null, the token set remains empty. */
|
||||||
|
xmlChar *value = dom_token_list_get_class_value(attr, &should_free);
|
||||||
|
if (value != NULL) {
|
||||||
|
/* 2. Otherwise, parse the token set. */
|
||||||
|
dom_ordered_set_parser(token_set, (const char *) value);
|
||||||
|
intern->cached_string = estrdup((const char *) value);
|
||||||
|
} else {
|
||||||
|
intern->cached_string = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_free) {
|
||||||
|
xmlFree(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dom_token_list_ensure_set_up_to_date(dom_token_list_object *intern)
|
||||||
|
{
|
||||||
|
bool should_free;
|
||||||
|
const xmlAttr *attr = dom_token_list_get_attr(intern);
|
||||||
|
xmlChar *value = dom_token_list_get_class_value(attr, &should_free);
|
||||||
|
|
||||||
|
/* xmlStrEqual will automatically handle equality rules of NULL vs "" (etc.) correctly. */
|
||||||
|
if (!xmlStrEqual(value, (const xmlChar *) intern->cached_string)) {
|
||||||
|
php_libxml_invalidate_cache_tag(&intern->cache_tag);
|
||||||
|
efree(intern->cached_string);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
zend_hash_destroy(token_set);
|
||||||
|
zend_hash_init(token_set, 0, NULL, NULL, false);
|
||||||
|
dom_token_list_update_set(intern, token_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_free) {
|
||||||
|
xmlFree(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dom_token_list_ctor(dom_token_list_object *intern, dom_object *element_obj)
|
||||||
|
{
|
||||||
|
php_libxml_node_ptr *ptr = element_obj->ptr;
|
||||||
|
ptr->refcount++;
|
||||||
|
intern->dom.ptr = ptr;
|
||||||
|
element_obj->document->refcount++;
|
||||||
|
intern->dom.document = element_obj->document;
|
||||||
|
|
||||||
|
intern->cache_tag.modification_nr = 0;
|
||||||
|
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
zend_hash_init(token_set, 0, NULL, NULL, false);
|
||||||
|
|
||||||
|
dom_token_list_update_set(intern, token_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dom_token_list_free_obj(zend_object *object)
|
||||||
|
{
|
||||||
|
dom_token_list_object *intern = php_dom_token_list_from_obj(object);
|
||||||
|
|
||||||
|
zend_object_std_dtor(object);
|
||||||
|
|
||||||
|
if (EXPECTED(intern->dom.ptr != NULL)) { /* Object initialized? */
|
||||||
|
xmlNodePtr node = dom_token_list_get_element(intern);
|
||||||
|
if (php_libxml_decrement_node_ptr_ref(intern->dom.ptr) == 0) {
|
||||||
|
php_libxml_node_free_resource(node);
|
||||||
|
}
|
||||||
|
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
zend_hash_destroy(token_set);
|
||||||
|
efree(intern->cached_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dom_token_list_item_exists(dom_token_list_object *token_list, zend_long index)
|
||||||
|
{
|
||||||
|
dom_token_list_ensure_set_up_to_date(token_list);
|
||||||
|
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(token_list);
|
||||||
|
return index >= 0 && index < zend_hash_num_elements(token_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dom_token_list_item_read(dom_token_list_object *token_list, zval *retval, zend_long index)
|
||||||
|
{
|
||||||
|
dom_token_list_ensure_set_up_to_date(token_list);
|
||||||
|
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(token_list);
|
||||||
|
if (index >= 0 && index < zend_hash_num_elements(token_set)) {
|
||||||
|
HashPosition position;
|
||||||
|
zend_hash_internal_pointer_reset_ex(token_set, &position);
|
||||||
|
while (index > 0) {
|
||||||
|
zend_hash_move_forward_ex(token_set, &position);
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
zend_string *str_index;
|
||||||
|
zend_hash_get_current_key_ex(token_set, &str_index, NULL, &position);
|
||||||
|
ZVAL_STR_COPY(retval, str_index);
|
||||||
|
} else {
|
||||||
|
/* Not an out of bounds ValueError, but NULL, as according to spec.
|
||||||
|
* This design choice allows for constructs like `item(x) ?? ...`
|
||||||
|
*
|
||||||
|
* In particular:
|
||||||
|
* https://dom.spec.whatwg.org/#interface-domtokenlist states DOMTokenList implements iterable<DOMString>.
|
||||||
|
* From https://webidl.spec.whatwg.org/#idl-iterable:
|
||||||
|
* If a single type parameter is given,
|
||||||
|
* then the interface has a value iterator and provides values of the specified type.
|
||||||
|
* This applies, and reading the definition of value iterator means we should support indexed properties.
|
||||||
|
* From https://webidl.spec.whatwg.org/#dfn-support-indexed-properties:
|
||||||
|
* An interface that defines an indexed property getter is said to support indexed properties.
|
||||||
|
* And indexed property getter is defined here: https://webidl.spec.whatwg.org/#dfn-indexed-property-getter
|
||||||
|
* Down below in their note they give an example of how an out-of-bounds access evaluates to undefined,
|
||||||
|
* which would map to NULL for us.
|
||||||
|
* This would also be consistent with how out-of-bounds array accesses in PHP result in NULL. */
|
||||||
|
ZVAL_NULL(retval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adapted from spl_offset_convert_to_long */
|
||||||
|
static zend_long dom_token_list_offset_convert_to_long(zval *offset, bool *failed)
|
||||||
|
{
|
||||||
|
*failed = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (Z_TYPE_P(offset)) {
|
||||||
|
case IS_STRING: {
|
||||||
|
zend_ulong index;
|
||||||
|
if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), index)) {
|
||||||
|
return (zend_long) index;
|
||||||
|
}
|
||||||
|
ZEND_FALLTHROUGH;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
*failed = true;
|
||||||
|
return 0;
|
||||||
|
case IS_DOUBLE:
|
||||||
|
return zend_dval_to_lval_safe(Z_DVAL_P(offset));
|
||||||
|
case IS_LONG:
|
||||||
|
return Z_LVAL_P(offset);
|
||||||
|
case IS_FALSE:
|
||||||
|
return 0;
|
||||||
|
case IS_TRUE:
|
||||||
|
return 1;
|
||||||
|
case IS_REFERENCE:
|
||||||
|
offset = Z_REFVAL_P(offset);
|
||||||
|
break;
|
||||||
|
case IS_RESOURCE:
|
||||||
|
zend_use_resource_as_offset(offset);
|
||||||
|
return Z_RES_HANDLE_P(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zval *dom_token_list_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
|
||||||
|
{
|
||||||
|
if (!offset) {
|
||||||
|
zend_throw_error(NULL, "Cannot append to Dom\\TokenList");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool failed;
|
||||||
|
zend_long index = dom_token_list_offset_convert_to_long(offset, &failed);
|
||||||
|
if (UNEXPECTED(failed)) {
|
||||||
|
zend_illegal_container_offset(object->ce->name, offset, type);
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
dom_token_list_item_read(php_dom_token_list_from_obj(object), rv, index);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int dom_token_list_has_dimension(zend_object *object, zval *offset, int check_empty)
|
||||||
|
{
|
||||||
|
bool failed;
|
||||||
|
zend_long index = dom_token_list_offset_convert_to_long(offset, &failed);
|
||||||
|
if (UNEXPECTED(failed)) {
|
||||||
|
zend_illegal_container_offset(object->ce->name, offset, BP_VAR_IS);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
dom_token_list_object *token_list = php_dom_token_list_from_obj(object);
|
||||||
|
if (check_empty) {
|
||||||
|
/* Need to perform an actual read to have the correct empty() semantics. */
|
||||||
|
zval rv;
|
||||||
|
dom_token_list_item_read(token_list, &rv, index);
|
||||||
|
int is_true = zend_is_true(&rv);
|
||||||
|
zval_ptr_dtor_nogc(&rv);
|
||||||
|
return is_true;
|
||||||
|
} else {
|
||||||
|
return dom_token_list_item_exists(token_list, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-length */
|
||||||
|
zend_result dom_token_list_length_read(dom_object *obj, zval *retval)
|
||||||
|
{
|
||||||
|
dom_token_list_object *token_list = php_dom_token_list_from_dom_obj(obj);
|
||||||
|
dom_token_list_ensure_set_up_to_date(token_list);
|
||||||
|
ZVAL_LONG(retval, zend_hash_num_elements(TOKEN_LIST_GET_SET(token_list)));
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-value
|
||||||
|
* and https://dom.spec.whatwg.org/#concept-dtl-serialize */
|
||||||
|
zend_result dom_token_list_value_read(dom_object *obj, zval *retval)
|
||||||
|
{
|
||||||
|
bool should_free;
|
||||||
|
dom_token_list_object *intern = php_dom_token_list_from_dom_obj(obj);
|
||||||
|
const xmlAttr *attr = dom_token_list_get_attr(intern);
|
||||||
|
xmlChar *value = dom_token_list_get_class_value(attr, &should_free);
|
||||||
|
ZVAL_STRING(retval, value ? (const char *) value : "");
|
||||||
|
if (should_free) {
|
||||||
|
xmlFree(value);
|
||||||
|
}
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-value */
|
||||||
|
zend_result dom_token_list_value_write(dom_object *obj, zval *newval)
|
||||||
|
{
|
||||||
|
dom_token_list_object *intern = php_dom_token_list_from_dom_obj(obj);
|
||||||
|
if (UNEXPECTED(zend_str_has_nul_byte(Z_STR_P(newval)))) {
|
||||||
|
zend_value_error("Value must not contain any null bytes");
|
||||||
|
return FAILURE;
|
||||||
|
}
|
||||||
|
xmlSetNsProp(dom_token_list_get_element(intern), NULL, BAD_CAST "class", BAD_CAST Z_STRVAL_P(newval));
|
||||||
|
/* Note: we don't update the set here, the set is always lazily updated for performance reasons. */
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-item */
|
||||||
|
PHP_METHOD(Dom_TokenList, item)
|
||||||
|
{
|
||||||
|
zend_long index;
|
||||||
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||||
|
Z_PARAM_LONG(index)
|
||||||
|
ZEND_PARSE_PARAMETERS_END();
|
||||||
|
|
||||||
|
/* 1. If index is equal to or greater than this’s token set’s size, then return null. */
|
||||||
|
/* 2. Return this’s token set[index]. */
|
||||||
|
dom_token_list_item_read(TOKEN_LIST_GET_INTERNAL(), return_value, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-contains */
|
||||||
|
PHP_METHOD(Dom_TokenList, contains)
|
||||||
|
{
|
||||||
|
zend_string *token;
|
||||||
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||||
|
Z_PARAM_PATH_STR(token)
|
||||||
|
ZEND_PARSE_PARAMETERS_END();
|
||||||
|
|
||||||
|
dom_token_list_object *token_list = TOKEN_LIST_GET_INTERNAL();
|
||||||
|
dom_token_list_ensure_set_up_to_date(token_list);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(token_list);
|
||||||
|
RETURN_BOOL(zend_hash_exists(token_set, token));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Steps taken from the add, remove, toggle, replace methods. */
|
||||||
|
static bool dom_validate_token(const zend_string *str)
|
||||||
|
{
|
||||||
|
/* 1. If token is the empty string, then throw a "SyntaxError" DOMException. */
|
||||||
|
if (ZSTR_LEN(str) == 0) {
|
||||||
|
php_dom_throw_error_with_message(SYNTAX_ERR, "The empty string is not a valid token", true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. If token contains any ASCII whitespace, then throw an "InvalidCharacterError" DOMException. */
|
||||||
|
if (dom_contains_ascii_whitespace(ZSTR_VAL(str))) {
|
||||||
|
php_dom_throw_error_with_message(INVALID_CHARACTER_ERR, "The token must not contain any ASCII whitespace", true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dom_validate_tokens_varargs(const zval *args, uint32_t argc)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0; i < argc; i++) {
|
||||||
|
if (Z_TYPE(args[i]) != IS_STRING) {
|
||||||
|
zend_argument_type_error(i + 1, "must be of type string, %s given", zend_zval_value_name(&args[i]));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zend_str_has_nul_byte(Z_STR(args[i]))) {
|
||||||
|
zend_argument_value_error(i + 1, "must not contain any null bytes");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dom_validate_token(Z_STR(args[i]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-add */
|
||||||
|
PHP_METHOD(Dom_TokenList, add)
|
||||||
|
{
|
||||||
|
zval *args;
|
||||||
|
uint32_t argc;
|
||||||
|
ZEND_PARSE_PARAMETERS_START(0, -1)
|
||||||
|
Z_PARAM_VARIADIC('*', args, argc)
|
||||||
|
ZEND_PARSE_PARAMETERS_END();
|
||||||
|
|
||||||
|
/* 1. For each token in tokens (...) */
|
||||||
|
if (!dom_validate_tokens_varargs(args, argc)) {
|
||||||
|
RETURN_THROWS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. For each token in tokens, append token to this’s token set. */
|
||||||
|
dom_token_list_object *intern = TOKEN_LIST_GET_INTERNAL();
|
||||||
|
dom_token_list_ensure_set_up_to_date(intern);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
for (uint32_t i = 0; i < argc; i++) {
|
||||||
|
dom_add_token(token_set, Z_STR(args[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Run the update steps. */
|
||||||
|
dom_token_list_update(intern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-remove */
|
||||||
|
PHP_METHOD(Dom_TokenList, remove)
|
||||||
|
{
|
||||||
|
zval *args;
|
||||||
|
uint32_t argc;
|
||||||
|
ZEND_PARSE_PARAMETERS_START(0, -1)
|
||||||
|
Z_PARAM_VARIADIC('*', args, argc)
|
||||||
|
ZEND_PARSE_PARAMETERS_END();
|
||||||
|
|
||||||
|
/* 1. For each token in tokens (...) */
|
||||||
|
if (!dom_validate_tokens_varargs(args, argc)) {
|
||||||
|
RETURN_THROWS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. For each token in tokens, remove token from this’s token set. */
|
||||||
|
dom_token_list_object *intern = TOKEN_LIST_GET_INTERNAL();
|
||||||
|
dom_token_list_ensure_set_up_to_date(intern);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
for (uint32_t i = 0; i < argc; i++) {
|
||||||
|
zend_hash_del(token_set, Z_STR(args[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Run the update steps. */
|
||||||
|
dom_token_list_update(intern);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-toggle */
|
||||||
|
PHP_METHOD(Dom_TokenList, toggle)
|
||||||
|
{
|
||||||
|
zend_string *token;
|
||||||
|
bool force, force_not_given = true;
|
||||||
|
ZEND_PARSE_PARAMETERS_START(1, 2)
|
||||||
|
Z_PARAM_PATH_STR(token)
|
||||||
|
Z_PARAM_OPTIONAL
|
||||||
|
Z_PARAM_BOOL_OR_NULL(force, force_not_given)
|
||||||
|
ZEND_PARSE_PARAMETERS_END();
|
||||||
|
|
||||||
|
/* Steps 1 - 2 */
|
||||||
|
if (!dom_validate_token(token)) {
|
||||||
|
RETURN_THROWS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. If this’s token set[token] exists, then: */
|
||||||
|
dom_token_list_object *intern = TOKEN_LIST_GET_INTERNAL();
|
||||||
|
dom_token_list_ensure_set_up_to_date(intern);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
zval *found_token = zend_hash_find(token_set, token);
|
||||||
|
if (found_token != NULL) {
|
||||||
|
ZEND_ASSERT(XtOffsetOf(Bucket, val) == 0 && "the cast only works if this is true");
|
||||||
|
Bucket *bucket = (Bucket *) found_token;
|
||||||
|
|
||||||
|
/* 3.1. If force is either not given or is false, then remove token from this’s token set,
|
||||||
|
* run the update steps and return false. */
|
||||||
|
if (force_not_given || !force) {
|
||||||
|
zend_hash_del_bucket(token_set, bucket);
|
||||||
|
dom_token_list_update(intern);
|
||||||
|
RETURN_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3.2. Return true. */
|
||||||
|
RETURN_TRUE;
|
||||||
|
}
|
||||||
|
/* 4. Otherwise, if force not given or is true, append token to this’s token set,
|
||||||
|
* run the update steps, and return true. */
|
||||||
|
else if (force_not_given || force) {
|
||||||
|
dom_add_token(token_set, token);
|
||||||
|
dom_token_list_update(intern);
|
||||||
|
RETURN_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Return false. */
|
||||||
|
RETURN_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#dom-domtokenlist-replace */
|
||||||
|
PHP_METHOD(Dom_TokenList, replace)
|
||||||
|
{
|
||||||
|
zend_string *token, *new_token;
|
||||||
|
ZEND_PARSE_PARAMETERS_START(2, 2)
|
||||||
|
Z_PARAM_PATH_STR(token)
|
||||||
|
Z_PARAM_PATH_STR(new_token)
|
||||||
|
ZEND_PARSE_PARAMETERS_END();
|
||||||
|
|
||||||
|
/* Steps 1 - 2 */
|
||||||
|
if (!dom_validate_token(token) || !dom_validate_token(new_token)) {
|
||||||
|
RETURN_THROWS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. If this’s token set does not contain token, then return false. */
|
||||||
|
dom_token_list_object *intern = TOKEN_LIST_GET_INTERNAL();
|
||||||
|
dom_token_list_ensure_set_up_to_date(intern);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
zval *found_token = zend_hash_find(token_set, token);
|
||||||
|
if (found_token == NULL) {
|
||||||
|
RETURN_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Replace token in this’s token set with newToken. */
|
||||||
|
ZEND_ASSERT(XtOffsetOf(Bucket, val) == 0 && "the cast only works if this is true");
|
||||||
|
Bucket *bucket = (Bucket *) found_token;
|
||||||
|
if (zend_hash_set_bucket_key(token_set, bucket, new_token) == NULL) {
|
||||||
|
/* It already exists, remove token instead. */
|
||||||
|
zend_hash_del_bucket(token_set, bucket);
|
||||||
|
} else {
|
||||||
|
Z_STR(bucket->val) = new_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Run the update steps. */
|
||||||
|
dom_token_list_update(intern);
|
||||||
|
|
||||||
|
/* 6. Return true. */
|
||||||
|
RETURN_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://dom.spec.whatwg.org/#concept-domtokenlist-validation */
|
||||||
|
PHP_METHOD(Dom_TokenList, supports)
|
||||||
|
{
|
||||||
|
zend_string *token;
|
||||||
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||||
|
Z_PARAM_PATH_STR(token)
|
||||||
|
ZEND_PARSE_PARAMETERS_END();
|
||||||
|
|
||||||
|
/* The spec designers have designed the TokenList API with future usages in mind.
|
||||||
|
* But right now, this should just always throw a TypeError because the only user is classList, which
|
||||||
|
* does not define a supported token set. */
|
||||||
|
zend_throw_error(zend_ce_type_error, "Attribute \"class\" does not define any supported tokens");
|
||||||
|
}
|
||||||
|
|
||||||
|
PHP_METHOD(Dom_TokenList, count)
|
||||||
|
{
|
||||||
|
ZEND_PARSE_PARAMETERS_NONE();
|
||||||
|
dom_token_list_object *intern = TOKEN_LIST_GET_INTERNAL();
|
||||||
|
dom_token_list_ensure_set_up_to_date(intern);
|
||||||
|
RETURN_LONG(zend_hash_num_elements(TOKEN_LIST_GET_SET(intern)));
|
||||||
|
}
|
||||||
|
|
||||||
|
PHP_METHOD(Dom_TokenList, getIterator)
|
||||||
|
{
|
||||||
|
ZEND_PARSE_PARAMETERS_NONE();
|
||||||
|
zend_create_internal_iterator_zval(return_value, ZEND_THIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dom_token_list_it_dtor(zend_object_iterator *iter)
|
||||||
|
{
|
||||||
|
zval_ptr_dtor(&iter->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dom_token_list_it_rewind(zend_object_iterator *iter)
|
||||||
|
{
|
||||||
|
dom_token_list_it *iterator = (dom_token_list_it *) iter;
|
||||||
|
dom_token_list_object *object = Z_TOKEN_LIST_P(&iter->data);
|
||||||
|
zend_hash_internal_pointer_reset_ex(TOKEN_LIST_GET_SET(object), &iterator->pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static zend_result dom_token_list_it_valid(zend_object_iterator *iter)
|
||||||
|
{
|
||||||
|
dom_token_list_it *iterator = (dom_token_list_it *) iter;
|
||||||
|
dom_token_list_object *object = Z_TOKEN_LIST_P(&iter->data);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(object);
|
||||||
|
|
||||||
|
dom_token_list_ensure_set_up_to_date(object);
|
||||||
|
|
||||||
|
iterator->pos = zend_hash_get_current_pos_ex(token_set, iterator->pos);
|
||||||
|
|
||||||
|
return iterator->pos >= token_set->nNumUsed ? FAILURE : SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static zval *dom_token_list_it_get_current_data(zend_object_iterator *iter)
|
||||||
|
{
|
||||||
|
dom_token_list_it *iterator = (dom_token_list_it *) iter;
|
||||||
|
dom_token_list_object *object = Z_TOKEN_LIST_P(&iter->data);
|
||||||
|
dom_token_list_ensure_set_up_to_date(object);
|
||||||
|
/* Caller manages the refcount of the data. */
|
||||||
|
return zend_hash_get_current_data_ex(TOKEN_LIST_GET_SET(object), &iterator->pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dom_token_list_it_get_current_key(zend_object_iterator *iter, zval *key)
|
||||||
|
{
|
||||||
|
dom_token_list_it *iterator = (dom_token_list_it *) iter;
|
||||||
|
dom_token_list_object *object = Z_TOKEN_LIST_P(&iter->data);
|
||||||
|
|
||||||
|
dom_token_list_ensure_set_up_to_date(object);
|
||||||
|
|
||||||
|
if (UNEXPECTED(php_libxml_is_cache_tag_stale(&object->cache_tag, &iterator->cache_tag))) {
|
||||||
|
iter->index = 0;
|
||||||
|
HashPosition pos;
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(object);
|
||||||
|
zend_hash_internal_pointer_reset_ex(token_set, &pos);
|
||||||
|
while (pos != iterator->pos) {
|
||||||
|
iter->index++;
|
||||||
|
zend_hash_move_forward_ex(token_set, &pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ZVAL_LONG(key, iter->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dom_token_list_it_move_forward(zend_object_iterator *iter)
|
||||||
|
{
|
||||||
|
dom_token_list_it *iterator = (dom_token_list_it *) iter;
|
||||||
|
dom_token_list_object *object = Z_TOKEN_LIST_P(&iter->data);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(object);
|
||||||
|
|
||||||
|
dom_token_list_ensure_set_up_to_date(object);
|
||||||
|
|
||||||
|
HashPosition current = iterator->pos;
|
||||||
|
HashPosition validated = zend_hash_get_current_pos_ex(token_set, iterator->pos);
|
||||||
|
|
||||||
|
/* Check if already moved due to user operations, if so don't move again but reset to the first valid position,
|
||||||
|
* otherwise move one forward. */
|
||||||
|
if (validated != current) {
|
||||||
|
iterator->pos = validated;
|
||||||
|
} else {
|
||||||
|
zend_hash_move_forward_ex(token_set, &iterator->pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const zend_object_iterator_funcs dom_token_list_it_funcs = {
|
||||||
|
dom_token_list_it_dtor,
|
||||||
|
dom_token_list_it_valid,
|
||||||
|
dom_token_list_it_get_current_data,
|
||||||
|
dom_token_list_it_get_current_key,
|
||||||
|
dom_token_list_it_move_forward,
|
||||||
|
dom_token_list_it_rewind,
|
||||||
|
NULL, /* invalidate_current */
|
||||||
|
NULL, /* get_gc */
|
||||||
|
};
|
||||||
|
|
||||||
|
zend_object_iterator *dom_token_list_get_iterator(zend_class_entry *ce, zval *object, int by_ref)
|
||||||
|
{
|
||||||
|
if (by_ref) {
|
||||||
|
zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dom_token_list_object *intern = Z_TOKEN_LIST_P(object);
|
||||||
|
dom_token_list_ensure_set_up_to_date(intern);
|
||||||
|
HashTable *token_set = TOKEN_LIST_GET_SET(intern);
|
||||||
|
|
||||||
|
dom_token_list_it *iterator = emalloc(sizeof(*iterator));
|
||||||
|
zend_iterator_init(&iterator->it);
|
||||||
|
zend_hash_internal_pointer_reset_ex(token_set, &iterator->pos);
|
||||||
|
ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object));
|
||||||
|
|
||||||
|
iterator->it.funcs = &dom_token_list_it_funcs;
|
||||||
|
iterator->cache_tag = intern->cache_tag;
|
||||||
|
|
||||||
|
return &iterator->it;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
44
ext/dom/token_list.h
Normal file
44
ext/dom/token_list.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
+----------------------------------------------------------------------+
|
||||||
|
| 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> |
|
||||||
|
+----------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TOKEN_LIST_H
|
||||||
|
#define TOKEN_LIST_H
|
||||||
|
|
||||||
|
typedef struct dom_token_list_object {
|
||||||
|
HashTable token_set;
|
||||||
|
/* Used to check if the token set is up to date. */
|
||||||
|
char *cached_string;
|
||||||
|
php_libxml_cache_tag cache_tag;
|
||||||
|
dom_object dom;
|
||||||
|
} dom_token_list_object;
|
||||||
|
|
||||||
|
static inline dom_token_list_object *php_dom_token_list_from_obj(zend_object *obj)
|
||||||
|
{
|
||||||
|
return (dom_token_list_object *)((char *) obj - XtOffsetOf(dom_token_list_object, dom.std));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline dom_token_list_object *php_dom_token_list_from_dom_obj(dom_object *obj)
|
||||||
|
{
|
||||||
|
return (dom_token_list_object *)((char *) obj - XtOffsetOf(dom_token_list_object, dom));
|
||||||
|
}
|
||||||
|
|
||||||
|
void dom_token_list_ctor(dom_token_list_object *intern, dom_object *element_obj);
|
||||||
|
void dom_token_list_free_obj(zend_object *object);
|
||||||
|
zval *dom_token_list_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
|
||||||
|
int dom_token_list_has_dimension(zend_object *object, zval *offset, int check_empty);
|
||||||
|
zend_object_iterator *dom_token_list_get_iterator(zend_class_entry *ce, zval *object, int by_ref);
|
||||||
|
|
||||||
|
#endif
|
|
@ -1313,26 +1313,32 @@ PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object,
|
||||||
return ret_refcount;
|
return ret_refcount;
|
||||||
}
|
}
|
||||||
|
|
||||||
PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object)
|
PHP_LIBXML_API int php_libxml_decrement_node_ptr_ref(php_libxml_node_ptr *ptr)
|
||||||
{
|
{
|
||||||
int ret_refcount = -1;
|
ZEND_ASSERT(ptr != NULL);
|
||||||
php_libxml_node_ptr *obj_node;
|
|
||||||
|
|
||||||
if (object != NULL && object->node != NULL) {
|
int ret_refcount = --ptr->refcount;
|
||||||
obj_node = (php_libxml_node_ptr *) object->node;
|
|
||||||
ret_refcount = --obj_node->refcount;
|
|
||||||
if (ret_refcount == 0) {
|
if (ret_refcount == 0) {
|
||||||
if (obj_node->node != NULL) {
|
if (ptr->node != NULL) {
|
||||||
obj_node->node->_private = NULL;
|
ptr->node->_private = NULL;
|
||||||
}
|
|
||||||
efree(obj_node);
|
|
||||||
}
|
}
|
||||||
|
if (ptr->_private) {
|
||||||
|
php_libxml_node_object *object = (php_libxml_node_object *) ptr->_private;
|
||||||
object->node = NULL;
|
object->node = NULL;
|
||||||
}
|
}
|
||||||
|
efree(ptr);
|
||||||
|
}
|
||||||
return ret_refcount;
|
return ret_refcount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object)
|
||||||
|
{
|
||||||
|
if (object != NULL && object->node != NULL) {
|
||||||
|
return php_libxml_decrement_node_ptr_ref(object->node);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp)
|
PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp)
|
||||||
{
|
{
|
||||||
int ret_refcount = -1;
|
int ret_refcount = -1;
|
||||||
|
|
|
@ -58,6 +58,9 @@ typedef struct _libxml_doc_props {
|
||||||
bool recover;
|
bool recover;
|
||||||
} libxml_doc_props;
|
} libxml_doc_props;
|
||||||
|
|
||||||
|
/* Modification tracking: when the object changes, we increment its counter.
|
||||||
|
* When this counter no longer matches the counter at the time of caching,
|
||||||
|
* we know that the object has changed and we have to update the cache. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
size_t modification_nr;
|
size_t modification_nr;
|
||||||
} php_libxml_cache_tag;
|
} php_libxml_cache_tag;
|
||||||
|
@ -129,23 +132,39 @@ static inline php_libxml_node_object *php_libxml_node_fetch_object(zend_object *
|
||||||
return (php_libxml_node_object *)((char*)(obj) - obj->handlers->offset);
|
return (php_libxml_node_object *)((char*)(obj) - obj->handlers->offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
static zend_always_inline void php_libxml_invalidate_node_list_cache(php_libxml_ref_obj *doc_ptr)
|
static zend_always_inline void php_libxml_invalidate_cache_tag(php_libxml_cache_tag *cache_tag)
|
||||||
{
|
{
|
||||||
if (!doc_ptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#if SIZEOF_SIZE_T == 8
|
#if SIZEOF_SIZE_T == 8
|
||||||
/* If one operation happens every nanosecond, then it would still require 584 years to overflow
|
/* If one operation happens every nanosecond, then it would still require 584 years to overflow
|
||||||
* the counter. So we'll just assume this never happens. */
|
* the counter. So we'll just assume this never happens. */
|
||||||
doc_ptr->cache_tag.modification_nr++;
|
cache_tag->modification_nr++;
|
||||||
#else
|
#else
|
||||||
size_t new_modification_nr = doc_ptr->cache_tag.modification_nr + 1;
|
size_t new_modification_nr = cache_tag->modification_nr + 1;
|
||||||
if (EXPECTED(new_modification_nr > 0)) { /* unsigned overflow; checking after addition results in one less instruction */
|
if (EXPECTED(new_modification_nr > 0)) { /* unsigned overflow; checking after addition results in one less instruction */
|
||||||
doc_ptr->cache_tag.modification_nr = new_modification_nr;
|
cache_tag->modification_nr = new_modification_nr;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static zend_always_inline bool php_libxml_is_cache_tag_stale(const php_libxml_cache_tag *object_tag, const php_libxml_cache_tag *cache_tag)
|
||||||
|
{
|
||||||
|
ZEND_ASSERT(object_tag != NULL);
|
||||||
|
ZEND_ASSERT(cache_tag != NULL);
|
||||||
|
/* See overflow comment in php_libxml_invalidate_node_list_cache(). */
|
||||||
|
#if SIZEOF_SIZE_T == 8
|
||||||
|
return cache_tag->modification_nr != object_tag->modification_nr;
|
||||||
|
#else
|
||||||
|
return cache_tag->modification_nr != object_tag->modification_nr || UNEXPECTED(object_tag->modification_nr == SIZE_MAX);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static zend_always_inline void php_libxml_invalidate_node_list_cache(php_libxml_ref_obj *doc_ptr)
|
||||||
|
{
|
||||||
|
if (doc_ptr) {
|
||||||
|
php_libxml_invalidate_cache_tag(&doc_ptr->cache_tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static zend_always_inline void php_libxml_invalidate_node_list_cache_from_doc(xmlDocPtr docp)
|
static zend_always_inline void php_libxml_invalidate_node_list_cache_from_doc(xmlDocPtr docp)
|
||||||
{
|
{
|
||||||
if (docp && docp->_private) { /* docp is NULL for detached nodes */
|
if (docp && docp->_private) { /* docp is NULL for detached nodes */
|
||||||
|
@ -169,6 +188,7 @@ typedef enum {
|
||||||
|
|
||||||
PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, xmlNodePtr node, void *private_data);
|
PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, xmlNodePtr node, void *private_data);
|
||||||
PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object);
|
PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object);
|
||||||
|
PHP_LIBXML_API int php_libxml_decrement_node_ptr_ref(php_libxml_node_ptr *ptr);
|
||||||
PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp);
|
PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp);
|
||||||
PHP_LIBXML_API int php_libxml_decrement_doc_ref_directly(php_libxml_ref_obj *document);
|
PHP_LIBXML_API int php_libxml_decrement_doc_ref_directly(php_libxml_ref_obj *document);
|
||||||
PHP_LIBXML_API int php_libxml_decrement_doc_ref(php_libxml_node_object *object);
|
PHP_LIBXML_API int php_libxml_decrement_doc_ref(php_libxml_node_object *object);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue