Code reuse
[elinks.git] / src / dom / node.c
blobfbf2d5c0561bbce9fa84a33131b6560ca7a23a68
1 /* The DOM node handling */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <string.h>
10 #include "elinks.h"
12 #include "dom/node.h"
13 #include "dom/string.h"
14 #include "util/hash.h"
15 #include "util/memory.h"
18 static void done_dom_node_data(struct dom_node *node);
20 /* Node lists */
22 #define DOM_NODE_LIST_GRANULARITY 0x7
24 #define DOM_NODE_LIST_BLOCK_SIZE \
25 (ALIGN_MEMORY_SIZE(1, DOM_NODE_LIST_GRANULARITY) * sizeof(struct dom_node *))
27 /* The node list struct has one node pointer */
28 #define DOM_NODE_LIST_SIZE(size) \
29 ((size - 1) * sizeof(struct dom_node *) + sizeof(struct dom_node_list))
31 static inline struct dom_node_list *
32 realloc_dom_node_list(struct dom_node_list **oldlist)
34 struct dom_node_list *list = *oldlist;
35 size_t size = list ? list->size : 0;
36 size_t oldsize = ALIGN_MEMORY_SIZE(size, DOM_NODE_LIST_GRANULARITY);
37 size_t newsize = ALIGN_MEMORY_SIZE(size + 1, DOM_NODE_LIST_GRANULARITY);
39 if (newsize <= oldsize) return list;
41 list = mem_realloc(list, DOM_NODE_LIST_SIZE(newsize));
42 if (!list) return NULL;
44 /* If this is the first reallocation clear the size */
45 if (!size) list->size = 0;
47 /* Clear the new block of entries */
48 memset(&list->entries[oldsize], 0, DOM_NODE_LIST_BLOCK_SIZE);
50 *oldlist = list;
52 return list;
55 struct dom_node_list *
56 add_to_dom_node_list(struct dom_node_list **list_ptr,
57 struct dom_node *node, int position)
59 struct dom_node_list *list;
61 assert(list_ptr && node);
63 list = realloc_dom_node_list(list_ptr);
64 if (!list) return NULL;
66 assertm(position < 0 || position <= list->size,
67 "position out of bound %d > %zu", position, list->size);
69 if (position < 0) {
70 position = list->size;
72 } else if (position < list->size) {
73 /* Make room if we have to add the node in the middle of the list */
74 struct dom_node **offset = &list->entries[position];
75 size_t size = (list->size - position) * sizeof(*offset);
77 memmove(offset + 1, offset, size);
80 list->size++;
81 list->entries[position] = node;
83 return list;
86 static void
87 del_from_dom_node_list(struct dom_node_list *list, struct dom_node *node)
89 struct dom_node *entry;
90 size_t i;
92 if (!list) return;
94 foreach_dom_node (list, entry, i) {
95 size_t successors;
97 if (entry != node) continue;
99 successors = list->size - (i + 1);
100 if (successors)
101 memmove(&list->entries[i], &list->entries[i+1],
102 sizeof(*list->entries) * successors);
103 list->size--;
107 void
108 done_dom_node_list(struct dom_node_list *list)
110 struct dom_node *node;
111 int i;
113 assert(list);
115 foreach_dom_node (list, node, i) {
116 /* Avoid that the node start messing with the node list. */
117 done_dom_node_data(node);
120 mem_free(list);
124 /* Node map */
126 struct dom_node_search {
127 struct dom_node *key;
128 unsigned int from, pos, to;
131 #define INIT_DOM_NODE_SEARCH(key, list) \
132 { (key), -1, 0, (list)->size, }
135 dom_node_casecmp(struct dom_node *node1, struct dom_node *node2)
137 if (node1->type == node2->type) {
138 switch (node1->type) {
139 case DOM_NODE_ELEMENT:
140 if (node1->data.element.type && node2->data.element.type)
141 return node1->data.element.type - node2->data.element.type;
142 break;
144 case DOM_NODE_ATTRIBUTE:
145 if (node1->data.attribute.type && node2->data.attribute.type)
146 return node1->data.attribute.type - node2->data.attribute.type;
147 break;
149 default:
150 break;
154 return dom_string_casecmp(&node1->string, &node2->string);
157 static inline int
158 get_bsearch_position(struct dom_node_list *list, int from, int to)
160 int pos = from + ((to - from) / 2);
162 assertm(0 <= pos && pos < list->size, "pos %d", pos);
163 return pos;
166 #define has_bsearch_node(from, to) ((from) + 1 < (to))
168 static inline struct dom_node *
169 dom_node_list_bsearch(struct dom_node_search *search, struct dom_node_list *list)
171 assert(has_bsearch_node(search->from, search->to));
173 do {
174 int pos = get_bsearch_position(list, search->from, search->to);
175 struct dom_node *node = list->entries[pos];
176 int difference = dom_node_casecmp(search->key, node);
178 search->pos = pos;
180 if (!difference) return node;
182 if (difference < 0) {
183 search->to = search->pos;
184 } else {
185 search->from = search->pos;
188 } while (has_bsearch_node(search->from, search->to));
190 return NULL;
194 get_dom_node_map_index(struct dom_node_list *list, struct dom_node *node)
196 struct dom_node_search search = INIT_DOM_NODE_SEARCH(node, list);
197 struct dom_node *match = dom_node_list_bsearch(&search, list);
199 return match ? search.pos : search.to;
202 struct dom_node *
203 get_dom_node_map_entry(struct dom_node_list *list, enum dom_node_type type,
204 uint16_t subtype, struct dom_string *name)
206 struct dom_node node = { type, 0, INIT_DOM_STRING(name->string, name->length) };
207 struct dom_node_search search = INIT_DOM_NODE_SEARCH(&node, list);
209 if (subtype) {
210 /* Set the subtype */
211 switch (type) {
212 case DOM_NODE_ELEMENT:
213 node.data.element.type = subtype;
214 break;
215 case DOM_NODE_ATTRIBUTE:
216 node.data.attribute.type = subtype;
217 break;
218 case DOM_NODE_PROCESSING_INSTRUCTION:
219 node.data.proc_instruction.type = subtype;
220 break;
221 default:
222 break;
226 return dom_node_list_bsearch(&search, list);
229 static int
230 get_dom_node_list_pos(struct dom_node_list *list, struct dom_node *node)
232 struct dom_node *entry;
233 int i;
235 assert(list);
236 if_assert_failed return -1;
238 foreach_dom_node (list, entry, i) {
239 if (entry == node)
240 return i;
243 return -1;
247 get_dom_node_list_index(struct dom_node *parent, struct dom_node *node)
249 struct dom_node_list **list = get_dom_node_list(parent, node);
251 return (list && *list) ? get_dom_node_list_pos(*list, node) : -1;
254 struct dom_node *
255 get_dom_node_prev(struct dom_node *node)
257 struct dom_node_list **list;
258 int index;
260 assert(node->parent);
261 if_assert_failed return NULL;
263 list = get_dom_node_list(node->parent, node);
264 /* node->parent != NULL, so the node must be in the
265 * appropriate list of the parent; the list thus cannot be
266 * empty. */
267 if (!list) return NULL;
268 assert(*list);
269 if_assert_failed return NULL;
271 index = get_dom_node_list_pos(*list, node);
272 assert(index >= 0); /* in particular, not -1 */
273 if_assert_failed return NULL;
275 if (index > 0)
276 return (*list)->entries[index - 1];
278 return NULL;
281 struct dom_node *
282 get_dom_node_next(struct dom_node *node)
284 struct dom_node_list **list;
285 int index;
287 assert(node->parent);
288 if_assert_failed return NULL;
290 list = get_dom_node_list(node->parent, node);
291 /* node->parent != NULL, so the node must be in the
292 * appropriate list of the parent; the list thus cannot be
293 * empty. */
294 if (!list) return NULL;
295 assert(*list);
296 if_assert_failed return NULL;
298 index = get_dom_node_list_pos(*list, node);
299 assert(index >= 0); /* in particular, not -1 */
300 if_assert_failed return NULL;
302 if (index + 1 < (*list)->size)
303 return (*list)->entries[index + 1];
305 return NULL;
308 struct dom_node *
309 get_dom_node_child(struct dom_node *parent, enum dom_node_type type,
310 int16_t subtype)
312 struct dom_node_list **list;
313 struct dom_node *node;
314 int index;
316 list = get_dom_node_list_by_type(parent, type);
317 if (!list) return NULL; /* parent doesn't support this type */
318 if (!*list) return NULL; /* list is empty and not yet allocated */
320 foreach_dom_node (*list, node, index) {
321 if (node->type != type)
322 continue;
324 if (!subtype) return node;
326 switch (type) {
327 case DOM_NODE_ELEMENT:
328 if (node->data.element.type == subtype)
329 return node;
330 break;
332 case DOM_NODE_ATTRIBUTE:
333 if (node->data.attribute.type == subtype)
334 return node;
335 break;
337 case DOM_NODE_PROCESSING_INSTRUCTION:
338 if (node->data.attribute.type == subtype)
339 return node;
340 break;
342 default:
343 return node;
347 return NULL;
351 /* Nodes */
353 struct dom_node *
354 init_dom_node_at(
355 #ifdef DEBUG_MEMLEAK
356 unsigned char *file, int line,
357 #endif
358 struct dom_node *parent, enum dom_node_type type,
359 struct dom_string *string, int allocated)
361 #ifdef DEBUG_MEMLEAK
362 struct dom_node *node = debug_mem_calloc(file, line, 1, sizeof(*node));
363 #else
364 struct dom_node *node = mem_calloc(1, sizeof(*node));
365 #endif
367 if (!node) return NULL;
369 node->type = type;
370 node->parent = parent;
372 /* Make it possible to add a node to a parent without allocating the
373 * strings. */
374 if (allocated >= 0) {
375 node->allocated = !!allocated;
376 } else if (parent) {
377 node->allocated = parent->allocated;
380 if (node->allocated) {
381 if (!init_dom_string(&node->string, string->string, string->length)) {
382 done_dom_node(node);
383 return NULL;
385 } else {
386 copy_dom_string(&node->string, string);
389 if (parent) {
390 struct dom_node_list **list = get_dom_node_list(parent, node);
391 int sort = (type == DOM_NODE_ATTRIBUTE);
392 int index;
394 assertm(list != NULL, "Adding node %d to bad parent %d",
395 node->type, parent->type);
397 index = *list && (*list)->size > 0 && sort
398 ? get_dom_node_map_index(*list, node) : -1;
400 if (!add_to_dom_node_list(list, node, index)) {
401 done_dom_node(node);
402 return NULL;
406 return node;
409 void
410 done_dom_node_data(struct dom_node *node)
412 union dom_node_data *data;
414 assert(node);
415 assert(node->type < DOM_NODES); /* unsigned comparison */
417 data = &node->data;
419 switch (node->type) {
420 case DOM_NODE_ATTRIBUTE:
421 if (node->allocated)
422 done_dom_string(&data->attribute.value);
423 break;
425 case DOM_NODE_DOCUMENT:
426 if (data->document.children)
427 done_dom_node_list(data->document.children);
428 break;
430 case DOM_NODE_ELEMENT:
431 if (data->element.children)
432 done_dom_node_list(data->element.children);
434 if (data->element.map)
435 done_dom_node_list(data->element.map);
436 break;
438 case DOM_NODE_PROCESSING_INSTRUCTION:
439 if (data->proc_instruction.map)
440 done_dom_node_list(data->proc_instruction.map);
441 if (node->allocated)
442 done_dom_string(&data->proc_instruction.instruction);
443 break;
445 default:
446 break;
449 if (node->allocated)
450 done_dom_string(&node->string);
452 /* call_dom_stack_callbacks() asserts that the node type is
453 * within range. If assertions are enabled, store an
454 * out-of-range value there to make the assertion more likely
455 * to fail if a callback has freed the node prematurely.
456 * This is not entirely reliable though, because the memory
457 * is freed below and may be allocated for some other purpose
458 * before the assertion. */
459 #ifndef CONFIG_FASTMEM
460 node->type = -1;
461 #endif
463 mem_free(node);
466 void
467 done_dom_node(struct dom_node *node)
469 assert(node);
471 if (node->parent) {
472 struct dom_node *parent = node->parent;
473 union dom_node_data *data = &parent->data;
475 switch (parent->type) {
476 case DOM_NODE_DOCUMENT:
477 del_from_dom_node_list(data->document.children, node);
478 break;
480 case DOM_NODE_ELEMENT:
481 del_from_dom_node_list(data->element.children, node);
482 del_from_dom_node_list(data->element.map, node);
483 break;
485 case DOM_NODE_PROCESSING_INSTRUCTION:
486 del_from_dom_node_list(data->proc_instruction.map, node);
487 break;
489 default:
490 break;
494 done_dom_node_data(node);
497 #define set_node_name(name, namelen, str) \
498 do { (name) = (str); (namelen) = sizeof(str) - 1; } while (0)
500 struct dom_string *
501 get_dom_node_name(struct dom_node *node)
503 static struct dom_string cdata_section_str = STATIC_DOM_STRING("#cdata-section");
504 static struct dom_string comment_str = STATIC_DOM_STRING("#comment");
505 static struct dom_string document_str = STATIC_DOM_STRING("#document");
506 static struct dom_string document_fragment_str = STATIC_DOM_STRING("#document-fragment");
507 static struct dom_string text_str = STATIC_DOM_STRING("#text");
509 assert(node);
511 switch (node->type) {
512 case DOM_NODE_CDATA_SECTION:
513 return &cdata_section_str;
515 case DOM_NODE_COMMENT:
516 return &comment_str;
518 case DOM_NODE_DOCUMENT:
519 return &document_str;
521 case DOM_NODE_DOCUMENT_FRAGMENT:
522 return &document_fragment_str;
524 case DOM_NODE_TEXT:
525 return &text_str;
527 case DOM_NODE_ATTRIBUTE:
528 case DOM_NODE_DOCUMENT_TYPE:
529 case DOM_NODE_ELEMENT:
530 case DOM_NODE_ENTITY:
531 case DOM_NODE_ENTITY_REFERENCE:
532 case DOM_NODE_NOTATION:
533 case DOM_NODE_PROCESSING_INSTRUCTION:
534 default:
535 return &node->string;
539 struct dom_string *
540 get_dom_node_value(struct dom_node *node)
542 assert(node);
544 switch (node->type) {
545 case DOM_NODE_ATTRIBUTE:
546 return &node->data.attribute.value;
548 case DOM_NODE_PROCESSING_INSTRUCTION:
549 return &node->data.proc_instruction.instruction;
551 case DOM_NODE_CDATA_SECTION:
552 case DOM_NODE_COMMENT:
553 case DOM_NODE_TEXT:
554 return &node->string;
556 case DOM_NODE_ENTITY_REFERENCE:
557 case DOM_NODE_NOTATION:
558 case DOM_NODE_DOCUMENT:
559 case DOM_NODE_DOCUMENT_FRAGMENT:
560 case DOM_NODE_DOCUMENT_TYPE:
561 case DOM_NODE_ELEMENT:
562 case DOM_NODE_ENTITY:
563 default:
564 return NULL;
568 struct dom_string *
569 get_dom_node_type_name(enum dom_node_type type)
571 static struct dom_string dom_node_type_names[DOM_NODES] = {
572 INIT_DOM_STRING(NULL, 0),
573 /* DOM_NODE_ELEMENT */ STATIC_DOM_STRING("element"),
574 /* DOM_NODE_ATTRIBUTE */ STATIC_DOM_STRING("attribute"),
575 /* DOM_NODE_TEXT */ STATIC_DOM_STRING("text"),
576 /* DOM_NODE_CDATA_SECTION */ STATIC_DOM_STRING("cdata-section"),
577 /* DOM_NODE_ENTITY_REFERENCE */ STATIC_DOM_STRING("entity-reference"),
578 /* DOM_NODE_ENTITY */ STATIC_DOM_STRING("entity"),
579 /* DOM_NODE_PROCESSING_INSTRUCTION */ STATIC_DOM_STRING("proc-instruction"),
580 /* DOM_NODE_COMMENT */ STATIC_DOM_STRING("comment"),
581 /* DOM_NODE_DOCUMENT */ STATIC_DOM_STRING("document"),
582 /* DOM_NODE_DOCUMENT_TYPE */ STATIC_DOM_STRING("document-type"),
583 /* DOM_NODE_DOCUMENT_FRAGMENT */ STATIC_DOM_STRING("document-fragment"),
584 /* DOM_NODE_NOTATION */ STATIC_DOM_STRING("notation"),
587 assert(type < DOM_NODES);
589 return &dom_node_type_names[type];