mirror of
https://github.com/php/php-src.git
synced 2024-11-27 11:53:33 +08:00
Fix bug #79701: getElementById does not correctly work with duplicate definitions
This is a long standing bug: IDs aren't properly tracked causing either outdated or plain incorrect results from getElementById. This PR implements a pragmatic solution in which we still try to use the ID lookup table to a degree, but only as a performance boost not as a "single source of truth". Full details are explained in the getElementById code. Closes GH-14349.
This commit is contained in:
parent
5fe799a4c6
commit
8dc2391bae
2
NEWS
2
NEWS
@ -45,6 +45,8 @@ PHP NEWS
|
||||
. Implemented "Improve callbacks in ext/dom and ext/xsl" RFC. (nielsdos)
|
||||
. Added DOMXPath::quote() static method. (divinity76)
|
||||
. Implemented opt-in ext/dom spec compliance RFC. (nielsdos)
|
||||
. Fixed bug #79701 (getElementById does not correctly work with duplicate
|
||||
definitions). (nielsdos)
|
||||
|
||||
- Fileinfo:
|
||||
. Update to libmagic 5.45. (nielsdos)
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
#include "php_dom.h"
|
||||
#include "dom_properties.h"
|
||||
#include "internal_helpers.h"
|
||||
|
||||
/*
|
||||
* class DOMAttr extends DOMNode
|
||||
@ -106,6 +107,16 @@ zend_result dom_attr_specified_read(dom_object *obj, zval *retval)
|
||||
|
||||
/* }}} */
|
||||
|
||||
void dom_attr_value_will_change(dom_object *obj, xmlAttrPtr attrp)
|
||||
{
|
||||
if (attrp->atype == XML_ATTRIBUTE_ID) {
|
||||
xmlRemoveID(attrp->doc, attrp);
|
||||
attrp->atype = XML_ATTRIBUTE_ID;
|
||||
}
|
||||
|
||||
dom_mark_ids_modified(obj->document);
|
||||
}
|
||||
|
||||
/* {{{ value string
|
||||
readonly=no
|
||||
URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-221662474
|
||||
@ -122,6 +133,8 @@ zend_result dom_attr_value_write(dom_object *obj, zval *newval)
|
||||
{
|
||||
DOM_PROP_NODE(xmlAttrPtr, attrp, obj);
|
||||
|
||||
dom_attr_value_will_change(obj, attrp);
|
||||
|
||||
/* Typed property, this is already a string */
|
||||
ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING);
|
||||
zend_string *str = Z_STR_P(newval);
|
||||
|
@ -1028,34 +1028,47 @@ Since: DOM Level 2
|
||||
*/
|
||||
PHP_METHOD(DOMDocument, getElementById)
|
||||
{
|
||||
zval *id;
|
||||
xmlDocPtr docp;
|
||||
xmlAttrPtr attrp;
|
||||
size_t idname_len;
|
||||
dom_object *intern;
|
||||
char *idname;
|
||||
|
||||
id = ZEND_THIS;
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &idname, &idname_len) == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||
Z_PARAM_STRING(idname, idname_len)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
|
||||
DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern);
|
||||
|
||||
attrp = xmlGetID(docp, BAD_CAST idname);
|
||||
|
||||
/* From the moment an ID is created, libxml2's behaviour is to cache that element, even
|
||||
* if that element is not yet attached to the document. Similarly, only upon destruction of
|
||||
* the element the ID is actually removed by libxml2. Since libxml2 has such behaviour deeply
|
||||
* ingrained in the library, and uses the cache for various purposes, it seems like a bad
|
||||
* idea and lost cause to fight it. Instead, we'll simply walk the tree upwards to check
|
||||
* if the node is attached to the document. */
|
||||
if (attrp && attrp->parent && php_dom_is_node_connected(attrp->parent)) {
|
||||
DOM_RET_OBJ((xmlNodePtr) attrp->parent, intern);
|
||||
/* If the document has not been manipulated yet, the ID cache will be in sync and we can trust its result.
|
||||
* This check mainly exists because a lot of times people just query a document without modifying it,
|
||||
* and we can allow quick access to IDs in that case. */
|
||||
if (!dom_is_document_cache_modified_since_parsing(intern->document)) {
|
||||
const xmlAttr *attrp = xmlGetID(docp, BAD_CAST idname);
|
||||
if (attrp && attrp->parent) {
|
||||
DOM_RET_OBJ(attrp->parent, intern);
|
||||
}
|
||||
} else {
|
||||
RETVAL_NULL();
|
||||
}
|
||||
/* From the moment an ID is created, libxml2's behaviour is to cache that element, even
|
||||
* if that element is not yet attached to the document. Similarly, only upon destruction of
|
||||
* the element the ID is actually removed by libxml2. Since libxml2 has such behaviour deeply
|
||||
* ingrained in the library, and uses the cache for various purposes, it seems like a bad
|
||||
* idea and lost cause to fight it. */
|
||||
|
||||
const xmlNode *base = (const xmlNode *) docp;
|
||||
const xmlNode *node = base->children;
|
||||
while (node != NULL) {
|
||||
if (node->type == XML_ELEMENT_NODE) {
|
||||
for (const xmlAttr *attr = node->properties; attr != NULL; attr = attr->next) {
|
||||
if (attr->atype == XML_ATTRIBUTE_ID && dom_compare_value(attr, BAD_CAST idname)) {
|
||||
DOM_RET_OBJ((xmlNodePtr) node, intern);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node = php_dom_next_in_tree_order(node, base);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* }}} end dom_document_get_element_by_id */
|
||||
|
||||
|
@ -183,7 +183,7 @@ zend_result dom_element_id_read(dom_object *obj, zval *retval)
|
||||
return dom_element_reflected_attribute_read(obj, retval, "id");
|
||||
}
|
||||
|
||||
static void php_set_attribute_id(xmlAttrPtr attrp, bool is_id);
|
||||
static void php_set_attribute_id(xmlAttrPtr attrp, bool is_id, php_libxml_ref_obj *document);
|
||||
|
||||
zend_result dom_element_id_write(dom_object *obj, zval *newval)
|
||||
{
|
||||
@ -191,7 +191,7 @@ zend_result dom_element_id_write(dom_object *obj, zval *newval)
|
||||
if (!attr) {
|
||||
return FAILURE;
|
||||
}
|
||||
php_set_attribute_id(attr, true);
|
||||
php_set_attribute_id(attr, true, obj->document);
|
||||
return SUCCESS;
|
||||
}
|
||||
/* }}} */
|
||||
@ -352,6 +352,16 @@ static xmlNodePtr dom_create_attribute(xmlNodePtr nodep, const char *name, const
|
||||
}
|
||||
}
|
||||
|
||||
static void dom_check_register_attribute_id(xmlAttrPtr attr, php_libxml_ref_obj *document)
|
||||
{
|
||||
dom_mark_ids_modified(document);
|
||||
|
||||
if (attr->atype != XML_ATTRIBUTE_ID && attr->doc->type == XML_HTML_DOCUMENT_NODE && attr->ns == NULL && xmlStrEqual(attr->name, BAD_CAST "id")) {
|
||||
/* To respect XML's ID behaviour, we only do this registration for HTML documents. */
|
||||
attr->atype = XML_ATTRIBUTE_ID;
|
||||
}
|
||||
}
|
||||
|
||||
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68F082
|
||||
Modern spec URL: https://dom.spec.whatwg.org/#dom-element-setattribute
|
||||
Since:
|
||||
@ -360,7 +370,6 @@ PHP_METHOD(DOMElement, setAttribute)
|
||||
{
|
||||
zval *id;
|
||||
xmlNode *nodep;
|
||||
xmlNodePtr attr = NULL;
|
||||
int name_valid;
|
||||
size_t name_len, value_len;
|
||||
dom_object *intern;
|
||||
@ -394,23 +403,28 @@ PHP_METHOD(DOMElement, setAttribute)
|
||||
}
|
||||
|
||||
/* Can't use xmlSetNsProp unconditionally here because that doesn't take into account the qualified name matching... */
|
||||
attr = (xmlNodePtr) php_dom_get_attribute_node(nodep, BAD_CAST name, name_len);
|
||||
xmlAttrPtr attr = php_dom_get_attribute_node(nodep, BAD_CAST name, name_len);
|
||||
if (attr != NULL) {
|
||||
dom_remove_all_children(attr);
|
||||
dom_attr_value_will_change(intern, attr);
|
||||
dom_remove_all_children((xmlNodePtr) attr);
|
||||
xmlNodePtr node = xmlNewDocText(attr->doc, BAD_CAST value);
|
||||
xmlAddChild(attr, node);
|
||||
xmlAddChild((xmlNodePtr) attr, node);
|
||||
} else {
|
||||
attr = (xmlNodePtr) xmlSetNsProp(nodep, NULL, name_processed, BAD_CAST value);
|
||||
attr = xmlSetNsProp(nodep, NULL, name_processed, BAD_CAST value);
|
||||
if (EXPECTED(attr != NULL)) {
|
||||
dom_check_register_attribute_id(attr, intern->document);
|
||||
}
|
||||
}
|
||||
|
||||
if (name_processed != BAD_CAST name) {
|
||||
efree(name_processed);
|
||||
}
|
||||
} else {
|
||||
attr = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
|
||||
xmlNodePtr attr = dom_get_attribute_or_nsdecl(intern, nodep, BAD_CAST name, name_len);
|
||||
if (attr != NULL) {
|
||||
switch (attr->type) {
|
||||
case XML_ATTRIBUTE_NODE:
|
||||
dom_attr_value_will_change(intern, (xmlAttrPtr) attr);
|
||||
node_list_unlink(attr->children);
|
||||
break;
|
||||
case XML_NAMESPACE_DECL:
|
||||
@ -693,7 +707,10 @@ static void dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAMETERS,
|
||||
|
||||
xmlAddChild(nodep, (xmlNodePtr) attrp);
|
||||
if (!modern) {
|
||||
dom_mark_ids_modified(intern->document);
|
||||
php_dom_reconcile_attribute_namespace_after_insertion(attrp);
|
||||
} else {
|
||||
dom_check_register_attribute_id(attrp, intern->document);
|
||||
}
|
||||
|
||||
/* Returns old property if removed otherwise NULL */
|
||||
@ -870,6 +887,8 @@ static void dom_set_attribute_ns_legacy(dom_object *intern, xmlNodePtr elemp, ch
|
||||
int errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len);
|
||||
|
||||
if (errorcode == 0) {
|
||||
dom_mark_ids_modified(intern->document);
|
||||
|
||||
if (uri_len > 0) {
|
||||
nodep = (xmlNodePtr) xmlHasNsProp(elemp, BAD_CAST localname, BAD_CAST uri);
|
||||
if (nodep != NULL && nodep->type != XML_ATTRIBUTE_DECL) {
|
||||
@ -958,8 +977,11 @@ static void dom_set_attribute_ns_modern(dom_object *intern, xmlNodePtr elemp, ze
|
||||
if (errorcode == 0) {
|
||||
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
|
||||
xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
|
||||
if (UNEXPECTED(xmlSetNsProp(elemp, ns, localname, BAD_CAST value) == NULL)) {
|
||||
xmlAttrPtr attr = xmlSetNsProp(elemp, ns, localname, BAD_CAST value);
|
||||
if (UNEXPECTED(attr == NULL)) {
|
||||
php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
|
||||
} else {
|
||||
dom_check_register_attribute_id(attr, intern->document);
|
||||
}
|
||||
} else {
|
||||
php_dom_throw_error(errorcode, /* strict */ true);
|
||||
@ -1270,20 +1292,16 @@ PHP_METHOD(DOMElement, hasAttributeNS)
|
||||
}
|
||||
/* }}} end dom_element_has_attribute_ns */
|
||||
|
||||
static void php_set_attribute_id(xmlAttrPtr attrp, bool is_id) /* {{{ */
|
||||
static void php_set_attribute_id(xmlAttrPtr attrp, bool is_id, php_libxml_ref_obj *document) /* {{{ */
|
||||
{
|
||||
if (is_id == 1 && attrp->atype != XML_ATTRIBUTE_ID) {
|
||||
xmlChar *id_val;
|
||||
|
||||
id_val = xmlNodeListGetString(attrp->doc, attrp->children, 1);
|
||||
if (id_val != NULL) {
|
||||
xmlAddID(NULL, attrp->doc, id_val, attrp);
|
||||
xmlFree(id_val);
|
||||
}
|
||||
} else if (is_id == 0 && attrp->atype == XML_ATTRIBUTE_ID) {
|
||||
if (is_id && attrp->atype != XML_ATTRIBUTE_ID) {
|
||||
attrp->atype = XML_ATTRIBUTE_ID;
|
||||
} else if (!is_id && attrp->atype == XML_ATTRIBUTE_ID) {
|
||||
xmlRemoveID(attrp->doc, attrp);
|
||||
attrp->atype = 0;
|
||||
}
|
||||
|
||||
dom_mark_ids_modified(document);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@ -1311,7 +1329,7 @@ PHP_METHOD(DOMElement, setIdAttribute)
|
||||
if (attrp == NULL || attrp->type == XML_ATTRIBUTE_DECL) {
|
||||
php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(intern->document));
|
||||
} else {
|
||||
php_set_attribute_id(attrp, is_id);
|
||||
php_set_attribute_id(attrp, is_id, intern->document);
|
||||
}
|
||||
}
|
||||
/* }}} end dom_element_set_id_attribute */
|
||||
@ -1340,7 +1358,7 @@ PHP_METHOD(DOMElement, setIdAttributeNS)
|
||||
if (attrp == NULL || attrp->type == XML_ATTRIBUTE_DECL) {
|
||||
php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(intern->document));
|
||||
} else {
|
||||
php_set_attribute_id(attrp, is_id);
|
||||
php_set_attribute_id(attrp, is_id, intern->document);
|
||||
}
|
||||
}
|
||||
/* }}} end dom_element_set_id_attribute_ns */
|
||||
@ -1367,7 +1385,7 @@ static void dom_element_set_id_attribute_node(INTERNAL_FUNCTION_PARAMETERS, zend
|
||||
if (attrp->parent != nodep) {
|
||||
php_dom_throw_error(NOT_FOUND_ERR, dom_get_strict_error(intern->document));
|
||||
} else {
|
||||
php_set_attribute_id(attrp, is_id);
|
||||
php_set_attribute_id(attrp, is_id, intern->document);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,4 +63,31 @@ DOM_DEF_GET_CE_FUNC(domimplementation)
|
||||
|
||||
#endif
|
||||
|
||||
static zend_always_inline size_t dom_minimum_modification_nr_since_parsing(php_libxml_ref_obj *doc_ptr)
|
||||
{
|
||||
/* For old-style DOM, we need a "new DOMDocument" + a load, so the minimum modification nr is 2.
|
||||
* For new-style DOM, we need only to call a named constructor, so the minimum modification nr is 1. */
|
||||
return doc_ptr->class_type == PHP_LIBXML_CLASS_MODERN ? 1 : 2;
|
||||
}
|
||||
|
||||
static zend_always_inline void dom_mark_document_cache_as_modified_since_parsing(php_libxml_ref_obj *doc_ptr)
|
||||
{
|
||||
if (doc_ptr) {
|
||||
doc_ptr->cache_tag.modification_nr = MAX(dom_minimum_modification_nr_since_parsing(doc_ptr) + 1,
|
||||
doc_ptr->cache_tag.modification_nr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Marks the ID cache as potentially stale */
|
||||
static zend_always_inline void dom_mark_ids_modified(php_libxml_ref_obj *doc_ptr)
|
||||
{
|
||||
/* Although this is currently a wrapper function, it's best to abstract the actual implementation away. */
|
||||
dom_mark_document_cache_as_modified_since_parsing(doc_ptr);
|
||||
}
|
||||
|
||||
static zend_always_inline bool dom_is_document_cache_modified_since_parsing(php_libxml_ref_obj *doc_ptr)
|
||||
{
|
||||
return !doc_ptr || doc_ptr->cache_tag.modification_nr > dom_minimum_modification_nr_since_parsing(doc_ptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -185,6 +185,7 @@ zend_result dom_node_node_value_write(dom_object *obj, zval *newval)
|
||||
/* Access to Element node is implemented as a convenience method */
|
||||
switch (nodep->type) {
|
||||
case XML_ATTRIBUTE_NODE:
|
||||
dom_attr_value_will_change(obj, (xmlAttrPtr) nodep);
|
||||
if (php_dom_follow_spec_intern(obj)) {
|
||||
dom_remove_all_children(nodep);
|
||||
xmlAddChild(nodep, xmlNewTextLen(BAD_CAST ZSTR_VAL(str), ZSTR_LEN(str)));
|
||||
|
@ -174,6 +174,7 @@ xmlDocPtr php_dom_create_html_doc(void);
|
||||
xmlEntityPtr dom_entity_reference_fetch_and_sync_declaration(xmlNodePtr reference);
|
||||
void dom_set_xml_class(php_libxml_ref_obj *document);
|
||||
bool dom_compare_value(const xmlAttr *attr, const xmlChar *value);
|
||||
void dom_attr_value_will_change(dom_object *obj, xmlAttrPtr attrp);
|
||||
|
||||
typedef enum {
|
||||
DOM_LOAD_STRING = 0,
|
||||
|
53
ext/dom/tests/bug79701/id_property.phpt
Normal file
53
ext/dom/tests/bug79701/id_property.phpt
Normal file
@ -0,0 +1,53 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - id property variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
$dom = Dom\XMLDocument::createFromString(<<<XML
|
||||
<root>
|
||||
<test1/>
|
||||
<test2/>
|
||||
</root>
|
||||
XML);
|
||||
|
||||
$test1 = $dom->documentElement->firstElementChild;
|
||||
$test2 = $test1->nextElementSibling;
|
||||
|
||||
echo "--- After parsing ---\n";
|
||||
var_dump($dom->getElementById("x")?->nodeName);
|
||||
|
||||
echo "--- After setting test2 ---\n";
|
||||
$test2->id = "x";
|
||||
var_dump($dom->getElementById("x")?->nodeName);
|
||||
echo "--- After setting test1 ---\n";
|
||||
$test1->id = "x";
|
||||
var_dump($dom->getElementById("x")?->nodeName);
|
||||
echo "--- After resetting test1 ---\n";
|
||||
$test1->id = "y";
|
||||
var_dump($dom->getElementById("x")?->nodeName);
|
||||
echo "--- After resetting test2 ---\n";
|
||||
$test2->id = "y";
|
||||
var_dump($dom->getElementById("x")?->nodeName);
|
||||
echo "--- After resetting test1 ---\n";
|
||||
$test1->id = "x";
|
||||
var_dump($dom->getElementById("x")?->nodeName);
|
||||
echo "--- After calling setIdAttribute with false on test1 ---\n";
|
||||
$test1->setIdAttribute("id", false);
|
||||
var_dump($dom->getElementById("x")?->nodeName);
|
||||
?>
|
||||
--EXPECT--
|
||||
--- After parsing ---
|
||||
NULL
|
||||
--- After setting test2 ---
|
||||
string(5) "test2"
|
||||
--- After setting test1 ---
|
||||
string(5) "test1"
|
||||
--- After resetting test1 ---
|
||||
string(5) "test2"
|
||||
--- After resetting test2 ---
|
||||
NULL
|
||||
--- After resetting test1 ---
|
||||
string(5) "test1"
|
||||
--- After calling setIdAttribute with false on test1 ---
|
||||
NULL
|
18
ext/dom/tests/bug79701/node.phpt
Normal file
18
ext/dom/tests/bug79701/node.phpt
Normal file
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - attribute node variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
$dom = Dom\HTMLDocument::createEmpty();
|
||||
$element = $dom->createElement('foo');
|
||||
$dom->append($element);
|
||||
$attr = $dom->createAttribute('id');
|
||||
$attr->value = 'test';
|
||||
var_dump($dom->getElementById('test')?->nodeName);
|
||||
$element->setAttributeNode($attr);
|
||||
var_dump($dom->getElementById('test')?->nodeName);
|
||||
?>
|
||||
--EXPECT--
|
||||
NULL
|
||||
string(3) "FOO"
|
29
ext/dom/tests/bug79701/prepend.phpt
Normal file
29
ext/dom/tests/bug79701/prepend.phpt
Normal file
@ -0,0 +1,29 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - prepending variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
$dom = new DOMDocument();
|
||||
$root = $dom->createElement('html');
|
||||
$dom->appendChild($root);
|
||||
|
||||
$el1 = $dom->createElement('p1');
|
||||
$el1->setAttribute('id', 'foo');
|
||||
$el1->setIdAttribute('id', true);
|
||||
|
||||
$root->appendChild($el1);
|
||||
|
||||
$el2 = $dom->createElement('p2');
|
||||
$el2->setAttribute('id', 'foo');
|
||||
$el2->setIdAttribute('id', true);
|
||||
|
||||
$root->appendChild($el2);
|
||||
unset($el1, $el2);
|
||||
|
||||
$root->removeChild($dom->getElementById('foo'));
|
||||
|
||||
var_dump($dom->getElementById('foo')?->nodeName);
|
||||
?>
|
||||
--EXPECT--
|
||||
string(2) "p2"
|
21
ext/dom/tests/bug79701/remove_attribute.phpt
Normal file
21
ext/dom/tests/bug79701/remove_attribute.phpt
Normal file
@ -0,0 +1,21 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - remove attribute variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
$dom = Dom\XMLDocument::createFromString(<<<XML
|
||||
<root>
|
||||
<test1 xml:id="x"/>
|
||||
<test2 xml:id="x"/>
|
||||
</root>
|
||||
XML);
|
||||
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
$dom->getElementById('x')->removeAttribute('xml:id');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
?>
|
||||
--EXPECTF--
|
||||
Warning: Dom\XMLDocument::createFromString(): ID x already defined in Entity, line: 3 in %s on line %d
|
||||
string(5) "test1"
|
||||
NULL
|
45
ext/dom/tests/bug79701/set_attr_value.phpt
Normal file
45
ext/dom/tests/bug79701/set_attr_value.phpt
Normal file
@ -0,0 +1,45 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - nodeValue / value variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
foreach (["value", "nodeValue"] as $property) {
|
||||
echo "--- Testing property \$$property ---\n";
|
||||
$dom = Dom\XMLDocument::createFromString(<<<XML
|
||||
<root>
|
||||
<test1 xml:id="x"/>
|
||||
<test2 xml:id="y"/>
|
||||
</root>
|
||||
XML);
|
||||
|
||||
$test1 = $dom->getElementById('x');
|
||||
$test2 = $dom->getElementById('y');
|
||||
|
||||
var_dump($test1?->nodeName);
|
||||
var_dump($test2?->nodeName);
|
||||
|
||||
$test1->getAttributeNode('xml:id')->$property = "y";
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
$test2->getAttributeNode('xml:id')->$property = "x";
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
--- Testing property $value ---
|
||||
string(5) "test1"
|
||||
string(5) "test2"
|
||||
NULL
|
||||
string(5) "test1"
|
||||
string(5) "test2"
|
||||
string(5) "test1"
|
||||
--- Testing property $nodeValue ---
|
||||
string(5) "test1"
|
||||
string(5) "test2"
|
||||
NULL
|
||||
string(5) "test1"
|
||||
string(5) "test2"
|
||||
string(5) "test1"
|
82
ext/dom/tests/bug79701/set_attribute_html.phpt
Normal file
82
ext/dom/tests/bug79701/set_attribute_html.phpt
Normal file
@ -0,0 +1,82 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - set attribute in html variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
foreach (["xml:id", "ID"] as $name) {
|
||||
$dom = Dom\HTMLDocument::createFromString(<<<HTML
|
||||
<p>
|
||||
<em id="x">1</em>
|
||||
<strong id="y">2</strong>
|
||||
</p>
|
||||
HTML, LIBXML_NOERROR);
|
||||
|
||||
echo "\n=== Attribute name $name ===\n\n";
|
||||
|
||||
$test1 = $dom->getElementById('x');
|
||||
$test2 = $dom->getElementById('y');
|
||||
|
||||
echo "--- After resetting em's id ---\n";
|
||||
|
||||
$test1->setAttribute($name, 'y');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting strong's id ---\n";
|
||||
|
||||
$test2->setAttribute($name, 'x');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting em's id ---\n";
|
||||
|
||||
$test1->setAttribute($name, 'z');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting strong's id ---\n";
|
||||
|
||||
$test2->setAttribute($name, 'z');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- Get id z ---\n";
|
||||
|
||||
var_dump($dom->getElementById('z')?->nodeName);
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
=== Attribute name xml:id ===
|
||||
|
||||
--- After resetting em's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- After resetting strong's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- After resetting em's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- After resetting strong's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- Get id z ---
|
||||
NULL
|
||||
|
||||
=== Attribute name ID ===
|
||||
|
||||
--- After resetting em's id ---
|
||||
NULL
|
||||
string(2) "EM"
|
||||
--- After resetting strong's id ---
|
||||
string(6) "STRONG"
|
||||
string(2) "EM"
|
||||
--- After resetting em's id ---
|
||||
string(6) "STRONG"
|
||||
NULL
|
||||
--- After resetting strong's id ---
|
||||
NULL
|
||||
NULL
|
||||
--- Get id z ---
|
||||
string(2) "EM"
|
85
ext/dom/tests/bug79701/set_attribute_ns_html.phpt
Normal file
85
ext/dom/tests/bug79701/set_attribute_ns_html.phpt
Normal file
@ -0,0 +1,85 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - set attribute ns in html variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
function test($namespace) {
|
||||
$dom = Dom\HTMLDocument::createFromString(<<<HTML
|
||||
<p>
|
||||
<em id="x">1</em>
|
||||
<strong id="y">2</strong>
|
||||
</p>
|
||||
HTML, LIBXML_NOERROR);
|
||||
|
||||
$test1 = $dom->getElementById('x');
|
||||
$test2 = $dom->getElementById('y');
|
||||
|
||||
echo "--- After resetting em's id ---\n";
|
||||
|
||||
$test1->setAttributeNS($namespace, "id", 'y');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting strong's id ---\n";
|
||||
|
||||
$test2->setAttributeNS($namespace, "id", 'x');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting em's id ---\n";
|
||||
|
||||
$test1->setAttributeNS($namespace, "id", 'z');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting strong's id ---\n";
|
||||
|
||||
$test2->setAttributeNS($namespace, "id", 'z');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- Get id z ---\n";
|
||||
|
||||
var_dump($dom->getElementById('z')?->nodeName);
|
||||
}
|
||||
|
||||
echo "=== Test empty namespace ===\n\n";
|
||||
test("");
|
||||
echo "\n=== Test \"urn:x\" namespace ===\n\n";
|
||||
test("urn:x");
|
||||
?>
|
||||
--EXPECT--
|
||||
=== Test empty namespace ===
|
||||
|
||||
--- After resetting em's id ---
|
||||
NULL
|
||||
string(2) "EM"
|
||||
--- After resetting strong's id ---
|
||||
string(6) "STRONG"
|
||||
string(2) "EM"
|
||||
--- After resetting em's id ---
|
||||
string(6) "STRONG"
|
||||
NULL
|
||||
--- After resetting strong's id ---
|
||||
NULL
|
||||
NULL
|
||||
--- Get id z ---
|
||||
string(2) "EM"
|
||||
|
||||
=== Test "urn:x" namespace ===
|
||||
|
||||
--- After resetting em's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- After resetting strong's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- After resetting em's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- After resetting strong's id ---
|
||||
string(2) "EM"
|
||||
string(6) "STRONG"
|
||||
--- Get id z ---
|
||||
NULL
|
143
ext/dom/tests/bug79701/set_attribute_xml.phpt
Normal file
143
ext/dom/tests/bug79701/set_attribute_xml.phpt
Normal file
@ -0,0 +1,143 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - set attribute in xml variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
function test($dom, $fn) {
|
||||
$test1 = $dom->getElementById('x');
|
||||
$test2 = $dom->getElementById('y');
|
||||
|
||||
echo "--- After resetting test1's id ---\n";
|
||||
|
||||
$fn($test1, 'xml:id', 'y');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting test2's id ---\n";
|
||||
|
||||
$fn($test2, 'xml:id', 'x');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting test1's id ---\n";
|
||||
|
||||
$fn($test1, 'xml:id', 'z');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- After resetting test2's id ---\n";
|
||||
|
||||
$fn($test2, 'xml:id', 'z');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
var_dump($dom->getElementById('y')?->nodeName);
|
||||
|
||||
echo "--- Get id z ---\n";
|
||||
|
||||
var_dump($dom->getElementById('z')?->nodeName);
|
||||
}
|
||||
|
||||
function getNamespace($name) {
|
||||
if (str_contains($name, 'xml:')) {
|
||||
return 'http://www.w3.org/XML/1998/namespace';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
$common_xml = <<<XML
|
||||
<root>
|
||||
<test1 xml:id="x"/>
|
||||
<test2 xml:id="y"/>
|
||||
</root>
|
||||
XML;
|
||||
|
||||
echo "\n=== DOMDocument: setAttribute ===\n\n";
|
||||
|
||||
$dom = new DOMDocument;
|
||||
$dom->loadXML($common_xml);
|
||||
test($dom, fn ($element, $name, $value) => $element->setAttribute($name, $value));
|
||||
|
||||
echo "\n=== DOMDocument: setAttributeNS ===\n\n";
|
||||
|
||||
$dom = new DOMDocument;
|
||||
$dom->loadXML($common_xml);
|
||||
test($dom, fn ($element, $name, $value) => $element->setAttributeNS(getNamespace($name), $name, $value));
|
||||
|
||||
echo "\n=== Dom\\XMLDocument: setAttribute ===\n\n";
|
||||
|
||||
$dom = Dom\XMLDocument::createFromString($common_xml);
|
||||
test($dom, fn ($element, $name, $value) => $element->setAttribute($name, $value));
|
||||
|
||||
echo "\n=== Dom\\XMLDocument: setAttributeNS ===\n\n";
|
||||
|
||||
$dom = Dom\XMLDocument::createFromString($common_xml);
|
||||
test($dom, fn ($element, $name, $value) => $element->setAttributeNS(getNamespace($name), $name, $value));
|
||||
?>
|
||||
--EXPECT--
|
||||
=== DOMDocument: setAttribute ===
|
||||
|
||||
--- After resetting test1's id ---
|
||||
NULL
|
||||
string(5) "test1"
|
||||
--- After resetting test2's id ---
|
||||
string(5) "test2"
|
||||
string(5) "test1"
|
||||
--- After resetting test1's id ---
|
||||
string(5) "test2"
|
||||
NULL
|
||||
--- After resetting test2's id ---
|
||||
NULL
|
||||
NULL
|
||||
--- Get id z ---
|
||||
string(5) "test1"
|
||||
|
||||
=== DOMDocument: setAttributeNS ===
|
||||
|
||||
--- After resetting test1's id ---
|
||||
NULL
|
||||
string(5) "test1"
|
||||
--- After resetting test2's id ---
|
||||
string(5) "test2"
|
||||
string(5) "test1"
|
||||
--- After resetting test1's id ---
|
||||
string(5) "test2"
|
||||
NULL
|
||||
--- After resetting test2's id ---
|
||||
NULL
|
||||
NULL
|
||||
--- Get id z ---
|
||||
string(5) "test1"
|
||||
|
||||
=== Dom\XMLDocument: setAttribute ===
|
||||
|
||||
--- After resetting test1's id ---
|
||||
NULL
|
||||
string(5) "test1"
|
||||
--- After resetting test2's id ---
|
||||
string(5) "test2"
|
||||
string(5) "test1"
|
||||
--- After resetting test1's id ---
|
||||
string(5) "test2"
|
||||
NULL
|
||||
--- After resetting test2's id ---
|
||||
NULL
|
||||
NULL
|
||||
--- Get id z ---
|
||||
string(5) "test1"
|
||||
|
||||
=== Dom\XMLDocument: setAttributeNS ===
|
||||
|
||||
--- After resetting test1's id ---
|
||||
NULL
|
||||
string(5) "test1"
|
||||
--- After resetting test2's id ---
|
||||
string(5) "test2"
|
||||
string(5) "test1"
|
||||
--- After resetting test1's id ---
|
||||
string(5) "test2"
|
||||
NULL
|
||||
--- After resetting test2's id ---
|
||||
NULL
|
||||
NULL
|
||||
--- Get id z ---
|
||||
string(5) "test1"
|
47
ext/dom/tests/bug79701/swap.phpt
Normal file
47
ext/dom/tests/bug79701/swap.phpt
Normal file
@ -0,0 +1,47 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - swapping variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
$dom = new DOMDocument;
|
||||
$dom->loadXML(<<<XML
|
||||
<root>
|
||||
<test1 xml:id="x"/>
|
||||
<test2 attr="x"/>
|
||||
</root>
|
||||
XML);
|
||||
|
||||
$test1 = $dom->getElementById('x');
|
||||
|
||||
echo "--- After parsing ---\n";
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
echo "--- After adding a new id attribute ---\n";
|
||||
$dom->getElementsByTagName('test2')[0]->setIdAttribute('attr', true);
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
echo "--- After removing the first id ---\n";
|
||||
$dom->getElementById('x')->remove();
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
echo "--- After removing the second id ---\n";
|
||||
$dom->getElementById('x')->remove();
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
echo "--- After re-adding the first id ---\n";
|
||||
$dom->documentElement->appendChild($test1);
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
echo "--- After changing the first id ---\n";
|
||||
$test1->setAttribute('xml:id', 'y');
|
||||
var_dump($dom->getElementById('x')?->nodeName);
|
||||
?>
|
||||
--EXPECT--
|
||||
--- After parsing ---
|
||||
string(5) "test1"
|
||||
--- After adding a new id attribute ---
|
||||
string(5) "test1"
|
||||
--- After removing the first id ---
|
||||
string(5) "test2"
|
||||
--- After removing the second id ---
|
||||
NULL
|
||||
--- After re-adding the first id ---
|
||||
string(5) "test1"
|
||||
--- After changing the first id ---
|
||||
NULL
|
17
ext/dom/tests/bug79701/toggle.phpt
Normal file
17
ext/dom/tests/bug79701/toggle.phpt
Normal file
@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - toggle variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
$dom = Dom\HTMLDocument::createFromString('<p id="test">foo</p>', LIBXML_NOERROR | LIBXML_HTML_NOIMPLIED);
|
||||
var_dump($dom->getElementById('test')?->nodeName);
|
||||
$dom->documentElement->toggleAttribute('id');
|
||||
var_dump($dom->getElementById('test')?->nodeName);
|
||||
$dom->documentElement->toggleAttribute('id');
|
||||
var_dump($dom->getElementById('test')?->nodeName);
|
||||
?>
|
||||
--EXPECT--
|
||||
string(1) "P"
|
||||
NULL
|
||||
NULL
|
16
ext/dom/tests/bug79701/unconnected.phpt
Normal file
16
ext/dom/tests/bug79701/unconnected.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
Bug #79701 (getElementById does not correctly work with duplicate definitions) - unconnected variation
|
||||
--EXTENSIONS--
|
||||
dom
|
||||
--FILE--
|
||||
<?php
|
||||
$dom = Dom\HTMLDocument::createEmpty();
|
||||
$element = $dom->createElement('foo');
|
||||
$element->setAttribute('id', 'test');
|
||||
var_dump($dom->getElementById('test')?->nodeName);
|
||||
$dom->append($element);
|
||||
var_dump($dom->getElementById('test')?->nodeName);
|
||||
?>
|
||||
--EXPECT--
|
||||
NULL
|
||||
string(3) "FOO"
|
Loading…
Reference in New Issue
Block a user