xref: /php-src/ext/dom/dom_iterators.c (revision 9cb23a3d)
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: Christian Stocker <chregu@php.net>                          |
14    |          Rob Richards <rrichards@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 "dom_ce.h"
26 
27 typedef struct nodeIterator {
28 	int cur;
29 	int index;
30 	xmlNode *node;
31 } nodeIterator;
32 
33 /* Function pointer typedef changed in 2.9.8, see https://github.com/GNOME/libxml2/commit/e03f0a199a67017b2f8052354cf732b2b4cae787 */
34 #if LIBXML_VERSION >= 20908
itemHashScanner(void * payload,void * data,const xmlChar * name)35 static void itemHashScanner (void *payload, void *data, const xmlChar *name) /* {{{ */
36 #else
37 static void itemHashScanner (void *payload, void *data, xmlChar *name)
38 #endif
39 {
40 	nodeIterator *priv = data;
41 
42 	if (priv->cur < priv->index) {
43 		priv->cur++;
44 	} else {
45 		if (priv->node == NULL) {
46 			priv->node = payload;
47 		}
48 	}
49 }
50 /* }}} */
51 
create_notation(const xmlChar * name,const xmlChar * ExternalID,const xmlChar * SystemID)52 xmlNodePtr create_notation(const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID) /* {{{ */
53 {
54 	xmlEntityPtr ret = xmlMalloc(sizeof(xmlEntity));
55 	memset(ret, 0, sizeof(xmlEntity));
56 	ret->type = XML_NOTATION_NODE;
57 	ret->name = xmlStrdup(name);
58 	ret->ExternalID = xmlStrdup(ExternalID);
59 	ret->SystemID = xmlStrdup(SystemID);
60 	return (xmlNodePtr) ret;
61 }
62 /* }}} */
63 
php_dom_libxml_hash_iter_ex(xmlHashTable * ht,int index)64 static xmlNode *php_dom_libxml_hash_iter_ex(xmlHashTable *ht, int index)
65 {
66 	int htsize;
67 
68 	if ((htsize = xmlHashSize(ht)) > 0 && index < htsize) {
69 		nodeIterator iter;
70 		iter.cur = 0;
71 		iter.index = index;
72 		iter.node = NULL;
73 		xmlHashScan(ht, itemHashScanner, &iter);
74 		return iter.node;
75 	} else {
76 		return NULL;
77 	}
78 }
79 
php_dom_libxml_hash_iter(dom_nnodemap_object * objmap,int index)80 xmlNode *php_dom_libxml_hash_iter(dom_nnodemap_object *objmap, int index)
81 {
82 	xmlNode *curnode = php_dom_libxml_hash_iter_ex(objmap->ht, index);
83 
84 	if (curnode != NULL && objmap->nodetype != XML_ENTITY_NODE) {
85 		xmlNotation *notation = (xmlNotation *) curnode;
86 		curnode = create_notation(notation->name, notation->PublicID, notation->SystemID);
87 	}
88 
89 	return curnode;
90 }
91 
php_dom_iterator_dtor(zend_object_iterator * iter)92 static void php_dom_iterator_dtor(zend_object_iterator *iter) /* {{{ */
93 {
94 	php_dom_iterator *iterator = (php_dom_iterator *)iter;
95 
96 	zval_ptr_dtor(&iterator->intern.data);
97 	zval_ptr_dtor(&iterator->curobj);
98 }
99 /* }}} */
100 
php_dom_iterator_valid(zend_object_iterator * iter)101 static zend_result php_dom_iterator_valid(zend_object_iterator *iter) /* {{{ */
102 {
103 	php_dom_iterator *iterator = (php_dom_iterator *)iter;
104 
105 	if (Z_TYPE(iterator->curobj) != IS_UNDEF) {
106 		return SUCCESS;
107 	} else {
108 		return FAILURE;
109 	}
110 }
111 /* }}} */
112 
php_dom_iterator_current_data(zend_object_iterator * iter)113 zval *php_dom_iterator_current_data(zend_object_iterator *iter) /* {{{ */
114 {
115 	php_dom_iterator *iterator = (php_dom_iterator *)iter;
116 	return Z_ISUNDEF(iterator->curobj) ? NULL : &iterator->curobj;
117 }
118 /* }}} */
119 
php_dom_iterator_current_key(zend_object_iterator * iter,zval * key)120 static void php_dom_iterator_current_key(zend_object_iterator *iter, zval *key) /* {{{ */
121 {
122 	php_dom_iterator *iterator = (php_dom_iterator *)iter;
123 	zval *object = &iterator->intern.data;
124 	zend_class_entry *ce = Z_OBJCE_P(object);
125 
126 	/* Nodelists have the index as a key while named node maps have the name as a key. */
127 	if (instanceof_function(ce, dom_nodelist_class_entry) || instanceof_function(ce, dom_modern_nodelist_class_entry)) {
128 		ZVAL_LONG(key, iter->index);
129 	} else {
130 		dom_object *intern = Z_DOMOBJ_P(&iterator->curobj);
131 
132 		if (intern != NULL && intern->ptr != NULL) {
133 			xmlNodePtr curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node;
134 			ZVAL_STRINGL(key, (char *) curnode->name, xmlStrlen(curnode->name));
135 		} else {
136 			ZVAL_NULL(key);
137 		}
138 	}
139 }
140 /* }}} */
141 
dom_fetch_first_iteration_item(dom_nnodemap_object * objmap)142 static xmlNodePtr dom_fetch_first_iteration_item(dom_nnodemap_object *objmap)
143 {
144 	xmlNodePtr basep = dom_object_get_node(objmap->baseobj);
145 	if (!basep) {
146 		return NULL;
147 	}
148 	if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) {
149 		if (objmap->nodetype == XML_ATTRIBUTE_NODE) {
150 			return (xmlNodePtr) basep->properties;
151 		} else {
152 			return dom_nodelist_iter_start_first_child(basep);
153 		}
154 	} else {
155 		zend_long curindex = 0;
156 		xmlNodePtr nodep = php_dom_first_child_of_container_node(basep);
157 		return dom_get_elements_by_tag_name_ns_raw(
158 			basep, nodep, objmap->ns, objmap->local, objmap->local_lower, &curindex, 0);
159 	}
160 }
161 
php_dom_iterator_move_forward(zend_object_iterator * iter)162 static void php_dom_iterator_move_forward(zend_object_iterator *iter) /* {{{ */
163 {
164 	xmlNodePtr curnode = NULL;
165 
166 	php_dom_iterator *iterator = (php_dom_iterator *)iter;
167 	if (Z_ISUNDEF(iterator->curobj)) {
168 		return;
169 	}
170 
171 	dom_object *intern = Z_DOMOBJ_P(&iterator->curobj);
172 	zval *object = &iterator->intern.data;
173 	dom_object *nnmap = Z_DOMOBJ_P(object);
174 	dom_nnodemap_object *objmap = nnmap->ptr;
175 
176 	if (intern != NULL && intern->ptr != NULL) {
177 		if (objmap->nodetype != XML_ENTITY_NODE &&
178 			objmap->nodetype != XML_NOTATION_NODE) {
179 			if (objmap->nodetype == DOM_NODESET) {
180 				HashTable *nodeht = HASH_OF(&objmap->baseobj_zv);
181 				zval *entry;
182 				zend_hash_move_forward_ex(nodeht, &iterator->pos);
183 				if ((entry = zend_hash_get_current_data_ex(nodeht, &iterator->pos))) {
184 					zval_ptr_dtor(&iterator->curobj);
185 					ZVAL_COPY(&iterator->curobj, entry);
186 					return;
187 				}
188 			} else {
189 				if (objmap->nodetype == XML_ATTRIBUTE_NODE ||
190 					objmap->nodetype == XML_ELEMENT_NODE) {
191 
192 					/* Note: keep legacy behaviour for non-spec mode. */
193 					if (php_dom_follow_spec_intern(intern) && php_dom_is_cache_tag_stale_from_doc_ptr(&iterator->cache_tag, intern->document)) {
194 						php_dom_mark_cache_tag_up_to_date_from_doc_ref(&iterator->cache_tag, intern->document);
195 						curnode = dom_fetch_first_iteration_item(objmap);
196 						zend_ulong index = 0;
197 						while (curnode != NULL && index++ < iter->index) {
198 							curnode = curnode->next;
199 						}
200 					} else {
201 						curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node;
202 						curnode = curnode->next;
203 					}
204 				} else {
205 					/* The collection is live, we nav the tree from the base object if we cannot
206 					 * use the cache to restart from the last point. */
207 					xmlNodePtr basenode = dom_object_get_node(objmap->baseobj);
208 
209 					/* We have a strong reference to the base node via baseobj_zv, this cannot become NULL */
210 					ZEND_ASSERT(basenode != NULL);
211 
212 					zend_long previndex;
213 					if (php_dom_is_cache_tag_stale_from_node(&iterator->cache_tag, basenode)) {
214 						php_dom_mark_cache_tag_up_to_date_from_node(&iterator->cache_tag, basenode);
215 						previndex = 0;
216 						curnode = php_dom_first_child_of_container_node(basenode);
217 					} else {
218 						previndex = iter->index - 1;
219 						curnode = (xmlNodePtr)((php_libxml_node_ptr *)intern->ptr)->node;
220 					}
221 					curnode = dom_get_elements_by_tag_name_ns_raw(
222 						basenode, curnode, objmap->ns, objmap->local, objmap->local_lower, &previndex, iter->index);
223 				}
224 			}
225 		} else {
226 			curnode = php_dom_libxml_hash_iter(objmap, iter->index);
227 		}
228 	}
229 
230 	zval_ptr_dtor(&iterator->curobj);
231 	ZVAL_UNDEF(&iterator->curobj);
232 
233 	if (curnode) {
234 		php_dom_create_object(curnode, &iterator->curobj, objmap->baseobj);
235 	}
236 }
237 /* }}} */
238 
239 static const zend_object_iterator_funcs php_dom_iterator_funcs = {
240 	php_dom_iterator_dtor,
241 	php_dom_iterator_valid,
242 	php_dom_iterator_current_data,
243 	php_dom_iterator_current_key,
244 	php_dom_iterator_move_forward,
245 	NULL,
246 	NULL,
247 	NULL, /* get_gc */
248 };
249 
php_dom_get_iterator(zend_class_entry * ce,zval * object,int by_ref)250 zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
251 {
252 	dom_object *intern;
253 	dom_nnodemap_object *objmap;
254 	xmlNodePtr curnode=NULL;
255 	HashTable *nodeht;
256 	zval *entry;
257 	php_dom_iterator *iterator;
258 
259 	if (by_ref) {
260 		zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
261 		return NULL;
262 	}
263 	iterator = emalloc(sizeof(php_dom_iterator));
264 	zend_iterator_init(&iterator->intern);
265 	iterator->cache_tag.modification_nr = 0;
266 
267 	ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object));
268 	iterator->intern.funcs = &php_dom_iterator_funcs;
269 
270 	ZVAL_UNDEF(&iterator->curobj);
271 
272 	intern = Z_DOMOBJ_P(object);
273 	objmap = (dom_nnodemap_object *)intern->ptr;
274 	if (objmap != NULL) {
275 		if (objmap->nodetype != XML_ENTITY_NODE &&
276 			objmap->nodetype != XML_NOTATION_NODE) {
277 			if (objmap->nodetype == DOM_NODESET) {
278 				nodeht = HASH_OF(&objmap->baseobj_zv);
279 				zend_hash_internal_pointer_reset_ex(nodeht, &iterator->pos);
280 				if ((entry = zend_hash_get_current_data_ex(nodeht, &iterator->pos))) {
281 					ZVAL_COPY(&iterator->curobj, entry);
282 				}
283 			} else {
284 				curnode = dom_fetch_first_iteration_item(objmap);
285 			}
286 		} else {
287 			curnode = php_dom_libxml_hash_iter(objmap, 0);
288 		}
289 	}
290 
291 	if (curnode) {
292 		php_dom_create_object(curnode, &iterator->curobj, objmap->baseobj);
293 	}
294 
295 	return &iterator->intern;
296 }
297 /* }}} */
298 
299 #endif
300