Skip to content

Commit b27984b

Browse files
committed
New namespace compatibility implementation
1 parent 12a106b commit b27984b

17 files changed

+425
-257
lines changed

ext/dom/document.c

+9-9
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,9 @@ PHP_METHOD(DOM_Document, createElement)
505505
}
506506

507507
if (docp->type == XML_HTML_DOCUMENT_NODE && php_dom_follow_spec_intern(intern)) {
508+
dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
508509
char *lower = zend_str_tolower_dup_ex(ZSTR_VAL(name), ZSTR_LEN(name));
509-
node = xmlNewDocRawNode(docp, dom_ns_fast_get_html_ns(docp), BAD_CAST (lower ? lower : ZSTR_VAL(name)), BAD_CAST value);
510+
node = xmlNewDocRawNode(docp, dom_libxml_ns_mapper_ensure_html_ns(ns_mapper), BAD_CAST (lower ? lower : ZSTR_VAL(name)), BAD_CAST value);
510511
if (lower) {
511512
efree(lower);
512513
}
@@ -834,7 +835,7 @@ PHP_METHOD(DOM_Document, importNode)
834835
if (nodep->doc == docp) {
835836
retnodep = nodep;
836837
} else {
837-
retnodep = dom_clone_node(nodep, docp, recursive);
838+
retnodep = dom_clone_node(php_dom_follow_spec_intern(intern) ? php_dom_get_ns_mapper(intern) : NULL, nodep, docp, recursive, php_dom_follow_spec_intern(intern));
838839
if (!retnodep) {
839840
RETURN_FALSE;
840841
}
@@ -896,7 +897,8 @@ PHP_METHOD(DOM_Document, createElementNS)
896897
if (errorcode == 0) {
897898
nodep = xmlNewDocRawNode(docp, NULL, localname, BAD_CAST value);
898899
if (EXPECTED(nodep != NULL)) {
899-
nodep->ns = dom_ns_create_local_as_is(docp, nodep, xmlDocGetRootElement(docp), uri ? ZSTR_VAL(uri) : NULL, prefix);
900+
dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
901+
nodep->ns = dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
900902
}
901903
}
902904

@@ -982,7 +984,8 @@ PHP_METHOD(DOM_Document, createAttributeNS)
982984

983985
if (uri != NULL && ZSTR_LEN(uri) > 0) {
984986
if (php_dom_follow_spec_intern(intern)) {
985-
nsptr = dom_ns_create_local_as_is(docp, root, root, ZSTR_VAL(uri), prefix);
987+
dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
988+
nsptr = dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
986989
} else {
987990
nsptr = xmlSearchNsByHref(docp, root, BAD_CAST ZSTR_VAL(uri));
988991

@@ -1160,8 +1163,8 @@ bool php_dom_adopt_node(xmlNodePtr nodep, dom_object *dom_object_new_document, x
11601163
if (php_dom_follow_spec_intern(dom_object_new_document)) {
11611164
xmlUnlinkNode(nodep);
11621165
xmlSetTreeDoc(nodep, new_document);
1163-
dom_libxml_reconcile_modern(nodep);
1164-
1166+
dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(dom_object_new_document);
1167+
dom_libxml_reconcile_modern(ns_mapper, nodep);
11651168
#if LIBXML_VERSION < 21000
11661169
libxml_fixup_name_and_content_element(original_document, new_document, nodep);
11671170
#endif
@@ -1471,15 +1474,13 @@ static void php_dom_finish_loading_document(zval *this, zval *return_value, xmlD
14711474
size_t old_modification_nr = 0;
14721475
if (intern != NULL) {
14731476
bool is_modern_api_class = false;
1474-
php_libxml_node_detach_reconcile_func reconcile_func = NULL;
14751477
xmlDocPtr docp = (xmlDocPtr) dom_object_get_node(intern);
14761478
dom_doc_propsptr doc_prop = NULL;
14771479
if (docp != NULL) {
14781480
const php_libxml_ref_obj *doc_ptr = intern->document;
14791481
ZEND_ASSERT(doc_ptr != NULL); /* Must exist, we have a document */
14801482
is_modern_api_class = doc_ptr->is_modern_api_class;
14811483
old_modification_nr = doc_ptr->cache_tag.modification_nr;
1482-
reconcile_func = doc_ptr->node_detach_reconcile_func;
14831484
php_libxml_decrement_node_ptr((php_libxml_node_object *) intern);
14841485
doc_prop = intern->document->doc_props;
14851486
intern->document->doc_props = NULL;
@@ -1494,7 +1495,6 @@ static void php_dom_finish_loading_document(zval *this, zval *return_value, xmlD
14941495
}
14951496
intern->document->doc_props = doc_prop;
14961497
intern->document->is_modern_api_class = is_modern_api_class;
1497-
intern->document->node_detach_reconcile_func = reconcile_func;
14981498
}
14991499

15001500
php_libxml_increment_node_ptr((php_libxml_node_object *)intern, (xmlNodePtr)newdoc, (void *)intern);

ext/dom/element.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,9 @@ static void dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAMETERS,
683683
}
684684

685685
xmlAddChild(nodep, (xmlNodePtr) attrp);
686-
php_dom_reconcile_attribute_namespace_after_insertion(attrp, !follow_spec);
686+
if (!follow_spec) {
687+
php_dom_reconcile_attribute_namespace_after_insertion(attrp);
688+
}
687689

688690
/* Returns old property if removed otherwise NULL */
689691
if (existattrp != NULL) {
@@ -920,7 +922,8 @@ static void dom_set_attribute_ns_modern(dom_object *intern, xmlNodePtr elemp, ze
920922
int errorcode = dom_validate_and_extract(uri, name, &localname, &prefix);
921923

922924
if (errorcode == 0) {
923-
xmlNsPtr ns = dom_ns_create_local_as_is(elemp->doc, elemp, elemp, uri ? ZSTR_VAL(uri) : NULL, prefix);
925+
dom_libxml_ns_mapper *ns_mapper = php_dom_get_ns_mapper(intern);
926+
xmlNsPtr ns = dom_libxml_ns_mapper_get_ns_raw_prefix_string(ns_mapper, prefix, xmlStrlen(prefix), uri);
924927
if (UNEXPECTED(xmlSetNsProp(elemp, ns, localname, BAD_CAST value) == NULL)) {
925928
php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
926929
}

ext/dom/html5_parser.c

+30-16
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
#include "php.h"
2222
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
2323
#include "html5_parser.h"
24-
#include "namespace_compat.h"
2524
#include <lexbor/html/parser.h>
2625
#include <lexbor/html/interfaces/element.h>
2726
#include <libxml/tree.h>
@@ -98,11 +97,16 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
9897
lxb_dom_node_t *start_node,
9998
xmlDocPtr lxml_doc,
10099
bool compact_text_nodes,
101-
bool create_default_ns
100+
bool create_default_ns,
101+
dom_libxml_ns_mapper *ns_mapper
102102
)
103103
{
104104
lexbor_libxml2_bridge_status retval = LEXBOR_LIBXML2_BRIDGE_STATUS_OK;
105105

106+
xmlNsPtr html_ns = dom_libxml_ns_mapper_ensure_html_ns(ns_mapper);
107+
xmlNsPtr xlink_ns = NULL;
108+
xmlNsPtr prefixed_xmlns_ns = NULL;
109+
106110
lexbor_array_obj_t work_list;
107111
lexbor_array_obj_init(&work_list, WORK_LIST_INIT_SIZE, sizeof(work_list_item));
108112

@@ -135,10 +139,16 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
135139
uintptr_t entering_namespace = element->node.ns;
136140
xmlNsPtr current_lxml_ns = current_stack_item->lxml_ns;
137141
if (create_default_ns && UNEXPECTED(entering_namespace != current_stack_item->current_active_namespace)) {
138-
const dom_ns_magic_token *magic_token = get_libxml_namespace_href(entering_namespace);
139-
current_lxml_ns = xmlNewNs(lxml_element, BAD_CAST magic_token, NULL);
140-
if (EXPECTED(current_lxml_ns != NULL)) {
141-
current_lxml_ns->_private = (void *) magic_token;
142+
if (entering_namespace == LXB_NS_HTML) {
143+
current_lxml_ns = html_ns;
144+
} else {
145+
const dom_ns_magic_token *magic_token = get_libxml_namespace_href(entering_namespace);
146+
zend_string *uri = zend_string_init((char *) magic_token, strlen((char *) magic_token), false);
147+
current_lxml_ns = dom_libxml_ns_mapper_get_ns(ns_mapper, NULL, uri);
148+
zend_string_release_ex(uri, false);
149+
if (EXPECTED(current_lxml_ns != NULL)) {
150+
current_lxml_ns->_private = (void *) magic_token;
151+
}
142152
}
143153
}
144154
/* Instead of xmlSetNs() because we know the arguments are valid. Prevents overhead. */
@@ -189,18 +199,20 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
189199

190200
if (attr->node.ns == LXB_NS_XMLNS) {
191201
if (strcmp((const char *) local_name, "xmlns") != 0) {
192-
lxml_attr->ns = dom_ns_create_local_as_is(lxml_doc, lxml_element, lxml_element, DOM_XMLNS_NS_URI, BAD_CAST "xmlns");
202+
if (prefixed_xmlns_ns == NULL) {
203+
prefixed_xmlns_ns = dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xmlns", DOM_XMLNS_NS_URI);
204+
}
205+
lxml_attr->ns = prefixed_xmlns_ns;
193206
} else {
194-
lxml_attr->ns = dom_ns_create_local_as_is(lxml_doc, lxml_element, lxml_element, DOM_XMLNS_NS_URI, NULL);
195-
}
196-
if (EXPECTED(lxml_attr->ns != NULL)) {
197-
lxml_attr->ns->_private = (void *) dom_ns_is_xmlns_magic_token;
207+
lxml_attr->ns = dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(ns_mapper);
198208
}
209+
lxml_attr->ns->_private = (void *) dom_ns_is_xmlns_magic_token;
199210
} else if (attr->node.ns == LXB_NS_XLINK) {
200-
lxml_attr->ns = dom_ns_create_local_as_is(lxml_doc, lxml_element, lxml_element, DOM_XLINK_NS_URI, BAD_CAST "xlink");
201-
if (EXPECTED(lxml_attr->ns != NULL)) {
202-
lxml_attr->ns->_private = (void *) dom_ns_is_xlink_magic_token;
211+
if (xlink_ns == NULL) {
212+
xlink_ns = dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(ns_mapper, "xlink", DOM_XLINK_NS_URI);
213+
xlink_ns->_private = (void *) dom_ns_is_xlink_magic_token;
203214
}
215+
lxml_attr->ns = xlink_ns;
204216
}
205217

206218
if (last_added_attr == NULL) {
@@ -291,7 +303,8 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document(
291303
lxb_html_document_t *document,
292304
xmlDocPtr *doc_out,
293305
bool compact_text_nodes,
294-
bool create_default_ns
306+
bool create_default_ns,
307+
dom_libxml_ns_mapper *ns_mapper
295308
)
296309
{
297310
#ifdef LIBXML_HTML_ENABLED
@@ -313,7 +326,8 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document(
313326
lxb_dom_interface_node(document)->last_child,
314327
lxml_doc,
315328
compact_text_nodes,
316-
create_default_ns
329+
create_default_ns,
330+
ns_mapper
317331
);
318332
if (status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK) {
319333
xmlFreeDoc(lxml_doc);

ext/dom/html5_parser.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifndef HTML5_PARSER_H
1818
#define HTML5_PARSER_H
1919

20+
#include "namespace_compat.h"
2021
#include <lexbor/html/parser.h>
2122
#include <libxml/tree.h>
2223
#include <Zend/zend_portability.h>
@@ -68,7 +69,8 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document(
6869
lxb_html_document_t *document,
6970
xmlDocPtr *doc_out,
7071
bool compact_text_nodes,
71-
bool create_default_ns
72+
bool create_default_ns,
73+
dom_libxml_ns_mapper *ns_mapper
7274
);
7375
void lexbor_libxml2_bridge_report_errors(
7476
const lexbor_libxml2_bridge_parse_context *ctx,

ext/dom/html_document.c

+16-21
Original file line numberDiff line numberDiff line change
@@ -350,23 +350,7 @@ static void dom_post_process_html5_loading(
350350
dom_place_remove_element_and_hoist_children(html_node, "body");
351351
}
352352
if (!observations->has_explicit_html_tag) {
353-
/* The HTML node has a single namespace declaration, that we must preserve after removing the node.
354-
* However, it's possible the namespace is NULL if DOM\HTML_NO_DEFAULT_NS was set. */
355-
if (!(options & DOM_HTML_NO_DEFAULT_NS)) {
356-
php_libxml_set_old_ns(lxml_doc, html_node->nsDef);
357-
html_node->nsDef = NULL;
358-
}
359353
dom_place_remove_element_and_hoist_children((xmlNodePtr) lxml_doc, "html");
360-
if (!(options & DOM_HTML_NO_DEFAULT_NS) && EXPECTED(lxml_doc->children != NULL)) {
361-
xmlNodePtr node = lxml_doc->children;
362-
while (node) {
363-
/* Fine to use the DOM wrap reconciliation here because it's the "modern" world of DOM,
364-
* and no user manipulation happened yet. */
365-
xmlDOMWrapCtxt dummy_ctxt = {0};
366-
xmlDOMWrapReconcileNamespaces(&dummy_ctxt, node, /* options */ 0);
367-
node = node->next;
368-
}
369-
}
370354
}
371355
}
372356
}
@@ -741,7 +725,7 @@ PHP_METHOD(DOM_HTMLDocument, createEmpty)
741725
NULL
742726
);
743727
intern->document->is_modern_api_class = true;
744-
intern->document->node_detach_reconcile_func = dom_libxml_reconcile_modern;
728+
intern->document->private_data = dom_libxml_ns_mapper_header(dom_libxml_ns_mapper_create());
745729
return;
746730

747731
oom:
@@ -852,15 +836,19 @@ PHP_METHOD(DOM_HTMLDocument, createFromString)
852836
goto fail_oom;
853837
}
854838

839+
dom_libxml_ns_mapper *ns_mapper = dom_libxml_ns_mapper_create();
840+
855841
xmlDocPtr lxml_doc;
856842
lexbor_libxml2_bridge_status bridge_status = lexbor_libxml2_bridge_convert_document(
857843
document,
858844
&lxml_doc,
859845
options & XML_PARSE_COMPACT,
860-
!(options & DOM_HTML_NO_DEFAULT_NS)
846+
!(options & DOM_HTML_NO_DEFAULT_NS),
847+
ns_mapper
861848
);
862849
lexbor_libxml2_bridge_copy_observations(parser->tree, &ctx.observations);
863850
if (UNEXPECTED(bridge_status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) {
851+
dom_libxml_ns_mapper_destroy(ns_mapper);
864852
php_libxml_ctx_error(
865853
NULL,
866854
"%s in %s",
@@ -887,7 +875,7 @@ PHP_METHOD(DOM_HTMLDocument, createFromString)
887875
NULL
888876
);
889877
intern->document->is_modern_api_class = true;
890-
intern->document->node_detach_reconcile_func = dom_libxml_reconcile_modern;
878+
intern->document->private_data = dom_libxml_ns_mapper_header(ns_mapper);
891879
return;
892880

893881
fail_oom:
@@ -899,6 +887,7 @@ PHP_METHOD(DOM_HTMLDocument, createFromString)
899887
PHP_METHOD(DOM_HTMLDocument, createFromFile)
900888
{
901889
const char *filename, *override_encoding = NULL;
890+
dom_libxml_ns_mapper *ns_mapper = NULL;
902891
size_t filename_len, override_encoding_len;
903892
zend_long options = 0;
904893
php_stream *stream = NULL;
@@ -1037,12 +1026,15 @@ PHP_METHOD(DOM_HTMLDocument, createFromFile)
10371026
goto fail_oom;
10381027
}
10391028

1029+
ns_mapper = dom_libxml_ns_mapper_create();
1030+
10401031
xmlDocPtr lxml_doc;
10411032
lexbor_libxml2_bridge_status bridge_status = lexbor_libxml2_bridge_convert_document(
10421033
document,
10431034
&lxml_doc,
10441035
options & XML_PARSE_COMPACT,
1045-
!(options & DOM_HTML_NO_DEFAULT_NS)
1036+
!(options & DOM_HTML_NO_DEFAULT_NS),
1037+
ns_mapper
10461038
);
10471039
lexbor_libxml2_bridge_copy_observations(parser->tree, &ctx.observations);
10481040
if (UNEXPECTED(bridge_status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) {
@@ -1103,12 +1095,15 @@ PHP_METHOD(DOM_HTMLDocument, createFromFile)
11031095
NULL
11041096
);
11051097
intern->document->is_modern_api_class = true;
1106-
intern->document->node_detach_reconcile_func = dom_libxml_reconcile_modern;
1098+
intern->document->private_data = dom_libxml_ns_mapper_header(ns_mapper);
11071099
return;
11081100

11091101
fail_oom:
11101102
php_dom_throw_error(INVALID_STATE_ERR, 1);
11111103
fail_general:
1104+
if (ns_mapper != NULL) {
1105+
dom_libxml_ns_mapper_destroy(ns_mapper);
1106+
}
11121107
lxb_html_document_destroy(document);
11131108
php_stream_close(stream);
11141109
if (opened_path != NULL) {

ext/dom/internal_macros.h

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| [email protected] so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Authors: Niels Dossche <[email protected]> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifndef DOM_INTERNAL_MACROS
18+
#define DOM_INTERNAL_MACROS
19+
20+
/* We're using the type flags of the zval to store an extra flag. */
21+
#define DOM_Z_OWNED(z, v) ZVAL_PTR(z, (void *) v)
22+
#define DOM_Z_UNOWNED(z, v) ZVAL_INDIRECT(z, (void *) v)
23+
#define DOM_Z_IS_OWNED(z) (Z_TYPE_P(z) == IS_PTR)
24+
25+
#endif

0 commit comments

Comments
 (0)