Skip to content

Commit fade6c4

Browse files
committed
Rework some stuff
1 parent 36d9cd2 commit fade6c4

File tree

9 files changed

+175
-61
lines changed

9 files changed

+175
-61
lines changed

ext/dom/dom_ce.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ extern PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
6666
extern PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
6767
#endif
6868
extern PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry;
69+
extern PHP_DOM_EXPORT zend_class_entry *dom_namespace_info_class_entry;
6970

7071
#endif /* DOM_CE_H */

ext/dom/element.c

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,22 +1755,58 @@ PHP_METHOD(DOM_Element, getInScopeNamespaces)
17551755
DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
17561756

17571757
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
1758-
php_dom_in_scope_ns namespaces = php_dom_get_in_scope_ns(ns_mapper, nodep, true);
17591758

1760-
array_init_size(return_value, namespaces.count);
1759+
HashTable prefix_to_ns_table;
1760+
zend_hash_init(&prefix_to_ns_table, 0, NULL, NULL, false);
1761+
zend_hash_real_init_mixed(&prefix_to_ns_table);
1762+
1763+
/* https://www.w3.org/TR/1999/REC-xpath-19991116/#namespace-nodes */
1764+
for (const xmlNode *cur = nodep; cur != NULL; cur = cur->parent) {
1765+
if (cur->type == XML_ELEMENT_NODE) {
1766+
for (const xmlAttr *attr = cur->properties; attr != NULL; attr = attr->next) {
1767+
if (attr->ns != NULL && php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)
1768+
&& attr->children != NULL && attr->children->content != NULL) {
1769+
const char *prefix = attr->ns->prefix == NULL ? NULL : (const char *) attr->name;
1770+
const char *key = prefix == NULL ? "" : prefix;
1771+
xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, prefix, (const char *) attr->children->content);
1772+
zend_hash_str_add_ptr(&prefix_to_ns_table, key, strlen(key), ns);
1773+
}
1774+
}
1775+
}
1776+
}
1777+
1778+
array_init_size(return_value, zend_hash_num_elements(&prefix_to_ns_table));
1779+
1780+
xmlNsPtr ns;
1781+
zend_string *prefix;
1782+
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&prefix_to_ns_table, prefix, ns) {
1783+
if (ZSTR_LEN(prefix) == 0 && (ns == NULL || ns->href == NULL || *ns->href == '\0')) {
1784+
/* Exception: "the value of the xmlns attribute for the nearest such element is non-empty" */
1785+
continue;
1786+
}
17611787

1762-
for (size_t i = 0; i < namespaces.count; i++) {
1763-
xmlNsPtr ns = namespaces.list[i];
1764-
if (ns == NULL) {
1765-
/* This case corresponds to xmlns="" */
1766-
add_assoc_null(return_value, "");
1788+
zval zv;
1789+
object_init_ex(&zv, dom_namespace_info_class_entry);
1790+
zend_object *obj = Z_OBJ(zv);
1791+
1792+
if (ZSTR_LEN(prefix) != 0) {
1793+
ZVAL_STRING(OBJ_PROP_NUM(obj, 0), (const char *) ZSTR_VAL(prefix));
17671794
} else {
1768-
const char *prefix = (const char *) ns->prefix;
1769-
add_assoc_stringl(return_value, prefix == NULL ? "" : prefix, (const char *) ns->href, xmlStrlen(ns->href));
1795+
ZVAL_NULL(OBJ_PROP_NUM(obj, 0));
17701796
}
1771-
}
17721797

1773-
php_dom_in_scope_ns_destroy(&namespaces);
1798+
if (ns != NULL && ns->href != NULL && *ns->href != '\0') {
1799+
ZVAL_STRING(OBJ_PROP_NUM(obj, 1), (const char *) ns->href);
1800+
} else {
1801+
ZVAL_NULL(OBJ_PROP_NUM(obj, 1));
1802+
}
1803+
1804+
ZVAL_OBJ_COPY(OBJ_PROP_NUM(obj, 2), &intern->std);
1805+
1806+
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &zv);
1807+
} ZEND_HASH_FOREACH_END();
1808+
1809+
zend_hash_destroy(&prefix_to_ns_table);
17741810
}
17751811

17761812
PHP_METHOD(DOM_Element, rename)

ext/dom/namespace_compat.c

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -455,17 +455,18 @@ PHP_DOM_EXPORT void php_dom_libxml_reconcile_modern(php_dom_libxml_ns_mapper *ns
455455
zend_hash_destroy(&ctx.old_ns_to_new_ns_ptr);
456456
}
457457

458-
PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_mapper *ns_mapper, const xmlNode *node, bool register_element_default_ns)
458+
PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_mapper *ns_mapper, const xmlNode *node)
459459
{
460460
ZEND_ASSERT(node != NULL);
461461

462-
php_dom_in_scope_ns in_scope_ns;
463-
in_scope_ns.origin_is_ns_compat = true;
464-
465462
/* libxml fetches all nsDef items from bottom to top - left to right, ignoring prefixes already in the list.
466463
* We don't have nsDef, but we can use the ns pointer (as that is necessarily in scope),
467464
* and check the xmlns attributes. */
468465
HashTable tmp_prefix_to_ns_table;
466+
467+
/* libxml fetches all nsDef items from bottom to top - left to right, ignoring prefixes already in the list.
468+
* We don't have nsDef, but we can use the ns pointer (as that is necessarily in scope),
469+
* and check the xmlns attributes. */
469470
zend_hash_init(&tmp_prefix_to_ns_table, 0, NULL, NULL, false);
470471
zend_hash_real_init_mixed(&tmp_prefix_to_ns_table);
471472

@@ -474,15 +475,9 @@ PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_map
474475
/* Register namespace of element */
475476
if (cur->ns != NULL) {
476477
const char *prefix = (const char *) cur->ns->prefix;
477-
if (register_element_default_ns || prefix != NULL) {
478-
if (prefix == NULL) {
479-
zend_hash_str_add_ptr(&tmp_prefix_to_ns_table, "", 0, cur->ns);
480-
} else {
481-
zend_hash_str_add_ptr(&tmp_prefix_to_ns_table, prefix, strlen(prefix), cur->ns);
482-
}
478+
if (prefix != NULL) {
479+
zend_hash_str_add_ptr(&tmp_prefix_to_ns_table, prefix, strlen(prefix), cur->ns);
483480
}
484-
} else if (register_element_default_ns) {
485-
zend_hash_str_add_ptr(&tmp_prefix_to_ns_table, "", 0, NULL);
486481
}
487482

488483
/* Register xmlns attributes */
@@ -499,6 +494,8 @@ PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_map
499494
}
500495
}
501496

497+
php_dom_in_scope_ns in_scope_ns;
498+
in_scope_ns.origin_is_ns_compat = true;
502499
in_scope_ns.count = zend_hash_num_elements(&tmp_prefix_to_ns_table);
503500
in_scope_ns.list = safe_emalloc(in_scope_ns.count, sizeof(xmlNsPtr), 0);
504501

ext/dom/namespace_compat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ typedef struct _php_dom_in_scope_ns {
7070
bool origin_is_ns_compat;
7171
} php_dom_in_scope_ns;
7272

73-
PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_mapper *ns_mapper, const xmlNode *node, bool register_element_default_ns);
73+
PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns(php_dom_libxml_ns_mapper *ns_mapper, const xmlNode *node);
7474
PHP_DOM_EXPORT php_dom_in_scope_ns php_dom_get_in_scope_ns_legacy(const xmlNode *node);
7575
PHP_DOM_EXPORT void php_dom_in_scope_ns_destroy(php_dom_in_scope_ns *in_scope_ns);
7676

ext/dom/php_dom.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ PHP_DOM_EXPORT zend_class_entry *dom_xpath_class_entry;
8282
PHP_DOM_EXPORT zend_class_entry *dom_modern_xpath_class_entry;
8383
#endif
8484
PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry;
85+
PHP_DOM_EXPORT zend_class_entry *dom_namespace_info_class_entry;
8586
/* }}} */
8687

8788
static zend_object_handlers dom_object_handlers;
@@ -802,6 +803,8 @@ PHP_MINIT_FUNCTION(dom)
802803
DOM_REGISTER_PROP_HANDLER(&dom_namespace_node_prop_handlers, "parentElement", dom_node_parent_element_read, NULL);
803804
zend_hash_add_new_ptr(&classes, dom_namespace_node_class_entry->name, &dom_namespace_node_prop_handlers);
804805

806+
dom_namespace_info_class_entry = register_class_DOM_NamespaceInfo();
807+
805808
dom_documentfragment_class_entry = register_class_DOMDocumentFragment(dom_node_class_entry, dom_parentnode_class_entry);
806809
dom_documentfragment_class_entry->create_object = dom_objects_new;
807810
dom_documentfragment_class_entry->default_object_handlers = &dom_object_handlers;

ext/dom/php_dom.stub.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,7 @@ public function replaceChildren(Node|string ...$nodes): void {}
13761376

13771377
public string $substitutedNodeValue;
13781378

1379+
/** @return list<NamespaceInfo> */
13791380
public function getInScopeNamespaces(): array {}
13801381

13811382
public function rename(?string $namespaceURI, string $qualifiedName): void {}
@@ -1645,6 +1646,20 @@ public function saveXML(?Node $node = null, int $options = 0): string|false {}
16451646
public function saveXMLFile(string $filename, int $options = 0): int|false {}
16461647
}
16471648

1649+
/**
1650+
* @not-serializable
1651+
* @strict-properties
1652+
*/
1653+
final class NamespaceInfo
1654+
{
1655+
public readonly ?string $prefix;
1656+
public readonly ?string $namespaceURI;
1657+
public readonly Element $element;
1658+
1659+
/** @implementation-alias DOM\Node::__construct */
1660+
private function __construct() {}
1661+
}
1662+
16481663
#ifdef LIBXML_XPATH_ENABLED
16491664
/** @not-serializable */
16501665
final class XPath

ext/dom/php_dom_arginfo.h

Lines changed: 38 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/dom/tests/modern/extensions/Element_getInScopeNamespaces.phpt

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ dom
55
--FILE--
66
<?php
77

8+
function dump($dom, $name) {
9+
echo "\n=== $name ===\n";
10+
$list = $dom->getElementsByTagName($name)[0]->getInScopeNamespaces();
11+
foreach ($list as $entry) {
12+
echo "prefix: ";
13+
var_dump($entry->prefix);
14+
echo "namespaceURI: ";
15+
var_dump($entry->namespaceURI);
16+
echo "element->nodeName: ";
17+
var_dump($entry->element->nodeName);
18+
echo "---\n";
19+
}
20+
}
21+
822
$dom = DOM\XMLDocument::createFromString(<<<XML
923
<root xmlns="urn:a">
1024
<child xmlns="">
@@ -16,41 +30,52 @@ $dom = DOM\XMLDocument::createFromString(<<<XML
1630
</root>
1731
XML);
1832

19-
var_dump($dom->getElementsByTagName('c:child')[0]->getInScopeNamespaces());
20-
var_dump($dom->getElementsByTagName('child')[0]->getInScopeNamespaces());
21-
var_dump($dom->getElementsByTagName('b:sibling')[0]->getInScopeNamespaces());
22-
var_dump($dom->getElementsByTagName('d:child')[0]->getInScopeNamespaces());
23-
var_dump($dom->documentElement->getInScopeNamespaces());
33+
dump($dom, 'c:child');
34+
dump($dom, 'child');
35+
dump($dom, 'b:sibling');
36+
dump($dom, 'd:child');
37+
dump($dom, 'root');
2438

2539
?>
2640
--EXPECT--
27-
array(2) {
28-
["c"]=>
29-
string(5) "urn:c"
30-
[""]=>
31-
NULL
32-
}
33-
array(1) {
34-
[""]=>
35-
NULL
36-
}
37-
array(3) {
38-
["b"]=>
39-
string(5) "urn:b"
40-
["d"]=>
41-
string(5) "urn:d"
42-
[""]=>
43-
string(5) "urn:a"
44-
}
45-
array(3) {
46-
["d"]=>
47-
string(6) "urn:d2"
48-
["b"]=>
49-
string(5) "urn:b"
50-
[""]=>
51-
string(5) "urn:a"
52-
}
53-
array(1) {
54-
[""]=>
55-
string(5) "urn:a"
56-
}
41+
=== c:child ===
42+
prefix: string(1) "c"
43+
namespaceURI: string(5) "urn:c"
44+
element->nodeName: string(7) "c:child"
45+
---
46+
47+
=== child ===
48+
49+
=== b:sibling ===
50+
prefix: string(1) "b"
51+
namespaceURI: string(5) "urn:b"
52+
element->nodeName: string(9) "b:sibling"
53+
---
54+
prefix: string(1) "d"
55+
namespaceURI: string(5) "urn:d"
56+
element->nodeName: string(9) "b:sibling"
57+
---
58+
prefix: NULL
59+
namespaceURI: string(5) "urn:a"
60+
element->nodeName: string(9) "b:sibling"
61+
---
62+
63+
=== d:child ===
64+
prefix: string(1) "d"
65+
namespaceURI: string(6) "urn:d2"
66+
element->nodeName: string(7) "d:child"
67+
---
68+
prefix: string(1) "b"
69+
namespaceURI: string(5) "urn:b"
70+
element->nodeName: string(7) "d:child"
71+
---
72+
prefix: NULL
73+
namespaceURI: string(5) "urn:a"
74+
element->nodeName: string(7) "d:child"
75+
---
76+
77+
=== root ===
78+
prefix: NULL
79+
namespaceURI: string(5) "urn:a"
80+
element->nodeName: string(4) "root"
81+
---

ext/dom/xpath.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type, bool modern)
287287
if (register_node_ns && nodep != NULL) {
288288
if (modern) {
289289
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(&intern->dom);
290-
in_scope_ns = php_dom_get_in_scope_ns(ns_mapper, nodep, false);
290+
in_scope_ns = php_dom_get_in_scope_ns(ns_mapper, nodep);
291291
} else {
292292
in_scope_ns = php_dom_get_in_scope_ns_legacy(nodep);
293293
}

0 commit comments

Comments
 (0)