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
|
|
|
|
init_dom_stack(struct dom_stack *stack, void *data,
|
|
|
|
dom_stack_callback_T callbacks[DOM_NODES],
|
|
|
|
size_t object_size)
|
|
|
|
{
|
|
|
|
assert(stack);
|
|
|
|
|
|
|
|
memset(stack, 0, sizeof(*stack));
|
|
|
|
|
|
|
|
stack->data = data;
|
|
|
|
stack->object_size = object_size;
|
|
|
|
|
|
|
|
if (callbacks)
|
|
|
|
memcpy(stack->callbacks, callbacks, DOM_STACK_CALLBACKS_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dom_node *
|
|
|
|
push_dom_node(struct dom_stack *stack, struct dom_node *node)
|
|
|
|
{
|
|
|
|
dom_stack_callback_T callback;
|
|
|
|
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++;
|
|
|
|
|
|
|
|
callback = stack->callbacks[node->type];
|
|
|
|
if (callback) {
|
2005-12-05 05:11:06 -05:00
|
|
|
void *state_data = get_dom_stack_state_data(stack, state);
|
|
|
|
|
|
|
|
node = callback(stack, node, state_data);
|
2005-11-15 04:43:52 -05:00
|
|
|
|
|
|
|
/* If the callback returned NULL pop the state immediately */
|
|
|
|
if (!node) {
|
|
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
stack->depth--;
|
|
|
|
assert(stack->depth >= 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
do_pop_dom_node(struct dom_stack *stack, struct dom_stack_state *parent)
|
|
|
|
{
|
|
|
|
struct dom_stack_state *state;
|
|
|
|
|
|
|
|
assert(stack);
|
|
|
|
if (!dom_stack_has_parents(stack)) return 0;
|
|
|
|
|
|
|
|
state = get_dom_stack_top(stack);
|
|
|
|
if (state->callback) {
|
2005-12-05 05:11:06 -05:00
|
|
|
void *state_data = get_dom_stack_state_data(stack, state);
|
|
|
|
|
2005-11-15 04:43:52 -05:00
|
|
|
/* Pass the node we are popping to and _not_ the state->node */
|
2005-12-05 05:11:06 -05:00
|
|
|
state->callback(stack, parent->node, state_data);
|
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);
|
|
|
|
if (!dom_stack_has_parents(stack)) return;
|
|
|
|
|
|
|
|
do_pop_dom_node(stack, get_dom_stack_parent(stack));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
pop_dom_nodes(struct dom_stack *stack, enum dom_node_type type,
|
|
|
|
unsigned char *string, uint16_t length)
|
|
|
|
{
|
|
|
|
struct dom_stack_state *state, *parent;
|
|
|
|
unsigned int pos;
|
|
|
|
|
|
|
|
if (!dom_stack_has_parents(stack)) return;
|
|
|
|
|
|
|
|
parent = search_dom_stack(stack, type, string, length);
|
|
|
|
if (!parent) return;
|
|
|
|
|
|
|
|
foreachback_dom_state (stack, state, pos) {
|
|
|
|
if (do_pop_dom_node(stack, parent))
|
|
|
|
break;;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
walk_dom_nodes(struct dom_stack *stack, struct dom_node *root)
|
|
|
|
{
|
|
|
|
assert(root && stack);
|
|
|
|
|
|
|
|
push_dom_node(stack, root);
|
|
|
|
|
|
|
|
while (dom_stack_has_parents(stack)) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|