Skip to content

Commit 154c251

Browse files
committed
Fix spec compliance error for DOMDocument::getElementsByTagNameNS
Spec link: https://dom.spec.whatwg.org/#concept-getelementsbytagnamens Spec says we should match any namespace when '*' is provided. This was however not the case: elements that didn't have a namespace were not returned. This patch fixes the error by modifying the namespace check. Closes GH-11343.
1 parent 9c59d22 commit 154c251

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ PHP NEWS
1111
. Fixed bug GH-10234 (Setting DOMAttr::textContent results in an empty
1212
attribute value). (nielsdos)
1313
. Fix return value in stub file for DOMNodeList::item. (divinity76)
14+
. Fix spec compliance error with '*' namespace for
15+
DOMDocument::getElementsByTagNameNS. (nielsdos)
1416

1517
- Opcache:
1618
. Fix allocation loop in zend_shared_alloc_startup(). (nielsdos)

ext/dom/php_dom.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,10 +1270,15 @@ xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *l
12701270
{
12711271
xmlNodePtr ret = NULL;
12721272

1273+
/* Note: The spec says that ns == '' must be transformed to ns == NULL. In other words, they are equivalent.
1274+
* PHP however does not do this and internally uses the empty string everywhere when the user provides ns == NULL.
1275+
* This is because for PHP ns == NULL has another meaning: "match every namespace" instead of "match the empty namespace". */
1276+
bool ns_match_any = ns == NULL || (ns[0] == '*' && ns[1] == '\0');
1277+
12731278
while (nodep != NULL && (*cur <= index || index == -1)) {
12741279
if (nodep->type == XML_ELEMENT_NODE) {
12751280
if (xmlStrEqual(nodep->name, (xmlChar *)local) || xmlStrEqual((xmlChar *)"*", (xmlChar *)local)) {
1276-
if (ns == NULL || (!strcmp(ns, "") && nodep->ns == NULL) || (nodep->ns != NULL && (xmlStrEqual(nodep->ns->href, (xmlChar *)ns) || xmlStrEqual((xmlChar *)"*", (xmlChar *)ns)))) {
1281+
if (ns_match_any || (!strcmp(ns, "") && nodep->ns == NULL) || (nodep->ns != NULL && xmlStrEqual(nodep->ns->href, (xmlChar *)ns))) {
12771282
if (*cur == index) {
12781283
ret = nodep;
12791284
break;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
DOMDocument::getElementsByTagNameNS() match any namespace
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
/* Sample document taken from https://www.php.net/manual/en/domdocument.getelementsbytagname.php */
9+
$xml = <<<EOD
10+
<?xml version="1.0" ?>
11+
<chapter xmlns:xi="http://www.w3.org/2001/XInclude">
12+
<title>Books of the other guy..</title>
13+
<para>
14+
<xi:include href="book.xml">
15+
<xi:fallback>
16+
<error>xinclude: book.xml not found</error>
17+
</xi:fallback>
18+
</xi:include>
19+
<include>
20+
This is another namespace
21+
</include>
22+
</para>
23+
</chapter>
24+
EOD;
25+
$dom = new DOMDocument;
26+
27+
// load the XML string defined above
28+
$dom->loadXML($xml);
29+
30+
function test($namespace, $local) {
31+
global $dom;
32+
$namespace_str = $namespace !== NULL ? "'$namespace'" : "null";
33+
echo "-- getElementsByTagNameNS($namespace_str, '$local') --\n";
34+
foreach ($dom->getElementsByTagNameNS($namespace, $local) as $element) {
35+
echo 'local name: \'', $element->localName, '\', prefix: \'', $element->prefix, "'\n";
36+
}
37+
}
38+
39+
// Should *also* include objects even without a namespace
40+
test(null, '*');
41+
// Should *also* include objects even without a namespace
42+
test('*', '*');
43+
// Should *only* include objects without a namespace
44+
test('', '*');
45+
// Should *only* include objects with the specified namespace
46+
test('http://www.w3.org/2001/XInclude', '*');
47+
// Should not give any output
48+
test('', 'fallback');
49+
// Should not give any output, because the null namespace is the same as the empty namespace
50+
test(null, 'fallback');
51+
// Should only output the include from the empty namespace
52+
test(null, 'include');
53+
54+
?>
55+
--EXPECT--
56+
-- getElementsByTagNameNS(null, '*') --
57+
local name: 'chapter', prefix: ''
58+
local name: 'title', prefix: ''
59+
local name: 'para', prefix: ''
60+
local name: 'error', prefix: ''
61+
local name: 'include', prefix: ''
62+
-- getElementsByTagNameNS('*', '*') --
63+
local name: 'chapter', prefix: ''
64+
local name: 'title', prefix: ''
65+
local name: 'para', prefix: ''
66+
local name: 'include', prefix: 'xi'
67+
local name: 'fallback', prefix: 'xi'
68+
local name: 'error', prefix: ''
69+
local name: 'include', prefix: ''
70+
-- getElementsByTagNameNS('', '*') --
71+
local name: 'chapter', prefix: ''
72+
local name: 'title', prefix: ''
73+
local name: 'para', prefix: ''
74+
local name: 'error', prefix: ''
75+
local name: 'include', prefix: ''
76+
-- getElementsByTagNameNS('http://www.w3.org/2001/XInclude', '*') --
77+
local name: 'include', prefix: 'xi'
78+
local name: 'fallback', prefix: 'xi'
79+
-- getElementsByTagNameNS('', 'fallback') --
80+
-- getElementsByTagNameNS(null, 'fallback') --
81+
-- getElementsByTagNameNS(null, 'include') --
82+
local name: 'include', prefix: ''

0 commit comments

Comments
 (0)