Skip to content

Commit d01c32a

Browse files
committed
implement selectedcontent element logic
now we pass 3 more tests from current html5lib-tests including 1 test from html5lib/html5lib-tests#182 at this point all tests from html5lib-tests are passing whatwg/html#11653 html5lib/html5lib-tests#182 html5lib/html5lib-tests#179 html5lib/html5lib-tests#180
1 parent 747f5ea commit d01c32a

2 files changed

Lines changed: 136 additions & 4 deletions

File tree

src/parser.c

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ typedef struct GumboInternalParserState {
358358
GumboNode* _head_element;
359359
GumboNode* _form_element;
360360

361+
GumboNode* _selectedcontent_target;
362+
361363
// The element used as fragment context when parsing in fragment mode
362364
GumboNode* _fragment_ctx;
363365

@@ -487,6 +489,7 @@ static void parser_state_init(GumboParser* parser) {
487489
gumbo_vector_init(parser, 5, &parser_state->_template_insertion_modes);
488490
parser_state->_head_element = NULL;
489491
parser_state->_form_element = NULL;
492+
parser_state->_selectedcontent_target = NULL;
490493
parser_state->_fragment_ctx = NULL;
491494
parser_state->_current_token = NULL;
492495
parser_state->_closed_body_tag = false;
@@ -913,6 +916,81 @@ static void record_end_of_element(
913916
: kGumboEmptyString;
914917
}
915918

919+
static GumboNode* clone_node_recursively(GumboParser* parser, const GumboNode* src) {
920+
if (!src) {
921+
return NULL;
922+
}
923+
924+
switch (src->type) {
925+
case GUMBO_NODE_TEXT:
926+
case GUMBO_NODE_CDATA:
927+
case GUMBO_NODE_WHITESPACE:
928+
case GUMBO_NODE_COMMENT: {
929+
GumboNode* t = create_node(parser, src->type);
930+
const char* s = src->v.text.text;
931+
t->v.text.text = s ? gumbo_copy_stringz(parser, s) : NULL;
932+
t->v.text.original_text.data = NULL;
933+
t->v.text.original_text.length = 0;
934+
t->v.text.start_pos = src->v.text.start_pos;
935+
return t;
936+
}
937+
938+
case GUMBO_NODE_ELEMENT:
939+
case GUMBO_NODE_TEMPLATE: {
940+
GumboNode* e = create_node(parser, src->type);
941+
GumboElement* dst = &e->v.element;
942+
const GumboElement* se = &src->v.element;
943+
944+
dst->tag = se->tag;
945+
dst->tag_namespace = se->tag_namespace;
946+
dst->original_tag = se->original_tag;
947+
dst->original_end_tag = se->original_end_tag;
948+
dst->start_pos = se->start_pos;
949+
dst->end_pos = se->end_pos;
950+
951+
gumbo_vector_init(parser, se->attributes.length ? se->attributes.length : 1, &dst->attributes);
952+
for (unsigned int i = 0; i < se->attributes.length; ++i) {
953+
const GumboAttribute* sa = se->attributes.data[i];
954+
GumboAttribute* a = gumbo_parser_allocate(parser, sizeof(GumboAttribute));
955+
memcpy(a, sa, sizeof(*a));
956+
a->name = gumbo_copy_stringz(parser, sa->name);
957+
a->value = gumbo_copy_stringz(parser, sa->value);
958+
gumbo_vector_add(parser, a, &dst->attributes);
959+
}
960+
961+
gumbo_vector_init(parser, se->children.length ? se->children.length : 1, &dst->children);
962+
for (unsigned int i = 0; i < se->children.length; ++i) {
963+
GumboNode* child_clone = clone_node_recursively(parser, se->children.data[i]);
964+
if (child_clone) {
965+
append_node(parser, e, child_clone);
966+
}
967+
}
968+
return e;
969+
}
970+
971+
default:
972+
return NULL;
973+
}
974+
}
975+
976+
static void maybe_clone_option_into_selectedcontent(GumboParser* parser, GumboParserState* state, GumboNode* option_node) {
977+
GumboNode* selectedcontent = state->_selectedcontent_target;
978+
if (!selectedcontent) {
979+
return;
980+
}
981+
if (option_node->type != GUMBO_NODE_ELEMENT && option_node->type != GUMBO_NODE_TEMPLATE) {
982+
return;
983+
}
984+
GumboVector* kids = &option_node->v.element.children;
985+
for (unsigned int i = 0; i < kids->length; ++i) {
986+
GumboNode* child = kids->data[i];
987+
GumboNode* clone = clone_node_recursively(parser, child);
988+
if (clone) {
989+
append_node(parser, selectedcontent, clone);
990+
}
991+
}
992+
}
993+
916994
static GumboNode* pop_current_node(GumboParser* parser) {
917995
GumboParserState* state = parser->_parser_state;
918996
maybe_flush_text_node_buffer(parser);
@@ -940,6 +1018,10 @@ static GumboNode* pop_current_node(GumboParser* parser) {
9401018
}
9411019
if (!is_closed_body_or_html_tag) {
9421020
record_end_of_element(state->_current_token, &current_node->v.element);
1021+
1022+
if (node_html_tag_is(current_node, GUMBO_TAG_OPTION)) {
1023+
maybe_clone_option_into_selectedcontent(parser, state, current_node);
1024+
}
9431025
}
9441026
return current_node;
9451027
}
@@ -1246,7 +1328,7 @@ GumboNode* clone_node(
12461328
// "Reconstruct active formatting elements" part of the spec.
12471329
// This implementation is based on the html5lib translation from the mess of
12481330
// GOTOs in the spec to reasonably structured programming.
1249-
// http://code.google.com/p/html5lib/source/browse/python/html5lib/treebuilders/_base.py
1331+
// https://github.com/html5lib/html5lib-python/blob/master/html5lib/treebuilders/base.py
12501332
static void reconstruct_active_formatting_elements(GumboParser* parser) {
12511333
GumboVector* elements = &parser->_parser_state->_active_formatting_elements;
12521334
// Step 1
@@ -1542,7 +1624,7 @@ static bool is_special_node(const GumboNode* node) {
15421624
TAG(MARQUEE), TAG(MENU), TAG(META), TAG(NAV), TAG(NOEMBED),
15431625
TAG(NOFRAMES), TAG(NOSCRIPT), TAG(OBJECT), TAG(OL), TAG(P),
15441626
TAG(PARAM), TAG(PLAINTEXT), TAG(PRE), TAG(SCRIPT), TAG(SECTION),
1545-
TAG(SELECT), TAG(STYLE), TAG(SUMMARY), TAG(TABLE), TAG(TBODY),
1627+
TAG(STYLE), TAG(SUMMARY), TAG(TABLE), TAG(TBODY),
15461628
TAG(TD), TAG(TEMPLATE), TAG(TEXTAREA), TAG(TFOOT), TAG(TH),
15471629
TAG(THEAD), TAG(TITLE), TAG(TR), TAG(UL), TAG(WBR), TAG(XMP),
15481630

@@ -2535,6 +2617,14 @@ static bool handle_in_body(GumboParser* parser, GumboToken* token) {
25352617
bool result = maybe_implicitly_close_p_tag(parser, token);
25362618
insert_element_from_token(parser, token);
25372619
return result;
2620+
} else if (tag_is(token, kStartTag, GUMBO_TAG_SELECTEDCONTENT)) {
2621+
GumboNode* selectedcontent = insert_element_from_token(parser, token);
2622+
state->_selectedcontent_target = selectedcontent;
2623+
return true;
2624+
} else if (tag_is(token, kEndTag, GUMBO_TAG_SELECTEDCONTENT)) {
2625+
implicitly_close_tags(parser, token, GUMBO_NAMESPACE_HTML, token->v.end_tag);
2626+
state->_selectedcontent_target = NULL;
2627+
return true;
25382628
} else if (tag_is(token, kStartTag, GUMBO_TAG_PLAINTEXT)) {
25392629
bool result = maybe_implicitly_close_p_tag(parser, token);
25402630
insert_element_from_token(parser, token);

tests/parser.cc

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,9 +1022,15 @@ TEST_F(GumboParserTest, ComplicatedSelect) {
10221022
GumboNode* select = GetChild(body, 0);
10231023
ASSERT_EQ(GUMBO_NODE_ELEMENT, select->type);
10241024
EXPECT_EQ(GUMBO_TAG_SELECT, GetTag(select));
1025-
ASSERT_EQ(1, GetChildCount(select));
1025+
ASSERT_EQ(2, GetChildCount(select));
10261026

1027-
GumboNode* optgroup = GetChild(select, 0);
1027+
GumboNode* div = GetChild(select, 0);
1028+
ASSERT_EQ(GUMBO_NODE_ELEMENT, div->type);
1029+
EXPECT_EQ(GUMBO_TAG_DIV, GetTag(div));
1030+
ASSERT_EQ(0, GetChildCount(div));
1031+
ASSERT_EQ(1, div->v.element.attributes.length);
1032+
1033+
GumboNode* optgroup = GetChild(select, 1);
10281034
ASSERT_EQ(GUMBO_NODE_ELEMENT, optgroup->type);
10291035
EXPECT_EQ(GUMBO_TAG_OPTGROUP, GetTag(optgroup));
10301036
ASSERT_EQ(1, GetChildCount(optgroup));
@@ -1123,6 +1129,42 @@ TEST_F(GumboParserTest, SelectInTable) {
11231129
ASSERT_EQ(0, GetChildCount(option));
11241130
}
11251131

1132+
TEST_F(GumboParserTest, Selectedcontent) {
1133+
Parse("<select><button><selectedcontent></button><option>hello");
1134+
1135+
GumboNode* body;
1136+
GetAndAssertBody(root_, &body);
1137+
ASSERT_EQ(1, GetChildCount(body));
1138+
1139+
GumboNode* select = GetChild(body, 0);
1140+
ASSERT_EQ(GUMBO_NODE_ELEMENT, select->type);
1141+
EXPECT_EQ(GUMBO_TAG_SELECT, GetTag(select));
1142+
ASSERT_EQ(2, GetChildCount(select));
1143+
1144+
GumboNode* button = GetChild(select, 0);
1145+
ASSERT_EQ(GUMBO_NODE_ELEMENT, button->type);
1146+
EXPECT_EQ(GUMBO_TAG_BUTTON, GetTag(button));
1147+
ASSERT_EQ(1, GetChildCount(button));
1148+
1149+
GumboNode* selectedcontent = GetChild(button, 0);
1150+
ASSERT_EQ(GUMBO_NODE_ELEMENT, selectedcontent->type);
1151+
EXPECT_EQ(GUMBO_TAG_SELECTEDCONTENT, GetTag(selectedcontent));
1152+
ASSERT_EQ(1, GetChildCount(selectedcontent));
1153+
1154+
GumboNode* text1 = GetChild(selectedcontent, 0);
1155+
ASSERT_EQ(GUMBO_NODE_TEXT, text1->type);
1156+
EXPECT_STREQ("hello", text1->v.text.text);
1157+
1158+
GumboNode* option = GetChild(select, 1);
1159+
ASSERT_EQ(GUMBO_NODE_ELEMENT, option->type);
1160+
EXPECT_EQ(GUMBO_TAG_OPTION, GetTag(option));
1161+
ASSERT_EQ(1, GetChildCount(option));
1162+
1163+
GumboNode* text = GetChild(option, 0);
1164+
ASSERT_EQ(GUMBO_NODE_TEXT, text->type);
1165+
EXPECT_STREQ("hello", text->v.text.text);
1166+
}
1167+
11261168
TEST_F(GumboParserTest, ImplicitColgroup) {
11271169
Parse("<table><col /><col /></table>");
11281170

0 commit comments

Comments
 (0)