php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
tree.c
Go to the documentation of this file.
1/*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Benjamin Eberlei <beberlei@php.net> |
14 | Niels Dossche <nielsdos@php.net> |
15 +----------------------------------------------------------------------+
16*/
17
18#ifdef HAVE_CONFIG_H
19#include <config.h>
20#endif
21
22#include "php.h"
23#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
24#include "../php_dom.h"
25#include "../internal_helpers.h"
26#include "../dom_properties.h"
27
28/* {{{ firstElementChild DomParentNode
29readonly=yes
30URL: https://www.w3.org/TR/dom/#dom-parentnode-firstelementchild
31*/
33{
34 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
35
36 xmlNodePtr first = nodep->children;
37
38 while (first && first->type != XML_ELEMENT_NODE) {
39 first = first->next;
40 }
41
43 return SUCCESS;
44}
45/* }}} */
46
47/* {{{ lastElementChild DomParentNode
48readonly=yes
49URL: https://www.w3.org/TR/dom/#dom-parentnode-lastelementchild
50*/
52{
53 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
54
55 xmlNodePtr last = nodep->last;
56
57 while (last && last->type != XML_ELEMENT_NODE) {
58 last = last->prev;
59 }
60
62 return SUCCESS;
63}
64/* }}} */
65
66/* {{{ childElementCount DomParentNode
67readonly=yes
68https://www.w3.org/TR/dom/#dom-parentnode-childelementcount
69*/
71{
72 DOM_PROP_NODE(xmlNodePtr, nodep, obj);
73
74 zend_long count = 0;
75 xmlNodePtr first = nodep->children;
76
77 while (first != NULL) {
78 if (first->type == XML_ELEMENT_NODE) {
79 count++;
80 }
81
82 first = first->next;
83 }
84
86
87 return SUCCESS;
88}
89/* }}} */
90
91static ZEND_COLD void dom_cannot_create_temp_nodes(void)
92{
93 php_dom_throw_error_with_message(INVALID_MODIFICATION_ERR, "Unable to allocate temporary nodes", /* strict */ true);
94}
95
96static bool dom_is_node_in_list(const zval *nodes, uint32_t nodesc, const xmlNode *node_to_find)
97{
98 for (uint32_t i = 0; i < nodesc; i++) {
99 if (Z_TYPE(nodes[i]) == IS_OBJECT) {
100 if (dom_object_get_node(Z_DOMOBJ_P(nodes + i)) == node_to_find) {
101 return true;
102 }
103 }
104 }
105
106 return false;
107}
108
109static xmlDocPtr dom_doc_from_context_node(xmlNodePtr contextNode)
110{
111 if (contextNode->type == XML_DOCUMENT_NODE || contextNode->type == XML_HTML_DOCUMENT_NODE) {
112 return (xmlDocPtr) contextNode;
113 } else {
114 return contextNode->doc;
115 }
116}
117
118/* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild):
119 * "Add a new node to @parent, at the end of the child (or property) list merging adjacent TEXT nodes (in which case @cur is freed)".
120 * So we must use a custom way of adding that does not merge. */
121static void dom_add_child_without_merging(xmlNodePtr parent, xmlNodePtr child)
122{
123 if (parent->children == NULL) {
124 parent->children = child;
125 } else {
126 xmlNodePtr last = parent->last;
127 last->next = child;
128 child->prev = last;
129 }
130 parent->last = child;
131 child->parent = parent;
132}
133
134static void dom_fragment_assign_parent_node(xmlNodePtr parentNode, xmlNodePtr fragment)
135{
136 xmlNodePtr node = fragment->children;
137
138 while (node != NULL) {
139 node->parent = parentNode;
140
141 if (node == fragment->last) {
142 break;
143 }
144 node = node->next;
145 }
146}
147
148/* This part is common logic between the pre-insertion validity and replaceChild code. */
149static bool dom_fragment_common_hierarchy_check_part(xmlNodePtr node, bool *seen_element)
150{
151 /* If node has more than one element child or has a Text node child. */
152 xmlNodePtr iter = node->children;
153 *seen_element = false;
154 while (iter != NULL) {
155 if (iter->type == XML_ELEMENT_NODE) {
156 if (*seen_element) {
157 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
158 return false;
159 }
160 *seen_element = true;
161 } else if (iter->type == XML_TEXT_NODE || iter->type == XML_CDATA_SECTION_NODE) {
162 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert text as a child of a document", /* strict */ true);
163 return false;
164 }
165 iter = iter->next;
166 }
167 return true;
168}
169
170/* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
171 * DocumentFragment validation part. */
172bool php_dom_fragment_insertion_hierarchy_check_pre_insertion(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
173{
174 bool seen_element;
175 if (!dom_fragment_common_hierarchy_check_part(node, &seen_element)) {
176 return false;
177 }
178
179 /* Otherwise, if node has one element child
180 * and either parent has an element child, child is a doctype, or child is non-null and a doctype is following child. */
181 if (seen_element) {
183 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
184 return false;
185 }
186
187 if (child != NULL && (child->type == XML_DTD_NODE || php_dom_has_sibling_following_node(child, XML_DTD_NODE))) {
188 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
189 return false;
190 }
191 }
192
193 return true;
194}
195
196/* https://dom.spec.whatwg.org/#concept-node-replace
197 * DocumentFragment validation part. */
198bool php_dom_fragment_insertion_hierarchy_check_replace(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
199{
200 bool seen_element;
201 if (!dom_fragment_common_hierarchy_check_part(node, &seen_element)) {
202 return false;
203 }
204
205 /* Otherwise, if node has one element child
206 * and either parent has an element child that is not child or a doctype is following child. */
207 if (seen_element) {
208 xmlNodePtr iter = parent->children;
209 while (iter != NULL) {
210 if (iter->type == XML_ELEMENT_NODE && iter != child) {
211 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
212 return false;
213 }
214 iter = iter->next;
215 }
216
217 ZEND_ASSERT(child != NULL);
219 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
220 return false;
221 }
222 }
223
224 return true;
225}
226
227/* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity */
228bool php_dom_pre_insert_is_parent_invalid(xmlNodePtr parent)
229{
230 return parent->type != XML_DOCUMENT_NODE
231 && parent->type != XML_HTML_DOCUMENT_NODE
232 && parent->type != XML_ELEMENT_NODE
233 && parent->type != XML_DOCUMENT_FRAG_NODE;
234}
235
236/* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity */
237static bool dom_is_pre_insert_valid_without_step_1(php_libxml_ref_obj *document, xmlNodePtr parentNode, xmlNodePtr node, xmlNodePtr child, xmlDocPtr documentNode)
238{
239 ZEND_ASSERT(parentNode != NULL);
240
241 /* 1. If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
242 * => This is possible because we can grab children of attributes etc... (see e.g. GH-16594) */
243 if (php_dom_pre_insert_is_parent_invalid(parentNode)) {
245 return false;
246 }
247
248 if (node->doc != documentNode) {
250 return false;
251 }
252
253 /* 3. If child is non-null and its parent is not parent, then throw a "NotFoundError" DOMException. */
254 if (child != NULL && child->parent != parentNode) {
256 return false;
257 }
258
259 bool parent_is_document = parentNode->type == XML_DOCUMENT_NODE || parentNode->type == XML_HTML_DOCUMENT_NODE;
260
261 if (/* 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException. */
262 dom_hierarchy(parentNode, node) != SUCCESS
263 /* 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. */
264 || node->type == XML_ATTRIBUTE_NODE
265 || (php_dom_follow_spec_doc_ref(document) && (
266 node->type == XML_ENTITY_REF_NODE
267 || node->type == XML_ENTITY_NODE
268 || node->type == XML_NOTATION_NODE
269 || node->type == XML_DOCUMENT_NODE
270 || node->type == XML_HTML_DOCUMENT_NODE
271 || node->type >= XML_ELEMENT_DECL))) {
273 return false;
274 }
275
276 if (php_dom_follow_spec_doc_ref(document)) {
277 /* 5. If either node is a Text node and parent is a document... */
278 if (parent_is_document && (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)) {
279 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert text as a child of a document", /* strict */ true);
280 return false;
281 }
282
283 /* 5. ..., or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException. */
284 if (!parent_is_document && node->type == XML_DTD_NODE) {
285 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot insert a document type into anything other than a document", /* strict */ true);
286 return false;
287 }
288
289 /* 6. If parent is a document, and any of the statements below, switched on the interface node implements,
290 * are true, then throw a "HierarchyRequestError" DOMException. */
291 if (parent_is_document) {
292 /* DocumentFragment */
293 if (node->type == XML_DOCUMENT_FRAG_NODE) {
294 if (!php_dom_fragment_insertion_hierarchy_check_pre_insertion(parentNode, node, child)) {
295 return false;
296 }
297 }
298 /* Element */
299 else if (node->type == XML_ELEMENT_NODE) {
300 /* parent has an element child, child is a doctype, or child is non-null and a doctype is following child. */
302 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one element child in a document", /* strict */ true);
303 return false;
304 }
305 if (child != NULL && (child->type == XML_DTD_NODE || php_dom_has_sibling_following_node(child, XML_DTD_NODE))) {
306 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
307 return false;
308 }
309 }
310 /* DocumentType */
311 else if (node->type == XML_DTD_NODE) {
312 /* parent has a doctype child, child is non-null and an element is preceding child, or child is null and parent has an element child. */
313 if (php_dom_has_child_of_type(parentNode, XML_DTD_NODE)) {
314 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Cannot have more than one document type", /* strict */ true);
315 return false;
316 }
318 || (child == NULL && php_dom_has_child_of_type(parentNode, XML_ELEMENT_NODE))) {
319 php_dom_throw_error_with_message(HIERARCHY_REQUEST_ERR, "Document types must be the first child in a document", /* strict */ true);
320 return false;
321 }
322 }
323 }
324 }
325
326 return true;
327}
328
329static void dom_free_node_after_zval_single_node_creation(xmlNodePtr node)
330{
331 /* For the object cases, the user did provide them, so they don't have to be freed as there are still references.
332 * For the newly created text nodes, we do have to free them. */
333 xmlNodePtr next;
334 for (xmlNodePtr child = node->children; child != NULL; child = next) {
335 next = child->next;
336 xmlUnlinkNode(child);
337 if (child->_private == NULL) {
338 xmlFreeNode(child);
339 }
340 }
341}
342
343/* https://dom.spec.whatwg.org/#converting-nodes-into-a-node */
344xmlNode* dom_zvals_to_single_node(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, uint32_t nodesc)
345{
346 xmlDoc *documentNode;
347 xmlNode *newNode;
348 dom_object *newNodeObj;
349
350 documentNode = dom_doc_from_context_node(contextNode);
351
352 /* 1. Let node be null. */
353 xmlNodePtr node = NULL;
354
355 /* 2. => handled in the loop. */
356
357 /* 3. If nodes contains one node, then set node to nodes[0]. */
358 if (nodesc == 1) {
359 /* ... and return */
360 if (Z_TYPE_P(nodes) == IS_OBJECT) {
361 return dom_object_get_node(Z_DOMOBJ_P(nodes));
362 } else {
363 ZEND_ASSERT(Z_TYPE_P(nodes) == IS_STRING);
364 node = xmlNewDocTextLen(documentNode, BAD_CAST Z_STRVAL_P(nodes), Z_STRLEN_P(nodes));
365 if (UNEXPECTED(node == NULL)) {
366 dom_cannot_create_temp_nodes();
367 }
368 return node;
369 }
370 }
371
372 node = xmlNewDocFragment(documentNode);
373 if (UNEXPECTED(!node)) {
374 dom_cannot_create_temp_nodes();
375 return NULL;
376 }
377
378 /* 4. Otherwise, set node to a new DocumentFragment node whose node document is document,
379 * and then append each node in nodes, if any, to it. */
380 for (uint32_t i = 0; i < nodesc; i++) {
381 if (Z_TYPE(nodes[i]) == IS_OBJECT) {
382 newNodeObj = Z_DOMOBJ_P(&nodes[i]);
383 newNode = dom_object_get_node(newNodeObj);
384
385 if (UNEXPECTED(!newNode)) {
386 php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
387 goto err;
388 }
389
390 if (!dom_is_pre_insert_valid_without_step_1(document, node, newNode, NULL, documentNode)) {
391 goto err;
392 }
393
394 if (newNode->parent != NULL) {
395 xmlUnlinkNode(newNode);
396 }
397
398 ZEND_ASSERT(newNodeObj->document == document);
399
400 if (newNode->type == XML_DOCUMENT_FRAG_NODE) {
401 /* Unpack document fragment nodes, the behaviour differs for different libxml2 versions. */
402 newNode = newNode->children;
403 while (newNode) {
404 xmlNodePtr next = newNode->next;
405 xmlUnlinkNode(newNode);
406 dom_add_child_without_merging(node, newNode);
407 newNode = next;
408 }
409 } else {
410 dom_add_child_without_merging(node, newNode);
411 }
412 } else {
413 /* 2. Replace each string in nodes with a new Text node whose data is the string and node document is document. */
414 ZEND_ASSERT(Z_TYPE(nodes[i]) == IS_STRING);
415
416 /* Text nodes can't violate the hierarchy at this point. */
417 newNode = xmlNewDocTextLen(documentNode, BAD_CAST Z_STRVAL(nodes[i]), Z_STRLEN(nodes[i]));
418 if (UNEXPECTED(newNode == NULL)) {
419 dom_cannot_create_temp_nodes();
420 goto err;
421 }
422 dom_add_child_without_merging(node, newNode);
423 }
424 }
425
426 /* 5. Return node. */
427 return node;
428
429err:
430 /* For the object cases, the user did provide them, so they don't have to be freed as there are still references.
431 * For the newly created text nodes, we do have to free them. */
432 dom_free_node_after_zval_single_node_creation(node);
433 xmlFree(node);
434 return NULL;
435}
436
437static zend_result dom_sanity_check_node_list_types(zval *nodes, uint32_t nodesc, zend_class_entry *node_ce)
438{
439 for (uint32_t i = 0; i < nodesc; i++) {
440 zend_uchar type = Z_TYPE(nodes[i]);
441 if (type == IS_OBJECT) {
442 const zend_class_entry *ce = Z_OBJCE(nodes[i]);
443
444 if (!instanceof_function(ce, node_ce)) {
445 zend_argument_type_error(i + 1, "must be of type %s|string, %s given", ZSTR_VAL(node_ce->name), zend_zval_type_name(&nodes[i]));
446 return FAILURE;
447 }
448 } else if (type == IS_STRING) {
449 if (Z_STRLEN(nodes[i]) > INT_MAX) {
450 zend_argument_value_error(i + 1, "must be less than or equal to %d bytes long", INT_MAX);
451 return FAILURE;
452 }
453 } else {
454 zend_argument_type_error(i + 1, "must be of type %s|string, %s given", ZSTR_VAL(node_ce->name), zend_zval_type_name(&nodes[i]));
455 return FAILURE;
456 }
457 }
458
459 return SUCCESS;
460}
461
462static void php_dom_pre_insert_helper(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr last)
463{
464 if (!insertion_point) {
465 /* Place it as last node */
466 if (parentNode->children) {
467 /* There are children */
468 newchild->prev = parentNode->last;
469 parentNode->last->next = newchild;
470 } else {
471 /* No children, because they moved out when they became a fragment */
472 parentNode->children = newchild;
473 }
474 parentNode->last = last;
475 } else {
476 /* Insert fragment before insertion_point */
477 last->next = insertion_point;
478 if (insertion_point->prev) {
479 insertion_point->prev->next = newchild;
480 newchild->prev = insertion_point->prev;
481 }
482 insertion_point->prev = last;
483 if (parentNode->children == insertion_point) {
484 parentNode->children = newchild;
485 }
486 }
487}
488
489static void dom_insert_node_list_cleanup(xmlNodePtr node)
490{
491 if (node->_private != NULL) {
492 /* Not a temporary node. */
493 return;
494 }
495 if (node->type == XML_DOCUMENT_FRAG_NODE) {
496 dom_free_node_after_zval_single_node_creation(node);
497 xmlFree(node); /* Don't free the children, now-empty fragment! */
498 } else if (node->type == XML_TEXT_NODE) {
499 ZEND_ASSERT(node->parent == NULL);
500 xmlFreeNode(node);
501 } else {
502 /* Must have been a directly-passed node. */
504 }
505}
506
507/* https://dom.spec.whatwg.org/#concept-node-pre-insert */
508static void dom_insert_node_list_unchecked(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent, xmlNodePtr insertion_point)
509{
510 /* Step 1 should be checked by the caller. */
511
512 if (node->type == XML_DOCUMENT_FRAG_NODE) {
513 /* Steps 2-3 are not applicable here, the condition is impossible. */
514
515 xmlNodePtr newchild = node->children;
516
517 /* 4. Insert node into parent before referenceChild (i.e. insertion_point here because of the impossible condition). */
518 if (newchild) {
519 xmlNodePtr last = node->last;
520 php_dom_pre_insert_helper(insertion_point, parent, newchild, last);
521 dom_fragment_assign_parent_node(parent, node);
522 if (!php_dom_follow_spec_doc_ref(document)) {
523 dom_reconcile_ns_list(parent->doc, newchild, last);
524 }
525 if (parent->doc && newchild->type == XML_DTD_NODE) {
526 parent->doc->intSubset = (xmlDtdPtr) newchild;
527 newchild->parent = (xmlNodePtr) parent->doc;
528 }
529 }
530
531 if (node->_private == NULL) {
532 xmlFree(node);
533 } else {
534 node->children = NULL;
535 node->last = NULL;
536 }
537 } else {
538 /* 2. Let referenceChild be child.
539 * 3. If referenceChild is node, then set referenceChild to node’s next sibling. */
540 if (insertion_point == node) {
541 insertion_point = node->next;
542 }
543
544 /* 4. Insert node into parent before referenceChild. */
545 xmlUnlinkNode(node);
546 php_dom_pre_insert_helper(insertion_point, parent, node, node);
547 node->parent = parent;
548 if (parent->doc && node->type == XML_DTD_NODE) {
549 parent->doc->intSubset = (xmlDtdPtr) node;
550 node->parent = (xmlNodePtr) parent->doc;
551 } else {
552 if (!php_dom_follow_spec_doc_ref(document)) {
553 dom_reconcile_ns(parent->doc, node);
554 }
555 }
556 }
557}
558
559/* https://dom.spec.whatwg.org/#concept-node-pre-insert */
560bool php_dom_pre_insert(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent, xmlNodePtr insertion_point)
561{
562 if (UNEXPECTED(node == NULL)) {
563 return false;
564 }
565
566 /* Step 1 checked here, other steps delegated to other function. */
567 if (dom_is_pre_insert_valid_without_step_1(document, parent, node, insertion_point, parent->doc)) {
568 dom_insert_node_list_unchecked(document, node, parent, insertion_point);
569 return true;
570 } else {
571 dom_insert_node_list_cleanup(node);
572 return false;
573 }
574}
575
576/* https://dom.spec.whatwg.org/#concept-node-append */
577void php_dom_node_append(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent)
578{
579 php_dom_pre_insert(document, node, parent, NULL);
580}
581
582/* https://dom.spec.whatwg.org/#dom-parentnode-append */
583void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc)
584{
585 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
586 return;
587 }
588
589 xmlNode *parentNode = dom_object_get_node(context);
590
591 php_libxml_invalidate_node_list_cache(context->document);
592
593 /* 1. Let node be the result of converting nodes into a node given nodes and this’s node document. */
594 xmlNodePtr node = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
595 if (UNEXPECTED(node == NULL)) {
596 return;
597 }
598
599 /* 2. Append node to this. */
600 php_dom_node_append(context->document, node, parentNode);
601}
602
603/* https://dom.spec.whatwg.org/#dom-parentnode-prepend */
604void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc)
605{
606 xmlNode *parentNode = dom_object_get_node(context);
607
608 if (parentNode->children == NULL) {
609 dom_parent_node_append(context, nodes, nodesc);
610 return;
611 }
612
613 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
614 return;
615 }
616
617 php_libxml_invalidate_node_list_cache(context->document);
618
619 /* 1. Let node be the result of converting nodes into a node given nodes and this’s node document. */
620 xmlNodePtr node = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
621 if (UNEXPECTED(node == NULL)) {
622 return;
623 }
624
625 /* 2. Pre-insert node into this before this’s first child. */
626 php_dom_pre_insert(context->document, node, parentNode, parentNode->children);
627}
628
629/* https://dom.spec.whatwg.org/#dom-childnode-after */
630void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc)
631{
632 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
633 return;
634 }
635
636 xmlNode *thisp = dom_object_get_node(context);
637
638 /* 1. Let parent be this’s parent. */
639 xmlNodePtr parentNode = thisp->parent;
640
641 /* 2. If parent is null, then return. */
642 if (UNEXPECTED(parentNode == NULL)) {
643 return;
644 }
645
646 /* 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null. */
647 xmlNodePtr viable_next_sibling = thisp->next;
648 while (viable_next_sibling && dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
649 viable_next_sibling = viable_next_sibling->next;
650 }
651
652 php_libxml_invalidate_node_list_cache(context->document);
653
654 /* 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. */
655 xmlNodePtr fragment = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
656
657 /* 5. Pre-insert node into parent before viableNextSibling. */
658 php_dom_pre_insert(context->document, fragment, parentNode, viable_next_sibling);
659}
660
661/* https://dom.spec.whatwg.org/#dom-childnode-before */
662void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc)
663{
664 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
665 return;
666 }
667
668 xmlNode *thisp = dom_object_get_node(context);
669
670 /* 1. Let parent be this’s parent. */
671 xmlNodePtr parentNode = thisp->parent;
672
673 /* 2. If parent is null, then return. */
674 if (UNEXPECTED(parentNode == NULL)) {
675 return;
676 }
677
678 /* 3. Let viablePreviousSibling be this’s first preceding sibling not in nodes; otherwise null. */
679 xmlNodePtr viable_previous_sibling = thisp->prev;
680 while (viable_previous_sibling && dom_is_node_in_list(nodes, nodesc, viable_previous_sibling)) {
681 viable_previous_sibling = viable_previous_sibling->prev;
682 }
683
684 php_libxml_invalidate_node_list_cache(context->document);
685
686 /* 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. */
687 xmlNodePtr fragment = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
688
689 /* 5. If viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling. */
690 if (!viable_previous_sibling) {
691 viable_previous_sibling = parentNode->children;
692 } else {
693 viable_previous_sibling = viable_previous_sibling->next;
694 }
695
696 /* 6. Pre-insert node into parent before viablePreviousSibling. */
697 php_dom_pre_insert(context->document, fragment, parentNode, viable_previous_sibling);
698}
699
700static zend_result dom_child_removal_preconditions(const xmlNode *child, const dom_object *context)
701{
702 if (dom_node_is_read_only(child) == SUCCESS ||
703 (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
705 return FAILURE;
706 }
707
708 if (!child->parent) {
710 return FAILURE;
711 }
712
713 return SUCCESS;
714}
715
717{
718 xmlNode *child = dom_object_get_node(context);
719
720 if (UNEXPECTED(dom_child_removal_preconditions(child, context) != SUCCESS)) {
721 return;
722 }
723
724 php_libxml_invalidate_node_list_cache(context->document);
725
726 xmlUnlinkNode(child);
727}
728
729/* https://dom.spec.whatwg.org/#dom-childnode-replacewith */
730void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
731{
732 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
733 return;
734 }
735
736 xmlNodePtr child = dom_object_get_node(context);
737
738 /* 1. Let parent be this’s parent. */
739 xmlNodePtr parentNode = child->parent;
740
741 /* 2. If parent is null, then return. */
742 if (UNEXPECTED(parentNode == NULL)) {
743 return;
744 }
745
746 /* 3. Let viableNextSibling be this’s first following sibling not in nodes; otherwise null. */
747 xmlNodePtr viable_next_sibling = child->next;
748 while (viable_next_sibling && dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
749 viable_next_sibling = viable_next_sibling->next;
750 }
751
752 if (UNEXPECTED(dom_child_removal_preconditions(child, context) != SUCCESS)) {
753 return;
754 }
755
756 php_libxml_invalidate_node_list_cache(context->document);
757
758 /* 4. Let node be the result of converting nodes into a node, given nodes and this’s node document. */
759 xmlNodePtr node = dom_zvals_to_single_node(context->document, parentNode, nodes, nodesc);
760 if (UNEXPECTED(node == NULL)) {
761 return;
762 }
763
764 /* Spec step 5-6: perform the replacement */
765 if (dom_is_pre_insert_valid_without_step_1(context->document, parentNode, node, viable_next_sibling, parentNode->doc)) {
766 /* Unlink it unless it became a part of the fragment.
767 * Freeing will be taken care of by the lifetime of the returned dom object. */
768 if (child->parent != node) {
769 xmlUnlinkNode(child);
770 }
771
772 dom_insert_node_list_unchecked(context->document, node, parentNode, viable_next_sibling);
773 } else {
774 dom_insert_node_list_cleanup(node);
775 }
776}
777
778/* https://dom.spec.whatwg.org/#dom-parentnode-replacechildren */
779void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc)
780{
781 if (UNEXPECTED(dom_sanity_check_node_list_types(nodes, nodesc, dom_get_node_ce(php_dom_follow_spec_doc_ref(context->document))) != SUCCESS)) {
782 return;
783 }
784
785 xmlNodePtr thisp = dom_object_get_node(context);
786
787 php_libxml_invalidate_node_list_cache(context->document);
788
789 /* 1. Let node be the result of converting nodes into a node given nodes and this’s node document. */
790 xmlNodePtr node = dom_zvals_to_single_node(context->document, thisp, nodes, nodesc);
791 if (UNEXPECTED(node == NULL)) {
792 return;
793 }
794
795 /* Spec steps 2-3: replace all */
796 if (dom_is_pre_insert_valid_without_step_1(context->document, thisp, node, NULL, thisp->doc)) {
798 php_dom_pre_insert(context->document, node, thisp, NULL);
799 } else {
800 dom_insert_node_list_cleanup(node);
801 }
802}
803
804#endif
count(Countable|array $value, int $mode=COUNT_NORMAL)
zend_result dom_parent_node_last_element_child_read(dom_object *obj, zval *retval)
#define DOM_PROP_NODE(type, name, obj)
zend_result dom_parent_node_child_element_count(dom_object *obj, zval *retval)
zend_result dom_parent_node_first_element_child_read(dom_object *obj, zval *retval)
void php_dom_throw_error_with_message(dom_exception_code error_code, const char *error_message, bool strict_error)
void php_dom_throw_error(dom_exception_code error_code, bool strict_error)
@ NO_MODIFICATION_ALLOWED_ERR
@ INVALID_STATE_ERR
@ NOT_FOUND_ERR
@ HIERARCHY_REQUEST_ERR
@ INVALID_MODIFICATION_ERR
@ WRONG_DOCUMENT_ERR
zend_ffi_type * type
Definition ffi.c:3812
char * err
Definition ffi.c:3029
#define NULL
Definition gdcache.h:45
#define SUCCESS
Definition hash_sha3.c:261
#define next(ls)
Definition minilua.c:2661
#define INT_MAX
Definition php.h:237
void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc)
void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc)
bool php_dom_pre_insert_is_parent_invalid(xmlNodePtr parent)
int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child)
bool php_dom_fragment_insertion_hierarchy_check_pre_insertion(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
void php_dom_node_append(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent)
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep)
bool dom_get_strict_error(php_libxml_ref_obj *document)
void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
bool php_dom_pre_insert(php_libxml_ref_obj *document, xmlNodePtr node, xmlNodePtr parent, xmlNodePtr insertion_point)
void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc)
bool php_dom_create_nullable_object(xmlNodePtr obj, zval *return_value, dom_object *domobj)
void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc)
bool php_dom_has_sibling_following_node(xmlNodePtr node, xmlElementType type)
void dom_child_node_remove(dom_object *context)
void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc)
bool php_dom_has_sibling_preceding_node(xmlNodePtr node, xmlElementType type)
bool php_dom_has_child_of_type(xmlNodePtr node, xmlElementType type)
int dom_node_is_read_only(const xmlNode *node)
void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
void dom_remove_all_children(xmlNodePtr nodep)
bool php_dom_fragment_insertion_hierarchy_check_replace(xmlNodePtr parent, xmlNodePtr node, xmlNodePtr child)
const XML_DTD_NODE
const XML_HTML_DOCUMENT_NODE
const XML_TEXT_NODE
const XML_ELEMENT_NODE
const XML_ATTRIBUTE_NODE
const XML_DOCUMENT_NODE
const XML_CDATA_SECTION_NODE
const XML_DOCUMENT_FRAG_NODE
const XML_ENTITY_NODE
const XML_NOTATION_NODE
const XML_ENTITY_REF_NODE
php_libxml_ref_obj * document
Definition xml_common.h:27
zend_string * name
Definition zend.h:149
Definition dce.c:49
PHP_DOM_EXPORT xmlNodePtr dom_object_get_node(dom_object *obj)
struct _dom_object dom_object
#define Z_DOMOBJ_P(zv)
Definition xml_common.h:36
ZEND_API const char * zend_zval_type_name(const zval *arg)
Definition zend_API.c:167
ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *format,...)
Definition zend_API.c:433
ZEND_API ZEND_COLD void zend_argument_type_error(uint32_t arg_num, const char *format,...)
Definition zend_API.c:423
struct _zval_struct zval
int32_t zend_long
Definition zend_long.h:42
int last
#define ZEND_ASSERT(c)
#define ZEND_UNREACHABLE()
#define ZEND_COLD
#define UNEXPECTED(condition)
struct _zend_class_entry zend_class_entry
#define ZSTR_VAL(zstr)
Definition zend_string.h:68
#define Z_TYPE_P(zval_p)
Definition zend_types.h:660
#define Z_STRVAL_P(zval_p)
Definition zend_types.h:975
#define ZVAL_LONG(z, l)
#define IS_STRING
Definition zend_types.h:606
#define Z_STRLEN_P(zval_p)
Definition zend_types.h:978
#define Z_STRVAL(zval)
Definition zend_types.h:974
@ FAILURE
Definition zend_types.h:61
#define Z_STRLEN(zval)
Definition zend_types.h:977
#define IS_OBJECT
Definition zend_types.h:608
ZEND_RESULT_CODE zend_result
Definition zend_types.h:64
unsigned char zend_uchar
Definition zend_types.h:57
#define Z_TYPE(zval)
Definition zend_types.h:659
#define Z_OBJCE(zval)
zval retval