]> granicus.if.org Git - php/commitdiff
[RFC] Implement new DOM Living Standard APIs in ext/dom
authorBenjamin Eberlei <kontakt@beberlei.de>
Fri, 28 Feb 2020 15:13:39 +0000 (16:13 +0100)
committerBenjamin Eberlei <kontakt@beberlei.de>
Fri, 28 Feb 2020 15:13:39 +0000 (16:13 +0100)
35 files changed:
ext/dom/characterdata.c
ext/dom/config.m4
ext/dom/config.w32
ext/dom/document.c
ext/dom/documentfragment.c
ext/dom/dom.stub.php
ext/dom/dom_arginfo.h
ext/dom/dom_fe.h
ext/dom/dom_properties.h
ext/dom/element.c
ext/dom/node.c
ext/dom/parentnode.c [new file with mode: 0644]
ext/dom/php_dom.c
ext/dom/php_dom.h
ext/dom/tests/DOM4_ChildNode_wrong_document.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_ElementSiblings.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_after.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_after_ns.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_append_ns.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_before.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_before_ns.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_prepend_ns.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_remove.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_removeDanglingElement.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_DOMNode_replaceWith.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_ParentNode.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_ParentNode_Fragment.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_ParentNode_append.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_ParentNode_append_invalidtypes.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_ParentNode_append_with_attributes.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_ParentNode_append_wrong_document.phpt [new file with mode: 0644]
ext/dom/tests/DOM4_ParentNode_prepend.phpt [new file with mode: 0644]
ext/dom/tests/bug69846.phpt
ext/dom/tests/dom_test.inc
ext/dom/tests/domobject_debug_handler.phpt

index b3e91e769cc98260343884eee4f42a55b24731aa..cff745a522a6d710e5f5d80e0a45d78e41fe66d7 100644 (file)
@@ -37,6 +37,10 @@ const zend_function_entry php_dom_characterdata_class_functions[] = {
        PHP_ME(domcharacterdata, insertData, arginfo_class_DOMCharacterData_insertData, ZEND_ACC_PUBLIC)
        PHP_ME(domcharacterdata, deleteData, arginfo_class_DOMCharacterData_deleteData, ZEND_ACC_PUBLIC)
        PHP_ME(domcharacterdata, replaceData, arginfo_class_DOMCharacterData_replaceData, ZEND_ACC_PUBLIC)
+       PHP_ME(domcharacterdata, remove, arginfo_class_DOMChildNode_remove, ZEND_ACC_PUBLIC)
+       PHP_ME(domcharacterdata, after, arginfo_class_DOMChildNode_after, ZEND_ACC_PUBLIC)
+       PHP_ME(domcharacterdata, before, arginfo_class_DOMChildNode_before, ZEND_ACC_PUBLIC)
+       PHP_ME(domcharacterdata, replaceWith, arginfo_class_DOMChildNode_replaceWith, ZEND_ACC_PUBLIC)
        PHP_FE_END
 };
 
@@ -361,4 +365,104 @@ PHP_METHOD(domcharacterdata, replaceData)
 }
 /* }}} end dom_characterdata_replace_data */
 
+PHP_METHOD(domcharacterdata, remove)
+{
+       zval *id = ZEND_THIS;
+       xmlNodePtr children, child;
+       dom_object *intern;
+       int stricterror;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       DOM_GET_OBJ(child, id, xmlNodePtr, intern);
+
+       if (dom_node_children_valid(child) == FAILURE) {
+               RETURN_NULL();
+       }
+
+       stricterror = dom_get_strict_error(intern->document);
+
+       if (dom_node_is_read_only(child) == SUCCESS ||
+               (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
+               php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
+               RETURN_NULL();
+       }
+
+       if (!child->parent) {
+               php_dom_throw_error(NOT_FOUND_ERR, stricterror);
+               RETURN_NULL();
+       }
+
+       children = child->parent->children;
+       if (!children) {
+               php_dom_throw_error(NOT_FOUND_ERR, stricterror);
+               RETURN_NULL();
+       }
+
+       while (children) {
+               if (children == child) {
+                       xmlUnlinkNode(child);
+                       RETURN_NULL();
+               }
+               children = children->next;
+       }
+
+       php_dom_throw_error(NOT_FOUND_ERR, stricterror);
+       RETURN_NULL();
+}
+
+PHP_METHOD(domcharacterdata, after)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_after(intern, args, argc);
+}
+
+PHP_METHOD(domcharacterdata, before)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_before(intern, args, argc);
+}
+
+PHP_METHOD(domcharacterdata, replaceWith)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_after(intern, args, argc);
+       dom_child_node_remove(intern);
+}
+
 #endif
index de3b54da4cd2398270b854c3d7d98dc911400034..6a83d10c8e245df21e4998c82b94c67812ff1185 100644 (file)
@@ -13,7 +13,7 @@ if test "$PHP_DOM" != "no"; then
   PHP_SETUP_LIBXML(DOM_SHARED_LIBADD, [
     AC_DEFINE(HAVE_DOM,1,[ ])
     PHP_NEW_EXTENSION(dom, [php_dom.c attr.c document.c \
-                            domexception.c \
+                            domexception.c parentnode.c \
                             processinginstruction.c cdatasection.c \
                             documentfragment.c domimplementation.c \
                             element.c node.c characterdata.c \
index cd5f5a6cb8737c3629a8320c05310b7cbaa066e0..7795445019e1a890d5b2c9e931e936c3b039f4ad 100644 (file)
@@ -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 \
-                       domexception.c processinginstruction.c \
+                       domexception.c parentnode.c processinginstruction.c \
                        cdatasection.c documentfragment.c domimplementation.c element.c \
                        node.c characterdata.c documenttype.c \
                        entity.c nodelist.c text.c comment.c \
index 2b66fccf8077a6c36501f01a0b611a55cce0eb6c..5cb114d45cb2fa34f40c46e889fda96f1bbb2d50 100644 (file)
@@ -82,6 +82,8 @@ const zend_function_entry php_dom_document_class_functions[] = { /* {{{ */
        PHP_ME(domdocument, relaxNGValidateSource, arginfo_class_DOMDocument_relaxNGValidateSource, ZEND_ACC_PUBLIC)
 #endif
        PHP_ME(domdocument, registerNodeClass, arginfo_class_DOMDocument_registerNodeClass, ZEND_ACC_PUBLIC)
+       PHP_ME(domdocument, append, arginfo_class_DOMParentNode_append, ZEND_ACC_PUBLIC)
+       PHP_ME(domdocument, prepend, arginfo_class_DOMParentNode_prepend, ZEND_ACC_PUBLIC)
        PHP_FE_END
 };
 /* }}} */
@@ -2128,4 +2130,48 @@ PHP_METHOD(domdocument, registerNodeClass)
 }
 /* }}} */
 
+/* {{{ proto void domdocument::append(string|DOMNode ...$nodes)
+URL: https://dom.spec.whatwg.org/#dom-parentnode-append
+Since: DOM Living Standard (DOM4)
+*/
+PHP_METHOD(domdocument, append)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_append(intern, args, argc);
+}
+/* }}} */
+
+/* {{{ proto void domdocument::prepend(string|DOMNode ...$nodes)
+URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
+Since: DOM Living Standard (DOM4)
+*/
+PHP_METHOD(domdocument, prepend)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_prepend(intern, args, argc);
+}
+/* }}} */
+
 #endif  /* HAVE_LIBXML && HAVE_DOM */
index d0f44a7d8f8efcc95cf24bd28bd5eb3973a72355..d6e1060d349d5648874bb377d2a08dbc2df59ff0 100644 (file)
@@ -34,6 +34,8 @@
 const zend_function_entry php_dom_documentfragment_class_functions[] = {
        PHP_ME(domdocumentfragment, __construct, arginfo_class_DOMDocumentFragment___construct, ZEND_ACC_PUBLIC)
        PHP_ME(domdocumentfragment, appendXML, arginfo_class_DOMDocumentFragment_appendXML, ZEND_ACC_PUBLIC)
+       PHP_ME(domdocumentfragment, append, arginfo_class_DOMParentNode_append, ZEND_ACC_PUBLIC)
+       PHP_ME(domdocumentfragment, prepend, arginfo_class_DOMParentNode_prepend, ZEND_ACC_PUBLIC)
        PHP_FE_END
 };
 
@@ -137,4 +139,48 @@ PHP_METHOD(domdocumentfragment, appendXML) {
 }
 /* }}} */
 
+/* {{{ proto void domdocumentfragment::append(string|DOMNode ...$nodes)
+URL: https://dom.spec.whatwg.org/#dom-parentnode-append
+Since: DOM Living Standard (DOM4)
+*/
+PHP_METHOD(domdocumentfragment, append)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_append(intern, args, argc);
+}
+/* }}} */
+
+/* {{{ proto void domdocumentfragment::prepend(string|DOMNode ...$nodes)
+URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
+Since: DOM Living Standard (DOM4)
+*/
+PHP_METHOD(domdocumentfragment, prepend)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_prepend(intern, args, argc);
+}
+/* }}} */
+
 #endif
index d88fb7f4c49085ad081b1f451d26b642ddc055ef..e87c2917c9e7a4823929365fdcf8329a8d70485f 100644 (file)
@@ -1,5 +1,28 @@
 <?php
 
+interface DOMChildNode
+{
+    public function remove(): void;
+
+    /** @var ...DOMNode|string $nodes */
+    public function before(... $nodes): void;
+
+    /** @var ...DOMNode|string $nodes */
+    public function after(...$nodes): void;
+
+    /** @var ...DOMNode|string $nodes */
+    public function replaceWith(...$nodes): void;
+}
+
+interface DOMParentNode
+{
+    /** @var ...DOMNode|string $nodes */
+    public function append(...$nodes): void;
+
+    /** @var ...DOMNode|string $nodes */
+    public function prepend(...$nodes): void;
+}
+
 class DOMNode {
     /** @return DOMNode|false */
     public function appendChild(DOMNode $newChild) {}
@@ -63,7 +86,7 @@ class DOMCdataSection {
     public function __construct(string $value) {}
 }
 
-class DOMCharacterData {
+class DOMCharacterData implements DOMChildNode {
     /** @return bool */
     public function appendData(string $data) {}
 
@@ -84,7 +107,7 @@ class DOMComment {
     public function __construct(string $value = "") {}
 }
 
-class DOMDocument {
+class DOMDocument implements DOMParentNode {
     public function __construct(string $version = "1.0", string $encoding = UNKNOWN) {}
 
     /** @return DOMAttr|false */
@@ -185,14 +208,14 @@ class DOMDocument {
     public function adoptNode(DOMNode $source) {}
 }
 
-class DOMDocumentFragment {
+class DOMDocumentFragment implements DOMParentNode {
     public function __construct() {}
 
     /** @return bool */
     public function appendXML(string $data) {}
 }
 
-class DOMElement {
+class DOMElement implements DOMParentNode, DOMChildNode {
     public function __construct(string $name, ?string $value = null, string $uri = "") {}
 
     /** @return string */
index eda6659f9e03db384c5ddbbaa750b15e6cb8f76e..07ddb19d7e256983be424117e566874a765f46eb 100644 (file)
@@ -1,5 +1,20 @@
 /* This is a generated file, edit the .stub.php file instead. */
 
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMChildNode_remove, 0, 0, IS_VOID, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMChildNode_before, 0, 0, IS_VOID, 0)
+       ZEND_ARG_VARIADIC_INFO(0, nodes)
+ZEND_END_ARG_INFO()
+
+#define arginfo_class_DOMChildNode_after arginfo_class_DOMChildNode_before
+
+#define arginfo_class_DOMChildNode_replaceWith arginfo_class_DOMChildNode_before
+
+#define arginfo_class_DOMParentNode_append arginfo_class_DOMChildNode_before
+
+#define arginfo_class_DOMParentNode_prepend arginfo_class_DOMChildNode_before
+
 ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOMNode_appendChild, 0, 0, 1)
        ZEND_ARG_OBJ_INFO(0, newChild, DOMNode, 0)
 ZEND_END_ARG_INFO()
index ce8e1e4adeb0aa95ccb40536d485e1bc1678cab2..3368b3fd640797a809b673b93e7205fef1bf3f0f 100644 (file)
@@ -19,6 +19,8 @@
 #define DOM_FE_H
 
 extern const zend_function_entry php_dom_domexception_class_functions[];
+extern const zend_function_entry php_dom_parent_node_class_functions[];
+extern const zend_function_entry php_dom_child_node_class_functions[];
 extern const zend_function_entry php_dom_domimplementation_class_functions[];
 extern const zend_function_entry php_dom_documentfragment_class_functions[];
 extern const zend_function_entry php_dom_document_class_functions[];
@@ -75,6 +77,8 @@ PHP_METHOD(domimplementation, getFeature);
 /* domdocumentfragment methods */
 PHP_METHOD(domdocumentfragment, __construct);
 PHP_METHOD(domdocumentfragment, appendXML);
+PHP_METHOD(domdocumentfragment, append);
+PHP_METHOD(domdocumentfragment, prepend);
 
 /* domdocument methods */
 PHP_METHOD(domdocument, createElement);
@@ -102,6 +106,8 @@ PHP_METHOD(domdocument, saveXML);
 PHP_METHOD(domdocument, validate);
 PHP_METHOD(domdocument, xinclude);
 PHP_METHOD(domdocument, registerNodeClass);
+PHP_METHOD(domdocument, append);
+PHP_METHOD(domdocument, prepend);
 
 #if defined(LIBXML_HTML_ENABLED)
 PHP_METHOD(domdocument, loadHTML);
@@ -152,6 +158,10 @@ PHP_METHOD(domcharacterdata, appendData);
 PHP_METHOD(domcharacterdata, insertData);
 PHP_METHOD(domcharacterdata, deleteData);
 PHP_METHOD(domcharacterdata, replaceData);
+PHP_METHOD(domcharacterdata, remove);
+PHP_METHOD(domcharacterdata, after);
+PHP_METHOD(domcharacterdata, before);
+PHP_METHOD(domcharacterdata, replaceWith);
 
 /* domattr methods */
 PHP_METHOD(domattr, isId);
@@ -177,6 +187,12 @@ PHP_METHOD(domelement, setIdAttribute);
 PHP_METHOD(domelement, setIdAttributeNS);
 PHP_METHOD(domelement, setIdAttributeNode);
 PHP_METHOD(domelement, __construct);
+PHP_METHOD(domelement, remove);
+PHP_METHOD(domelement, after);
+PHP_METHOD(domelement, before);
+PHP_METHOD(domelement, append);
+PHP_METHOD(domelement, prepend);
+PHP_METHOD(domelement, replaceWith);
 
 /* domtext methods */
 PHP_METHOD(domtext, splitText);
index 793e18c3635cca2ea44a32ea654c3ad79cc6b1d4..b4b040135697e935e42e380d896ef62ebd4ddea9 100644 (file)
@@ -87,6 +87,11 @@ int dom_entity_version_write(dom_object *obj, zval *newval);
 /* namednodemap properties */
 int dom_namednodemap_length_read(dom_object *obj, zval *retval);
 
+/* parent node properties */
+int dom_parent_node_first_element_child_read(dom_object *obj, zval *retval);
+int dom_parent_node_last_element_child_read(dom_object *obj, zval *retval);
+int dom_parent_node_child_element_count(dom_object *obj, zval *retval);
+
 /* node properties */
 int dom_node_node_name_read(dom_object *obj, zval *retval);
 int dom_node_node_value_read(dom_object *obj, zval *retval);
@@ -98,6 +103,8 @@ int dom_node_first_child_read(dom_object *obj, zval *retval);
 int dom_node_last_child_read(dom_object *obj, zval *retval);
 int dom_node_previous_sibling_read(dom_object *obj, zval *retval);
 int dom_node_next_sibling_read(dom_object *obj, zval *retval);
+int dom_node_previous_element_sibling_read(dom_object *obj, zval *retval);
+int dom_node_next_element_sibling_read(dom_object *obj, zval *retval);
 int dom_node_attributes_read(dom_object *obj, zval *retval);
 int dom_node_owner_document_read(dom_object *obj, zval *retval);
 int dom_node_namespace_uri_read(dom_object *obj, zval *retval);
index f4bbc3bfeaac9bad1e1ed153d072cdd464c4922f..6dfda8a85c92375ae6712209af045100c040a1fb 100644 (file)
@@ -51,6 +51,12 @@ const zend_function_entry php_dom_element_class_functions[] = { /* {{{ */
        PHP_ME(domelement, setIdAttributeNS, arginfo_class_DOMElement_setIdAttributeNS, ZEND_ACC_PUBLIC)
        PHP_ME(domelement, setIdAttributeNode, arginfo_class_DOMElement_setIdAttributeNode, ZEND_ACC_PUBLIC)
        PHP_ME(domelement, __construct, arginfo_class_DOMElement___construct, ZEND_ACC_PUBLIC)
+       PHP_ME(domelement, remove, arginfo_class_DOMChildNode_remove, ZEND_ACC_PUBLIC)
+       PHP_ME(domelement, after, arginfo_class_DOMChildNode_after, ZEND_ACC_PUBLIC)
+       PHP_ME(domelement, before, arginfo_class_DOMChildNode_before, ZEND_ACC_PUBLIC)
+       PHP_ME(domelement, replaceWith, arginfo_class_DOMChildNode_replaceWith, ZEND_ACC_PUBLIC)
+       PHP_ME(domelement, append, arginfo_class_DOMParentNode_append, ZEND_ACC_PUBLIC)
+       PHP_ME(domelement, prepend, arginfo_class_DOMParentNode_prepend, ZEND_ACC_PUBLIC)
        PHP_FE_END
 };
 /* }}} */
@@ -1183,4 +1189,126 @@ PHP_METHOD(domelement, setIdAttributeNode)
 }
 /* }}} end dom_element_set_id_attribute_node */
 
+/* {{{ proto void DOMElement::remove();
+URL:
+Since:
+*/
+PHP_METHOD(domelement, remove)
+{
+       zval *id;
+       xmlNodePtr child;
+       dom_object *intern;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(child, id, xmlNodePtr, intern);
+
+       dom_child_node_remove(intern);
+}
+/* }}} end DOMElement::remove */
+
+PHP_METHOD(domelement, after)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_after(intern, args, argc);
+}
+
+PHP_METHOD(domelement, before)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_before(intern, args, argc);
+}
+
+/* {{{ proto void domelement::append(string|DOMNode ...$nodes)
+URL: https://dom.spec.whatwg.org/#dom-parentnode-append
+Since: DOM Living Standard (DOM4)
+*/
+PHP_METHOD(domelement, append)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_append(intern, args, argc);
+}
+/* }}} end DOMElement::append */
+
+/* {{{ proto void domelement::prepend(string|DOMNode ...$nodes)
+URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
+Since: DOM Living Standard (DOM4)
+*/
+PHP_METHOD(domelement, prepend)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_prepend(intern, args, argc);
+}
+/* }}} end DOMElement::prepend */
+
+/* {{{ proto void domelement::replaceWith(string|DOMNode ...$nodes)
+URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
+Since: DOM Living Standard (DOM4)
+*/
+PHP_METHOD(domelement, replaceWith)
+{
+       int argc;
+       zval *args, *id;
+       dom_object *intern;
+       xmlNode *context;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
+               RETURN_THROWS();
+       }
+
+       id = ZEND_THIS;
+       DOM_GET_OBJ(context, id, xmlNodePtr, intern);
+
+       dom_parent_node_after(intern, args, argc);
+       dom_child_node_remove(intern);
+}
+/* }}} end DOMElement::prepend */
+
 #endif
index 457e0ae476df4794ddd2d79d19e5188fcec8ead4..9029478e1587497d4c47912972c19b479b9b245d 100644 (file)
@@ -53,36 +53,12 @@ const zend_function_entry php_dom_node_class_functions[] = { /* {{{ */
 };
 /* }}} */
 
-static void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
-{
-       xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL;
-
-       if (nodep->type == XML_ELEMENT_NODE) {
-               /* Following if block primarily used for inserting nodes created via createElementNS */
-               if (nodep->nsDef != NULL) {
-                       curns = nodep->nsDef;
-                       while (curns) {
-                               nsdftptr = curns->next;
-                               if (curns->href != NULL) {
-                                       if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) &&
-                                               (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
-                                               curns->next = NULL;
-                                               if (prevns == NULL) {
-                                                       nodep->nsDef = nsdftptr;
-                                               } else {
-                                                       prevns->next = nsdftptr;
-                                               }
-                                               dom_set_old_ns(doc, curns);
-                                               curns = prevns;
-                                       }
-                               }
-                               prevns = curns;
-                               curns = nsdftptr;
-                       }
-               }
-               xmlReconciliateNs(doc, nodep);
-       }
-}
+const zend_function_entry php_dom_child_node_class_functions[] = { /* {{{ */
+       PHP_ABSTRACT_ME(DOMChildNode, remove, arginfo_class_DOMChildNode_remove)
+       PHP_ABSTRACT_ME(DOMChildNode, after, arginfo_class_DOMChildNode_after)
+       PHP_ABSTRACT_ME(DOMChildNode, before, arginfo_class_DOMChildNode_before)
+       PHP_FE_END
+};
 /* }}} */
 
 /* {{{ nodeName        string
@@ -333,7 +309,6 @@ int dom_node_child_nodes_read(dom_object *obj, zval *retval)
 
        return SUCCESS;
 }
-
 /* }}} */
 
 /* {{{ firstChild DomNode
@@ -454,6 +429,72 @@ int dom_node_next_sibling_read(dom_object *obj, zval *retval)
 
 /* }}} */
 
+/* {{{ previousElementSibling  DomNode
+readonly=yes
+URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-640FB3C8
+Since:
+*/
+int dom_node_previous_element_sibling_read(dom_object *obj, zval *retval)
+{
+       xmlNode *nodep, *prevsib;
+
+       nodep = dom_object_get_node(obj);
+
+       if (nodep == NULL) {
+               php_dom_throw_error(INVALID_STATE_ERR, 0);
+               return FAILURE;
+       }
+
+       prevsib = nodep->prev;
+
+       while (prevsib && prevsib->type != XML_ELEMENT_NODE) {
+               prevsib = prevsib->prev;
+       }
+
+       if (!prevsib) {
+               ZVAL_NULL(retval);
+               return SUCCESS;
+       }
+
+       php_dom_create_object(prevsib, retval, obj);
+       return SUCCESS;
+}
+
+/* }}} */
+
+/* {{{ nextElementSibling      DomNode
+readonly=yes
+URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-6AC54C2F
+Since:
+*/
+int dom_node_next_element_sibling_read(dom_object *obj, zval *retval)
+{
+       xmlNode *nodep, *nextsib;
+
+       nodep = dom_object_get_node(obj);
+
+       if (nodep == NULL) {
+               php_dom_throw_error(INVALID_STATE_ERR, 0);
+               return FAILURE;
+       }
+
+       nextsib = nodep->next;
+
+       while (nextsib != NULL && nextsib->type != XML_ELEMENT_NODE) {
+               nextsib = nextsib->next;
+       }
+
+       if (!nextsib) {
+               ZVAL_NULL(retval);
+               return SUCCESS;
+       }
+
+       php_dom_create_object(nextsib, retval, obj);
+       return SUCCESS;
+}
+
+/* }}} */
+
 /* {{{ attributes      DomNamedNodeMap
 readonly=yes
 URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-84CF096
diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c
new file mode 100644 (file)
index 0000000..109b74d
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 7                                                        |
+   +----------------------------------------------------------------------+
+   | 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:           |
+   | http://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: Benjamin Eberlei <beberlei@php.net>                         |
+   +----------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+#if HAVE_LIBXML && HAVE_DOM
+#include "php_dom.h"
+#include "dom_arginfo.h"
+
+
+/* {{{ DOMParentNode methods */
+const zend_function_entry php_dom_parent_node_class_functions[] = { /* {{{ */
+       PHP_ABSTRACT_ME(DOMParentNode, append, arginfo_class_DOMParentNode_append)
+       PHP_ABSTRACT_ME(DOMParentNode, prepend, arginfo_class_DOMParentNode_prepend)
+       PHP_FE_END
+};
+/* }}} */
+
+/* {{{ firstElementChild DomParentNode
+readonly=yes
+URL: https://www.w3.org/TR/dom/#dom-parentnode-firstelementchild
+*/
+int dom_parent_node_first_element_child_read(dom_object *obj, zval *retval)
+{
+       xmlNode *nodep, *first = NULL;
+
+       nodep = dom_object_get_node(obj);
+
+       if (nodep == NULL) {
+               php_dom_throw_error(INVALID_STATE_ERR, 0);
+               return FAILURE;
+       }
+
+       if (dom_node_children_valid(nodep) == SUCCESS) {
+               first = nodep->children;
+
+               while (first && first->type != XML_ELEMENT_NODE) {
+                       first = first->next;
+               }
+       }
+
+       if (!first) {
+               ZVAL_NULL(retval);
+               return SUCCESS;
+       }
+
+       php_dom_create_object(first, retval, obj);
+       return SUCCESS;
+}
+/* }}} */
+
+/* {{{ lastElementChild DomParentNode
+readonly=yes
+URL: https://www.w3.org/TR/dom/#dom-parentnode-lastelementchild
+*/
+int dom_parent_node_last_element_child_read(dom_object *obj, zval *retval)
+{
+       xmlNode *nodep, *last = NULL;
+
+       nodep = dom_object_get_node(obj);
+
+       if (nodep == NULL) {
+               php_dom_throw_error(INVALID_STATE_ERR, 0);
+               return FAILURE;
+       }
+
+       if (dom_node_children_valid(nodep) == SUCCESS) {
+               last = nodep->last;
+
+               while (last && last->type != XML_ELEMENT_NODE) {
+                       last = last->prev;
+               }
+       }
+
+       if (!last) {
+               ZVAL_NULL(retval);
+               return SUCCESS;
+       }
+
+       php_dom_create_object(last, retval, obj);
+       return SUCCESS;
+}
+/* }}} */
+
+/* {{{ childElementCount DomParentNode
+readonly=yes
+https://www.w3.org/TR/dom/#dom-parentnode-childelementcount
+*/
+int dom_parent_node_child_element_count(dom_object *obj, zval *retval)
+{
+       xmlNode *nodep, *first = NULL;
+       zend_long count = 0;
+
+       nodep = dom_object_get_node(obj);
+
+       if (nodep == NULL) {
+               php_dom_throw_error(INVALID_STATE_ERR, 0);
+               return FAILURE;
+       }
+
+       if (dom_node_children_valid(nodep) == SUCCESS) {
+               first = nodep->children;
+
+               while (first != NULL) {
+                       if (first->type == XML_ELEMENT_NODE) {
+                               count++;
+                       }
+
+                       first = first->next;
+               }
+       }
+
+       ZVAL_LONG(retval, count);
+
+       return SUCCESS;
+}
+/* }}} */
+
+xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, int nodesc)
+{
+       int i;
+       xmlDoc *documentNode;
+       xmlNode *fragment;
+       xmlNode *newNode;
+       zend_class_entry *ce;
+       dom_object *newNodeObj;
+       int stricterror;
+
+       if (contextNode->type == XML_DOCUMENT_NODE || contextNode->type == XML_HTML_DOCUMENT_NODE) {
+               documentNode = (xmlDoc *) contextNode;
+       } else {
+               documentNode = contextNode->doc;
+       }
+
+       fragment = xmlNewDocFragment(documentNode);
+
+       if (!fragment) {
+               return NULL;
+       }
+
+       stricterror = dom_get_strict_error(document);
+
+       for (i = 0; i < nodesc; i++) {
+               if (Z_TYPE(nodes[i]) == IS_OBJECT) {
+                       ce = Z_OBJCE(nodes[i]);
+
+                       if (instanceof_function(ce, dom_node_class_entry)) {
+                               newNodeObj = Z_DOMOBJ_P(&nodes[i]);
+                               newNode = dom_object_get_node(newNodeObj);
+
+                               if (newNode->doc != documentNode) {
+                                       php_dom_throw_error(WRONG_DOCUMENT_ERR, stricterror);
+                                       return NULL;
+                               }
+
+                               if (newNode->parent != NULL) {
+                                       xmlUnlinkNode(newNode);
+                               }
+
+                               newNodeObj->document = document;
+                               xmlSetTreeDoc(newNode, documentNode);
+
+                               if (!xmlAddChild(fragment, newNode)) {
+                                       xmlFree(fragment);
+
+                                       php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
+                                       return NULL;
+                               }
+
+                               continue;
+                       } else {
+                               xmlFree(fragment);
+
+                               zend_type_error("Invalid argument type must be either DOMNode or string");
+                               return NULL;
+                       }
+               } else if (Z_TYPE(nodes[i]) == IS_STRING) {
+                       newNode = xmlNewDocText(documentNode, (xmlChar *) Z_STRVAL(nodes[i]));
+
+                       xmlSetTreeDoc(newNode, documentNode);
+
+                       if (!xmlAddChild(fragment, newNode)) {
+                               xmlFree(fragment);
+
+                               return NULL;
+                       }
+               } else {
+                       xmlFree(fragment);
+
+                       zend_type_error("Invalid argument type must be either DOMNode or string");
+
+                       return NULL;
+               }
+       }
+
+       return fragment;
+}
+
+static void dom_fragment_assign_parent_node(xmlNodePtr parentNode, xmlNodePtr fragment)
+{
+       xmlNodePtr node = fragment->children;
+
+       while (node != NULL) {
+               node->parent = parentNode;
+
+               if (node == fragment->last) {
+                       break;
+               }
+               node = node->next;
+       }
+
+       fragment->children = NULL;
+       fragment->last = NULL;
+}
+
+void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc)
+{
+       xmlNode *parentNode = dom_object_get_node(context);
+       xmlNodePtr newchild, prevsib;
+       xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
+
+       if (fragment == NULL) {
+               return;
+       }
+
+       newchild = fragment->children;
+       prevsib = parentNode->last;
+
+       if (newchild) {
+               if (prevsib != NULL) {
+                       prevsib->next = newchild;
+               } else {
+                       parentNode->children = newchild;
+               }
+
+               parentNode->last = fragment->last;
+
+               newchild->prev = prevsib;
+
+               dom_fragment_assign_parent_node(parentNode, fragment);
+
+               dom_reconcile_ns(parentNode->doc, newchild);
+       }
+
+       xmlFree(fragment);
+}
+
+void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
+{
+       xmlNode *parentNode = dom_object_get_node(context);
+
+       if (parentNode->children == NULL) {
+               dom_parent_node_append(context, nodes, nodesc);
+               return;
+       }
+
+       xmlNodePtr newchild, nextsib;
+       xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
+
+       if (fragment == NULL) {
+               return;
+       }
+
+       newchild = fragment->children;
+       nextsib = parentNode->children;
+
+       if (newchild) {
+               parentNode->children = newchild;
+               fragment->last->next = nextsib;
+               nextsib->prev = fragment->last;
+
+               dom_fragment_assign_parent_node(parentNode, fragment);
+
+               dom_reconcile_ns(parentNode->doc, newchild);
+       }
+
+       xmlFree(fragment);
+}
+
+void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
+{
+       xmlNode *prevsib = dom_object_get_node(context);
+       xmlNodePtr newchild, parentNode;
+       xmlNode *fragment;
+
+       int stricterror = dom_get_strict_error(context->document);
+
+       if (!prevsib->parent) {
+               php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
+               return;
+       }
+
+       parentNode = prevsib->parent;
+       fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
+
+       if (fragment == NULL) {
+               return;
+       }
+
+       newchild = fragment->children;
+
+       if (newchild) {
+               fragment->last->next = prevsib->next;
+               prevsib->next = newchild;
+
+               newchild->prev = prevsib;
+
+               dom_fragment_assign_parent_node(parentNode, fragment);
+               dom_reconcile_ns(prevsib->doc, newchild);
+       }
+
+       xmlFree(fragment);
+}
+
+void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
+{
+       xmlNode *nextsib = dom_object_get_node(context);
+       xmlNodePtr newchild, prevsib, parentNode;
+       xmlNode *fragment;
+
+       prevsib = nextsib->prev;
+       parentNode = nextsib->parent;
+       fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
+
+       if (fragment == NULL) {
+               return;
+       }
+
+       newchild = fragment->children;
+
+       if (newchild) {
+               if (parentNode->children == nextsib) {
+                       parentNode->children = newchild;
+               } else {
+                       prevsib->next = newchild;
+               }
+               fragment->last->next = nextsib;
+               nextsib->prev = fragment->last;
+
+               newchild->prev = prevsib;
+
+               dom_fragment_assign_parent_node(parentNode, fragment);
+
+               dom_reconcile_ns(nextsib->doc, newchild);
+       }
+
+       xmlFree(fragment);
+}
+
+void dom_child_node_remove(dom_object *context)
+{
+       xmlNode *child = dom_object_get_node(context);
+       xmlNodePtr children;
+       int stricterror;
+
+       if (dom_node_children_valid(child) == FAILURE) {
+               return;
+       }
+
+       stricterror = dom_get_strict_error(context->document);
+
+       if (dom_node_is_read_only(child) == SUCCESS ||
+               (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
+               php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
+               return;
+       }
+
+       if (!child->parent) {
+               php_dom_throw_error(NOT_FOUND_ERR, stricterror);
+               return;
+       }
+
+       children = child->parent->children;
+       if (!children) {
+               php_dom_throw_error(NOT_FOUND_ERR, stricterror);
+               return;
+       }
+
+       while (children) {
+               if (children == child) {
+                       xmlUnlinkNode(child);
+                       return;
+               }
+               children = children->next;
+       }
+
+       php_dom_throw_error(NOT_FOUND_ERR, stricterror);
+}
+
+#endif
index 98c4e703c5bd1367740d983840a38e77ab25a547..dae52faeeb9bcd231ec1c2ec10ee0b701da42910 100644 (file)
@@ -35,6 +35,8 @@
 /* {{{ class entries */
 PHP_DOM_EXPORT zend_class_entry *dom_node_class_entry;
 PHP_DOM_EXPORT zend_class_entry *dom_domexception_class_entry;
+PHP_DOM_EXPORT zend_class_entry *dom_parentnode_class_entry;
+PHP_DOM_EXPORT zend_class_entry *dom_childnode_class_entry;
 PHP_DOM_EXPORT zend_class_entry *dom_domimplementation_class_entry;
 PHP_DOM_EXPORT zend_class_entry *dom_documentfragment_class_entry;
 PHP_DOM_EXPORT zend_class_entry *dom_document_class_entry;
@@ -66,6 +68,7 @@ zend_object_handlers dom_xpath_object_handlers;
 static HashTable classes;
 /* {{{ prop handler tables */
 static HashTable dom_document_prop_handlers;
+static HashTable dom_documentfragment_prop_handlers;
 static HashTable dom_node_prop_handlers;
 static HashTable dom_nodelist_prop_handlers;
 static HashTable dom_namednodemap_prop_handlers;
@@ -585,6 +588,12 @@ PHP_MINIT_FUNCTION(dom)
        dom_domexception_class_entry->ce_flags |= ZEND_ACC_FINAL;
        zend_declare_property_long(dom_domexception_class_entry, "code", sizeof("code")-1, 0, ZEND_ACC_PUBLIC);
 
+       INIT_CLASS_ENTRY(ce, "DOMParentNode", php_dom_parent_node_class_functions);
+       dom_parentnode_class_entry = zend_register_internal_interface(&ce);
+
+       INIT_CLASS_ENTRY(ce, "DOMChildNode", php_dom_child_node_class_functions);
+       dom_childnode_class_entry = zend_register_internal_interface(&ce);
+
        REGISTER_DOM_CLASS(ce, "DOMImplementation", NULL, php_dom_domimplementation_class_functions, dom_domimplementation_class_entry);
 
        REGISTER_DOM_CLASS(ce, "DOMNode", NULL, php_dom_node_class_functions, dom_node_class_entry);
@@ -622,7 +631,15 @@ PHP_MINIT_FUNCTION(dom)
        zend_hash_add_ptr(&classes, ce.name, &dom_namespace_node_prop_handlers);
 
        REGISTER_DOM_CLASS(ce, "DOMDocumentFragment", dom_node_class_entry, php_dom_documentfragment_class_functions, dom_documentfragment_class_entry);
-       zend_hash_add_ptr(&classes, ce.name, &dom_node_prop_handlers);
+       zend_hash_init(&dom_documentfragment_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
+
+       dom_register_prop_handler(&dom_documentfragment_prop_handlers, "firstElementChild", sizeof("firstElementChild")-1, dom_parent_node_first_element_child_read, NULL);
+       dom_register_prop_handler(&dom_documentfragment_prop_handlers, "lastElementChild", sizeof("lastElementChild")-1, dom_parent_node_last_element_child_read, NULL);
+       dom_register_prop_handler(&dom_documentfragment_prop_handlers, "childElementCount", sizeof("childElementCount")-1, dom_parent_node_child_element_count, NULL);
+
+       zend_hash_merge(&dom_documentfragment_prop_handlers, &dom_node_prop_handlers, dom_copy_prop_handler, 0);
+       zend_hash_add_ptr(&classes, ce.name, &dom_documentfragment_prop_handlers);
+       zend_class_implements(dom_documentfragment_class_entry, 1, dom_parentnode_class_entry);
 
        REGISTER_DOM_CLASS(ce, "DOMDocument", dom_node_class_entry, php_dom_document_class_functions, dom_document_class_entry);
        zend_hash_init(&dom_document_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
@@ -646,8 +663,13 @@ PHP_MINIT_FUNCTION(dom)
        dom_register_prop_handler(&dom_document_prop_handlers, "recover", sizeof("recover")-1, dom_document_recover_read, dom_document_recover_write);
        dom_register_prop_handler(&dom_document_prop_handlers, "substituteEntities", sizeof("substituteEntities")-1, dom_document_substitue_entities_read, dom_document_substitue_entities_write);
 
+       dom_register_prop_handler(&dom_document_prop_handlers, "firstElementChild", sizeof("firstElementChild")-1, dom_parent_node_first_element_child_read, NULL);
+       dom_register_prop_handler(&dom_document_prop_handlers, "lastElementChild", sizeof("lastElementChild")-1, dom_parent_node_last_element_child_read, NULL);
+       dom_register_prop_handler(&dom_document_prop_handlers, "childElementCount", sizeof("childElementCount")-1, dom_parent_node_child_element_count, NULL);
+
        zend_hash_merge(&dom_document_prop_handlers, &dom_node_prop_handlers, dom_copy_prop_handler, 0);
        zend_hash_add_ptr(&classes, ce.name, &dom_document_prop_handlers);
+       zend_class_implements(dom_document_class_entry, 1, dom_parentnode_class_entry);
 
        INIT_CLASS_ENTRY(ce, "DOMNodeList", php_dom_nodelist_class_functions);
        ce.create_object = dom_nnodemap_objects_new;
@@ -674,9 +696,13 @@ PHP_MINIT_FUNCTION(dom)
        zend_hash_init(&dom_characterdata_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
        dom_register_prop_handler(&dom_characterdata_prop_handlers, "data", sizeof("data")-1, dom_characterdata_data_read, dom_characterdata_data_write);
        dom_register_prop_handler(&dom_characterdata_prop_handlers, "length", sizeof("length")-1, dom_characterdata_length_read, NULL);
+       dom_register_prop_handler(&dom_characterdata_prop_handlers, "previousElementSibling", sizeof("previousElementSibling")-1, dom_node_previous_element_sibling_read, NULL);
+       dom_register_prop_handler(&dom_characterdata_prop_handlers, "nextElementSibling", sizeof("nextElementSibling")-1, dom_node_next_element_sibling_read, NULL);
        zend_hash_merge(&dom_characterdata_prop_handlers, &dom_node_prop_handlers, dom_copy_prop_handler, 0);
        zend_hash_add_ptr(&classes, ce.name, &dom_characterdata_prop_handlers);
 
+       zend_class_implements(dom_characterdata_class_entry, 1, dom_childnode_class_entry);
+
        REGISTER_DOM_CLASS(ce, "DOMAttr", dom_node_class_entry, php_dom_attr_class_functions, dom_attr_class_entry);
 
        zend_hash_init(&dom_attr_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
@@ -693,9 +719,16 @@ PHP_MINIT_FUNCTION(dom)
        zend_hash_init(&dom_element_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
        dom_register_prop_handler(&dom_element_prop_handlers, "tagName", sizeof("tagName")-1, dom_element_tag_name_read, NULL);
        dom_register_prop_handler(&dom_element_prop_handlers, "schemaTypeInfo", sizeof("schemaTypeInfo")-1, dom_element_schema_type_info_read, NULL);
+       dom_register_prop_handler(&dom_element_prop_handlers, "firstElementChild", sizeof("firstElementChild")-1, dom_parent_node_first_element_child_read, NULL);
+       dom_register_prop_handler(&dom_element_prop_handlers, "lastElementChild", sizeof("lastElementChild")-1, dom_parent_node_last_element_child_read, NULL);
+       dom_register_prop_handler(&dom_element_prop_handlers, "childElementCount", sizeof("childElementCount")-1, dom_parent_node_child_element_count, NULL);
+       dom_register_prop_handler(&dom_element_prop_handlers, "previousElementSibling", sizeof("previousElementSibling")-1, dom_node_previous_element_sibling_read, NULL);
+       dom_register_prop_handler(&dom_element_prop_handlers, "nextElementSibling", sizeof("nextElementSibling")-1, dom_node_next_element_sibling_read, NULL);
        zend_hash_merge(&dom_element_prop_handlers, &dom_node_prop_handlers, dom_copy_prop_handler, 0);
        zend_hash_add_ptr(&classes, ce.name, &dom_element_prop_handlers);
 
+       zend_class_implements(dom_element_class_entry, 2, dom_parentnode_class_entry, dom_childnode_class_entry);
+
        REGISTER_DOM_CLASS(ce, "DOMText", dom_characterdata_class_entry, php_dom_text_class_functions, dom_text_class_entry);
 
        zend_hash_init(&dom_text_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1);
@@ -851,6 +884,7 @@ PHP_MINFO_FUNCTION(dom)
 PHP_MSHUTDOWN_FUNCTION(dom) /* {{{ */
 {
        zend_hash_destroy(&dom_document_prop_handlers);
+       zend_hash_destroy(&dom_documentfragment_prop_handlers);
        zend_hash_destroy(&dom_node_prop_handlers);
        zend_hash_destroy(&dom_namespace_node_prop_handlers);
        zend_hash_destroy(&dom_nodelist_prop_handlers);
@@ -1344,6 +1378,38 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) {
 }
 /* }}} end dom_set_old_ns */
 
+void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
+{
+       xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL;
+
+       if (nodep->type == XML_ELEMENT_NODE) {
+               /* Following if block primarily used for inserting nodes created via createElementNS */
+               if (nodep->nsDef != NULL) {
+                       curns = nodep->nsDef;
+                       while (curns) {
+                               nsdftptr = curns->next;
+                               if (curns->href != NULL) {
+                                       if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) &&
+                                               (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
+                                               curns->next = NULL;
+                                               if (prevns == NULL) {
+                                                       nodep->nsDef = nsdftptr;
+                                               } else {
+                                                       prevns->next = nsdftptr;
+                                               }
+                                               dom_set_old_ns(doc, curns);
+                                               curns = prevns;
+                                       }
+                               }
+                               prevns = curns;
+                               curns = nsdftptr;
+                       }
+               }
+               xmlReconciliateNs(doc, nodep);
+       }
+}
+/* }}} */
+
 /*
 http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS
 
index 6d9eaef46d598a1128e6de9b99c53608b3d59b7f..e1058e849b74cc5d3f6a7b8c82bef9054174cc71 100644 (file)
@@ -109,6 +109,7 @@ void node_list_unlink(xmlNodePtr node);
 int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, int name_len);
 xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix);
 void dom_set_old_ns(xmlDoc *doc, xmlNs *ns);
+void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep);
 xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName);
 void dom_normalize (xmlNodePtr nodep);
 xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *local, int *cur, int index);
@@ -125,6 +126,12 @@ xmlNode *php_dom_libxml_notation_iter(xmlHashTable *ht, int index);
 zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref);
 void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce);
 
+void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc);
+void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc);
+void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc);
+void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc);
+void dom_child_node_remove(dom_object *context);
+
 #define REGISTER_DOM_CLASS(ce, name, parent_ce, funcs, entry) \
 INIT_CLASS_ENTRY(ce, name, funcs); \
 ce.create_object = dom_objects_new; \
diff --git a/ext/dom/tests/DOM4_ChildNode_wrong_document.phpt b/ext/dom/tests/DOM4_ChildNode_wrong_document.phpt
new file mode 100644 (file)
index 0000000..dc16cc1
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+DOMChildNode::after(), before, replaceWith with DOMNode from wrong document throws exception
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom1 = new DOMDocument;
+$dom1->loadXML('<test/>');
+
+$dom2 = new DOMDocument;
+$dom2->loadXML('<test><foo /></test>');
+
+$element = $dom1->documentElement;
+
+try {
+    $element->after($dom2->documentElement->firstChild);
+    echo "FAIL";
+} catch (DOMException $e) {
+    echo $e->getMessage() . "\n";
+}
+
+try {
+    $element->before($dom2->documentElement->firstChild);
+    echo "FAIL";
+} catch (DOMException $e) {
+    echo $e->getMessage() . "\n";
+}
+
+try {
+    $element->replaceWith($dom2->documentElement->firstChild);
+    echo "FAIL";
+} catch (DOMException $e) {
+    echo $e->getMessage();
+}
+?>
+--EXPECT--
+Wrong Document Error
+Wrong Document Error
+Wrong Document Error
diff --git a/ext/dom/tests/DOM4_DOMNode_ElementSiblings.phpt b/ext/dom/tests/DOM4_DOMNode_ElementSiblings.phpt
new file mode 100644 (file)
index 0000000..8a27837
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+DOMNode: Element Siblings
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test>foo<bar>FirstElement</bar><bar>LastElement</bar>bar</test>');
+
+$element = $dom->documentElement;
+print_node($element->firstElementChild->nextElementSibling);
+print_node($element->lastElementChild->previousElementSibling);
+?>
+--EXPECT--
+Node Name: bar
+Node Type: 1
+Num Children: 1
+Node Content: LastElement
+
+Node Name: bar
+Node Type: 1
+Num Children: 1
+Node Content: FirstElement
diff --git a/ext/dom/tests/DOM4_DOMNode_after.phpt b/ext/dom/tests/DOM4_DOMNode_after.phpt
new file mode 100644 (file)
index 0000000..c53fdba
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+DOMNode::after()
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test><mark>first</mark><mark>second</mark></test>');
+
+$element = $dom->documentElement->firstElementChild;
+$secondMark = $dom->documentElement->lastElementChild;
+
+$element->after(
+  'text inserted after',
+  $dom->createElement('inserted-after', 'content')
+);
+
+$secondMark->after('text inserted after second');
+
+print_node_list_compact($dom->documentElement->childNodes);
+?>
+--EXPECT--
+<mark>
+  first
+</mark>
+text inserted after
+<inserted-after>
+  content
+</inserted-after>
+<mark>
+  second
+</mark>
+text inserted after second
diff --git a/ext/dom/tests/DOM4_DOMNode_after_ns.phpt b/ext/dom/tests/DOM4_DOMNode_after_ns.phpt
new file mode 100644 (file)
index 0000000..0b28846
--- /dev/null
@@ -0,0 +1,29 @@
+--TEST--
+DOMNode::after() with namespace
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$doc  = new DOMDocument('1.0', 'utf-8');
+$doc->formatOutput = true;
+
+$root = $doc->createElementNS('http://www.w3.org/2005/Atom', 'element');
+$doc->appendChild($root);
+$root->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:g', 'http://base.google.com/ns/1.0');
+
+$item = $doc->createElementNS('http://base.google.com/ns/1.0', 'g:item_type', 'house');
+$root->append($item);
+
+$item2 = $doc->createElementNS('http://base.google.com/ns/1.0', 'g:item_type', 'street');
+$item->after($item2);
+
+echo $doc->saveXML(), "\n";
+?>
+--EXPECT--
+<?xml version="1.0" encoding="utf-8"?>
+<element xmlns="http://www.w3.org/2005/Atom" xmlns:g="http://base.google.com/ns/1.0">
+  <g:item_type>house</g:item_type>
+  <g:item_type>street</g:item_type>
+</element>
diff --git a/ext/dom/tests/DOM4_DOMNode_append_ns.phpt b/ext/dom/tests/DOM4_DOMNode_append_ns.phpt
new file mode 100644 (file)
index 0000000..936addb
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+DOMNode::append() with namespace
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$doc  = new DOMDocument('1.0', 'utf-8');
+$doc->formatOutput = true;
+
+$root = $doc->createElementNS('http://www.w3.org/2005/Atom', 'element');
+$doc->appendChild($root);
+$root->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:g', 'http://base.google.com/ns/1.0');
+
+$item = $doc->createElementNS('http://base.google.com/ns/1.0', 'g:item_type', 'house');
+$root->append($item);
+
+echo $doc->saveXML(), "\n";
+?>
+--EXPECT--
+<?xml version="1.0" encoding="utf-8"?>
+<element xmlns="http://www.w3.org/2005/Atom" xmlns:g="http://base.google.com/ns/1.0">
+  <g:item_type>house</g:item_type>
+</element>
diff --git a/ext/dom/tests/DOM4_DOMNode_before.phpt b/ext/dom/tests/DOM4_DOMNode_before.phpt
new file mode 100644 (file)
index 0000000..b3806af
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+DOMNode::before()
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test><mark>first</mark><mark>second</mark></test>');
+
+$element = $dom->documentElement->firstElementChild;
+$secondMark = $dom->documentElement->lastElementChild;
+
+$element->before(
+  $dom->createElement('inserted-before', 'content'),
+  'text inserted before'
+);
+
+$secondMark->before('text inserted before second');
+
+print_node_list_compact($dom->documentElement->childNodes);
+?>
+--EXPECT--
+<inserted-before>
+  content
+</inserted-before>
+text inserted before
+<mark>
+  first
+</mark>
+text inserted before second
+<mark>
+  second
+</mark>
diff --git a/ext/dom/tests/DOM4_DOMNode_before_ns.phpt b/ext/dom/tests/DOM4_DOMNode_before_ns.phpt
new file mode 100644 (file)
index 0000000..6ff2ca7
--- /dev/null
@@ -0,0 +1,29 @@
+--TEST--
+DOMNode::before() with namespace
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$doc  = new DOMDocument('1.0', 'utf-8');
+$doc->formatOutput = true;
+
+$root = $doc->createElementNS('http://www.w3.org/2005/Atom', 'element');
+$doc->appendChild($root);
+$root->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:g', 'http://base.google.com/ns/1.0');
+
+$item = $doc->createElementNS('http://base.google.com/ns/1.0', 'g:item_type', 'house');
+$root->append($item);
+
+$item2 = $doc->createElementNS('http://base.google.com/ns/1.0', 'g:item_type', 'street');
+$item->before($item2);
+
+echo $doc->saveXML(), "\n";
+?>
+--EXPECT--
+<?xml version="1.0" encoding="utf-8"?>
+<element xmlns="http://www.w3.org/2005/Atom" xmlns:g="http://base.google.com/ns/1.0">
+  <g:item_type>street</g:item_type>
+  <g:item_type>house</g:item_type>
+</element>
diff --git a/ext/dom/tests/DOM4_DOMNode_prepend_ns.phpt b/ext/dom/tests/DOM4_DOMNode_prepend_ns.phpt
new file mode 100644 (file)
index 0000000..c31cb6f
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+DOMNode::prepend() with namespace
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$doc  = new DOMDocument('1.0', 'utf-8');
+$doc->formatOutput = true;
+
+$root = $doc->createElementNS('http://www.w3.org/2005/Atom', 'element');
+$doc->appendChild($root);
+$root->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:g', 'http://base.google.com/ns/1.0');
+
+$item = $doc->createElementNS('http://base.google.com/ns/1.0', 'g:item_type', 'house');
+$root->prepend($item);
+
+echo $doc->saveXML(), "\n";
+?>
+--EXPECT--
+<?xml version="1.0" encoding="utf-8"?>
+<element xmlns="http://www.w3.org/2005/Atom" xmlns:g="http://base.google.com/ns/1.0">
+  <g:item_type>house</g:item_type>
+</element>
diff --git a/ext/dom/tests/DOM4_DOMNode_remove.phpt b/ext/dom/tests/DOM4_DOMNode_remove.phpt
new file mode 100644 (file)
index 0000000..8eb1609
--- /dev/null
@@ -0,0 +1,30 @@
+--TEST--
+DOMNode::remove()
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test><one>first</one><two>second</two></test>');
+
+$element = $dom->documentElement;
+print_node($element->firstChild);
+$returnValue = $element->firstChild->remove();
+print_node($element->firstChild);
+var_dump($returnValue);
+?>
+--EXPECT--
+Node Name: one
+Node Type: 1
+Num Children: 1
+Node Content: first
+
+Node Name: two
+Node Type: 1
+Num Children: 1
+Node Content: second
+
+NULL
+
diff --git a/ext/dom/tests/DOM4_DOMNode_removeDanglingElement.phpt b/ext/dom/tests/DOM4_DOMNode_removeDanglingElement.phpt
new file mode 100644 (file)
index 0000000..ceedac4
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+DOMNode::remove() dangling element
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+
+$dom = new DOMDocument();
+
+$element = $dom->createElement('test');
+
+try {
+    $element->remove();
+} catch (DOMException $e) {
+    echo $e->getMessage();
+}
+--EXPECT--
+Not Found Error
diff --git a/ext/dom/tests/DOM4_DOMNode_replaceWith.phpt b/ext/dom/tests/DOM4_DOMNode_replaceWith.phpt
new file mode 100644 (file)
index 0000000..ceaf726
--- /dev/null
@@ -0,0 +1,27 @@
+--TEST--
+DOMNode::replaceWith()
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test><mark>first</mark><mark>second</mark></test>');
+
+$element = $dom->documentElement->firstChild;
+$element->replaceWith(
+  $dom->createElement('element', 'content'),
+  'content'
+);
+
+print_node_list_compact($dom->documentElement->childNodes);
+?>
+--EXPECT--
+<element>
+  content
+</element>
+content
+<mark>
+  second
+</mark>
diff --git a/ext/dom/tests/DOM4_ParentNode.phpt b/ext/dom/tests/DOM4_ParentNode.phpt
new file mode 100644 (file)
index 0000000..f5bc5da
--- /dev/null
@@ -0,0 +1,51 @@
+--TEST--
+DOMParentNode: Child Element Handling
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test>foo<bar>FirstElement</bar><bar>LastElement</bar>bar</test>');
+
+var_dump($dom instanceof DOMParentNode);
+print_node($dom->firstElementChild);
+print_node($dom->lastElementChild);
+var_dump($dom->childElementCount);
+
+$element = $dom->documentElement;
+var_dump($element instanceof DOMParentNode);
+print_node($element->firstElementChild);
+print_node($element->lastElementChild);
+var_dump($element->childElementCount);
+var_dump($element->lastElementChild->firstElementChild);
+var_dump($element->lastElementChild->lastElementChild);
+var_dump($element->lastElementChild->childElementCount);
+?>
+--EXPECT--
+bool(true)
+Node Name: test
+Node Type: 1
+Num Children: 4
+
+Node Name: test
+Node Type: 1
+Num Children: 4
+
+int(1)
+bool(true)
+Node Name: bar
+Node Type: 1
+Num Children: 1
+Node Content: FirstElement
+
+Node Name: bar
+Node Type: 1
+Num Children: 1
+Node Content: LastElement
+
+int(2)
+NULL
+NULL
+int(0)
diff --git a/ext/dom/tests/DOM4_ParentNode_Fragment.phpt b/ext/dom/tests/DOM4_ParentNode_Fragment.phpt
new file mode 100644 (file)
index 0000000..4f7fee3
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+DOMParentNode: Child Element Handling
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test></test>');
+
+$fragment = $dom->createDocumentFragment();
+$fragment->appendChild($dom->createTextNode('foo'));
+$fragment->appendChild($dom->createElement('bar', 'FirstElement'));
+$fragment->appendChild($dom->createElement('bar', 'LastElement'));
+$fragment->appendChild($dom->createTextNode('bar'));
+
+var_dump($fragment instanceof DOMParentNode);
+print_node($fragment->firstElementChild);
+print_node($fragment->lastElementChild);
+var_dump($fragment->childElementCount);
+var_dump($fragment->lastElementChild->firstElementChild);
+var_dump($fragment->lastElementChild->lastElementChild);
+var_dump($fragment->lastElementChild->childElementCount);
+?>
+--EXPECT--
+bool(true)
+Node Name: bar
+Node Type: 1
+Num Children: 1
+Node Content: FirstElement
+
+Node Name: bar
+Node Type: 1
+Num Children: 1
+Node Content: LastElement
+
+int(2)
+NULL
+NULL
+int(0)
diff --git a/ext/dom/tests/DOM4_ParentNode_append.phpt b/ext/dom/tests/DOM4_ParentNode_append.phpt
new file mode 100644 (file)
index 0000000..2bab2a4
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+DOMParentNode::append()
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+declare(strict_types=1);
+
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test><mark/><mark /><mark /></test>');
+
+$element = $dom->documentElement;
+$element->append(
+  $dom->createElement('element', 'content'),
+  'content'
+);
+
+var_dump($dom->documentElement->childElementCount);
+print_node_list_compact($element->childNodes);
+
+$element->append(
+  $dom->createElement('element', 'content'),
+  'content'
+);
+var_dump($dom->documentElement->childElementCount);
+?>
+--EXPECT--
+int(4)
+<mark>
+</mark>
+<mark>
+</mark>
+<mark>
+</mark>
+<element>
+  content
+</element>
+content
+int(5)
diff --git a/ext/dom/tests/DOM4_ParentNode_append_invalidtypes.phpt b/ext/dom/tests/DOM4_ParentNode_append_invalidtypes.phpt
new file mode 100644 (file)
index 0000000..ab715ca
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+DOMParentNode::append() exception on invalid argument
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test />');
+
+try {
+    $dom->documentElement->append(array());
+} catch(TypeError $e) {
+    echo "OK! {$e->getMessage()}";
+}
+--EXPECT--
+OK! Invalid argument type must be either DOMNode or string
diff --git a/ext/dom/tests/DOM4_ParentNode_append_with_attributes.phpt b/ext/dom/tests/DOM4_ParentNode_append_with_attributes.phpt
new file mode 100644 (file)
index 0000000..3fb2667
--- /dev/null
@@ -0,0 +1,26 @@
+--TEST--
+DOMParentNode::append() with attributes
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test attr-one="21"/>');
+
+$replacement = $dom->createAttribute('attr-one');
+$replacement->value ='42';
+$addition = $dom->createAttribute('attr-two');
+$addition->value = '23';
+
+$element = $dom->documentElement;
+
+try {
+    $element->append($replacement, $addition);
+} catch (DOMException $e) {
+    echo $e->getMessage();
+}
+?>
+--EXPECT--
+Hierarchy Request Error
diff --git a/ext/dom/tests/DOM4_ParentNode_append_wrong_document.phpt b/ext/dom/tests/DOM4_ParentNode_append_wrong_document.phpt
new file mode 100644 (file)
index 0000000..8d5be43
--- /dev/null
@@ -0,0 +1,33 @@
+--TEST--
+DOMParentNode::append() with DOMNode from wrong document throws exception
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+require_once("dom_test.inc");
+
+$dom1 = new DOMDocument;
+$dom1->loadXML('<test/>');
+
+$dom2 = new DOMDocument;
+$dom2->loadXML('<test><foo /></test>');
+
+$element = $dom1->documentElement;
+
+try {
+    $element->append($dom2->documentElement->firstChild);
+    echo "FAIL";
+} catch (DOMException $e) {
+    echo $e->getMessage() . "\n";
+}
+
+try {
+    $element->prepend($dom2->documentElement->firstChild);
+    echo "FAIL";
+} catch (DOMException $e) {
+    echo $e->getMessage();
+}
+?>
+--EXPECT--
+Wrong Document Error
+Wrong Document Error
diff --git a/ext/dom/tests/DOM4_ParentNode_prepend.phpt b/ext/dom/tests/DOM4_ParentNode_prepend.phpt
new file mode 100644 (file)
index 0000000..6b6ad2f
--- /dev/null
@@ -0,0 +1,49 @@
+--TEST--
+DOMParentNode::prepend()
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+declare(strict_types=1);
+
+require_once("dom_test.inc");
+
+$dom = new DOMDocument;
+$dom->loadXML('<test><mark/><mark><nested /></mark><mark/></test>');
+
+$element = $dom->documentElement;
+$firstMark = $element->childNodes[0];
+$element->prepend(
+  $dom->createElement('element', 'content'),
+  'content'
+);
+
+var_dump($element->childElementCount);
+print_node_list_compact($element->childNodes);
+
+$element = $dom->documentElement;
+$element->prepend(
+  $dom->createElement('element', 'content'),
+  'content'
+);
+var_dump($element->childElementCount);
+
+$firstMark->prepend('content');
+print_node_list_compact($firstMark->childNodes);
+?>
+--EXPECT--
+int(4)
+<element>
+  content
+</element>
+content
+<mark>
+</mark>
+<mark>
+  <nested>
+  </nested>
+</mark>
+<mark>
+</mark>
+int(5)
+content
index 74db47c5711d5f668b12db3d983e63638010773d..a47d30a0576eb7939dd3189e1249cc198fe80f9f 100644 (file)
@@ -30,7 +30,7 @@ foreach ($dataNodes AS $node) {
 ?>
 --EXPECTF--
 int(3)
-object(DOMText)#%d (19) {
+object(DOMText)#%d (21) {
   ["wholeText"]=>
   string(3) "
   "
@@ -39,6 +39,10 @@ object(DOMText)#%d (19) {
   "
   ["length"]=>
   int(3)
+  ["previousElementSibling"]=>
+  NULL
+  ["nextElementSibling"]=>
+  NULL
   ["nodeName"]=>
   string(5) "#text"
   ["nodeValue"]=>
@@ -74,11 +78,21 @@ object(DOMText)#%d (19) {
   string(3) "
   "
 }
-object(DOMElement)#%d (18) {
+object(DOMElement)#%d (23) {
   ["tagName"]=>
   string(5) "form1"
   ["schemaTypeInfo"]=>
   NULL
+  ["firstElementChild"]=>
+  string(22) "(object value omitted)"
+  ["lastElementChild"]=>
+  string(22) "(object value omitted)"
+  ["childElementCount"]=>
+  int(3)
+  ["previousElementSibling"]=>
+  NULL
+  ["nextElementSibling"]=>
+  NULL
   ["nodeName"]=>
   string(5) "form1"
   ["nodeValue"]=>
@@ -120,7 +134,7 @@ object(DOMElement)#%d (18) {
     Value C
   "
 }
-object(DOMText)#%d (19) {
+object(DOMText)#%d (21) {
   ["wholeText"]=>
   string(1) "
 "
@@ -129,6 +143,10 @@ object(DOMText)#%d (19) {
 "
   ["length"]=>
   int(1)
+  ["previousElementSibling"]=>
+  NULL
+  ["nextElementSibling"]=>
+  NULL
   ["nodeName"]=>
   string(5) "#text"
   ["nodeValue"]=>
index 93264ea2aafc1c1cc994fc0fbe2622ddf0674bd8..04bfa04d53fa2a017ee74af2fe16af7c9b70d060 100644 (file)
@@ -48,4 +48,22 @@ function print_node_list($nodelist)
   }
 }
 
+function print_node_compact($node, $spaces)
+{
+    if ($node->nodeType == 3) {
+        print str_repeat(" ", $spaces) . trim($node->nodeValue) . "\n";
+    } else {
+        print str_repeat(" ", $spaces) . "<" . $node->nodeName . ">\n";
+        print_node_list_compact($node->childNodes, $spaces + 2);
+        print str_repeat(" ", $spaces) . "</" . $node->nodeName . ">\n";
+    }
+}
+
+function print_node_list_compact($nodelist, $spaces = 0)
+{
+  foreach ($nodelist as $node) {
+    print_node_compact($node, $spaces);
+  }
+}
+
 ?>
index d2834e815cfec5e8581dbdc878d74d0c452efdcf..3545b78dd46650b90d177ef1867aa1bd98cf005c 100644 (file)
@@ -39,6 +39,9 @@ DOMDocument Object
     [preserveWhiteSpace] => 1
     [recover] => 
     [substituteEntities] => 
+    [firstElementChild] => (object value omitted)
+    [lastElementChild] => (object value omitted)
+    [childElementCount] => 1
     [nodeName] => #document
     [nodeValue] => 
     [nodeType] => 9