xref: /PHP-8.3/ext/dom/parentnode.c (revision 043b9e1f)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | https://www.php.net/license/3_01.txt                                 |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Benjamin Eberlei <beberlei@php.net>                         |
16    +----------------------------------------------------------------------+
17 */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
25 #include "php_dom.h"
26 
27 /* {{{ firstElementChild DomParentNode
28 readonly=yes
29 URL: https://www.w3.org/TR/dom/#dom-parentnode-firstelementchild
30 */
dom_parent_node_first_element_child_read(dom_object * obj,zval * retval)31 int dom_parent_node_first_element_child_read(dom_object *obj, zval *retval)
32 {
33 	xmlNode *nodep, *first = NULL;
34 
35 	nodep = dom_object_get_node(obj);
36 
37 	if (nodep == NULL) {
38 		php_dom_throw_error(INVALID_STATE_ERR, 1);
39 		return FAILURE;
40 	}
41 
42 	if (dom_node_children_valid(nodep) == SUCCESS) {
43 		first = nodep->children;
44 
45 		while (first && first->type != XML_ELEMENT_NODE) {
46 			first = first->next;
47 		}
48 	}
49 
50 	if (!first) {
51 		ZVAL_NULL(retval);
52 		return SUCCESS;
53 	}
54 
55 	php_dom_create_object(first, retval, obj);
56 	return SUCCESS;
57 }
58 /* }}} */
59 
60 /* {{{ lastElementChild DomParentNode
61 readonly=yes
62 URL: https://www.w3.org/TR/dom/#dom-parentnode-lastelementchild
63 */
dom_parent_node_last_element_child_read(dom_object * obj,zval * retval)64 int dom_parent_node_last_element_child_read(dom_object *obj, zval *retval)
65 {
66 	xmlNode *nodep, *last = NULL;
67 
68 	nodep = dom_object_get_node(obj);
69 
70 	if (nodep == NULL) {
71 		php_dom_throw_error(INVALID_STATE_ERR, 1);
72 		return FAILURE;
73 	}
74 
75 	if (dom_node_children_valid(nodep) == SUCCESS) {
76 		last = nodep->last;
77 
78 		while (last && last->type != XML_ELEMENT_NODE) {
79 			last = last->prev;
80 		}
81 	}
82 
83 	if (!last) {
84 		ZVAL_NULL(retval);
85 		return SUCCESS;
86 	}
87 
88 	php_dom_create_object(last, retval, obj);
89 	return SUCCESS;
90 }
91 /* }}} */
92 
93 /* {{{ childElementCount DomParentNode
94 readonly=yes
95 https://www.w3.org/TR/dom/#dom-parentnode-childelementcount
96 */
dom_parent_node_child_element_count(dom_object * obj,zval * retval)97 int dom_parent_node_child_element_count(dom_object *obj, zval *retval)
98 {
99 	xmlNode *nodep, *first = NULL;
100 	zend_long count = 0;
101 
102 	nodep = dom_object_get_node(obj);
103 
104 	if (nodep == NULL) {
105 		php_dom_throw_error(INVALID_STATE_ERR, 1);
106 		return FAILURE;
107 	}
108 
109 	if (dom_node_children_valid(nodep) == SUCCESS) {
110 		first = nodep->children;
111 
112 		while (first != NULL) {
113 			if (first->type == XML_ELEMENT_NODE) {
114 				count++;
115 			}
116 
117 			first = first->next;
118 		}
119 	}
120 
121 	ZVAL_LONG(retval, count);
122 
123 	return SUCCESS;
124 }
125 /* }}} */
126 
dom_is_node_in_list(const zval * nodes,uint32_t nodesc,const xmlNodePtr node_to_find)127 static bool dom_is_node_in_list(const zval *nodes, uint32_t nodesc, const xmlNodePtr node_to_find)
128 {
129 	for (uint32_t i = 0; i < nodesc; i++) {
130 		if (Z_TYPE(nodes[i]) == IS_OBJECT) {
131 			if (dom_object_get_node(Z_DOMOBJ_P(nodes + i)) == node_to_find) {
132 				return true;
133 			}
134 		}
135 	}
136 
137 	return false;
138 }
139 
dom_doc_from_context_node(xmlNodePtr contextNode)140 static xmlDocPtr dom_doc_from_context_node(xmlNodePtr contextNode)
141 {
142 	if (contextNode->type == XML_DOCUMENT_NODE || contextNode->type == XML_HTML_DOCUMENT_NODE) {
143 		return (xmlDocPtr) contextNode;
144 	} else {
145 		return contextNode->doc;
146 	}
147 }
148 
149 /* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild):
150  * "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)".
151  * So we must use a custom way of adding that does not merge. */
dom_add_child_without_merging(xmlNodePtr parent,xmlNodePtr child)152 static void dom_add_child_without_merging(xmlNodePtr parent, xmlNodePtr child)
153 {
154 	if (parent->children == NULL) {
155 		parent->children = child;
156 	} else {
157 		xmlNodePtr last = parent->last;
158 		last->next = child;
159 		child->prev = last;
160 	}
161 	parent->last = child;
162 	child->parent = parent;
163 }
164 
dom_zvals_to_fragment(php_libxml_ref_obj * document,xmlNode * contextNode,zval * nodes,int nodesc)165 xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, int nodesc)
166 {
167 	xmlDoc *documentNode;
168 	xmlNode *fragment;
169 	xmlNode *newNode;
170 	dom_object *newNodeObj;
171 
172 	documentNode = dom_doc_from_context_node(contextNode);
173 
174 	fragment = xmlNewDocFragment(documentNode);
175 
176 	if (!fragment) {
177 		return NULL;
178 	}
179 
180 	for (uint32_t i = 0; i < nodesc; i++) {
181 		if (Z_TYPE(nodes[i]) == IS_OBJECT) {
182 			newNodeObj = Z_DOMOBJ_P(&nodes[i]);
183 			newNode = dom_object_get_node(newNodeObj);
184 
185 			if (newNode->parent != NULL) {
186 				xmlUnlinkNode(newNode);
187 			}
188 
189 			newNodeObj->document = document;
190 			xmlSetTreeDoc(newNode, documentNode);
191 
192 			/* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild):
193 			 * "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)".
194 			 * So we must take a copy if this situation arises to prevent a use-after-free. */
195 			bool will_free = newNode->type == XML_TEXT_NODE && fragment->last && fragment->last->type == XML_TEXT_NODE;
196 			if (will_free) {
197 				newNode = xmlCopyNode(newNode, 0);
198 			}
199 
200 			if (newNode->type == XML_DOCUMENT_FRAG_NODE) {
201 				/* Unpack document fragment nodes, the behaviour differs for different libxml2 versions. */
202 				newNode = newNode->children;
203 				while (newNode) {
204 					xmlNodePtr next = newNode->next;
205 					xmlUnlinkNode(newNode);
206 					dom_add_child_without_merging(fragment, newNode);
207 					newNode = next;
208 				}
209 			} else if (!xmlAddChild(fragment, newNode)) {
210 				if (will_free) {
211 					xmlFreeNode(newNode);
212 				}
213 				goto err;
214 			}
215 		} else {
216 			ZEND_ASSERT(Z_TYPE(nodes[i]) == IS_STRING);
217 
218 			newNode = xmlNewDocText(documentNode, (xmlChar *) Z_STRVAL(nodes[i]));
219 
220 			if (!xmlAddChild(fragment, newNode)) {
221 				xmlFreeNode(newNode);
222 				goto err;
223 			}
224 		}
225 	}
226 
227 	return fragment;
228 
229 err:
230 	xmlFreeNode(fragment);
231 	return NULL;
232 }
233 
dom_fragment_assign_parent_node(xmlNodePtr parentNode,xmlNodePtr fragment)234 static void dom_fragment_assign_parent_node(xmlNodePtr parentNode, xmlNodePtr fragment)
235 {
236 	xmlNodePtr node = fragment->children;
237 
238 	while (node != NULL) {
239 		node->parent = parentNode;
240 
241 		if (node == fragment->last) {
242 			break;
243 		}
244 		node = node->next;
245 	}
246 
247 	fragment->children = NULL;
248 	fragment->last = NULL;
249 }
250 
dom_sanity_check_node_list_for_insertion(php_libxml_ref_obj * document,xmlNodePtr parentNode,zval * nodes,int nodesc)251 static zend_result dom_sanity_check_node_list_for_insertion(php_libxml_ref_obj *document, xmlNodePtr parentNode, zval *nodes, int nodesc)
252 {
253 	if (UNEXPECTED(parentNode == NULL)) {
254 		/* No error required, this must be a no-op per spec */
255 		return FAILURE;
256 	}
257 
258 	xmlDocPtr documentNode = dom_doc_from_context_node(parentNode);
259 
260 	for (uint32_t i = 0; i < nodesc; i++) {
261 		zend_uchar type = Z_TYPE(nodes[i]);
262 		if (type == IS_OBJECT) {
263 			const zend_class_entry *ce = Z_OBJCE(nodes[i]);
264 
265 			if (instanceof_function(ce, dom_node_class_entry)) {
266 				xmlNodePtr node = dom_object_get_node(Z_DOMOBJ_P(nodes + i));
267 
268 				if (!node) {
269 					php_dom_throw_error(INVALID_STATE_ERR, /* strict */ true);
270 					return FAILURE;
271 				}
272 
273 				if (node->doc != documentNode) {
274 					php_dom_throw_error(WRONG_DOCUMENT_ERR, dom_get_strict_error(document));
275 					return FAILURE;
276 				}
277 
278 				if (node->type == XML_ATTRIBUTE_NODE || dom_hierarchy(parentNode, node) != SUCCESS) {
279 					php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(document));
280 					return FAILURE;
281 				}
282 			} else {
283 				zend_argument_type_error(i + 1, "must be of type DOMNode|string, %s given", zend_zval_type_name(&nodes[i]));
284 				return FAILURE;
285 			}
286 		} else if (type != IS_STRING) {
287 			zend_argument_type_error(i + 1, "must be of type DOMNode|string, %s given", zend_zval_type_name(&nodes[i]));
288 			return FAILURE;
289 		}
290 	}
291 
292 	return SUCCESS;
293 }
294 
dom_pre_insert(xmlNodePtr insertion_point,xmlNodePtr parentNode,xmlNodePtr newchild,xmlNodePtr fragment)295 static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr fragment)
296 {
297 	if (!insertion_point) {
298 		/* Place it as last node */
299 		if (parentNode->children) {
300 			/* There are children */
301 			newchild->prev = parentNode->last;
302 			parentNode->last->next = newchild;
303 		} else {
304 			/* No children, because they moved out when they became a fragment */
305 			parentNode->children = newchild;
306 		}
307 		parentNode->last = fragment->last;
308 	} else {
309 		/* Insert fragment before insertion_point */
310 		fragment->last->next = insertion_point;
311 		if (insertion_point->prev) {
312 			insertion_point->prev->next = newchild;
313 			newchild->prev = insertion_point->prev;
314 		}
315 		insertion_point->prev = fragment->last;
316 		if (parentNode->children == insertion_point) {
317 			parentNode->children = newchild;
318 		}
319 	}
320 }
321 
dom_parent_node_append(dom_object * context,zval * nodes,uint32_t nodesc)322 void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc)
323 {
324 	xmlNode *parentNode = dom_object_get_node(context);
325 	xmlNodePtr newchild, prevsib;
326 
327 	if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
328 		return;
329 	}
330 
331 	php_libxml_invalidate_node_list_cache(context->document);
332 
333 	xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
334 
335 	if (fragment == NULL) {
336 		return;
337 	}
338 
339 	newchild = fragment->children;
340 	prevsib = parentNode->last;
341 
342 	if (newchild) {
343 		if (prevsib != NULL) {
344 			prevsib->next = newchild;
345 		} else {
346 			parentNode->children = newchild;
347 		}
348 
349 		xmlNodePtr last = fragment->last;
350 		parentNode->last = last;
351 
352 		newchild->prev = prevsib;
353 
354 		dom_fragment_assign_parent_node(parentNode, fragment);
355 
356 		dom_reconcile_ns_list(parentNode->doc, newchild, last);
357 	}
358 
359 	xmlFree(fragment);
360 }
361 
dom_parent_node_prepend(dom_object * context,zval * nodes,uint32_t nodesc)362 void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc)
363 {
364 	xmlNode *parentNode = dom_object_get_node(context);
365 
366 	if (parentNode->children == NULL) {
367 		dom_parent_node_append(context, nodes, nodesc);
368 		return;
369 	}
370 
371 	if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
372 		return;
373 	}
374 
375 	php_libxml_invalidate_node_list_cache(context->document);
376 
377 	xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
378 
379 	if (fragment == NULL) {
380 		return;
381 	}
382 
383 	xmlNode *newchild = fragment->children;
384 
385 	if (newchild) {
386 		xmlNodePtr last = fragment->last;
387 
388 		dom_pre_insert(parentNode->children, parentNode, newchild, fragment);
389 
390 		dom_fragment_assign_parent_node(parentNode, fragment);
391 
392 		dom_reconcile_ns_list(parentNode->doc, newchild, last);
393 	}
394 
395 	xmlFree(fragment);
396 }
397 
dom_parent_node_after(dom_object * context,zval * nodes,uint32_t nodesc)398 void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc)
399 {
400 	/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-after */
401 
402 	xmlNode *prevsib = dom_object_get_node(context);
403 	xmlNodePtr newchild, parentNode;
404 	xmlNode *fragment;
405 	xmlDoc *doc;
406 
407 	/* Spec step 1 */
408 	parentNode = prevsib->parent;
409 
410 	/* Sanity check for fragment, includes spec step 2 */
411 	if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
412 		return;
413 	}
414 
415 	/* Spec step 3: find first following child not in nodes; otherwise null */
416 	xmlNodePtr viable_next_sibling = prevsib->next;
417 	while (viable_next_sibling) {
418 		if (!dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
419 			break;
420 		}
421 		viable_next_sibling = viable_next_sibling->next;
422 	}
423 
424 	doc = prevsib->doc;
425 
426 	php_libxml_invalidate_node_list_cache(context->document);
427 
428 	/* Spec step 4: convert nodes into fragment */
429 	fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
430 
431 	if (fragment == NULL) {
432 		return;
433 	}
434 
435 	newchild = fragment->children;
436 
437 	if (newchild) {
438 		xmlNodePtr last = fragment->last;
439 
440 		/* Step 5: place fragment into the parent before viable_next_sibling */
441 		dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment);
442 
443 		dom_fragment_assign_parent_node(parentNode, fragment);
444 		dom_reconcile_ns_list(doc, newchild, last);
445 	}
446 
447 	xmlFree(fragment);
448 }
449 
dom_parent_node_before(dom_object * context,zval * nodes,uint32_t nodesc)450 void dom_parent_node_before(dom_object *context, zval *nodes, uint32_t nodesc)
451 {
452 	/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-before */
453 
454 	xmlNode *nextsib = dom_object_get_node(context);
455 	xmlNodePtr newchild, parentNode;
456 	xmlNode *fragment;
457 	xmlDoc *doc;
458 
459 	/* Spec step 1 */
460 	parentNode = nextsib->parent;
461 
462 	/* Sanity check for fragment, includes spec step 2 */
463 	if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
464 		return;
465 	}
466 
467 	/* Spec step 3: find first following child not in nodes; otherwise null */
468 	xmlNodePtr viable_previous_sibling = nextsib->prev;
469 	while (viable_previous_sibling) {
470 		if (!dom_is_node_in_list(nodes, nodesc, viable_previous_sibling)) {
471 			break;
472 		}
473 		viable_previous_sibling = viable_previous_sibling->prev;
474 	}
475 
476 	doc = nextsib->doc;
477 
478 	php_libxml_invalidate_node_list_cache(context->document);
479 
480 	/* Spec step 4: convert nodes into fragment */
481 	fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
482 
483 	if (fragment == NULL) {
484 		return;
485 	}
486 
487 	newchild = fragment->children;
488 
489 	if (newchild) {
490 		xmlNodePtr last = fragment->last;
491 
492 		/* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */
493 		if (!viable_previous_sibling) {
494 			viable_previous_sibling = parentNode->children;
495 		} else {
496 			viable_previous_sibling = viable_previous_sibling->next;
497 		}
498 		/* Step 6: place fragment into the parent after viable_previous_sibling */
499 		dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment);
500 
501 		dom_fragment_assign_parent_node(parentNode, fragment);
502 		dom_reconcile_ns_list(doc, newchild, last);
503 	}
504 
505 	xmlFree(fragment);
506 }
507 
dom_child_removal_preconditions(const xmlNodePtr child,int stricterror)508 static zend_result dom_child_removal_preconditions(const xmlNodePtr child, int stricterror)
509 {
510 	if (dom_node_is_read_only(child) == SUCCESS ||
511 		(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
512 		php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
513 		return FAILURE;
514 	}
515 
516 	if (!child->parent) {
517 		php_dom_throw_error(NOT_FOUND_ERR, stricterror);
518 		return FAILURE;
519 	}
520 
521 	if (dom_node_children_valid(child->parent) == FAILURE) {
522 		return FAILURE;
523 	}
524 
525 	xmlNodePtr children = child->parent->children;
526 	if (!children) {
527 		php_dom_throw_error(NOT_FOUND_ERR, stricterror);
528 		return FAILURE;
529 	}
530 
531 	return SUCCESS;
532 }
533 
dom_child_node_remove(dom_object * context)534 void dom_child_node_remove(dom_object *context)
535 {
536 	xmlNode *child = dom_object_get_node(context);
537 	int stricterror;
538 
539 	stricterror = dom_get_strict_error(context->document);
540 
541 	if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
542 		return;
543 	}
544 
545 	php_libxml_invalidate_node_list_cache(context->document);
546 
547 	xmlUnlinkNode(child);
548 }
549 
dom_child_replace_with(dom_object * context,zval * nodes,uint32_t nodesc)550 void dom_child_replace_with(dom_object *context, zval *nodes, uint32_t nodesc)
551 {
552 	/* Spec link: https://dom.spec.whatwg.org/#dom-childnode-replacewith */
553 
554 	xmlNodePtr child = dom_object_get_node(context);
555 
556 	/* Spec step 1 */
557 	xmlNodePtr parentNode = child->parent;
558 
559 	/* Sanity check for fragment, includes spec step 2 */
560 	if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) {
561 		return;
562 	}
563 
564 	int stricterror = dom_get_strict_error(context->document);
565 	if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
566 		return;
567 	}
568 
569 	/* Spec step 3: find first following child not in nodes; otherwise null */
570 	xmlNodePtr viable_next_sibling = child->next;
571 	while (viable_next_sibling) {
572 		if (!dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) {
573 			break;
574 		}
575 		viable_next_sibling = viable_next_sibling->next;
576 	}
577 
578 	xmlDocPtr doc = parentNode->doc;
579 	php_libxml_invalidate_node_list_cache(context->document);
580 
581 	/* Spec step 4: convert nodes into fragment */
582 	xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
583 	if (UNEXPECTED(fragment == NULL)) {
584 		return;
585 	}
586 
587 	/* Spec step 5: perform the replacement */
588 
589 	xmlNodePtr newchild = fragment->children;
590 
591 	/* Unlink it unless it became a part of the fragment.
592 	 * Freeing will be taken care of by the lifetime of the returned dom object. */
593 	if (child->parent != fragment) {
594 		xmlUnlinkNode(child);
595 	}
596 
597 	if (newchild) {
598 		xmlNodePtr last = fragment->last;
599 
600 		dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment);
601 
602 		dom_fragment_assign_parent_node(parentNode, fragment);
603 		dom_reconcile_ns_list(doc, newchild, last);
604 	}
605 
606 	xmlFree(fragment);
607 }
608 
dom_parent_node_replace_children(dom_object * context,zval * nodes,uint32_t nodesc)609 void dom_parent_node_replace_children(dom_object *context, zval *nodes, uint32_t nodesc)
610 {
611 	/* Spec link: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren */
612 
613 	xmlNodePtr thisp = dom_object_get_node(context);
614 	/* Note: Only rule 2 of pre-insertion validity can be broken */
615 	if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, thisp, nodes, nodesc) != SUCCESS)) {
616 		return;
617 	}
618 
619 	xmlNodePtr fragment = dom_zvals_to_fragment(context->document, thisp, nodes, nodesc);
620 	if (UNEXPECTED(fragment == NULL)) {
621 		return;
622 	}
623 
624 	php_libxml_invalidate_node_list_cache(context->document);
625 
626 	dom_remove_all_children(thisp);
627 
628 	xmlNodePtr newchild = fragment->children;
629 	if (newchild) {
630 		xmlNodePtr last = fragment->last;
631 
632 		dom_pre_insert(NULL, thisp, newchild, fragment);
633 
634 		dom_fragment_assign_parent_node(thisp, fragment);
635 		dom_reconcile_ns_list(thisp->doc, newchild, last);
636 	}
637 
638 	xmlFree(fragment);
639 }
640 
641 #endif
642