/** * Pop elements until an element of type "element" has been popped. * * \return HUBBUB_OK on success, appropriate error otherwise. */ hubbub_error element_stack_pop_until(hubbub_treebuilder *treebuilder, element_type type) { element_type otype = UNKNOWN; void *node; hubbub_ns ns; while (otype != type) { element_stack_pop(treebuilder, &ns, &otype, &node); assert((signed) treebuilder->context.current_node >= 0); } return HUBBUB_OK; }
/** * Clear the stack back to a table context: "the UA must, while the current * node is not a table element or an html element, pop elements from the stack * of open elements." */ static inline void clear_stack_table_context(hubbub_treebuilder *treebuilder) { hubbub_ns ns; element_type type = current_node(treebuilder); void *node; while (type != TABLE && type != HTML) { element_stack_pop(treebuilder, &ns, &type, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); type = current_node(treebuilder); } }
/** * Handle </tr> and anything that acts "as if" </tr> was emitted. * * \param treebuilder The treebuilder instance * \return True to reprocess the token, false otherwise */ static hubbub_error act_as_if_end_tag_tr(hubbub_treebuilder *treebuilder) { hubbub_ns ns; element_type otype; void *node; /** \todo fragment case */ table_clear_stack(treebuilder); element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->tree_handler->unref_node(treebuilder->tree_handler->ctx, node); treebuilder->context.mode = IN_TABLE_BODY; return HUBBUB_REPROCESS; }
/** * Clear the stack back to a table row context. * * \param treebuilder The treebuilder instance */ static void table_clear_stack(hubbub_treebuilder *treebuilder) { element_type cur_node = current_node(treebuilder); while (cur_node != TR && cur_node != HTML) { hubbub_ns ns; element_type type; void *node; element_stack_pop(treebuilder, &ns, &type, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); cur_node = current_node(treebuilder); } return; }
/** * Close implied end tags * * \param treebuilder The treebuilder instance * \param except Tag type to exclude from processing [DD,DT,LI,OPTION, * OPTGROUP,P,RP,RT], UNKNOWN to exclude nothing */ void close_implied_end_tags(hubbub_treebuilder *treebuilder, element_type except) { element_type type; type = treebuilder->context.element_stack[ treebuilder->context.current_node].type; while (type == DD || type == DT || type == LI || type == OPTION || type == OPTGROUP || type == P || type == RP || type == RT) { hubbub_ns ns; element_type otype; void *node; if (except != UNKNOWN && type == except) break; element_stack_pop(treebuilder, &ns, &otype, &node); type = treebuilder->context.element_stack[ treebuilder->context.current_node].type; } }
/** * Handle tokens in "generic rcdata" insertion mode * * \param treebuilder The treebuilder instance * \param token The token to process * \return True to reprocess the token, false otherwise */ hubbub_error handle_generic_rcdata(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; bool done = false; if (treebuilder->context.strip_leading_lr && token->type != HUBBUB_TOKEN_CHARACTER) { /* Reset the LR stripping flag */ treebuilder->context.strip_leading_lr = false; } switch (token->type) { case HUBBUB_TOKEN_CHARACTER: { hubbub_string chars = token->data.character; if (treebuilder->context.strip_leading_lr) { if (chars.ptr[0] == '\n') { chars.ptr++; chars.len--; } treebuilder->context.strip_leading_lr = false; } if (chars.len == 0) break; err = append_text(treebuilder, &chars); } break; case HUBBUB_TOKEN_END_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type != treebuilder->context.collect.type) { /** \todo parse error */ } if ((treebuilder->context.enable_scripting == true) && (type == SCRIPT)) { err = complete_script(treebuilder); } done = true; } break; case HUBBUB_TOKEN_EOF: /** \todo if the current node's a script, * mark it as already executed */ /** \todo parse error */ done = true; err = HUBBUB_REPROCESS; break; case HUBBUB_TOKEN_COMMENT: case HUBBUB_TOKEN_DOCTYPE: case HUBBUB_TOKEN_START_TAG: /* Should never happen */ assert(0); break; } if (done) { hubbub_ns ns; element_type otype; void *node; /* Pop the current node from the stack */ element_stack_pop(treebuilder, &ns, &otype, &node); /* Return to previous insertion mode */ treebuilder->context.mode = treebuilder->context.collect.mode; } return err; }
/** * Handle token in "in head" insertion mode * * \param treebuilder The treebuilder instance * \param token The token to handle * \return True to reprocess token, false otherwise */ hubbub_error handle_in_head(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; bool handled = false; switch (token->type) { case HUBBUB_TOKEN_CHARACTER: err = process_characters_expect_whitespace(treebuilder, token, true); break; case HUBBUB_TOKEN_COMMENT: err = process_comment_append(treebuilder, token, treebuilder->context.element_stack[ treebuilder->context.current_node].node); break; case HUBBUB_TOKEN_DOCTYPE: /** \todo parse error */ break; case HUBBUB_TOKEN_START_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == HTML) { /* Process as if "in body" */ err = handle_in_body(treebuilder, token); } else if (type == BASE || type == COMMAND || type == LINK) { err = insert_element(treebuilder, &token->data.tag, false); /** \todo ack sc flag */ } else if (type == META) { err = process_meta_in_head(treebuilder, token); } else if (type == TITLE) { err = parse_generic_rcdata(treebuilder, token, true); } else if (type == NOFRAMES || type == STYLE) { err = parse_generic_rcdata(treebuilder, token, false); } else if (type == NOSCRIPT) { if (treebuilder->context.enable_scripting) { err = parse_generic_rcdata(treebuilder, token, false); } else { err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; treebuilder->context.mode = IN_HEAD_NOSCRIPT; } } else if (type == SCRIPT) { /** \todo need to ensure that the client callback * sets the parser-inserted/already-executed script * flags. */ err = parse_generic_rcdata(treebuilder, token, false); } else if (type == HEAD) { /** \todo parse error */ } else { err = HUBBUB_REPROCESS; } } break; case HUBBUB_TOKEN_END_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == HEAD) { handled = true; } else if (type == HTML || type == BODY || type == BR) { err = HUBBUB_REPROCESS; } /** \todo parse error */ } break; case HUBBUB_TOKEN_EOF: err = HUBBUB_REPROCESS; break; } if (handled || err == HUBBUB_REPROCESS) { hubbub_ns ns; element_type otype; void *node; element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->context.mode = AFTER_HEAD; } return err; }
/** * Handle tokens in "in head noscript" insertion mode * * \param treebuilder The treebuilder instance * \param token The token to process * \return True to reprocess the token, false otherwise */ hubbub_error handle_in_head_noscript(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; bool handled = false; switch (token->type) { case HUBBUB_TOKEN_CHARACTER: err = process_characters_expect_whitespace(treebuilder, token, true); break; case HUBBUB_TOKEN_COMMENT: err = handle_in_head(treebuilder, token); break; case HUBBUB_TOKEN_DOCTYPE: /** \todo parse error */ break; case HUBBUB_TOKEN_START_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == HTML) { /* Process as "in body" */ err = handle_in_body(treebuilder, token); } else if (type == NOSCRIPT) { handled = true; } else if (type == LINK || type == META || type == NOFRAMES || type == STYLE) { /* Process as "in head" */ err = handle_in_head(treebuilder, token); } else if (type == HEAD || type == NOSCRIPT) { /** \todo parse error */ } else { /** \todo parse error */ err = HUBBUB_REPROCESS; } } break; case HUBBUB_TOKEN_END_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == NOSCRIPT) { handled = true; } else if (type == BR) { /** \todo parse error */ err = HUBBUB_REPROCESS; } else { /** \todo parse error */ } } break; case HUBBUB_TOKEN_EOF: /** \todo parse error */ err = HUBBUB_REPROCESS; break; } if (handled || err == HUBBUB_REPROCESS) { hubbub_ns ns; element_type otype; void *node; element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->tree_handler->unref_node( treebuilder->tree_handler->ctx, node); treebuilder->context.mode = IN_HEAD; } return err; }
/** * Handle tokens in "in caption" insertion mode * * \param treebuilder The treebuilder instance * \param token The token to process * \return True to reprocess the token, false otherwise */ hubbub_error handle_in_caption(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; bool handled = false; switch (token->type) { case HUBBUB_TOKEN_START_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == CAPTION || type == COL || type == COLGROUP || type == TBODY || type == TD || type == TFOOT || type == TH || type == THEAD || type == TR) { /** \todo parse error */ err = HUBBUB_REPROCESS; } else { /* Process as if "in body" */ err = handle_in_body(treebuilder, token); } } break; case HUBBUB_TOKEN_END_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == CAPTION) { handled = true; } else if (type == TABLE) { /** \todo parse error if type == TABLE */ err = HUBBUB_REPROCESS; } else if (type == BODY || type == COL || type == COLGROUP || type == HTML || type == TBODY || type == TD || type == TFOOT || type == TH || type == THEAD || type == TR) { /** \todo parse error */ } else { /* Process as if "in body" */ err = handle_in_body(treebuilder, token); } } break; case HUBBUB_TOKEN_CHARACTER: case HUBBUB_TOKEN_COMMENT: case HUBBUB_TOKEN_DOCTYPE: case HUBBUB_TOKEN_EOF: /* Process as if "in body" */ err = handle_in_body(treebuilder, token); break; } if (handled || err == HUBBUB_REPROCESS) { hubbub_ns ns; element_type otype = UNKNOWN; void *node; /** \todo fragment case */ close_implied_end_tags(treebuilder, UNKNOWN); while (otype != CAPTION) { /** \todo parse error */ element_stack_pop(treebuilder, &ns, &otype, &node); } clear_active_formatting_list_to_marker(treebuilder); treebuilder->context.mode = IN_TABLE; } return err; }
/** * Reconstruct the list of active formatting elements * * \param treebuilder Treebuilder instance containing list * \return HUBBUB_OK on success, appropriate error otherwise. */ hubbub_error reconstruct_active_formatting_list(hubbub_treebuilder *treebuilder) { hubbub_error error = HUBBUB_OK; formatting_list_entry *entry, *initial_entry; uint32_t sp = treebuilder->context.current_node; if (treebuilder->context.formatting_list == NULL) return HUBBUB_OK; entry = treebuilder->context.formatting_list_end; /* Assumption: HTML and TABLE elements are not inserted into the list */ if (is_scoping_element(entry->details.type) || entry->stack_index != 0) return HUBBUB_OK; while (entry->prev != NULL) { entry = entry->prev; if (is_scoping_element(entry->details.type) || entry->stack_index != 0) { entry = entry->next; break; } } /* Save initial entry for later */ initial_entry = entry; /* Process formatting list entries, cloning nodes and * inserting them into the DOM and element stack */ while (entry != NULL) { void *clone, *appended; bool foster; element_type type = current_node(treebuilder); error = treebuilder->tree_handler->clone_node( treebuilder->tree_handler->ctx, entry->details.node, false, &clone); if (error != HUBBUB_OK) goto cleanup; foster = treebuilder->context.in_table_foster && (type == TABLE || type == TBODY || type == TFOOT || type == THEAD || type == TR); if (foster) { error = aa_insert_into_foster_parent(treebuilder, clone, &appended); } else { error = treebuilder->tree_handler->append_child( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node, clone, &appended); } if (error != HUBBUB_OK) goto cleanup; error = element_stack_push(treebuilder, entry->details.ns, entry->details.type, appended); if (error != HUBBUB_OK) { remove_node_from_dom(treebuilder, appended); goto cleanup; } entry = entry->next; } /* Now, replace the formatting list entries */ for (entry = initial_entry; entry != NULL; entry = entry->next) { void *node; hubbub_ns prev_ns; element_type prev_type; void *prev_node; uint32_t prev_stack_index; node = treebuilder->context.element_stack[++sp].node; error = formatting_list_replace(treebuilder, entry, entry->details.ns, entry->details.type, node, sp, &prev_ns, &prev_type, &prev_node, &prev_stack_index); /* Cannot fail. Ensure this. */ assert(error == HUBBUB_OK); } return HUBBUB_OK; cleanup: /* An error occurred while cloning nodes and inserting them. * We must restore the state on entry here. */ while (treebuilder->context.current_node > sp) { hubbub_ns ns; element_type type; void *node; element_stack_pop(treebuilder, &ns, &type, &node); remove_node_from_dom(treebuilder, node); } return error; }
/** * Handle tokens in "in row" insertion mode * * \param treebuilder The treebuilder instance * \param token The token to process * \return True to reprocess the token, false otherwise */ hubbub_error handle_in_row(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; switch (token->type) { case HUBBUB_TOKEN_START_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == TH || type == TD) { table_clear_stack(treebuilder); err = insert_element(treebuilder, &token->data.tag, true); if (err != HUBBUB_OK) return err; treebuilder->context.mode = IN_CELL; /* ref node for formatting list */ treebuilder->tree_handler->ref_node( treebuilder->tree_handler->ctx, treebuilder->context.element_stack[ treebuilder->context.current_node].node); err = formatting_list_append(treebuilder, token->data.tag.ns, type, treebuilder->context.element_stack[ treebuilder->context.current_node].node, treebuilder->context.current_node); if (err != HUBBUB_OK) { hubbub_ns ns; element_type type; void *node; /* Revert changes */ remove_node_from_dom(treebuilder, treebuilder->context.element_stack[ treebuilder->context.current_node].node); element_stack_pop(treebuilder, &ns, &type, &node); return err; } } else if (type == CAPTION || type == COL || type == COLGROUP || type == TBODY || type == TFOOT || type == THEAD || type == TR) { err = act_as_if_end_tag_tr(treebuilder); } else { err = handle_in_table(treebuilder, token); } } break; case HUBBUB_TOKEN_END_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == TR) { /* We're done with this token, but act_as_if_end_tag_tr * will return HUBBUB_REPROCESS. Therefore, ignore the * return value. */ (void) act_as_if_end_tag_tr(treebuilder); } else if (type == TABLE) { err = act_as_if_end_tag_tr(treebuilder); } else if (type == BODY || type == CAPTION || type == COL || type == COLGROUP || type == HTML || type == TD || type == TH) { /** \todo parse error */ /* Ignore the token */ } else { err = handle_in_table(treebuilder, token); } } break; case HUBBUB_TOKEN_CHARACTER: case HUBBUB_TOKEN_COMMENT: case HUBBUB_TOKEN_DOCTYPE: case HUBBUB_TOKEN_EOF: err = handle_in_table(treebuilder, token); break; } return err; }
/** * Handle tokens in "in column group" insertion mode * * \param treebuilder The treebuilder instance * \param token The token to process * \return True to reprocess the token, false otherwise */ hubbub_error handle_in_column_group(hubbub_treebuilder *treebuilder, const hubbub_token *token) { hubbub_error err = HUBBUB_OK; bool handled = false; switch (token->type) { case HUBBUB_TOKEN_CHARACTER: err = process_characters_expect_whitespace(treebuilder, token, true); break; case HUBBUB_TOKEN_COMMENT: err = process_comment_append(treebuilder, token, treebuilder->context.element_stack[ treebuilder->context.current_node].node); break; case HUBBUB_TOKEN_DOCTYPE: /** \todo parse error */ break; case HUBBUB_TOKEN_START_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == HTML) { /* Process as if "in body" */ err = handle_in_body(treebuilder, token); } else if (type == COL) { err = insert_element(treebuilder, &token->data.tag, false); /** \todo ack sc flag */ } else { err = HUBBUB_REPROCESS; } } break; case HUBBUB_TOKEN_END_TAG: { element_type type = element_type_from_name(treebuilder, &token->data.tag.name); if (type == COLGROUP) { /** \todo fragment case */ handled = true; } else if (type == COL) { /** \todo parse error */ } else { err = HUBBUB_REPROCESS; } } break; case HUBBUB_TOKEN_EOF: /** \todo fragment case */ err = HUBBUB_REPROCESS; break; } if (handled || err == HUBBUB_REPROCESS) { hubbub_ns ns; element_type otype; void *node; /* Pop the current node (which will be a colgroup) */ element_stack_pop(treebuilder, &ns, &otype, &node); treebuilder->context.mode = IN_TABLE; } return err; }