From 275ae3c5f7bd7609309e8e093c8e7cf36709eb4a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:29:58 +0200 Subject: [PATCH] Add Dom\DocumentFragment::getElementById() --- ext/dom/document.c | 18 ++++-------------- ext/dom/documentfragment.c | 19 +++++++++++++++++++ ext/dom/php_dom.c | 18 ++++++++++++++++++ ext/dom/php_dom.h | 1 + ext/dom/php_dom.stub.php | 2 ++ ext/dom/php_dom_arginfo.h | 12 ++++++++---- 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/ext/dom/document.c b/ext/dom/document.c index 4a10f4dd19b6f..49ffbf2ac83ac 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -1032,7 +1032,7 @@ PHP_METHOD(DOMDocument, getElementById) char *idname; ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_STRING(idname, idname_len) + Z_PARAM_PATH(idname, idname_len) ZEND_PARSE_PARAMETERS_END(); DOM_GET_OBJ(docp, ZEND_THIS, xmlDocPtr, intern); @@ -1051,20 +1051,10 @@ PHP_METHOD(DOMDocument, getElementById) * 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); + xmlNodePtr node = dom_scan_id(base, BAD_CAST idname); + if (node) { + DOM_RET_OBJ(node, intern); } } } diff --git a/ext/dom/documentfragment.c b/ext/dom/documentfragment.c index 76870faedfa4c..0b84a36bb7c9d 100644 --- a/ext/dom/documentfragment.c +++ b/ext/dom/documentfragment.c @@ -93,4 +93,23 @@ PHP_METHOD(DOMDocumentFragment, appendXML) { } /* }}} */ +PHP_METHOD(Dom_DocumentFragment, getElementById) +{ + xmlNodePtr base; + size_t idname_len; + dom_object *intern; + char *idname; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_PATH(idname, idname_len) + ZEND_PARSE_PARAMETERS_END(); + + DOM_GET_OBJ(base, ZEND_THIS, xmlNodePtr, intern); + + xmlNodePtr node = dom_scan_id(base, BAD_CAST idname); + if (node) { + DOM_RET_OBJ(node, intern); + } +} + #endif diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 3619eaef12a5f..92f45fc2eea9a 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -2720,4 +2720,22 @@ xmlDocPtr php_dom_create_html_doc(void) return lxml_doc; } +xmlNodePtr dom_scan_id(const xmlNode *base, const xmlChar *idname) +{ + xmlNodePtr 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)) { + return node; + } + } + } + + node = php_dom_next_in_tree_order(node, base); + } + + return NULL; +} + #endif /* HAVE_DOM */ diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index e44f74eadeb37..5d7c4c00ed99e 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -158,6 +158,7 @@ bool php_dom_create_nullable_object(xmlNodePtr obj, zval *return_value, dom_obje xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive); void dom_set_document_ref_pointers(xmlNodePtr node, php_libxml_ref_obj *document); void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *document); +xmlNodePtr dom_scan_id(const xmlNode *base, const xmlChar *idname); /* Prop getters by offset */ zval *dom_get_prop_checked_offset(dom_object *obj, uint32_t offset, const char *name); diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 37a1bea62b627..ed1574a13591e 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -1899,6 +1899,8 @@ public function replaceChildren(Node|string ...$nodes): void {} public function querySelector(string $selectors): ?Element {} /** @implementation-alias Dom\Element::querySelectorAll */ public function querySelectorAll(string $selectors): NodeList {} + + public function getElementById(string $elementId): ?Element {} } class Entity extends Node diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 1f3f9fd0a8a24..685aa85fed35d 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e3495cb89e4466d9102abb10bf6461989b7c8ba9 */ + * Stub hash: f0ef8e4f5b6568a53a1789c0a8b17c3d3124f9ca */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_dom_import_simplexml, 0, 1, DOMAttr|DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) @@ -898,6 +898,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_Dom_DocumentFragment_querySelectorAll arginfo_class_Dom_ParentNode_querySelectorAll +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_DocumentFragment_getElementById, 0, 1, Dom\\Element, 1) + ZEND_ARG_TYPE_INFO(0, elementId, IS_STRING, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_Dom_Document_getElementsByTagName arginfo_class_Dom_Element_getElementsByTagName #define arginfo_class_Dom_Document_getElementsByTagNameNS arginfo_class_Dom_Element_getElementsByTagNameNS @@ -949,9 +953,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_Document_createAttribut ZEND_ARG_TYPE_INFO(0, qualifiedName, IS_STRING, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Dom_Document_getElementById, 0, 1, Dom\\Element, 1) - ZEND_ARG_TYPE_INFO(0, elementId, IS_STRING, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_Dom_Document_getElementById arginfo_class_Dom_DocumentFragment_getElementById ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Dom_Document_registerNodeClass, 0, 2, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, baseClass, IS_STRING, 0) @@ -1290,6 +1292,7 @@ ZEND_METHOD(Dom_CharacterData, appendData); ZEND_METHOD(Dom_CharacterData, insertData); ZEND_METHOD(Dom_CharacterData, deleteData); ZEND_METHOD(Dom_CharacterData, replaceData); +ZEND_METHOD(Dom_DocumentFragment, getElementById); ZEND_METHOD(Dom_Document, createElement); ZEND_METHOD(Dom_Document, createElementNS); ZEND_METHOD(Dom_Document, createProcessingInstruction); @@ -1711,6 +1714,7 @@ static const zend_function_entry class_Dom_DocumentFragment_methods[] = { ZEND_RAW_FENTRY("replaceChildren", zim_DOMElement_replaceChildren, arginfo_class_Dom_DocumentFragment_replaceChildren, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("querySelector", zim_Dom_Element_querySelector, arginfo_class_Dom_DocumentFragment_querySelector, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("querySelectorAll", zim_Dom_Element_querySelectorAll, arginfo_class_Dom_DocumentFragment_querySelectorAll, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_ME(Dom_DocumentFragment, getElementById, arginfo_class_Dom_DocumentFragment_getElementById, ZEND_ACC_PUBLIC) ZEND_FE_END };