Skip to content

Properly support template elements in DOM #14906

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ PHP NEWS
- Core:
. Fixed bug GH-14801 (Fix build for armv7). (andypost)

- DOM:
. Improve support for template elements. (nielsdos)

- GD:
. Check overflow/underflow for imagescale/imagefilter. (David Carlier)

Expand Down
2 changes: 1 addition & 1 deletion ext/dom/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion ext/dom/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
1 change: 1 addition & 0 deletions ext/dom/document.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
#include "namespace_compat.h"
#include "private_data.h"
#include "xml_serializer.h"
#include "internal_helpers.h"
#include "dom_properties.h"
Expand Down
15 changes: 9 additions & 6 deletions ext/dom/domimplementation.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -266,7 +267,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");
Expand Down Expand Up @@ -307,7 +309,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) {
Expand Down Expand Up @@ -337,7 +339,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 */
Expand Down Expand Up @@ -366,7 +368,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. */
Expand Down Expand Up @@ -396,7 +399,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();
}

Expand All @@ -408,7 +411,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);
}
/* }}} */

Expand Down
11 changes: 11 additions & 0 deletions ext/dom/element.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2030,6 +2031,16 @@ 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_throw_error_with_message(
INVALID_MODIFICATION_ERR,
"It is not possible to rename the template element because it hosts a document fragment",
/* strict */ true
);
goto cleanup;
}
}

php_libxml_invalidate_node_list_cache(intern->document);
Expand Down
61 changes: 46 additions & 15 deletions ext/dom/html5_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
#include "html5_parser.h"
#include "private_data.h"
#include <lexbor/html/parser.h>
#include <lexbor/html/interfaces/element.h>
#include <lexbor/html/interfaces/template_element.h>
#include <lexbor/dom/dom.h>
#include <libxml/parserInternals.h>
#include <libxml/HTMLtree.h>
#include <Zend/zend.h>

#define WORK_LIST_INIT_SIZE 128
/* libxml2 reserves 2 pointer-sized words for interned strings */
Expand Down Expand Up @@ -63,14 +64,20 @@ static unsigned short sanitize_line_nr(size_t line)
return (unsigned short) line;
}

static const php_dom_ns_magic_token *get_libxml_namespace_href(uintptr_t lexbor_namespace)
struct lxml_ns {
const php_dom_ns_magic_token *token;
const char *href;
size_t href_len;
};

static struct lxml_ns get_libxml_namespace_href(uintptr_t lexbor_namespace)
{
if (lexbor_namespace == LXB_NS_SVG) {
return php_dom_ns_is_svg_magic_token;
return (struct lxml_ns) { php_dom_ns_is_svg_magic_token, ZEND_STRL(DOM_SVG_NS_URI) };
} else if (lexbor_namespace == LXB_NS_MATH) {
return php_dom_ns_is_mathml_magic_token;
return (struct lxml_ns) { php_dom_ns_is_mathml_magic_token, ZEND_STRL(DOM_MATHML_NS_URI) };
} else {
return php_dom_ns_is_html_magic_token;
return (struct lxml_ns) { php_dom_ns_is_html_magic_token, ZEND_STRL(DOM_XHTML_NS_URI) };
}
}

Expand Down Expand Up @@ -102,11 +109,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;
Expand Down Expand Up @@ -146,24 +154,47 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert(
if (entering_namespace == LXB_NS_HTML) {
current_lxml_ns = html_ns;
} else {
const php_dom_ns_magic_token *magic_token = get_libxml_namespace_href(entering_namespace);
zend_string *uri = zend_string_init((char *) magic_token, strlen((char *) magic_token), false);
struct lxml_ns ns = get_libxml_namespace_href(entering_namespace);
zend_string *uri = zend_string_init(ns.href, ns.href_len, false);
current_lxml_ns = php_dom_libxml_ns_mapper_get_ns(ns_mapper, NULL, uri);
zend_string_release_ex(uri, false);
if (EXPECTED(current_lxml_ns != NULL)) {
current_lxml_ns->_private = (void *) magic_token;
current_lxml_ns->_private = (void *) ns.token;
}
}
}
/* 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)) {
if (create_default_ns) {
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;
dom_add_element_ns_hook(private_data, 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
);
}
Expand Down Expand Up @@ -307,7 +338,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();
Expand All @@ -320,7 +351,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);
Expand All @@ -336,7 +367,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);
Expand All @@ -349,7 +380,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);
Expand Down
4 changes: 2 additions & 2 deletions ext/dom/html5_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ 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,
xmlDocPtr lxml_doc,
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,
Expand Down
25 changes: 21 additions & 4 deletions ext/dom/html5_serializer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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));
Expand Down Expand Up @@ -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". */
Expand Down
2 changes: 2 additions & 0 deletions ext/dom/html5_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@

#include <Zend/zend_types.h>
#include <libxml/tree.h>
#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);
Expand Down
Loading
Loading