diff --git a/NEWS b/NEWS index dd949084cce..06ee2b27e6f 100644 --- a/NEWS +++ b/NEWS @@ -38,8 +38,6 @@ PHP NEWS . Fix "invalid state error" with cloned namespace declarations. (nielsdos) . Fixed bug #55294 and #47530 and #47847 (various namespace reconciliation issues). (nielsdos) - . Fixed bug GH-11404 (DOMDocument::saveXML and friends omit xmlns="" - declaration for null namespace). (nielsdos) . Fixed bug #80332 (Completely broken array access functionality with DOMNamedNodeMap). (nielsdos) diff --git a/ext/dom/document.c b/ext/dom/document.c index 199c39b5ff5..93091df83a0 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -878,10 +878,6 @@ PHP_METHOD(DOMDocument, createElementNS) if (errorcode == 0) { if (xmlValidateName((xmlChar *) localname, 0) == 0) { - /* https://dom.spec.whatwg.org/#validate-and-extract: demands us to set an empty string uri to NULL */ - if (uri_len == 0) { - uri = NULL; - } nodep = xmlNewDocNode(docp, NULL, (xmlChar *) localname, (xmlChar *) value); if (nodep != NULL && uri != NULL) { nsptr = xmlSearchNsByHref(nodep->doc, nodep, (xmlChar *) uri); diff --git a/ext/dom/element.c b/ext/dom/element.c index 79786ed9078..44c576a0736 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -56,10 +56,6 @@ PHP_METHOD(DOMElement, __construct) if (uri_len > 0) { errorcode = dom_check_qname(name, &localname, &prefix, uri_len, name_len); if (errorcode == 0) { - /* https://dom.spec.whatwg.org/#validate-and-extract: demands us to set an empty string uri to NULL */ - if (uri_len == 0) { - uri = NULL; - } nodep = xmlNewNode (NULL, (xmlChar *)localname); if (nodep != NULL && uri != NULL) { nsptr = dom_get_ns(nodep, uri, &errorcode, prefix); diff --git a/ext/dom/node.c b/ext/dom/node.c index dc8c96b85ff..bcf4ee487d3 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -531,6 +531,7 @@ Since: DOM Level 2 int dom_node_namespace_uri_read(dom_object *obj, zval *retval) { xmlNode *nodep = dom_object_get_node(obj); + char *str = NULL; if (nodep == NULL) { php_dom_throw_error(INVALID_STATE_ERR, 1); @@ -542,19 +543,20 @@ int dom_node_namespace_uri_read(dom_object *obj, zval *retval) case XML_ATTRIBUTE_NODE: case XML_NAMESPACE_DECL: if (nodep->ns != NULL) { - char *str = (char *) nodep->ns->href; - /* https://dom.spec.whatwg.org/#concept-attribute: namespaceUri is "null or a non-empty string" */ - if (str != NULL && str[0] != '\0') { - ZVAL_STRING(retval, str); - return SUCCESS; - } + str = (char *) nodep->ns->href; } break; default: + str = NULL; break; } - ZVAL_NULL(retval); + if (str != NULL) { + ZVAL_STRING(retval, str); + } else { + ZVAL_NULL(retval); + } + return SUCCESS; } diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index be0534eb644..ba532045186 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1496,22 +1496,6 @@ static void dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep xmlDOMWrapReconcileNamespaces(&dummy_ctxt, nodep, /* options */ 0); } -static bool dom_must_replace_namespace_by_empty_default(xmlDocPtr doc, xmlNodePtr nodep) -{ - xmlNsPtr default_ns = xmlSearchNs(doc, nodep->parent, NULL); - return default_ns != NULL && default_ns->href != NULL && default_ns->href[0] != '\0'; -} - -static void dom_replace_namespace_by_empty_default(xmlDocPtr doc, xmlNodePtr nodep) -{ - ZEND_ASSERT(nodep->ns == NULL); - /* The node uses the default empty namespace, but the current default namespace is non-empty. - * We can't unconditionally do this because otherwise libxml2 creates an xmlns="" declaration. - * Note: there's no point searching the oldNs list, because we haven't found it in the tree anyway. - * Ideally this would be pre-allocated but unfortunately libxml2 doesn't offer such a functionality. */ - xmlSetNs(nodep, xmlNewNs(nodep, (const xmlChar *) "", NULL)); -} - void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ { /* Although the node type will be checked by the libxml2 API, @@ -1519,10 +1503,6 @@ void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ if (nodep->type == XML_ELEMENT_NODE) { dom_reconcile_ns_internal(doc, nodep, nodep->parent); dom_libxml_reconcile_ensure_namespaces_are_declared(nodep); - /* Check nodep->ns first to avoid an expensive lookup. */ - if (nodep->ns == NULL && dom_must_replace_namespace_by_empty_default(doc, nodep)) { - dom_replace_namespace_by_empty_default(doc, nodep); - } } } /* }}} */ @@ -1546,30 +1526,12 @@ static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlN void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last) { - bool did_compute_must_replace_namespace_by_empty_default = false; - bool must_replace_namespace_by_empty_default = false; - dom_reconcile_ns_list_internal(doc, nodep, last, nodep->parent); - /* The loop is outside of the recursion in the above call because * dom_libxml_reconcile_ensure_namespaces_are_declared() performs its own recursion. */ while (true) { /* The internal libxml2 call will already check the node type, no need for us to do it here. */ dom_libxml_reconcile_ensure_namespaces_are_declared(nodep); - - /* We don't have to handle the children, because if their ns's are NULL they'll just take on the default - * which should've been reconciled before. */ - if (nodep->ns == NULL) { - /* This is an optimistic approach: we assume that most of the time we don't need the result of the computation. */ - if (!did_compute_must_replace_namespace_by_empty_default) { - did_compute_must_replace_namespace_by_empty_default = true; - must_replace_namespace_by_empty_default = dom_must_replace_namespace_by_empty_default(doc, nodep); - } - if (must_replace_namespace_by_empty_default) { - dom_replace_namespace_by_empty_default(doc, nodep); - } - } - if (nodep == last) { break; } diff --git a/ext/dom/tests/bug47530.phpt b/ext/dom/tests/bug47530.phpt index ab0b030c94b..0fb990e0e7b 100644 --- a/ext/dom/tests/bug47530.phpt +++ b/ext/dom/tests/bug47530.phpt @@ -121,7 +121,7 @@ test_appendChild_with_shadowing(); -- Test document fragment without import -- - + string(7) "foo:bar" string(19) "https://php.net/bar" -- Test document import -- diff --git a/ext/dom/tests/gh11404.phpt b/ext/dom/tests/gh11404.phpt deleted file mode 100644 index 6c868de508c..00000000000 --- a/ext/dom/tests/gh11404.phpt +++ /dev/null @@ -1,160 +0,0 @@ ---TEST-- -GH-11404: DOMDocument::savexml and friends ommit xmlns="" declaration for null namespace, creating incorrect xml representation of the DOM ---EXTENSIONS-- -dom ---FILE-- -createElement('a'); - $nodeB = $dom->createElementNS(null, 'b'); - $nodeC = $dom->createElementNS('', 'c'); - $nodeD = $dom->createElement('d'); - $nodeD->setAttributeNS('some:ns', 'x:attrib', 'val'); - $nodeE = $dom->createElementNS('some:ns', 'e'); - // And these two respect the default ns. - $nodeE->setAttributeNS(null, 'attrib1', 'val'); - $nodeE->setAttributeNS('', 'attrib2', 'val'); - - $dom->documentElement->appendChild($nodeA); - $dom->documentElement->appendChild($nodeB); - $dom->documentElement->appendChild($nodeC); - $dom->documentElement->appendChild($nodeD); - $dom->documentElement->appendChild($nodeE); - - var_dump($nodeA->namespaceURI); - var_dump($nodeB->namespaceURI); - var_dump($nodeC->namespaceURI); - var_dump($nodeD->namespaceURI); - var_dump($nodeE->namespaceURI); - - echo $dom->saveXML(); - - // Create a subtree without using a fragment - $subtree = $dom->createElement('subtree'); - $subtree->appendChild($dom->createElementNS('some:ns', 'subtreechild1')); - $subtree->firstElementChild->appendChild($dom->createElement('subtreechild2')); - $dom->documentElement->appendChild($subtree); - - echo $dom->saveXML(); - - // Create a subtree with the use of a fragment - $subtree = $dom->createDocumentFragment(); - $subtree->appendChild($child3 = $dom->createElement('child3')); - $child3->appendChild($dom->createElement('child4')); - $subtree->appendChild($dom->createElement('child5')); - $dom->documentElement->appendChild($subtree); - - echo $dom->saveXML(); -} - -$dom1 = new DOMDocument; -$dom1->loadXML(''); -testAppendAndAttributes($dom1); - -echo "-- Test append and attributes: without default namespace variation --\n"; - -$dom1 = new DOMDocument; -$dom1->loadXML(''); -testAppendAndAttributes($dom1); - -echo "-- Test import --\n"; - -function testImport(?string $href, string $toBeImported) { - $dom1 = new DOMDocument; - $decl = $href === NULL ? '' : "xmlns=\"$href\""; - $dom1->loadXML(''); - - $dom2 = new DOMDocument; - $dom2->loadXML('' . $toBeImported); - - $dom1->documentElement->append( - $imported = $dom1->importNode($dom2->documentElement, true) - ); - - var_dump($imported->namespaceURI); - - echo $dom1->saveXML(); -} - -testImport(null, ''); -testImport('', ''); -testImport('some:ns', ''); -testImport('', '
'); -testImport('some:ns', '
'); - -echo "-- Namespace URI comparison --\n"; - -$dom1 = new DOMDocument; -$dom1->loadXML('
'); -var_dump($dom1->firstElementChild->namespaceURI); -var_dump($dom1->firstElementChild->firstElementChild->namespaceURI); - -$dom1 = new DOMDocument; -$dom1->appendChild($dom1->createElementNS('a:b', 'parent')); -$dom1->firstElementChild->appendChild($dom1->createElementNS('a:b', 'child1')); -$dom1->firstElementChild->appendChild($second = $dom1->createElement('child2')); -var_dump($dom1->firstElementChild->namespaceURI); -var_dump($dom1->firstElementChild->firstElementChild->namespaceURI); -var_dump($second->namespaceURI); -echo $dom1->saveXML(); - -$dom1 = new DOMDocument; -$dom1->loadXML(''); -var_dump($dom1->firstElementChild->namespaceURI); -$dom1->firstElementChild->appendChild($dom1->createElementNS('a:b', 'tag')); -var_dump($dom1->firstElementChild->firstElementChild->namespaceURI); -?> ---EXPECT-- --- Test append and attributes: with default namespace variation -- -NULL -NULL -NULL -NULL -string(7) "some:ns" - - - - - - --- Test append and attributes: without default namespace variation -- -NULL -NULL -NULL -NULL -string(7) "some:ns" - - - - - - --- Test import -- -NULL - - -NULL - - -NULL - - -NULL - -
-string(7) "some:ns" - -
--- Namespace URI comparison -- -string(3) "a:b" -string(3) "a:b" -string(3) "a:b" -string(3) "a:b" -NULL - - -string(3) "a:b" -string(3) "a:b"