diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 6867e5acf128..d3401f007dc4 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1369,11 +1369,16 @@ void dom_normalize (xmlNodePtr nodep) /* {{{ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) */ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) { - xmlNs *cur; - if (doc == NULL) return; + ZEND_ASSERT(ns->next == NULL); + + /* Note: we'll use a prepend strategy instead of append to + * make sure we don't lose performance when the list is long. + * As libxml2 could assume the xml node is the first one, we'll place our + * new entries after the first one. */ + if (doc->oldNs == NULL) { doc->oldNs = (xmlNsPtr) xmlMalloc(sizeof(xmlNs)); if (doc->oldNs == NULL) { @@ -1383,13 +1388,10 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) { doc->oldNs->type = XML_LOCAL_NAMESPACE; doc->oldNs->href = xmlStrdup(XML_XML_NAMESPACE); doc->oldNs->prefix = xmlStrdup((const xmlChar *)"xml"); + } else { + ns->next = doc->oldNs->next; } - - cur = doc->oldNs; - while (cur->next != NULL) { - cur = cur->next; - } - cur->next = ns; + doc->oldNs->next = ns; } /* }}} end dom_set_old_ns */ @@ -1411,6 +1413,9 @@ static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep) } else { prevns->next = nsdftptr; } + /* Note: we can't get here if the ns is already on the oldNs list. + * This is because in that case the definition won't be on the node, and + * therefore won't be in the nodep->nsDef list. */ dom_set_old_ns(doc, curns); curns = prevns; } @@ -1509,22 +1514,38 @@ NAMESPACE_ERR: Raised if /* {{{ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) */ xmlNsPtr dom_get_ns(xmlNodePtr nodep, char *uri, int *errorcode, char *prefix) { - xmlNsPtr nsptr = NULL; - - *errorcode = 0; + xmlNsPtr nsptr; if (! ((prefix && !strcmp (prefix, "xml") && strcmp(uri, (char *)XML_XML_NAMESPACE)) || (prefix && !strcmp (prefix, "xmlns") && strcmp(uri, (char *)DOM_XMLNS_NAMESPACE)) || (prefix && !strcmp(uri, (char *)DOM_XMLNS_NAMESPACE) && strcmp (prefix, "xmlns")))) { + /* Reuse the old namespaces from doc->oldNs if possible, before creating a new one. + * This will prevent the oldNs list from growing with duplicates. */ + xmlDocPtr doc = nodep->doc; + if (doc && doc->oldNs != NULL) { + nsptr = doc->oldNs; + do { + if (xmlStrEqual(nsptr->prefix, (xmlChar *)prefix) && xmlStrEqual(nsptr->href, (xmlChar *)uri)) { + goto out; + } + nsptr = nsptr->next; + } while (nsptr); + } + /* Couldn't reuse one, create a new one. */ nsptr = xmlNewNs(nodep, (xmlChar *)uri, (xmlChar *)prefix); + if (UNEXPECTED(nsptr == NULL)) { + goto err; + } + } else { + goto err; } - if (nsptr == NULL) { - *errorcode = NAMESPACE_ERR; - } - +out: + *errorcode = 0; return nsptr; - +err: + *errorcode = NAMESPACE_ERR; + return NULL; } /* }}} end dom_get_ns */ diff --git a/ext/dom/tests/reconcile_reused_namespace.phpt b/ext/dom/tests/reconcile_reused_namespace.phpt new file mode 100644 index 000000000000..5f9ab6c0d80f --- /dev/null +++ b/ext/dom/tests/reconcile_reused_namespace.phpt @@ -0,0 +1,42 @@ +--TEST-- +Reconcile a reused namespace from doc->oldNs +--EXTENSIONS-- +dom +--FILE-- +createElementNS('http://www.w3.org/2000/xhtml', 'html'); + +$dom->loadXML(<< + +XML); +$root = $dom->firstElementChild; + +echo "Add first\n"; +$element = $dom->createElementNS('http://example.com/B', 'p', 'Hello World'); +$root->appendChild($element); + +echo "Add second\n"; +$element = $dom->createElementNS('http://example.com/A', 'p', 'Hello World'); +$root->appendChild($element); + +echo "Add third\n"; +$element = $dom->createElementNS('http://example.com/A', 'p', 'Hello World'); +$root->appendChild($element); + +var_dump($dom->saveXML()); + +?> +--EXPECT-- +Add first +Add second +Add third +string(201) " +Hello WorldHello WorldHello World +"