Skip to content

Commit 92b0894

Browse files
committed
dom: fix attribute namespaces from foreign content in the HTML parser
The bridge registered an id as XML_ATTRIBUTE_ID only when lexbor reported the HTML namespace, so ids on SVG and MathML elements never reached getElementById(). Separately, lexbor initializes every attribute node with its element's namespace and overrides that only through the foreign-attribute adjust table, which the parser wires up for SVG and MathML alone, so a fragment parsed with an xlink, xml or xmlns context element came back as <z xlink:id="q" xlink:foo="1">. Restrict the three namespace branches to attributes lexbor adjusted, and key the id registration off the namespace the bridge assigned. Closes GH-23598
1 parent 7300fa5 commit 92b0894

3 files changed

Lines changed: 68 additions & 4 deletions

File tree

‎NEWS‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ PHP NEWS
88
(Ilia Alshanetsky)
99
. Fixed bug GH-23365 (DOMNode::insertBefore($n, $n) drops the node and
1010
leaves a self-referencing sibling list). (David Carlier)
11+
. Fixed Dom\HTMLDocument::getElementById() not finding ids of SVG and
12+
MathML elements. (Ilia Alshanetsky)
13+
. Fixed Dom\HTMLDocument giving attributes the namespace of their element
14+
when a fragment is parsed with an xlink, xml or xmlns context element.
15+
(Ilia Alshanetsky)
1116

1217
- Intl:
1318
. Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state

‎ext/dom/html5_parser.c‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
241241
lxml_attr->children = lxml_attr->last = lxml_text;
242242
lxml_text->parent = (xmlNodePtr) lxml_attr;
243243

244-
if (attr->node.ns == LXB_NS_XMLNS) {
244+
if (attr->node.ns == LXB_NS_XMLNS && (attr->node.prefix || strcmp((const char *) local_name, "xmlns") == 0)) {
245245
if (strcmp((const char *) local_name, "xmlns") != 0) {
246246
if (prefixed_xmlns_ns == NULL) {
247247
prefixed_xmlns_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xmlns", DOM_XMLNS_NS_URI);
@@ -251,13 +251,13 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
251251
lxml_attr->ns = php_dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(ns_mapper);
252252
}
253253
lxml_attr->ns->_private = (void *) php_dom_ns_is_xmlns_magic_token;
254-
} else if (attr->node.ns == LXB_NS_XLINK) {
254+
} else if (attr->node.prefix && attr->node.ns == LXB_NS_XLINK) {
255255
if (xlink_ns == NULL) {
256256
xlink_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xlink", DOM_XLINK_NS_URI);
257257
xlink_ns->_private = (void *) php_dom_ns_is_xlink_magic_token;
258258
}
259259
lxml_attr->ns = xlink_ns;
260-
} else if (attr->node.ns == LXB_NS_XML) {
260+
} else if (attr->node.prefix && attr->node.ns == LXB_NS_XML) {
261261
if (xml_ns == NULL) {
262262
xml_ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xml", DOM_XML_NS_URI);
263263
xml_ns->_private = (void *) php_dom_ns_is_xml_magic_token;
@@ -274,7 +274,7 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
274274
last_added_attr = lxml_attr;
275275

276276
/* xmlIsID does some other stuff too that is irrelevant here. */
277-
if (local_name_length == 2 && local_name[0] == 'i' && local_name[1] == 'd' && attr->node.ns == LXB_NS_HTML) {
277+
if (local_name_length == 2 && local_name[0] == 'i' && local_name[1] == 'd' && lxml_attr->ns == NULL) {
278278
if (xmlAddID(NULL, lxml_doc, value, lxml_attr) == 0) {
279279
/* If the ID already exists, the ID attribute still needs to be marked as an ID. */
280280
lxml_attr->atype = XML_ATTRIBUTE_ID;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
Unprefixed attributes in foreign content are in no namespace
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
$html = '<!DOCTYPE html><html><body><svg id="s" xlink:href="#a" xml:lang="en" xmlns:xlink="urn:x"><rect xml:id="r"/></svg><math id="m"></math><p id="p"></p></body></html>';
8+
$doc = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
9+
10+
var_dump([
11+
'svg #s' => $doc->getElementById('s')?->tagName,
12+
'math #m' => $doc->getElementById('m')?->tagName,
13+
'html #p' => $doc->getElementById('p')?->tagName,
14+
'xml:id #r' => $doc->getElementById('r')?->tagName,
15+
]);
16+
17+
foreach ($doc->getElementById('s')->attributes as $attr) {
18+
echo $attr->name, ' => ', var_export($attr->namespaceURI, true), "\n";
19+
}
20+
21+
$contexts = [
22+
'http://www.w3.org/1999/xlink' => 'z',
23+
'http://www.w3.org/XML/1998/namespace' => 'z',
24+
'http://www.w3.org/2000/xmlns/' => 'xmlns',
25+
];
26+
foreach ($contexts as $uri => $name) {
27+
$fragment_doc = Dom\HTMLDocument::createEmpty();
28+
$context = $fragment_doc->createElementNS($uri, $name);
29+
$fragment_doc->appendChild($context);
30+
$context->innerHTML = '<z id="q" xlink:href="#a"></z>';
31+
32+
echo $uri, "\n ", $context->innerHTML, "\n ";
33+
var_dump($fragment_doc->getElementById('q')?->tagName);
34+
}
35+
?>
36+
--EXPECT--
37+
array(4) {
38+
["svg #s"]=>
39+
string(3) "svg"
40+
["math #m"]=>
41+
string(4) "math"
42+
["html #p"]=>
43+
string(1) "P"
44+
["xml:id #r"]=>
45+
NULL
46+
}
47+
id => NULL
48+
xlink:href => 'http://www.w3.org/1999/xlink'
49+
xml:lang => 'http://www.w3.org/XML/1998/namespace'
50+
xmlns:xlink => 'http://www.w3.org/2000/xmlns/'
51+
http://www.w3.org/1999/xlink
52+
<z id="q" xlink:href="#a"></z>
53+
string(1) "Z"
54+
http://www.w3.org/XML/1998/namespace
55+
<z id="q" xlink:href="#a"></z>
56+
string(1) "Z"
57+
http://www.w3.org/2000/xmlns/
58+
<z id="q" xlink:href="#a"></z>
59+
string(1) "Z"

0 commit comments

Comments
 (0)