/** The DOM stack interface. * * @file dom/stack.h * * The DOM stack interface is used by the parser when building a DOM * tree and by the various DOM tree traversers. It allows for several * stack contexts to be registered, each defining their own type * specific node handlers or callbacks (#dom_stack_callback_T) which * will be called upon a node being pushed onto or popped from the * stack. As an example a example, by defining DOM_STACK_TRACE it is be * possible to add a DOM stack tracer (using #add_dom_stack_tracer) to * debug all push and pop operations. * * The stack interface provides a method for automatically having * objects allocated when a new node is pushed onto the stack. This * object can then be used for storing private state information * belonging to the individual contexts. This is done by setting the * #dom_stack_context_info.object_size member to a non-zero value, * usually using sizeof(). * * States on the stack can be marked immutable meaning that they will * be "locked" down so that any operations to pop them will fail. This * can be used when parsing a "subdocument", e.g. output from * ECMAScripts "document.write" function, where badly formatted SGML * should not be allowed to change the root document. * * In some situations, it may be desired to avoid building a complete * DOM tree in memory when only one document traversal is required, for * example when highlighting SGML code. This can be done by passing the * #DOM_STACK_FLAG_FREE_NODES flag to #init_dom_stack. Nodes that are * popped from the stack will immediately be deallocated. A pop node * callback can also request that a node is deallocated and removed from * the DOM tree by returning the #DOM_CODE_FREE_NODE return code. An * example of this can be seen in the DOM configuration module where * comment nodes may be pruned from the tree. */ /* TODO: Write about: * - How to access stack states and context state objects. * - Using search_dom_stack() and walk_dom_nodes(). */ #ifndef EL_DOM_STACK_H #define EL_DOM_STACK_H #include "dom/code.h" #include "dom/node.h" #include "util/error.h" #include "util/hash.h" #ifdef __cplusplus extern "C" { #endif struct dom_stack; /** DOM stack callback * * Used by contexts, for 'hooking' into the node traversing. * * This callback must not call done_dom_node() to free the node it * gets as a parameter, because call_dom_node_callbacks() may be * intending to call more callbacks for the same node. Instead, the * callback can return ::DOM_CODE_FREE_NODE, and the node will then * be freed after the callbacks of all contexts have been called. */ typedef enum dom_code (*dom_stack_callback_T)(struct dom_stack *, struct dom_node *, void *); #define DOM_STACK_MAX_DEPTH 4096 /** DOM stack state * * This state records what node and where it is placed. */ struct dom_stack_state { /** The node assiciated with the state */ struct dom_node *node; /** * The depth of the state in the stack. This is amongst other things * used to get the state object data. */ unsigned int depth; /** Whether this stack state can be popped with pop_dom_node(), * pop_dom_nodes(), or pop_dom_state(). */ unsigned int immutable:1; }; /** DOM stack context info * * To add a DOM stack context define this struct either statically or on the * stack. */ struct dom_stack_context_info { /** * The number of bytes to allocate on the stack for each state's * data member. Zero means no state data should be allocated. * */ size_t object_size; /** Callbacks to be called when pushing nodes. */ dom_stack_callback_T push[DOM_NODES]; /** Callbacks to be called when popping nodes. */ dom_stack_callback_T pop[DOM_NODES]; }; /** DOM stack context * * This holds 'runtime' data for the stack context. */ struct dom_stack_context { /** Data specific to the context. */ void *data; /** * This is one big array of context specific objects. For the SGML * parser this holds DTD-oriented info about the node (recorded in * struct #sgml_node_info). E.g. whether an element node is optional. */ char *state_objects; /** Info about node callbacks and such. */ struct dom_stack_context_info *info; }; /** Flags for controlling the DOM stack */ enum dom_stack_flag { /** No flag needed. */ DOM_STACK_FLAG_NONE = 0, /** Free nodes when popping by calling done_dom_node(). */ DOM_STACK_FLAG_FREE_NODES = 1, }; /** The DOM stack * * The DOM stack is a convenient way to traverse DOM trees. Also it * maintains needed state info and is therefore also a holder of the current * context since the stack is used to when the DOM tree is manipulated. */ struct dom_stack { /** The states currently on the stack. */ struct dom_stack_state *states; /** The depth of the stack. */ size_t depth; /** Flags given to ref:[init_dom_stack]. */ enum dom_stack_flag flags; /** Contexts for the pushed and popped nodes. */ struct dom_stack_context **contexts; /** The number of active contexts. */ size_t contexts_size; /** * The current context. Only meaningful within * #dom_stack_callback_T functions. */ struct dom_stack_context *current; }; /** Check whether stack is empty or not * * @param stack The stack to check. * * @returns Non-zero if stack is empty. */ #define dom_stack_is_empty(stack) \ (!(stack)->states || (stack)->depth == 0) /** Access state by offset from top * * @param stack The stack to fetch the state from. * @param top_offset The offset from the stack top, zero is the top. * * @returns The requested state. */ static inline struct dom_stack_state * get_dom_stack_state(struct dom_stack *stack, int top_offset) { assertm(stack->depth - 1 - top_offset >= 0, "Attempting to access invalid state"); return &stack->states[stack->depth - 1 - top_offset]; } /** Access the stack top * * @param stack The stack to get the top state from. * * @returns The top state. */ #define get_dom_stack_top(stack) \ get_dom_stack_state(stack, 0) /** Access context specific state data * * Similar to ref:[get_dom_stack_state], this will fetch the data * associated with the state for the given context. * * @param context The context to get data from. * @param state The stack state to get data from. * * @returns The state data or NULL if * #dom_stack_context_info.object_size was zero. */ static inline void * get_dom_stack_state_data(struct dom_stack_context *context, struct dom_stack_state *state) { size_t object_size = context->info->object_size; if (!object_size) return NULL; assert(context->state_objects); return (void *) &context->state_objects[state->depth * object_size]; } /*#define DOM_STACK_TRACE*/ /** Get debug info from the DOM stack * * Define `DOM_STACK_TRACE` to have debug info about the nodes added printed to * the log. It will define add_dom_stack_tracer() to not be a no-op. * * Run as: * * ELINKS_LOG=/tmp/dom-dump.txt ./elinks -no-connect URL * * to have the debug dumped into a file. */ #ifdef DOM_STACK_TRACE #define add_dom_stack_tracer(stack, name) \ add_dom_stack_context(stack, name, &dom_stack_trace_context_info) extern struct dom_stack_context_info dom_stack_trace_context_info; #else #define add_dom_stack_tracer(stack, name) /* Nada */ #endif /** The state iterators * * To safely iterate through the stack state iterators. */ /** Iterate the stack from bottom to top. */ #define foreach_dom_stack_state(stack, state, pos) \ for ((pos) = 0; (pos) < (stack)->depth; (pos)++) \ if (((state) = &(stack)->states[(pos)])) /** Iterate the stack from top to bottom. */ #define foreachback_dom_stack_state(stack, state, pos) \ for ((pos) = (stack)->depth - 1; (pos) >= 0; (pos)--) \ if (((state) = &(stack)->states[(pos)])) /* Life cycle functions. */ /** Initialise a DOM stack * * @param stack Pointer to a (preallocated) stack. * @param flags Any flags needed for controlling the behaviour of the stack. */ void init_dom_stack(struct dom_stack *stack, enum dom_stack_flag flags); /** Release a DOM stack * * Free all resources collected by the stack. * * @param stack The stack to release. */ void done_dom_stack(struct dom_stack *stack); /** Add a context to the stack * * This is needed if either you want to have the stack allocated objects for * created states and/or if you want to install callbacks for pushing or * popping. * * @param stack The stack where the context should be created. * @param data Private data to be stored in ref:[dom_stack_context.data]. * @param context_info Information about state objects and node callbacks. * * @returns A pointer to the newly created context or NULL. */ struct dom_stack_context * add_dom_stack_context(struct dom_stack *stack, void *data, struct dom_stack_context_info *context_info); /** Unregister a stack context * * This should be done especially for temporary stack contexts (without any * callbacks) so that they do not increasing the memory usage. */ void done_dom_stack_context(struct dom_stack *stack, struct dom_stack_context *context); /** Push a node onto the stack * * Makes the pushed node the new top of the stack. * * @param stack The stack to push onto. * @param node The node to push onto the stack. * * @returns If an error occurs the node is released with * #done_dom_node and NULL is returned. Else the pushed * node is returned. */ enum dom_code push_dom_node(struct dom_stack *stack, struct dom_node *node); /** Pop the top stack state * * @param stack The stack to pop from. */ void pop_dom_node(struct dom_stack *stack); /** Conditionally pop the stack states * * Searches the stack (using ref:[search_dom_stack]) for a specific node and * pops all states until that particular state is met. * * @note The popping is stopped if an immutable state is * encountered. */ void pop_dom_nodes(struct dom_stack *stack, enum dom_node_type type, struct dom_string *string); /** Pop all states until target state * * Pop all stack states until a specific state is reached. The target state * is also popped. * * @param stack The stack to pop from. * @param target The state to pop until and including. */ void pop_dom_state(struct dom_stack *stack, struct dom_stack_state *target); /** Search the stack states * * The string comparison is done against the ref:[dom_node.string] member of * the of the state nodes. * * @param stack The stack to search in. * @param type The type of node to match against. * @param string The string to match against. * * @returns A state that matched the type and string or NULL. */ struct dom_stack_state * search_dom_stack(struct dom_stack *stack, enum dom_node_type type, struct dom_string *string); /** Walk all nodes reachable from a given node * * Visits each node in the DOM tree rooted at a given node, pre-order style. * * @param stack The stack to use for walking the nodes. * @param root The root node to start from. * * It is assummed that the given stack has been initialised with * #init_dom_stack and that the caller already added one or more context * to the stack. */ void walk_dom_nodes(struct dom_stack *stack, struct dom_node *root); #ifdef __cplusplus } #endif #endif