2005-11-15 04:43:52 -05:00
|
|
|
/* The DOM tree navigation interface */
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "elinks.h"
|
|
|
|
|
|
|
|
#include "document/dom/node.h"
|
|
|
|
#include "document/dom/stack.h"
|
|
|
|
#include "util/memory.h"
|
|
|
|
#include "util/string.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* Navigator states */
|
|
|
|
|
|
|
|
#define DOM_STACK_STATE_GRANULARITY 0x7
|
|
|
|
#define DOM_STACK_CALLBACKS_SIZE (sizeof(dom_stack_callback_T) * DOM_NODES)
|
|
|
|
|
|
|
|
static inline struct dom_stack_state *
|
|
|
|
realloc_dom_stack_states(struct dom_stack_state **states, size_t size)
|
|
|
|
{
|
|
|
|
return mem_align_alloc(states, size, size + 1,
|
|
|
|
struct dom_stack_state,
|
|
|
|
DOM_STACK_STATE_GRANULARITY);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned char *
|
|
|
|
realloc_dom_stack_state_objects(struct dom_stack *stack)
|
|
|
|
{
|
|
|
|
#ifdef DEBUG_MEMLEAK
|
|
|
|
return mem_align_alloc__(__FILE__, __LINE__, (void **) &stack->state_objects,
|
|
|
|
stack->depth, stack->depth + 1,
|
|
|
|
stack->object_size,
|
|
|
|
DOM_STACK_STATE_GRANULARITY);
|
|
|
|
#else
|
|
|
|
return mem_align_alloc__((void **) &stack->state_objects,
|
|
|
|
stack->depth, stack->depth + 1,
|
|
|
|
stack->object_size,
|
|
|
|
DOM_STACK_STATE_GRANULARITY);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2005-12-12 11:41:09 -05:00
|
|
|
init_dom_stack(struct dom_stack *stack, void *data,
|
2005-12-20 13:20:04 -05:00
|
|
|
struct dom_stack_callbacks *callbacks,
|
2005-12-07 21:02:27 -05:00
|
|
|
size_t object_size, int keep_nodes)
|
2005-11-15 04:43:52 -05:00
|
|
|
{
|
|
|
|
assert(stack);
|
|
|
|
|
|
|
|
memset(stack, 0, sizeof(*stack));
|
|
|
|
|
2005-12-12 11:41:09 -05:00
|
|
|
stack->data = data;
|
2005-11-15 04:43:52 -05:00
|
|
|
stack->object_size = object_size;
|
2005-12-07 21:02:27 -05:00
|
|
|
stack->keep_nodes = !!keep_nodes;
|
2005-12-20 13:20:04 -05:00
|
|
|
stack->callbacks = callbacks;
|
2005-11-15 04:43:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
done_dom_stack(struct dom_stack *stack)
|
|
|
|
{
|
|
|
|
assert(stack);
|
|
|
|
|
|
|
|
mem_free_if(stack->states);
|
|
|
|
mem_free_if(stack->state_objects);
|
|
|
|
|
|
|
|
memset(stack, 0, sizeof(*stack));
|
|
|
|
}
|
|
|
|
|
2005-12-20 14:01:18 -05:00
|
|
|
enum dom_stack_action {
|
|
|
|
DOM_STACK_PUSH,
|
|
|
|
DOM_STACK_POP,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
call_dom_stack_callbacks(struct dom_stack *stack, struct dom_stack_state *state,
|
|
|
|
enum dom_stack_action action)
|
|
|
|
{
|
|
|
|
dom_stack_callback_T callback;
|
|
|
|
|
|
|
|
if (!stack->callbacks)
|
|
|
|
callback = NULL;
|
|
|
|
else if (action == DOM_STACK_PUSH)
|
|
|
|
callback = stack->callbacks->push[state->node->type];
|
|
|
|
else
|
|
|
|
callback = stack->callbacks->pop[state->node->type];
|
|
|
|
|
|
|
|
if (callback) {
|
|
|
|
void *state_data = get_dom_stack_state_data(stack, state);
|
|
|
|
|
|
|
|
callback(stack, state->node, state_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-11-15 04:43:52 -05:00
|
|
|
struct dom_node *
|
|
|
|
push_dom_node(struct dom_stack *stack, struct dom_node *node)
|
|
|
|
{
|
|
|
|
struct dom_stack_state *state;
|
|
|
|
|
|
|
|
assert(stack && node);
|
|
|
|
assert(0 < node->type && node->type < DOM_NODES);
|
|
|
|
|
|
|
|
if (stack->depth > DOM_STACK_MAX_DEPTH) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
state = realloc_dom_stack_states(&stack->states, stack->depth);
|
|
|
|
if (!state) {
|
|
|
|
done_dom_node(node);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
state += stack->depth;
|
|
|
|
|
|
|
|
if (stack->object_size) {
|
|
|
|
unsigned char *state_objects;
|
|
|
|
|
|
|
|
state_objects = realloc_dom_stack_state_objects(stack);
|
|
|
|
if (!state_objects) {
|
|
|
|
done_dom_node(node);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2005-12-05 05:11:06 -05:00
|
|
|
state->depth = stack->depth;
|
2005-11-15 04:43:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
state->node = node;
|
|
|
|
|
|
|
|
/* Grow the state array to the new depth so the state accessors work
|
|
|
|
* in the callbacks */
|
|
|
|
stack->depth++;
|
2005-12-20 14:01:18 -05:00
|
|
|
call_dom_stack_callbacks(stack, state, DOM_STACK_PUSH);
|
2005-11-15 04:43:52 -05:00
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_pop_dom_node(struct dom_stack *stack, struct dom_stack_state *parent)
|
|
|
|
{
|
|
|
|
struct dom_stack_state *state;
|
|
|
|
|
2005-12-18 20:15:36 -05:00
|
|
|
assert(stack && !dom_stack_is_empty(stack));
|
2005-11-15 04:43:52 -05:00
|
|
|
|
|
|
|
state = get_dom_stack_top(stack);
|
2005-12-18 20:34:26 -05:00
|
|
|
if (state->immutable)
|
|
|
|
return 1;
|
|
|
|
|
2005-12-20 14:01:18 -05:00
|
|
|
call_dom_stack_callbacks(stack, state, DOM_STACK_POP);
|
2005-11-15 04:43:52 -05:00
|
|
|
|
2005-12-07 21:02:27 -05:00
|
|
|
if (!stack->keep_nodes)
|
|
|
|
done_dom_node(state->node);
|
|
|
|
|
2005-11-15 04:43:52 -05:00
|
|
|
stack->depth--;
|
|
|
|
assert(stack->depth >= 0);
|
|
|
|
|
2005-12-05 05:11:06 -05:00
|
|
|
if (stack->object_size) {
|
|
|
|
void *state_data = get_dom_stack_state_data(stack, state);
|
2005-11-15 04:43:52 -05:00
|
|
|
|
2005-12-05 05:11:06 -05:00
|
|
|
memset(state_data, 0, stack->object_size);
|
2005-11-15 04:43:52 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
|
|
|
|
return state == parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
pop_dom_node(struct dom_stack *stack)
|
|
|
|
{
|
|
|
|
assert(stack);
|
2005-12-18 20:15:36 -05:00
|
|
|
|
|
|
|
if (dom_stack_is_empty(stack)) return;
|
2005-11-15 04:43:52 -05:00
|
|
|
|
|
|
|
do_pop_dom_node(stack, get_dom_stack_parent(stack));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
pop_dom_nodes(struct dom_stack *stack, enum dom_node_type type,
|
2005-12-10 15:42:49 -05:00
|
|
|
struct dom_string *string)
|
2005-11-15 04:43:52 -05:00
|
|
|
{
|
2005-12-05 13:40:35 -05:00
|
|
|
struct dom_stack_state *state;
|
2005-11-15 04:43:52 -05:00
|
|
|
|
2005-12-18 20:15:36 -05:00
|
|
|
assert(stack);
|
|
|
|
|
|
|
|
if (dom_stack_is_empty(stack)) return;
|
2005-11-15 04:43:52 -05:00
|
|
|
|
2005-12-10 15:42:49 -05:00
|
|
|
state = search_dom_stack(stack, type, string);
|
2005-12-05 13:40:35 -05:00
|
|
|
if (state)
|
2005-12-15 16:05:30 -05:00
|
|
|
pop_dom_state(stack, state);
|
2005-12-05 13:40:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2005-12-15 16:05:30 -05:00
|
|
|
pop_dom_state(struct dom_stack *stack, struct dom_stack_state *target)
|
2005-12-05 13:40:35 -05:00
|
|
|
{
|
|
|
|
struct dom_stack_state *state;
|
|
|
|
unsigned int pos;
|
|
|
|
|
2005-12-18 20:15:36 -05:00
|
|
|
assert(stack);
|
|
|
|
|
2005-12-05 13:40:35 -05:00
|
|
|
if (!target) return;
|
|
|
|
|
2005-12-18 20:15:36 -05:00
|
|
|
if (dom_stack_is_empty(stack)) return;
|
2005-11-15 04:43:52 -05:00
|
|
|
|
2005-12-18 20:51:32 -05:00
|
|
|
foreachback_dom_stack_state (stack, state, pos) {
|
2005-12-05 13:40:35 -05:00
|
|
|
if (do_pop_dom_node(stack, target))
|
2005-11-15 04:43:52 -05:00
|
|
|
break;;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
walk_dom_nodes(struct dom_stack *stack, struct dom_node *root)
|
|
|
|
{
|
|
|
|
assert(root && stack);
|
|
|
|
|
|
|
|
push_dom_node(stack, root);
|
|
|
|
|
2005-12-18 20:15:36 -05:00
|
|
|
while (!dom_stack_is_empty(stack)) {
|
2005-11-15 04:43:52 -05:00
|
|
|
struct dom_stack_state *state = get_dom_stack_top(stack);
|
|
|
|
struct dom_node_list *list = state->list;
|
|
|
|
struct dom_node *node = state->node;
|
|
|
|
|
|
|
|
switch (node->type) {
|
|
|
|
case DOM_NODE_DOCUMENT:
|
|
|
|
if (!list) list = node->data.document.children;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DOM_NODE_ELEMENT:
|
|
|
|
if (!list) list = node->data.element.map;
|
|
|
|
|
|
|
|
if (list == node->data.element.children) break;
|
|
|
|
if (is_dom_node_list_member(list, state->index)
|
|
|
|
&& list == node->data.element.map)
|
|
|
|
break;
|
|
|
|
|
|
|
|
list = node->data.element.children;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DOM_NODE_PROCESSING_INSTRUCTION:
|
|
|
|
if (!list) list = node->data.proc_instruction.map;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DOM_NODE_DOCUMENT_TYPE:
|
|
|
|
if (!list) list = node->data.document_type.entities;
|
|
|
|
|
|
|
|
if (list == node->data.document_type.notations) break;
|
|
|
|
if (is_dom_node_list_member(list, state->index)
|
|
|
|
&& list == node->data.document_type.entities)
|
|
|
|
break;
|
|
|
|
|
|
|
|
list = node->data.document_type.notations;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DOM_NODE_ATTRIBUTE:
|
|
|
|
case DOM_NODE_TEXT:
|
|
|
|
case DOM_NODE_CDATA_SECTION:
|
|
|
|
case DOM_NODE_COMMENT:
|
|
|
|
case DOM_NODE_NOTATION:
|
|
|
|
case DOM_NODE_DOCUMENT_FRAGMENT:
|
|
|
|
case DOM_NODE_ENTITY_REFERENCE:
|
|
|
|
case DOM_NODE_ENTITY:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reset list state if it is a new list */
|
|
|
|
if (list != state->list) {
|
|
|
|
state->list = list;
|
|
|
|
state->index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we have next child node */
|
|
|
|
if (is_dom_node_list_member(list, state->index)) {
|
|
|
|
struct dom_node *child = list->entries[state->index++];
|
|
|
|
|
|
|
|
if (push_dom_node(stack, child))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
pop_dom_node(stack);
|
|
|
|
}
|
|
|
|
}
|