Skip to content

Commit 70f5af1

Browse files
committed
Introduce NamespaceInfo and add getDescendantNamespaces
1 parent 36d9cd2 commit 70f5af1

File tree

10 files changed

+350
-62
lines changed

10 files changed

+350
-62
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: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,6 +1742,59 @@ zend_result dom_modern_element_substituted_node_value_write(dom_object *obj, zva
17421742
return SUCCESS;
17431743
}
17441744

1745+
static void dom_element_get_in_scope_namespace_info(php_dom_libxml_ns_mapper *ns_mapper, HashTable *result, xmlNodePtr nodep, dom_object *intern)
1746+
{
1747+
HashTable prefix_to_ns_table;
1748+
zend_hash_init(&prefix_to_ns_table, 0, NULL, NULL, false);
1749+
zend_hash_real_init_mixed(&prefix_to_ns_table);
1750+
1751+
/* https://www.w3.org/TR/1999/REC-xpath-19991116/#namespace-nodes */
1752+
for (const xmlNode *cur = nodep; cur != NULL; cur = cur->parent) {
1753+
if (cur->type == XML_ELEMENT_NODE) {
1754+
for (const xmlAttr *attr = cur->properties; attr != NULL; attr = attr->next) {
1755+
if (attr->ns != NULL && php_dom_ns_is_fast_ex(attr->ns, php_dom_ns_is_xmlns_magic_token)
1756+
&& attr->children != NULL && attr->children->content != NULL) {
1757+
const char *prefix = attr->ns->prefix == NULL ? NULL : (const char *) attr->name;
1758+
const char *key = prefix == NULL ? "" : prefix;
1759+
xmlNsPtr ns = php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, prefix, (const char *) attr->children->content);
1760+
zend_hash_str_add_ptr(&prefix_to_ns_table, key, strlen(key), ns);
1761+
}
1762+
}
1763+
}
1764+
}
1765+
1766+
xmlNsPtr ns;
1767+
zend_string *prefix;
1768+
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&prefix_to_ns_table, prefix, ns) {
1769+
if (ZSTR_LEN(prefix) == 0 && (ns == NULL || ns->href == NULL || *ns->href == '\0')) {
1770+
/* Exception: "the value of the xmlns attribute for the nearest such element is non-empty" */
1771+
continue;
1772+
}
1773+
1774+
zval zv;
1775+
object_init_ex(&zv, dom_namespace_info_class_entry);
1776+
zend_object *obj = Z_OBJ(zv);
1777+
1778+
if (ZSTR_LEN(prefix) != 0) {
1779+
ZVAL_STR_COPY(OBJ_PROP_NUM(obj, 0), prefix);
1780+
} else {
1781+
ZVAL_NULL(OBJ_PROP_NUM(obj, 0));
1782+
}
1783+
1784+
if (ns != NULL && ns->href != NULL && *ns->href != '\0') {
1785+
ZVAL_STRING(OBJ_PROP_NUM(obj, 1), (const char *) ns->href);
1786+
} else {
1787+
ZVAL_NULL(OBJ_PROP_NUM(obj, 1));
1788+
}
1789+
1790+
php_dom_create_object(nodep, OBJ_PROP_NUM(obj, 2), intern);
1791+
1792+
zend_hash_next_index_insert_new(result, &zv);
1793+
} ZEND_HASH_FOREACH_END();
1794+
1795+
zend_hash_destroy(&prefix_to_ns_table);
1796+
}
1797+
17451798
PHP_METHOD(DOM_Element, getInScopeNamespaces)
17461799
{
17471800
zval *id;
@@ -1755,22 +1808,47 @@ PHP_METHOD(DOM_Element, getInScopeNamespaces)
17551808
DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
17561809

17571810
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);
17591811

1760-
array_init_size(return_value, namespaces.count);
1812+
array_init(return_value);
1813+
HashTable *result = Z_ARRVAL_P(return_value);
17611814

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, "");
1767-
} 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));
1770-
}
1815+
dom_element_get_in_scope_namespace_info(ns_mapper, result, nodep, intern);
1816+
}
1817+
1818+
PHP_METHOD(DOM_Element, getDescendantNamespaces)
1819+
{
1820+
zval *id;
1821+
xmlNode *nodep;
1822+
dom_object *intern;
1823+
1824+
if (zend_parse_parameters_none() != SUCCESS) {
1825+
RETURN_THROWS();
17711826
}
17721827

1773-
php_dom_in_scope_ns_destroy(&namespaces);
1828+
DOM_GET_THIS_OBJ(nodep, id, xmlNodePtr, intern);
1829+
1830+
php_dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
1831+
1832+
array_init(return_value);
1833+
HashTable *result = Z_ARRVAL_P(return_value);
1834+
1835+
dom_element_get_in_scope_namespace_info(ns_mapper, result, nodep, intern);
1836+
1837+
xmlNodePtr cur = nodep->children;
1838+
while (cur != NULL) {
1839+
if (cur->type == XML_ELEMENT_NODE) {
1840+
/* TODO: this could be more optimized by updating the same HashTable repeatedly
1841+
* instead of recreating it on every node. */
1842+
dom_element_get_in_scope_namespace_info(ns_mapper, result, cur, intern);
1843+
1844+
if (cur->children) {
1845+
cur = cur->children;
1846+
continue;
1847+
}
1848+
}
1849+
1850+
cur = php_dom_next_in_tree_order(cur, nodep);
1851+
}
17741852
}
17751853

17761854
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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,8 +1376,12 @@ public function replaceChildren(Node|string ...$nodes): void {}
13761376

13771377
public string $substitutedNodeValue;
13781378

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

1382+
/** @return list<NamespaceInfo> */
1383+
public function getDescendantNamespaces(): array {}
1384+
13811385
public function rename(?string $namespaceURI, string $qualifiedName): void {}
13821386
}
13831387

@@ -1645,6 +1649,20 @@ public function saveXML(?Node $node = null, int $options = 0): string|false {}
16451649
public function saveXMLFile(string $filename, int $options = 0): int|false {}
16461650
}
16471651

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

ext/dom/php_dom_arginfo.h

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

0 commit comments

Comments
 (0)