From 0dc6e42920cc8eb69355020ebefb1b5f0233b152 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:08:38 +0200 Subject: [PATCH 01/10] Split off private data from the ns mapper --- ext/dom/domimplementation.c | 14 +++++++------ ext/dom/html_document.c | 20 ++++++++++-------- ext/dom/namespace_compat.c | 42 ++++++++++++++++++++++++------------- ext/dom/namespace_compat.h | 17 +++++++++------ ext/dom/node.c | 16 +++++++------- ext/dom/php_dom.c | 12 +++++------ ext/dom/php_dom.h | 3 +++ ext/dom/xml_common.h | 8 +++---- ext/dom/xml_document.c | 7 ++++--- 9 files changed, 83 insertions(+), 56 deletions(-) diff --git a/ext/dom/domimplementation.c b/ext/dom/domimplementation.c index 58a50b06d074e..710232d78286e 100644 --- a/ext/dom/domimplementation.c +++ b/ext/dom/domimplementation.c @@ -266,7 +266,8 @@ PHP_METHOD(Dom_Implementation, createDocument) xmlDocPtr document = NULL; xmlChar *localname = NULL, *prefix = NULL; - php_dom_libxml_ns_mapper *ns_mapper = php_dom_libxml_ns_mapper_create(); + php_dom_private_data *private_data = php_dom_private_data_create(); + php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); /* 1. Let document be a new XMLDocument. */ document = xmlNewDoc(BAD_CAST "1.0"); @@ -307,7 +308,7 @@ PHP_METHOD(Dom_Implementation, createDocument) NULL ); dom_set_xml_class(intern->document); - intern->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper); + intern->document->private_data = php_dom_libxml_private_data_header(private_data); /* 4. If doctype is non-null, append doctype to document. */ if (doctype != NULL) { @@ -337,7 +338,7 @@ PHP_METHOD(Dom_Implementation, createDocument) xmlFree(localname); xmlFree(prefix); xmlFreeDoc(document); - php_dom_libxml_ns_mapper_destroy(ns_mapper); + php_dom_private_data_destroy(private_data); RETURN_THROWS(); } /* }}} end dom_domimplementation_create_document */ @@ -366,7 +367,8 @@ PHP_METHOD(Dom_Implementation, createHTMLDocument) /* 3. Append a new doctype, with "html" as its name and with its node document set to doc, to doc. */ xmlDtdPtr dtd = xmlCreateIntSubset(doc, BAD_CAST "html", NULL, NULL); - php_dom_libxml_ns_mapper *ns_mapper = php_dom_libxml_ns_mapper_create(); + php_dom_private_data *private_data = php_dom_private_data_create(); + php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); xmlNsPtr html_ns = php_dom_libxml_ns_mapper_ensure_html_ns(ns_mapper); /* 4. Append the result of creating an element given doc, html, and the HTML namespace, to doc. */ @@ -396,7 +398,7 @@ PHP_METHOD(Dom_Implementation, createHTMLDocument) if (UNEXPECTED(dtd == NULL || html_element == NULL || head_element == NULL || (title != NULL && title_element == NULL) || body_element == NULL)) { php_dom_throw_error(INVALID_STATE_ERR, true); xmlFreeDoc(doc); - php_dom_libxml_ns_mapper_destroy(ns_mapper); + php_dom_private_data_destroy(private_data); RETURN_THROWS(); } @@ -408,7 +410,7 @@ PHP_METHOD(Dom_Implementation, createHTMLDocument) NULL ); dom_set_xml_class(intern->document); - intern->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper); + intern->document->private_data = php_dom_libxml_private_data_header(private_data); } /* }}} */ diff --git a/ext/dom/html_document.c b/ext/dom/html_document.c index c8a805318c356..4c451f8a525ed 100644 --- a/ext/dom/html_document.c +++ b/ext/dom/html_document.c @@ -757,7 +757,7 @@ PHP_METHOD(Dom_HTMLDocument, createEmpty) NULL ); dom_set_xml_class(intern->document); - intern->document->private_data = php_dom_libxml_ns_mapper_header(php_dom_libxml_ns_mapper_create()); + intern->document->private_data = php_dom_libxml_private_data_header(php_dom_private_data_create()); return; oom: @@ -878,7 +878,8 @@ PHP_METHOD(Dom_HTMLDocument, createFromString) goto fail_oom; } - php_dom_libxml_ns_mapper *ns_mapper = php_dom_libxml_ns_mapper_create(); + php_dom_private_data *private_data = php_dom_private_data_create(); + php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); xmlDocPtr lxml_doc; lexbor_libxml2_bridge_status bridge_status = lexbor_libxml2_bridge_convert_document( @@ -890,7 +891,7 @@ PHP_METHOD(Dom_HTMLDocument, createFromString) ); lexbor_libxml2_bridge_copy_observations(parser->tree, &ctx.observations); if (UNEXPECTED(bridge_status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) { - php_dom_libxml_ns_mapper_destroy(ns_mapper); + php_dom_private_data_destroy(private_data); php_libxml_ctx_error( NULL, "%s in %s", @@ -918,7 +919,7 @@ PHP_METHOD(Dom_HTMLDocument, createFromString) ); dom_set_xml_class(intern->document); intern->document->quirks_mode = ctx.observations.quirks_mode; - intern->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper); + intern->document->private_data = php_dom_libxml_private_data_header(private_data); return; fail_oom: @@ -930,7 +931,7 @@ PHP_METHOD(Dom_HTMLDocument, createFromString) PHP_METHOD(Dom_HTMLDocument, createFromFile) { const char *filename, *override_encoding = NULL; - php_dom_libxml_ns_mapper *ns_mapper = NULL; + php_dom_private_data *private_data = NULL; size_t filename_len, override_encoding_len; zend_long options = 0; php_stream *stream = NULL; @@ -1069,7 +1070,8 @@ PHP_METHOD(Dom_HTMLDocument, createFromFile) goto fail_oom; } - ns_mapper = php_dom_libxml_ns_mapper_create(); + private_data = php_dom_private_data_create(); + php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); xmlDocPtr lxml_doc; lexbor_libxml2_bridge_status bridge_status = lexbor_libxml2_bridge_convert_document( @@ -1139,14 +1141,14 @@ PHP_METHOD(Dom_HTMLDocument, createFromFile) ); dom_set_xml_class(intern->document); intern->document->quirks_mode = ctx.observations.quirks_mode; - intern->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper); + intern->document->private_data = php_dom_libxml_private_data_header(private_data); return; fail_oom: php_dom_throw_error(INVALID_STATE_ERR, true); fail_general: - if (ns_mapper != NULL) { - php_dom_libxml_ns_mapper_destroy(ns_mapper); + if (private_data != NULL) { + php_dom_private_data_destroy(private_data); } lxb_html_document_destroy(document); php_stream_close(stream); diff --git a/ext/dom/namespace_compat.c b/ext/dom/namespace_compat.c index 51df10e2e087b..8472fd36c62b1 100644 --- a/ext/dom/namespace_compat.c +++ b/ext/dom/namespace_compat.c @@ -32,7 +32,6 @@ PHP_DOM_EXPORT const php_dom_ns_magic_token *php_dom_ns_is_xml_magic_token = (co PHP_DOM_EXPORT const php_dom_ns_magic_token *php_dom_ns_is_xmlns_magic_token = (const php_dom_ns_magic_token *) DOM_XMLNS_NS_URI; struct php_dom_libxml_ns_mapper { - php_libxml_private_data_header header; /* This is used almost all the time for HTML documents, so it makes sense to cache this. */ xmlNsPtr html_ns; /* Used for every prefixless namespace declaration in XML, so also very common. */ @@ -40,6 +39,11 @@ struct php_dom_libxml_ns_mapper { HashTable uri_to_prefix_map; }; +typedef struct php_dom_private_data { + php_libxml_private_data_header header; + struct php_dom_libxml_ns_mapper ns_mapper; +} php_dom_private_data; + static void php_dom_libxml_ns_mapper_prefix_map_element_dtor(zval *zv) { if (DOM_Z_IS_OWNED(zv)) { @@ -69,25 +73,25 @@ static HashTable *php_dom_libxml_ns_mapper_ensure_prefix_map(php_dom_libxml_ns_m return prefix_map; } -static void php_dom_libxml_ns_mapper_header_destroy(php_libxml_private_data_header *header) +static void php_dom_libxml_private_data_destroy(php_libxml_private_data_header *header) { - php_dom_libxml_ns_mapper_destroy((php_dom_libxml_ns_mapper *) header); + php_dom_private_data_destroy((php_dom_private_data *) header); } -PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_libxml_ns_mapper_create(void) +PHP_DOM_EXPORT php_dom_private_data *php_dom_private_data_create(void) { - php_dom_libxml_ns_mapper *mapper = emalloc(sizeof(*mapper)); - mapper->header.dtor = php_dom_libxml_ns_mapper_header_destroy; - mapper->html_ns = NULL; - mapper->prefixless_xmlns_ns = NULL; - zend_hash_init(&mapper->uri_to_prefix_map, 0, NULL, ZVAL_PTR_DTOR, false); + php_dom_private_data *mapper = emalloc(sizeof(*mapper)); + mapper->header.dtor = php_dom_libxml_private_data_destroy; + mapper->ns_mapper.html_ns = NULL; + mapper->ns_mapper.prefixless_xmlns_ns = NULL; + zend_hash_init(&mapper->ns_mapper.uri_to_prefix_map, 0, NULL, ZVAL_PTR_DTOR, false); return mapper; } -void php_dom_libxml_ns_mapper_destroy(php_dom_libxml_ns_mapper *mapper) +void php_dom_private_data_destroy(php_dom_private_data *data) { - zend_hash_destroy(&mapper->uri_to_prefix_map); - efree(mapper); + zend_hash_destroy(&data->ns_mapper.uri_to_prefix_map); + efree(data); } static xmlNsPtr php_dom_libxml_ns_mapper_ensure_cached_ns(php_dom_libxml_ns_mapper *mapper, xmlNsPtr *ptr, const char *uri, size_t length, const php_dom_ns_magic_token *token) @@ -229,9 +233,19 @@ static xmlNsPtr php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(php_dom_l return ns; } -PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_ns_mapper_header(php_dom_libxml_ns_mapper *mapper) +PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_private_data_header(php_dom_private_data *private_data) +{ + return private_data == NULL ? NULL : &private_data->header; +} + +PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *private_data) +{ + return private_data == NULL ? NULL : &private_data->ns_mapper; +} + +PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_get_ns_mapper(dom_object *object) { - return mapper == NULL ? NULL : &mapper->header; + return &php_dom_get_private_data(object)->ns_mapper; } typedef struct { diff --git a/ext/dom/namespace_compat.h b/ext/dom/namespace_compat.h index bf0a295e1bee6..5a7c3f7b71eae 100644 --- a/ext/dom/namespace_compat.h +++ b/ext/dom/namespace_compat.h @@ -33,6 +33,12 @@ typedef struct php_dom_ns_magic_token php_dom_ns_magic_token; struct php_dom_libxml_ns_mapper; typedef struct php_dom_libxml_ns_mapper php_dom_libxml_ns_mapper; +struct php_dom_private_data; +typedef struct php_dom_private_data php_dom_private_data; + +typedef struct php_libxml_private_data_header php_libxml_private_data_header; +struct php_libxml_private_data_header; + PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_html_magic_token; PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_mathml_magic_token; PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_svg_magic_token; @@ -40,21 +46,20 @@ PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_xlink_magic_to PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_xml_magic_token; PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_xmlns_magic_token; -typedef struct php_libxml_private_data_header php_libxml_private_data_header; -struct php_libxml_private_data_header; - /* These functions make it possible to make a namespace declaration also visible as an attribute by * creating an equivalent attribute node. */ -PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_libxml_ns_mapper_create(void); -PHP_DOM_EXPORT void php_dom_libxml_ns_mapper_destroy(php_dom_libxml_ns_mapper *mapper); +PHP_DOM_EXPORT php_dom_private_data *php_dom_private_data_create(void); +PHP_DOM_EXPORT void php_dom_private_data_destroy(php_dom_private_data *data); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_libxml_ns_mapper *mapper); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(php_dom_libxml_ns_mapper *mapper); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns(php_dom_libxml_ns_mapper *mapper, zend_string *prefix, zend_string *uri); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(php_dom_libxml_ns_mapper *mapper, const xmlChar *prefix, size_t prefix_len, zend_string *uri); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(php_dom_libxml_ns_mapper *mapper, const char *prefix, const char *uri); -PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_ns_mapper_header(php_dom_libxml_ns_mapper *mapper); +PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_private_data_header(php_dom_private_data *private_data); +PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_get_ns_mapper(dom_object *object); +PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *private_data); PHP_DOM_EXPORT void php_dom_ns_compat_mark_attribute_list(php_dom_libxml_ns_mapper *mapper, xmlNodePtr node); PHP_DOM_EXPORT void php_dom_libxml_reconcile_modern(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node); PHP_DOM_EXPORT void php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp); diff --git a/ext/dom/node.c b/ext/dom/node.c index ef669963de78b..ff7631825b498 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -1444,21 +1444,21 @@ PHP_METHOD(DOMNode, cloneNode) DOM_GET_OBJ(n, id, xmlNodePtr, intern); - php_dom_libxml_ns_mapper *ns_mapper = NULL; + php_dom_private_data *private_data = NULL; bool clone_document = n->type == XML_DOCUMENT_NODE || n->type == XML_HTML_DOCUMENT_NODE; if (php_dom_follow_spec_intern(intern)) { if (clone_document) { - ns_mapper = php_dom_libxml_ns_mapper_create(); + private_data = php_dom_private_data_create(); } else { - ns_mapper = php_dom_get_ns_mapper(intern); + private_data = php_dom_get_private_data(intern); } } - node = dom_clone_node(ns_mapper, n, n->doc, recursive); + node = dom_clone_node(php_dom_ns_mapper_from_private(private_data), n, n->doc, recursive); if (!node) { - if (clone_document && ns_mapper != NULL) { - php_dom_libxml_ns_mapper_destroy(ns_mapper); + if (clone_document && private_data != NULL) { + php_dom_private_data_destroy(private_data); } RETURN_FALSE; } @@ -1466,7 +1466,7 @@ PHP_METHOD(DOMNode, cloneNode) /* If document cloned we want a new document proxy */ if (clone_document) { dom_object *new_intern; - if (ns_mapper) { + if (private_data) { /* We have the issue here that we can't create a modern node without an intern. * Fortunately, it's impossible to have a custom document class for the modern DOM (final base class), * so we can solve this by invoking the instantiation helper directly. */ @@ -1478,7 +1478,7 @@ PHP_METHOD(DOMNode, cloneNode) } php_dom_update_document_after_clone(intern, n, new_intern, node); ZEND_ASSERT(new_intern->document->private_data == NULL); - new_intern->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper); + new_intern->document->private_data = php_dom_libxml_private_data_header(private_data); } else { if (node->type == XML_ATTRIBUTE_NODE && n->ns != NULL && node->ns == NULL) { /* Let reconciliation deal with this. The lifetime of the namespace poses no problem diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index e6e7a23600d94..cb6439fe263c0 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -593,21 +593,21 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */ if (instanceof_function(intern->std.ce, dom_node_class_entry) || instanceof_function(intern->std.ce, dom_modern_node_class_entry)) { xmlNodePtr node = (xmlNodePtr)dom_object_get_node(intern); if (node != NULL) { - php_dom_libxml_ns_mapper *ns_mapper = NULL; + php_dom_private_data *private_data = NULL; if (php_dom_follow_spec_intern(intern)) { if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) { - ns_mapper = php_dom_libxml_ns_mapper_create(); + private_data = php_dom_private_data_create(); } else { - ns_mapper = php_dom_get_ns_mapper(intern); + private_data = php_dom_get_private_data(intern); } } - xmlNodePtr cloned_node = dom_clone_node(ns_mapper, node, node->doc, true); + xmlNodePtr cloned_node = dom_clone_node(php_dom_ns_mapper_from_private(private_data), node, node->doc, true); if (cloned_node != NULL) { dom_update_refcount_after_clone(intern, node, clone, cloned_node); } - if (ns_mapper != NULL) { - clone->document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper); + if (private_data != NULL) { + clone->document->private_data = php_dom_libxml_private_data_header(private_data); } } } diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index aa1f3316c206a..624e3cbaa5760 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -114,6 +114,9 @@ typedef enum dom_iterator_type { DOM_HTMLCOLLECTION, } dom_iterator_type; +struct php_dom_libxml_ns_mapper; +typedef struct php_dom_libxml_ns_mapper php_dom_libxml_ns_mapper; + static inline dom_object_namespace_node *php_dom_namespace_node_obj_from_obj(zend_object *obj) { return (dom_object_namespace_node*)((char*)(obj) - XtOffsetOf(dom_object_namespace_node, dom.std)); } diff --git a/ext/dom/xml_common.h b/ext/dom/xml_common.h index 94c753132b893..8e5734d5a914e 100644 --- a/ext/dom/xml_common.h +++ b/ext/dom/xml_common.h @@ -80,13 +80,13 @@ PHP_DOM_EXPORT xmlNodePtr dom_object_get_node(dom_object *obj); __id = ZEND_THIS; \ DOM_GET_OBJ(__ptr, __id, __prtype, __intern); -struct php_dom_libxml_ns_mapper; -typedef struct php_dom_libxml_ns_mapper php_dom_libxml_ns_mapper; +struct php_dom_private_data; +typedef struct php_dom_private_data php_dom_private_data; -static zend_always_inline php_dom_libxml_ns_mapper *php_dom_get_ns_mapper(dom_object *intern) +static zend_always_inline php_dom_private_data *php_dom_get_private_data(dom_object *intern) { ZEND_ASSERT(intern->document != NULL); - return (php_dom_libxml_ns_mapper *) intern->document->private_data; + return (php_dom_private_data *) intern->document->private_data; } static zend_always_inline xmlNodePtr php_dom_next_in_tree_order(const xmlNode *nodep, const xmlNode *basep) diff --git a/ext/dom/xml_document.c b/ext/dom/xml_document.c index e1511b65ef798..d5032e3fb801d 100644 --- a/ext/dom/xml_document.c +++ b/ext/dom/xml_document.c @@ -122,7 +122,7 @@ PHP_METHOD(Dom_XMLDocument, createEmpty) NULL ); dom_set_xml_class(intern->document); - intern->document->private_data = php_dom_libxml_ns_mapper_header(php_dom_libxml_ns_mapper_create()); + intern->document->private_data = php_dom_libxml_private_data_header(php_dom_private_data_create()); return; oom: @@ -236,8 +236,9 @@ static void load_from_helper(INTERNAL_FUNCTION_PARAMETERS, int mode) void dom_document_convert_to_modern(php_libxml_ref_obj *document, xmlDocPtr lxml_doc) { - php_dom_libxml_ns_mapper *ns_mapper = php_dom_libxml_ns_mapper_create(); - document->private_data = php_dom_libxml_ns_mapper_header(ns_mapper); + php_dom_private_data *private_data = php_dom_private_data_create(); + php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); + document->private_data = php_dom_libxml_private_data_header(private_data); dom_mark_namespaces_as_attributes_too(ns_mapper, lxml_doc); } From 5b1b6d29e5c0e70d9c408bc2352f04e9d5098dd7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:19:59 +0200 Subject: [PATCH 02/10] Refactor XML serializer such that passing context is easier --- ext/dom/xml_serializer.c | 107 ++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/ext/dom/xml_serializer.c b/ext/dom/xml_serializer.c index 0c5f12023aa6d..3d7363b86efca 100644 --- a/ext/dom/xml_serializer.c +++ b/ext/dom/xml_serializer.c @@ -66,9 +66,13 @@ typedef struct { const xmlChar *prefix, *name; } dom_qname_pair; +typedef struct dom_xml_serialize_ctx { + xmlSaveCtxtPtr ctxt; + xmlOutputBufferPtr out; +} dom_xml_serialize_ctx; + static int dom_xml_serialization_algorithm( - xmlSaveCtxtPtr ctxt, - xmlOutputBufferPtr out, + dom_xml_serialize_ctx *ctx, dom_xml_ns_prefix_map *namespace_prefix_map, xmlNodePtr node, const xmlChar *namespace, @@ -895,8 +899,7 @@ static int dom_xml_output_indents(xmlOutputBufferPtr out, int indent) /* https://w3c.github.io/DOM-Parsing/#dfn-xml-serializing-an-element-node */ static int dom_xml_serialize_element_node( - xmlSaveCtxtPtr ctxt, - xmlOutputBufferPtr out, + dom_xml_serialize_ctx *ctx, const xmlChar *namespace, dom_xml_ns_prefix_map *namespace_prefix_map, xmlNodePtr element, @@ -916,7 +919,7 @@ static int dom_xml_serialize_element_node( bool should_format = indent >= 0 && element->children != NULL && dom_xml_should_format_element(element); /* 2. Let markup be the string "<" (U+003C LESS-THAN SIGN). */ - TRY(xmlOutputBufferWriteLit(out, "<")); + TRY(xmlOutputBufferWriteLit(ctx->out, "<")); /* 3. Let qualified name be an empty string. * => We're going to do it a bit differently. @@ -966,7 +969,7 @@ static int dom_xml_serialize_element_node( } /* 11.4. Append the value of qualified name to markup. */ - TRY_OR_CLEANUP(dom_xml_output_qname(out, &qualified_name)); + TRY_OR_CLEANUP(dom_xml_output_qname(ctx->out, &qualified_name)); } /* 12. Otherwise, inherited ns is not equal to ns */ else { @@ -1011,7 +1014,7 @@ static int dom_xml_serialize_element_node( } /* 12.4.3. Append the value of qualified name to markup. */ - TRY_OR_CLEANUP(dom_xml_output_qname(out, &qualified_name)); + TRY_OR_CLEANUP(dom_xml_output_qname(ctx->out, &qualified_name)); } /* 12.5. Otherwise, if prefix is not null, then: */ else if (prefix != NULL) { @@ -1033,14 +1036,14 @@ static int dom_xml_serialize_element_node( qualified_name.name = element->name; /* 12.5.4. Append the value of qualified name to markup. */ - TRY_OR_CLEANUP(dom_xml_output_qname(out, &qualified_name)); + TRY_OR_CLEANUP(dom_xml_output_qname(ctx->out, &qualified_name)); /* 12.5.5. Append the following to markup, in the order listed: ... */ - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, " xmlns:")); /* 12.5.5.1 - 12.5.5.2 */ - TRY_OR_CLEANUP(xmlOutputBufferWriteString(out, (const char *) prefix)); - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, "=\"")); - TRY_OR_CLEANUP(dom_xml_common_text_serialization(out, (const char *) ns, true)); - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, "\"")); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, " xmlns:")); /* 12.5.5.1 - 12.5.5.2 */ + TRY_OR_CLEANUP(xmlOutputBufferWriteString(ctx->out, (const char *) prefix)); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, "=\"")); + TRY_OR_CLEANUP(dom_xml_common_text_serialization(ctx->out, (const char *) ns, true)); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, "\"")); /* 12.5.6. If local default namespace is not null ... (editorial numbering error: https://github.com/w3c/DOM-Parsing/issues/43) */ if (local_default_namespace != NULL) { @@ -1064,24 +1067,24 @@ static int dom_xml_serialize_element_node( inherited_ns = ns; /* 12.6.4. Append the value of qualified name to markup. */ - TRY_OR_CLEANUP(dom_xml_output_qname(out, &qualified_name)); + TRY_OR_CLEANUP(dom_xml_output_qname(ctx->out, &qualified_name)); /* 12.6.5. Append the following to markup, in the order listed: ... */ - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, " xmlns=\"")); /* 12.6.5.1 - 12.6.5.2 */ - TRY_OR_CLEANUP(dom_xml_common_text_serialization(out, (const char *) ns, true)); - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, "\"")); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, " xmlns=\"")); /* 12.6.5.1 - 12.6.5.2 */ + TRY_OR_CLEANUP(dom_xml_common_text_serialization(ctx->out, (const char *) ns, true)); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, "\"")); } /* 12.7. Otherwise, the node has a local default namespace that matches ns ... */ else { qualified_name.name = element->name; inherited_ns = ns; - TRY_OR_CLEANUP(dom_xml_output_qname(out, &qualified_name)); + TRY_OR_CLEANUP(dom_xml_output_qname(ctx->out, &qualified_name)); } } /* 13. Append to markup the result of the XML serialization of node's attributes given map, prefix index, * local prefixes map, ignore namespace definition attribute flag, and require well-formed flag. */ - TRY_OR_CLEANUP(dom_xml_serialize_attributes(out, element, &map, &local_prefixes_map, prefix_index, ignore_namespace_definition_attribute, require_well_formed)); + TRY_OR_CLEANUP(dom_xml_serialize_attributes(ctx->out, element, &map, &local_prefixes_map, prefix_index, ignore_namespace_definition_attribute, require_well_formed)); /* 14. If ns is the HTML namespace, and the node's list of children is empty, and the node's localName matches * any one of the following void elements: ... */ @@ -1109,19 +1112,19 @@ static int dom_xml_serialize_element_node( || dom_local_name_compare_ex(element, "source", strlen("source"), name_length) || dom_local_name_compare_ex(element, "track", strlen("track"), name_length) || dom_local_name_compare_ex(element, "wbr", strlen("wbr"), name_length)) { - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, " /")); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, " /")); skip_end_tag = true; } } else { /* 15. If ns is not the HTML namespace, and the node's list of children is empty, * then append "/" (U+002F SOLIDUS) to markup and set the skip end tag flag to true. */ - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, "/")); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, "/")); skip_end_tag = true; } } /* 16. Append ">" (U+003E GREATER-THAN SIGN) to markup. */ - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, ">")); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, ">")); /* 17. If the value of skip end tag is true, then return the value of markup and skip the remaining steps. */ if (!skip_end_tag) { @@ -1136,20 +1139,20 @@ static int dom_xml_serialize_element_node( /* 19. Otherwise, append to markup the result of running the XML serialization algorithm on each of node's children. */ for (xmlNodePtr child = element->children; child != NULL; child = child->next) { if (should_format) { - TRY_OR_CLEANUP(dom_xml_output_indents(out, indent)); + TRY_OR_CLEANUP(dom_xml_output_indents(ctx->out, indent)); } - TRY_OR_CLEANUP(dom_xml_serialization_algorithm(ctxt, out, &map, child, inherited_ns, prefix_index, indent, require_well_formed)); + TRY_OR_CLEANUP(dom_xml_serialization_algorithm(ctx, &map, child, inherited_ns, prefix_index, indent, require_well_formed)); } if (should_format) { indent--; - TRY_OR_CLEANUP(dom_xml_output_indents(out, indent)); + TRY_OR_CLEANUP(dom_xml_output_indents(ctx->out, indent)); } /* 20. Append the following to markup, in the order listed: */ - TRY_OR_CLEANUP(xmlOutputBufferWriteLit(out, "")); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, "out, &qualified_name)); + TRY_OR_CLEANUP(xmlOutputBufferWriteLit(ctx->out, ">")); } /* 21. Return the value of markup. @@ -1166,8 +1169,7 @@ static int dom_xml_serialize_element_node( /* https://w3c.github.io/DOM-Parsing/#xml-serializing-a-documentfragment-node */ static int dom_xml_serializing_a_document_fragment_node( - xmlSaveCtxtPtr ctxt, - xmlOutputBufferPtr out, + dom_xml_serialize_ctx *ctx, dom_xml_ns_prefix_map *namespace_prefix_map, xmlNodePtr node, const xmlChar *namespace, @@ -1182,7 +1184,7 @@ static int dom_xml_serializing_a_document_fragment_node( /* 2. For each child child of node, in tree order, run the XML serialization algorithm on the child ... */ xmlNodePtr child = node->children; while (child != NULL) { - TRY(dom_xml_serialization_algorithm(ctxt, out, namespace_prefix_map, child, namespace, prefix_index, indent, require_well_formed)); + TRY(dom_xml_serialization_algorithm(ctx, namespace_prefix_map, child, namespace, prefix_index, indent, require_well_formed)); child = child->next; } @@ -1193,8 +1195,7 @@ static int dom_xml_serializing_a_document_fragment_node( /* https://w3c.github.io/DOM-Parsing/#dfn-xml-serializing-a-document-node */ static int dom_xml_serializing_a_document_node( - xmlSaveCtxtPtr ctxt, - xmlOutputBufferPtr out, + dom_xml_serialize_ctx *ctx, dom_xml_ns_prefix_map *namespace_prefix_map, xmlNodePtr node, const xmlChar *namespace, @@ -1210,16 +1211,16 @@ static int dom_xml_serializing_a_document_node( node->children = NULL; /* https://github.com/w3c/DOM-Parsing/issues/50 */ - TRY(xmlOutputBufferFlush(out)); - TRY(xmlSaveDoc(ctxt, (xmlDocPtr) node)); - TRY(xmlSaveFlush(ctxt)); + TRY(xmlOutputBufferFlush(ctx->out)); + TRY(xmlSaveDoc(ctx->ctxt, (xmlDocPtr) node)); + TRY(xmlSaveFlush(ctx->ctxt)); node->children = child; /* 2. For each child child of node, in tree order, run the XML serialization algorithm on the child passing along the provided arguments, * and append the result to serialized document. */ while (child != NULL) { - TRY(dom_xml_serialization_algorithm(ctxt, out, namespace_prefix_map, child, namespace, prefix_index, indent, require_well_formed)); + TRY(dom_xml_serialization_algorithm(ctx, namespace_prefix_map, child, namespace, prefix_index, indent, require_well_formed)); child = child->next; } @@ -1230,8 +1231,7 @@ static int dom_xml_serializing_a_document_node( /* https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization-algorithm */ static int dom_xml_serialization_algorithm( - xmlSaveCtxtPtr ctxt, - xmlOutputBufferPtr out, + dom_xml_serialize_ctx *ctx, dom_xml_ns_prefix_map *namespace_prefix_map, xmlNodePtr node, const xmlChar *namespace, @@ -1243,36 +1243,36 @@ static int dom_xml_serialization_algorithm( /* If node's interface is: */ switch (node->type) { case XML_ELEMENT_NODE: - return dom_xml_serialize_element_node(ctxt, out, namespace, namespace_prefix_map, node, prefix_index, indent, require_well_formed); + return dom_xml_serialize_element_node(ctx, namespace, namespace_prefix_map, node, prefix_index, indent, require_well_formed); case XML_DOCUMENT_FRAG_NODE: - return dom_xml_serializing_a_document_fragment_node(ctxt, out, namespace_prefix_map, node, namespace, prefix_index, indent, require_well_formed); + return dom_xml_serializing_a_document_fragment_node(ctx, namespace_prefix_map, node, namespace, prefix_index, indent, require_well_formed); case XML_HTML_DOCUMENT_NODE: case XML_DOCUMENT_NODE: - return dom_xml_serializing_a_document_node(ctxt, out, namespace_prefix_map, node, namespace, prefix_index, indent, require_well_formed); + return dom_xml_serializing_a_document_node(ctx, namespace_prefix_map, node, namespace, prefix_index, indent, require_well_formed); case XML_TEXT_NODE: - return dom_xml_serialize_text_node(out, node, require_well_formed); + return dom_xml_serialize_text_node(ctx->out, node, require_well_formed); case XML_COMMENT_NODE: - return dom_xml_serialize_comment_node(out, node, require_well_formed); + return dom_xml_serialize_comment_node(ctx->out, node, require_well_formed); case XML_PI_NODE: - return dom_xml_serialize_processing_instruction(out, node, require_well_formed); + return dom_xml_serialize_processing_instruction(ctx->out, node, require_well_formed); case XML_CDATA_SECTION_NODE: - return dom_xml_serialize_cdata_section_node(out, node); + return dom_xml_serialize_cdata_section_node(ctx->out, node); case XML_ATTRIBUTE_NODE: - return dom_xml_serialize_attribute_node(out, node); + return dom_xml_serialize_attribute_node(ctx->out, node); default: - TRY(xmlOutputBufferFlush(out)); - TRY(xmlSaveTree(ctxt, node)); - TRY(xmlSaveFlush(ctxt)); + TRY(xmlOutputBufferFlush(ctx->out)); + TRY(xmlSaveTree(ctx->ctxt, node)); + TRY(xmlSaveFlush(ctx->ctxt)); if (node->type == XML_DTD_NODE) { - return xmlOutputBufferWriteLit(out, "\n"); + return xmlOutputBufferWriteLit(ctx->out, "\n"); } return 0; } @@ -1297,8 +1297,11 @@ int dom_xml_serialize(xmlSaveCtxtPtr ctxt, xmlOutputBufferPtr out, xmlNodePtr no unsigned int prefix_index = 1; /* 5. Return the result of running the XML serialization algorithm ... */ + dom_xml_serialize_ctx ctx; + ctx.out = out; + ctx.ctxt = ctxt; int indent = format ? 0 : -1; - int result = dom_xml_serialization_algorithm(ctxt, out, &namespace_prefix_map, node, namespace, &prefix_index, indent, require_well_formed); + int result = dom_xml_serialization_algorithm(&ctx, &namespace_prefix_map, node, namespace, &prefix_index, indent, require_well_formed); dom_xml_ns_prefix_map_dtor(&namespace_prefix_map); From 0ca7791142a7c549d73375129dd0c0d3b1e6504e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:07:12 +0200 Subject: [PATCH 03/10] Support templated content The template element in HTML 5 is special in the sense that it does not add its contents into the DOM tree, but instead keeps them in a separate shadow DOM document fragment. Interacting with the DOM tree cannot touch the elements in the document fragment. --- ext/dom/config.m4 | 2 +- ext/dom/config.w32 | 2 +- ext/dom/domimplementation.c | 1 + ext/dom/element.c | 6 + ext/dom/html5_parser.c | 38 ++++-- ext/dom/html5_parser.h | 4 +- ext/dom/html5_serializer.c | 25 +++- ext/dom/html5_serializer.h | 2 + ext/dom/html_document.c | 14 +- ext/dom/inner_html_mixin.c | 11 +- ext/dom/internal_helpers.h | 8 ++ ext/dom/namespace_compat.c | 40 +----- ext/dom/namespace_compat.h | 10 -- ext/dom/node.c | 1 + ext/dom/php_dom.c | 17 ++- ext/dom/private_data.c | 127 ++++++++++++++++++ ext/dom/private_data.h | 47 +++++++ .../modern/common/template_participation.phpt | 83 ++++++++++++ .../tests/modern/common/template_rename.phpt | 29 ++++ ext/dom/xml_document.c | 11 +- ext/dom/xml_serializer.c | 21 ++- ext/dom/xml_serializer.h | 5 +- ext/libxml/libxml.c | 6 +- 23 files changed, 424 insertions(+), 86 deletions(-) create mode 100644 ext/dom/private_data.c create mode 100644 ext/dom/private_data.h create mode 100644 ext/dom/tests/modern/common/template_participation.phpt create mode 100644 ext/dom/tests/modern/common/template_rename.phpt diff --git a/ext/dom/config.m4 b/ext/dom/config.m4 index f16804d468ba6..8bb41d9ee8118 100644 --- a/ext/dom/config.m4 +++ b/ext/dom/config.m4 @@ -27,7 +27,7 @@ if test "$PHP_DOM" != "no"; then $LEXBOR_DIR/ns/ns.c \ $LEXBOR_DIR/tag/tag.c" PHP_NEW_EXTENSION(dom, [php_dom.c attr.c document.c infra.c \ - xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c \ + xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c private_data.c \ domexception.c \ parentnode/tree.c parentnode/css_selectors.c \ processinginstruction.c cdatasection.c \ diff --git a/ext/dom/config.w32 b/ext/dom/config.w32 index 081190a67f047..8035c5b84f0fc 100644 --- a/ext/dom/config.w32 +++ b/ext/dom/config.w32 @@ -8,7 +8,7 @@ if (PHP_DOM == "yes") { CHECK_HEADER_ADD_INCLUDE("libxml/parser.h", "CFLAGS_DOM", PHP_PHP_BUILD + "\\include\\libxml2") ) { EXTENSION("dom", "php_dom.c attr.c document.c infra.c \ - xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c \ + xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c private_data.c \ domexception.c processinginstruction.c \ cdatasection.c documentfragment.c domimplementation.c element.c inner_html_mixin.c \ node.c characterdata.c documenttype.c \ diff --git a/ext/dom/domimplementation.c b/ext/dom/domimplementation.c index 710232d78286e..0c2b2c61b308d 100644 --- a/ext/dom/domimplementation.c +++ b/ext/dom/domimplementation.c @@ -23,6 +23,7 @@ #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" #include "namespace_compat.h" +#include "private_data.h" /* * class DOMImplementation diff --git a/ext/dom/element.c b/ext/dom/element.c index 9795c0bd7aa8f..54d12252417e1 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -24,6 +24,7 @@ #include "zend_enum.h" #include "php_dom.h" #include "namespace_compat.h" +#include "private_data.h" #include "internal_helpers.h" #include "dom_properties.h" #include "token_list.h" @@ -2030,6 +2031,11 @@ PHP_METHOD(Dom_Element, rename) } goto cleanup; } + + /* If we currently have a template but the new element type won't be a template, then throw away the templated content. */ + if (is_currently_html_ns && xmlStrEqual(nodep->name, BAD_CAST "template") && !xmlStrEqual(localname, BAD_CAST "template")) { + php_dom_remove_templated_content(php_dom_get_private_data(intern), nodep); + } } php_libxml_invalidate_node_list_cache(intern->document); diff --git a/ext/dom/html5_parser.c b/ext/dom/html5_parser.c index 66c490b1d169f..7632f2303c836 100644 --- a/ext/dom/html5_parser.c +++ b/ext/dom/html5_parser.c @@ -22,12 +22,13 @@ #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" #include "html5_parser.h" +#include "private_data.h" #include #include +#include #include #include #include -#include #define WORK_LIST_INIT_SIZE 128 /* libxml2 reserves 2 pointer-sized words for interned strings */ @@ -102,11 +103,12 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( xmlNodePtr root, bool compact_text_nodes, bool create_default_ns, - php_dom_libxml_ns_mapper *ns_mapper + php_dom_private_data *private_data ) { lexbor_libxml2_bridge_status retval = LEXBOR_LIBXML2_BRIDGE_STATUS_OK; + php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); xmlNsPtr html_ns = php_dom_libxml_ns_mapper_ensure_html_ns(ns_mapper); xmlNsPtr xlink_ns = NULL; xmlNsPtr prefixed_xmlns_ns = NULL; @@ -158,12 +160,32 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( /* Instead of xmlSetNs() because we know the arguments are valid. Prevents overhead. */ lxml_element->ns = current_lxml_ns; - for (lxb_dom_node_t *child_node = element->node.last_child; child_node != NULL; child_node = child_node->prev) { + /* Handle template element by creating a fragment node to contain its children. + * Other types of nodes contain their children directly. */ + xmlNodePtr lxml_child_parent = lxml_element; + lxb_dom_node_t *child_node = element->node.last_child; + if (lxb_html_tree_node_is(&element->node, LXB_TAG_TEMPLATE)) { + lxml_child_parent = xmlNewDocFragment(lxml_doc); + if (UNEXPECTED(lxml_child_parent == NULL)) { + retval = LEXBOR_LIBXML2_BRIDGE_STATUS_OOM; + break; + } + + lxml_child_parent->parent = lxml_element; + php_dom_add_templated_content(private_data, lxml_element, lxml_child_parent); + + lxb_html_template_element_t *template = lxb_html_interface_template(&element->node); + if (template->content != NULL) { + child_node = template->content->node.last_child; + } + } + + for (; child_node != NULL; child_node = child_node->prev) { lexbor_libxml2_bridge_work_list_item_push( &work_list, child_node, entering_namespace, - lxml_element, + lxml_child_parent, current_lxml_ns ); } @@ -307,7 +329,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document( xmlDocPtr *doc_out, bool compact_text_nodes, bool create_default_ns, - php_dom_libxml_ns_mapper *ns_mapper + php_dom_private_data *private_data ) { xmlDocPtr lxml_doc = php_dom_create_html_doc(); @@ -320,7 +342,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document( (xmlNodePtr) lxml_doc, compact_text_nodes, create_default_ns, - ns_mapper + private_data ); if (status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK) { xmlFreeDoc(lxml_doc); @@ -336,7 +358,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( xmlNodePtr *fragment_out, bool compact_text_nodes, bool create_default_ns, - php_dom_libxml_ns_mapper *ns_mapper + php_dom_private_data *private_data ) { xmlNodePtr fragment = xmlNewDocFragment(lxml_doc); @@ -349,7 +371,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( fragment, compact_text_nodes, create_default_ns, - ns_mapper + private_data ); if (status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK) { xmlFreeNode(fragment); diff --git a/ext/dom/html5_parser.h b/ext/dom/html5_parser.h index bfa6223f6a534..a56166639a602 100644 --- a/ext/dom/html5_parser.h +++ b/ext/dom/html5_parser.h @@ -71,7 +71,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document( xmlDocPtr *doc_out, bool compact_text_nodes, bool create_default_ns, - php_dom_libxml_ns_mapper *ns_mapper + php_dom_private_data *private_data ); lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( lxb_dom_node_t *start_node, @@ -79,7 +79,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( xmlNodePtr *fragment_out, bool compact_text_nodes, bool create_default_ns, - php_dom_libxml_ns_mapper *ns_mapper + php_dom_private_data *private_data ); void lexbor_libxml2_bridge_report_errors( const lexbor_libxml2_bridge_parse_context *ctx, diff --git a/ext/dom/html5_serializer.c b/ext/dom/html5_serializer.c index 215acb6da9543..c87d3480a5f3c 100644 --- a/ext/dom/html5_serializer.c +++ b/ext/dom/html5_serializer.c @@ -289,9 +289,13 @@ static zend_result dom_html5_serialize_node(dom_html5_serialize_context *ctx, co case XML_ELEMENT_NODE: { TRY(dom_html5_serialize_element_start(ctx, node)); - if (node->children) { + const xmlNode *children = node->children; + if (php_dom_ns_is_fast(node, php_dom_ns_is_html_magic_token) && xmlStrEqual(node->name, BAD_CAST "template")) { + children = php_dom_retrieve_templated_content(ctx->private_data, node); + } + if (children) { if (!dom_html5_serializes_as_void(node)) { - node = node->children; + node = children; continue; } } else { @@ -301,6 +305,14 @@ static zend_result dom_html5_serialize_node(dom_html5_serialize_context *ctx, co break; } + case XML_DOCUMENT_FRAG_NODE: { + if (node->children) { + node = node->children; + continue; + } + break; + } + /* Only exists for compatibility with XML and old DOM. */ case XML_ENTITY_REF_NODE: { TRY(dom_html5_serialize_entity_ref(ctx, node)); @@ -346,10 +358,15 @@ zend_result dom_html5_serialize(dom_html5_serialize_context *ctx, const xmlNode } /* Step 2 not needed because we're not using a string to store the serialized data */ - /* Step 3 not needed because we don't support template contents yet */ + + /* Step 3. If the node is a template element, then let the node instead be the template element's template contents (a DocumentFragment node). */ + xmlNodePtr children = php_dom_retrieve_templated_content(ctx->private_data, node); + if (!children) { + children = node->children; + } /* Step 4 */ - return dom_html5_serialize_node(ctx, node->children, node); + return dom_html5_serialize_node(ctx, children, node); } /* Variant on the above that is equivalent to the "outer HTML". */ diff --git a/ext/dom/html5_serializer.h b/ext/dom/html5_serializer.h index 8ddedd48495ab..27fd1c92f24fb 100644 --- a/ext/dom/html5_serializer.h +++ b/ext/dom/html5_serializer.h @@ -19,11 +19,13 @@ #include #include +#include "private_data.h" typedef struct { zend_result (*write_string)(void *application_data, const char *buf); zend_result (*write_string_len)(void *application_data, const char *buf, size_t len); void *application_data; + php_dom_private_data *private_data; } dom_html5_serialize_context; zend_result dom_html5_serialize(dom_html5_serialize_context *ctx, const xmlNode *node); diff --git a/ext/dom/html_document.c b/ext/dom/html_document.c index 4c451f8a525ed..79092c8015abd 100644 --- a/ext/dom/html_document.c +++ b/ext/dom/html_document.c @@ -25,6 +25,7 @@ #include "html5_parser.h" #include "html5_serializer.h" #include "namespace_compat.h" +#include "private_data.h" #include "dom_properties.h" #include #include @@ -879,7 +880,6 @@ PHP_METHOD(Dom_HTMLDocument, createFromString) } php_dom_private_data *private_data = php_dom_private_data_create(); - php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); xmlDocPtr lxml_doc; lexbor_libxml2_bridge_status bridge_status = lexbor_libxml2_bridge_convert_document( @@ -887,7 +887,7 @@ PHP_METHOD(Dom_HTMLDocument, createFromString) &lxml_doc, options & XML_PARSE_COMPACT, !(options & DOM_HTML_NO_DEFAULT_NS), - ns_mapper + private_data ); lexbor_libxml2_bridge_copy_observations(parser->tree, &ctx.observations); if (UNEXPECTED(bridge_status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) { @@ -1071,7 +1071,6 @@ PHP_METHOD(Dom_HTMLDocument, createFromFile) } private_data = php_dom_private_data_create(); - php_dom_libxml_ns_mapper *ns_mapper = php_dom_ns_mapper_from_private(private_data); xmlDocPtr lxml_doc; lexbor_libxml2_bridge_status bridge_status = lexbor_libxml2_bridge_convert_document( @@ -1079,7 +1078,7 @@ PHP_METHOD(Dom_HTMLDocument, createFromFile) &lxml_doc, options & XML_PARSE_COMPACT, !(options & DOM_HTML_NO_DEFAULT_NS), - ns_mapper + private_data ); lexbor_libxml2_bridge_copy_observations(parser->tree, &ctx.observations); if (UNEXPECTED(bridge_status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) { @@ -1206,7 +1205,7 @@ static zend_result dom_saveHTML_write_string(void *application_data, const char return dom_saveHTML_write_string_len(application_data, buf, strlen(buf)); } -static zend_result dom_common_save(dom_output_ctx *output_ctx, const xmlDoc *docp, const xmlNode *node) +static zend_result dom_common_save(dom_output_ctx *output_ctx, dom_object *intern, const xmlDoc *docp, const xmlNode *node) { /* Initialize everything related to encoding & decoding */ const lxb_encoding_data_t *decoding_data = lxb_encoding_data(LXB_ENCODING_UTF_8); @@ -1239,6 +1238,7 @@ static zend_result dom_common_save(dom_output_ctx *output_ctx, const xmlDoc *doc ctx.write_string_len = dom_saveHTML_write_string_len; ctx.write_string = dom_saveHTML_write_string; ctx.application_data = output_ctx; + ctx.private_data = php_dom_get_private_data(intern); if (UNEXPECTED(dom_html5_serialize_outer(&ctx, node) != SUCCESS)) { return FAILURE; } @@ -1297,7 +1297,7 @@ PHP_METHOD(Dom_HTMLDocument, saveHtmlFile) dom_output_ctx output_ctx; output_ctx.output_data = stream; output_ctx.write_output = dom_write_output_stream; - if (UNEXPECTED(dom_common_save(&output_ctx, docp, (const xmlNode *) docp) != SUCCESS)) { + if (UNEXPECTED(dom_common_save(&output_ctx, intern, docp, (const xmlNode *) docp) != SUCCESS)) { php_stream_close(stream); RETURN_FALSE; } @@ -1336,7 +1336,7 @@ PHP_METHOD(Dom_HTMLDocument, saveHtml) output_ctx.output_data = &buf; output_ctx.write_output = dom_write_output_smart_str; /* Can't fail because dom_write_output_smart_str() can't fail. */ - zend_result result = dom_common_save(&output_ctx, docp, node); + zend_result result = dom_common_save(&output_ctx, intern, docp, node); ZEND_ASSERT(result == SUCCESS); RETURN_STR(smart_str_extract(&buf)); diff --git a/ext/dom/inner_html_mixin.c b/ext/dom/inner_html_mixin.c index 6db71462d1aee..a82b65597477f 100644 --- a/ext/dom/inner_html_mixin.c +++ b/ext/dom/inner_html_mixin.c @@ -68,6 +68,7 @@ zend_result dom_element_inner_html_read(dom_object *obj, zval *retval) if (context_document->type == XML_HTML_DOCUMENT_NODE) { smart_str output = {0}; dom_html5_serialize_context ctx; + ctx.private_data = php_dom_get_private_data(obj); ctx.application_data = &output; ctx.write_string = dom_inner_html_write_string; ctx.write_string_len = dom_inner_html_write_string_len; @@ -86,11 +87,12 @@ zend_result dom_element_inner_html_read(dom_object *obj, zval *retval) xmlCharEncodingHandlerPtr handler = xmlFindCharEncodingHandler("UTF-8"); xmlOutputBufferPtr out = xmlOutputBufferCreateIO(dom_write_smart_str, NULL, &str, handler); if (EXPECTED(out != NULL)) { + php_dom_private_data *private_data = php_dom_get_private_data(obj); /* Note: the innerHTML mixin sets the well-formed flag to true. */ xmlNodePtr child = node->children; status = 0; while (child != NULL && status == 0) { - status = dom_xml_serialize(ctxt, out, child, false, true); + status = dom_xml_serialize(ctxt, out, child, false, true, private_data); child = child->next; } status |= xmlOutputBufferFlush(out); @@ -205,7 +207,7 @@ static xmlNodePtr dom_html_fragment_parsing_algorithm(dom_object *obj, xmlNodePt xmlNodePtr fragment = NULL; if (node != NULL) { /* node->last_child could be NULL, but that is allowed. */ - lexbor_libxml2_bridge_status status = lexbor_libxml2_bridge_convert_fragment(node->last_child, context_node->doc, &fragment, true, true, php_dom_get_ns_mapper(obj)); + lexbor_libxml2_bridge_status status = lexbor_libxml2_bridge_convert_fragment(node->last_child, context_node->doc, &fragment, true, true, php_dom_get_private_data(obj)); if (UNEXPECTED(status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) { php_dom_throw_error(INVALID_STATE_ERR, true); } @@ -349,6 +351,11 @@ zend_result dom_element_inner_html_write(dom_object *obj, zval *newval) return FAILURE; } + xmlNodePtr template_content = php_dom_retrieve_templated_content(php_dom_get_private_data(obj), context_node); + if (template_content != NULL) { + context_node = template_content; + } + /* We skip the steps involving the template element as context node since we don't do special handling for that. */ dom_remove_all_children(context_node); return php_dom_pre_insert(obj->document, fragment, context_node, NULL) ? SUCCESS : FAILURE; diff --git a/ext/dom/internal_helpers.h b/ext/dom/internal_helpers.h index 95f49d5f480b6..c87ea49c61438 100644 --- a/ext/dom/internal_helpers.h +++ b/ext/dom/internal_helpers.h @@ -89,4 +89,12 @@ static zend_always_inline bool dom_is_document_cache_modified_since_parsing(php_ return !doc_ptr || doc_ptr->cache_tag.modification_nr > dom_minimum_modification_nr_since_parsing(doc_ptr); } +static zend_always_inline zend_long dom_mangle_pointer_for_key(const void *ptr) +{ + zend_ulong value = (zend_ulong) (uintptr_t) ptr; + /* Rotate 3/4 bits for better hash distribution because the low 3/4 bits are normally 0. */ + const size_t rol_amount = (SIZEOF_ZEND_LONG == 8) ? 4 : 3; + return (value >> rol_amount) | (value << (sizeof(value) * 8 - rol_amount)); +} + #endif diff --git a/ext/dom/namespace_compat.c b/ext/dom/namespace_compat.c index 8472fd36c62b1..f9f8108ab0f51 100644 --- a/ext/dom/namespace_compat.c +++ b/ext/dom/namespace_compat.c @@ -42,6 +42,7 @@ struct php_dom_libxml_ns_mapper { typedef struct php_dom_private_data { php_libxml_private_data_header header; struct php_dom_libxml_ns_mapper ns_mapper; + HashTable *template_fragments; } php_dom_private_data; static void php_dom_libxml_ns_mapper_prefix_map_element_dtor(zval *zv) @@ -73,27 +74,6 @@ static HashTable *php_dom_libxml_ns_mapper_ensure_prefix_map(php_dom_libxml_ns_m return prefix_map; } -static void php_dom_libxml_private_data_destroy(php_libxml_private_data_header *header) -{ - php_dom_private_data_destroy((php_dom_private_data *) header); -} - -PHP_DOM_EXPORT php_dom_private_data *php_dom_private_data_create(void) -{ - php_dom_private_data *mapper = emalloc(sizeof(*mapper)); - mapper->header.dtor = php_dom_libxml_private_data_destroy; - mapper->ns_mapper.html_ns = NULL; - mapper->ns_mapper.prefixless_xmlns_ns = NULL; - zend_hash_init(&mapper->ns_mapper.uri_to_prefix_map, 0, NULL, ZVAL_PTR_DTOR, false); - return mapper; -} - -void php_dom_private_data_destroy(php_dom_private_data *data) -{ - zend_hash_destroy(&data->ns_mapper.uri_to_prefix_map); - efree(data); -} - static xmlNsPtr php_dom_libxml_ns_mapper_ensure_cached_ns(php_dom_libxml_ns_mapper *mapper, xmlNsPtr *ptr, const char *uri, size_t length, const php_dom_ns_magic_token *token) { if (EXPECTED(*ptr != NULL)) { @@ -233,16 +213,6 @@ static xmlNsPtr php_dom_libxml_ns_mapper_store_and_normalize_parsed_ns(php_dom_l return ns; } -PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_private_data_header(php_dom_private_data *private_data) -{ - return private_data == NULL ? NULL : &private_data->header; -} - -PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *private_data) -{ - return private_data == NULL ? NULL : &private_data->ns_mapper; -} - PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_get_ns_mapper(dom_object *object) { return &php_dom_get_private_data(object)->ns_mapper; @@ -366,14 +336,6 @@ PHP_DOM_EXPORT void php_dom_reconcile_attribute_namespace_after_insertion(xmlAtt } } -static zend_always_inline zend_long dom_mangle_pointer_for_key(void *ptr) -{ - zend_ulong value = (zend_ulong) (uintptr_t) ptr; - /* Rotate 3/4 bits for better hash distribution because the low 3/4 bits are normally 0. */ - const size_t rol_amount = (SIZEOF_ZEND_LONG == 8) ? 4 : 3; - return (value >> rol_amount) | (value << (sizeof(value) * 8 - rol_amount)); -} - static zend_always_inline void php_dom_libxml_reconcile_modern_single_node(dom_libxml_reconcile_ctx *ctx, xmlNodePtr node) { ZEND_ASSERT(node->ns != NULL); diff --git a/ext/dom/namespace_compat.h b/ext/dom/namespace_compat.h index 5a7c3f7b71eae..23c80acc7fd78 100644 --- a/ext/dom/namespace_compat.h +++ b/ext/dom/namespace_compat.h @@ -33,12 +33,6 @@ typedef struct php_dom_ns_magic_token php_dom_ns_magic_token; struct php_dom_libxml_ns_mapper; typedef struct php_dom_libxml_ns_mapper php_dom_libxml_ns_mapper; -struct php_dom_private_data; -typedef struct php_dom_private_data php_dom_private_data; - -typedef struct php_libxml_private_data_header php_libxml_private_data_header; -struct php_libxml_private_data_header; - PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_html_magic_token; PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_mathml_magic_token; PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_svg_magic_token; @@ -49,17 +43,13 @@ PHP_DOM_EXPORT extern const php_dom_ns_magic_token *php_dom_ns_is_xmlns_magic_to /* These functions make it possible to make a namespace declaration also visible as an attribute by * creating an equivalent attribute node. */ -PHP_DOM_EXPORT php_dom_private_data *php_dom_private_data_create(void); -PHP_DOM_EXPORT void php_dom_private_data_destroy(php_dom_private_data *data); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_libxml_ns_mapper *mapper); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_ensure_prefixless_xmlns_ns(php_dom_libxml_ns_mapper *mapper); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns(php_dom_libxml_ns_mapper *mapper, zend_string *prefix, zend_string *uri); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_prefix_string(php_dom_libxml_ns_mapper *mapper, const xmlChar *prefix, size_t prefix_len, zend_string *uri); PHP_DOM_EXPORT xmlNsPtr php_dom_libxml_ns_mapper_get_ns_raw_strings_nullsafe(php_dom_libxml_ns_mapper *mapper, const char *prefix, const char *uri); -PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_private_data_header(php_dom_private_data *private_data); PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_get_ns_mapper(dom_object *object); -PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *private_data); PHP_DOM_EXPORT void php_dom_ns_compat_mark_attribute_list(php_dom_libxml_ns_mapper *mapper, xmlNodePtr node); PHP_DOM_EXPORT void php_dom_libxml_reconcile_modern(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node); PHP_DOM_EXPORT void php_dom_reconcile_attribute_namespace_after_insertion(xmlAttrPtr attrp); diff --git a/ext/dom/node.c b/ext/dom/node.c index ff7631825b498..971e7f4d9b5cd 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -23,6 +23,7 @@ #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" #include "namespace_compat.h" +#include "private_data.h" #include "internal_helpers.h" #include "dom_properties.h" diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index cb6439fe263c0..6e4461732390d 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -27,6 +27,7 @@ #include "nodelist.h" #include "html_collection.h" #include "namespace_compat.h" +#include "private_data.h" #include "internal_helpers.h" #include "php_dom_arginfo.h" #include "dom_properties.h" @@ -1389,8 +1390,20 @@ void dom_objects_free_storage(zend_object *object) zend_object_std_dtor(&intern->std); - if (intern->ptr != NULL && ((php_libxml_node_ptr *)intern->ptr)->node != NULL) { - if (((xmlNodePtr) ((php_libxml_node_ptr *)intern->ptr)->node)->type != XML_DOCUMENT_NODE && ((xmlNodePtr) ((php_libxml_node_ptr *)intern->ptr)->node)->type != XML_HTML_DOCUMENT_NODE) { + php_libxml_node_ptr *ptr = intern->ptr; + if (ptr != NULL && ptr->node != NULL) { + xmlNodePtr node = ptr->node; + + if (node->type != XML_DOCUMENT_NODE && node->type != XML_HTML_DOCUMENT_NODE) { + /* Destroy associated template content. */ + php_dom_private_data *private_data; + if (node->type == XML_ELEMENT_NODE + && ptr->refcount == 1 + && intern->document != NULL + && (private_data = php_dom_get_private_data(intern))) { + php_dom_remove_templated_content(private_data, node); + } + php_libxml_node_decrement_resource((php_libxml_node_object *) intern); } else { php_libxml_decrement_node_ptr((php_libxml_node_object *) intern); diff --git a/ext/dom/private_data.c b/ext/dom/private_data.c new file mode 100644 index 0000000000000..606f8e6021a47 --- /dev/null +++ b/ext/dom/private_data.c @@ -0,0 +1,127 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Niels Dossche | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "php.h" +#if defined(HAVE_LIBXML) && defined(HAVE_DOM) +#include "php_dom.h" +#include "private_data.h" +#include "internal_helpers.h" + +typedef struct php_dom_private_data { + php_libxml_private_data_header header; + struct php_dom_libxml_ns_mapper ns_mapper; + HashTable *template_fragments; +} php_dom_private_data; + +static void php_dom_libxml_private_data_destroy(php_libxml_private_data_header *header) +{ + php_dom_private_data_destroy((php_dom_private_data *) header); +} + +PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_private_data_header(php_dom_private_data *private_data) +{ + return private_data == NULL ? NULL : &private_data->header; +} + +PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *private_data) +{ + return private_data == NULL ? NULL : &private_data->ns_mapper; +} + +PHP_DOM_EXPORT php_dom_private_data *php_dom_private_data_create(void) +{ + php_dom_private_data *mapper = emalloc(sizeof(*mapper)); + mapper->header.dtor = php_dom_libxml_private_data_destroy; + mapper->ns_mapper.html_ns = NULL; + mapper->ns_mapper.prefixless_xmlns_ns = NULL; + zend_hash_init(&mapper->ns_mapper.uri_to_prefix_map, 0, NULL, ZVAL_PTR_DTOR, false); + mapper->template_fragments = NULL; + return mapper; +} + +void php_dom_private_data_destroy(php_dom_private_data *data) +{ + zend_hash_destroy(&data->ns_mapper.uri_to_prefix_map); + if (data->template_fragments != NULL) { + zend_hash_destroy(data->template_fragments); + FREE_HASHTABLE(data->template_fragments); + } + efree(data); +} + +static void php_dom_free_templated_content(php_dom_private_data *private_data, xmlNodePtr base) +{ + /* Note: it's not possible to obtain a userland reference to these yet, so we can just free them without worrying + * about their proxies. + * Note 2: it's possible to have nested template content. */ + + if (zend_hash_num_elements(private_data->template_fragments) > 0) { + /* There's more templated content, try to free it. */ + xmlNodePtr current = base->children; + while (current != NULL) { + if (current->type == XML_ELEMENT_NODE) { + php_dom_remove_templated_content(private_data, current); + } + + current = php_dom_next_in_tree_order(current, base); + } + } + + xmlFreeNode(base); +} + +void php_dom_add_templated_content(php_dom_private_data *private_data, const xmlNode *template_node, xmlNodePtr fragment) +{ + if (private_data->template_fragments == NULL) { + ALLOC_HASHTABLE(private_data->template_fragments); + zend_hash_init(private_data->template_fragments, 0, NULL, NULL, false); + zend_hash_real_init_mixed(private_data->template_fragments); + } + + zend_hash_index_add_new_ptr(private_data->template_fragments, dom_mangle_pointer_for_key(template_node), fragment); +} + +xmlNodePtr php_dom_retrieve_templated_content(php_dom_private_data *private_data, const xmlNode *template_node) +{ + if (private_data->template_fragments == NULL) { + return NULL; + } + + return zend_hash_index_find_ptr(private_data->template_fragments, dom_mangle_pointer_for_key(template_node)); +} + +void php_dom_remove_templated_content(php_dom_private_data *private_data, const xmlNode *template_node) +{ + if (private_data->template_fragments != NULL) { + /* Deletion needs to be done not via a destructor because we can't access private_data from there. */ + zval *zv = zend_hash_index_find(private_data->template_fragments, dom_mangle_pointer_for_key(template_node)); + if (zv != NULL) { + xmlNodePtr node = Z_PTR_P(zv); + ZEND_ASSERT(offsetof(Bucket, val) == 0 && "Type cast only works if this is true"); + Bucket* bucket = (Bucket*) zv; + /* First remove it from the bucket before freeing the content, otherwise recursion could make the bucket + * pointer invalid due to hash table structure changes. */ + zend_hash_del_bucket(private_data->template_fragments, bucket); + php_dom_free_templated_content(private_data, node); + } + } +} + +#endif /* HAVE_LIBXML && HAVE_DOM */ diff --git a/ext/dom/private_data.h b/ext/dom/private_data.h new file mode 100644 index 0000000000000..046b66a855928 --- /dev/null +++ b/ext/dom/private_data.h @@ -0,0 +1,47 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Niels Dossche | + +----------------------------------------------------------------------+ +*/ + +#ifndef PRIVATE_DATA_H +#define PRIVATE_DATA_H + +#include "xml_common.h" + +struct php_dom_libxml_ns_mapper { + /* This is used almost all the time for HTML documents, so it makes sense to cache this. */ + xmlNsPtr html_ns; + /* Used for every prefixless namespace declaration in XML, so also very common. */ + xmlNsPtr prefixless_xmlns_ns; + HashTable uri_to_prefix_map; +}; + +typedef struct php_libxml_private_data_header php_libxml_private_data_header; +struct php_libxml_private_data_header; + +struct php_dom_private_data; +typedef struct php_dom_private_data php_dom_private_data; + +struct php_dom_libxml_ns_mapper; +typedef struct php_dom_libxml_ns_mapper php_dom_libxml_ns_mapper; + +PHP_DOM_EXPORT php_libxml_private_data_header *php_dom_libxml_private_data_header(php_dom_private_data *private_data); +PHP_DOM_EXPORT php_dom_libxml_ns_mapper *php_dom_ns_mapper_from_private(php_dom_private_data *private_data); +PHP_DOM_EXPORT php_dom_private_data *php_dom_private_data_create(void); +void php_dom_private_data_destroy(php_dom_private_data *data); +void php_dom_add_templated_content(php_dom_private_data *private_data, const xmlNode *template_node, xmlNodePtr fragment); +xmlNodePtr php_dom_retrieve_templated_content(php_dom_private_data *private_data, const xmlNode *template_node); +void php_dom_remove_templated_content(php_dom_private_data *private_data, const xmlNode *template_node); + +#endif diff --git a/ext/dom/tests/modern/common/template_participation.phpt b/ext/dom/tests/modern/common/template_participation.phpt new file mode 100644 index 0000000000000..374a56102b2ee --- /dev/null +++ b/ext/dom/tests/modern/common/template_participation.phpt @@ -0,0 +1,83 @@ +--TEST-- +