Skip to content

Commit 6dddad6

Browse files
committed
support multiple option elements with selectedcontent
html5lib/html5lib-tests#183
1 parent d01c32a commit 6dddad6

4 files changed

Lines changed: 125 additions & 3 deletions

File tree

src/parser.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ typedef char gumbo_tagset[GUMBO_TAG_LAST];
4747
#define TAGSET_INCLUDES(tagset, namespace, tag) \
4848
(tag < GUMBO_TAG_LAST && tagset[(int) tag] & (1 << (int) namespace))
4949

50+
typedef enum {
51+
GUMBO_SELECTEDCONTENT_EMPTY,
52+
GUMBO_SELECTEDCONTENT_AUTOMATIC, // determined by first instance of option
53+
GUMBO_SELECTEDCONTENT_FORCED, // determined by option with selected attribute
54+
} GumboSelectedcontentState;
55+
5056
// selected forward declarations as it is getting hard to find
5157
// an appropriate order
5258
static bool node_html_tag_is(const GumboNode*, GumboTag);
@@ -359,6 +365,7 @@ typedef struct GumboInternalParserState {
359365
GumboNode* _form_element;
360366

361367
GumboNode* _selectedcontent_target;
368+
GumboSelectedcontentState _selectedcontent_state;
362369

363370
// The element used as fragment context when parsing in fragment mode
364371
GumboNode* _fragment_ctx;
@@ -490,6 +497,7 @@ static void parser_state_init(GumboParser* parser) {
490497
parser_state->_head_element = NULL;
491498
parser_state->_form_element = NULL;
492499
parser_state->_selectedcontent_target = NULL;
500+
parser_state->_selectedcontent_state = GUMBO_SELECTEDCONTENT_EMPTY;
493501
parser_state->_fragment_ctx = NULL;
494502
parser_state->_current_token = NULL;
495503
parser_state->_closed_body_tag = false;
@@ -981,6 +989,20 @@ static void maybe_clone_option_into_selectedcontent(GumboParser* parser, GumboPa
981989
if (option_node->type != GUMBO_NODE_ELEMENT && option_node->type != GUMBO_NODE_TEMPLATE) {
982990
return;
983991
}
992+
if (state->_selectedcontent_state == GUMBO_SELECTEDCONTENT_FORCED) {
993+
return;
994+
}
995+
bool is_selected_option = !!gumbo_get_attribute(&option_node->v.element.attributes, "selected");
996+
if (state->_selectedcontent_state == GUMBO_SELECTEDCONTENT_AUTOMATIC) {
997+
if (!is_selected_option) {
998+
return;
999+
}
1000+
GumboVector* oldies = &selectedcontent->v.element.children;
1001+
for (unsigned int i = 0; i < oldies->length; ++i) {
1002+
destroy_node(parser, oldies->data[i]);
1003+
}
1004+
gumbo_vector_clear(parser, &selectedcontent->v.element.children);
1005+
}
9841006
GumboVector* kids = &option_node->v.element.children;
9851007
for (unsigned int i = 0; i < kids->length; ++i) {
9861008
GumboNode* child = kids->data[i];
@@ -989,6 +1011,7 @@ static void maybe_clone_option_into_selectedcontent(GumboParser* parser, GumboPa
9891011
append_node(parser, selectedcontent, clone);
9901012
}
9911013
}
1014+
state->_selectedcontent_state = is_selected_option ? GUMBO_SELECTEDCONTENT_FORCED : GUMBO_SELECTEDCONTENT_AUTOMATIC;
9921015
}
9931016

9941017
static GumboNode* pop_current_node(GumboParser* parser) {
@@ -2620,6 +2643,7 @@ static bool handle_in_body(GumboParser* parser, GumboToken* token) {
26202643
} else if (tag_is(token, kStartTag, GUMBO_TAG_SELECTEDCONTENT)) {
26212644
GumboNode* selectedcontent = insert_element_from_token(parser, token);
26222645
state->_selectedcontent_target = selectedcontent;
2646+
state->_selectedcontent_state = GUMBO_SELECTEDCONTENT_EMPTY;
26232647
return true;
26242648
} else if (tag_is(token, kEndTag, GUMBO_TAG_SELECTEDCONTENT)) {
26252649
implicitly_close_tags(parser, token, GUMBO_NAMESPACE_HTML, token->v.end_tag);

src/vector.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ void gumbo_vector_init(struct GumboInternalParser* parser,
3939
}
4040
}
4141

42+
void gumbo_vector_clear(
43+
struct GumboInternalParser* parser, GumboVector* vector) {
44+
vector->length = 0;
45+
}
46+
4247
void gumbo_vector_destroy(
4348
struct GumboInternalParser* parser, GumboVector* vector) {
4449
if (vector->capacity > 0) {

src/vector.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ struct GumboInternalParser;
3131
void gumbo_vector_init(struct GumboInternalParser* parser,
3232
size_t initial_capacity, GumboVector* vector);
3333

34+
void gumbo_vector_clear(
35+
struct GumboInternalParser* parser, GumboVector* vector);
36+
3437
// Frees the memory used by an GumboVector. Does not free the contained
3538
// pointers.
3639
void gumbo_vector_destroy(

tests/parser.cc

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,9 +1151,9 @@ TEST_F(GumboParserTest, Selectedcontent) {
11511151
EXPECT_EQ(GUMBO_TAG_SELECTEDCONTENT, GetTag(selectedcontent));
11521152
ASSERT_EQ(1, GetChildCount(selectedcontent));
11531153

1154-
GumboNode* text1 = GetChild(selectedcontent, 0);
1155-
ASSERT_EQ(GUMBO_NODE_TEXT, text1->type);
1156-
EXPECT_STREQ("hello", text1->v.text.text);
1154+
GumboNode* selectedtext = GetChild(selectedcontent, 0);
1155+
ASSERT_EQ(GUMBO_NODE_TEXT, selectedtext->type);
1156+
EXPECT_STREQ("hello", selectedtext->v.text.text);
11571157

11581158
GumboNode* option = GetChild(select, 1);
11591159
ASSERT_EQ(GUMBO_NODE_ELEMENT, option->type);
@@ -1165,6 +1165,96 @@ TEST_F(GumboParserTest, Selectedcontent) {
11651165
EXPECT_STREQ("hello", text->v.text.text);
11661166
}
11671167

1168+
TEST_F(GumboParserTest, SelectedcontentTwoOptions) {
1169+
Parse("<select><button><selectedcontent></button><option>hello<option>world");
1170+
1171+
GumboNode* body;
1172+
GetAndAssertBody(root_, &body);
1173+
ASSERT_EQ(1, GetChildCount(body));
1174+
1175+
GumboNode* select = GetChild(body, 0);
1176+
ASSERT_EQ(GUMBO_NODE_ELEMENT, select->type);
1177+
EXPECT_EQ(GUMBO_TAG_SELECT, GetTag(select));
1178+
ASSERT_EQ(3, GetChildCount(select));
1179+
1180+
GumboNode* button = GetChild(select, 0);
1181+
ASSERT_EQ(GUMBO_NODE_ELEMENT, button->type);
1182+
EXPECT_EQ(GUMBO_TAG_BUTTON, GetTag(button));
1183+
ASSERT_EQ(1, GetChildCount(button));
1184+
1185+
GumboNode* selectedcontent = GetChild(button, 0);
1186+
ASSERT_EQ(GUMBO_NODE_ELEMENT, selectedcontent->type);
1187+
EXPECT_EQ(GUMBO_TAG_SELECTEDCONTENT, GetTag(selectedcontent));
1188+
ASSERT_EQ(1, GetChildCount(selectedcontent));
1189+
1190+
GumboNode* selectedtext = GetChild(selectedcontent, 0);
1191+
ASSERT_EQ(GUMBO_NODE_TEXT, selectedtext->type);
1192+
EXPECT_STREQ("hello", selectedtext->v.text.text);
1193+
1194+
GumboNode* option1 = GetChild(select, 1);
1195+
ASSERT_EQ(GUMBO_NODE_ELEMENT, option1->type);
1196+
EXPECT_EQ(GUMBO_TAG_OPTION, GetTag(option1));
1197+
ASSERT_EQ(1, GetChildCount(option1));
1198+
1199+
GumboNode* text1 = GetChild(option1, 0);
1200+
ASSERT_EQ(GUMBO_NODE_TEXT, text1->type);
1201+
EXPECT_STREQ("hello", text1->v.text.text);
1202+
1203+
GumboNode* option2 = GetChild(select, 2);
1204+
ASSERT_EQ(GUMBO_NODE_ELEMENT, option2->type);
1205+
EXPECT_EQ(GUMBO_TAG_OPTION, GetTag(option2));
1206+
ASSERT_EQ(1, GetChildCount(option2));
1207+
1208+
GumboNode* text2 = GetChild(option2, 0);
1209+
ASSERT_EQ(GUMBO_NODE_TEXT, text2->type);
1210+
EXPECT_STREQ("world", text2->v.text.text);
1211+
}
1212+
1213+
TEST_F(GumboParserTest, SelectedcontentSelectedOption) {
1214+
Parse("<select><button><selectedcontent></button><option>hello<option selected>world");
1215+
1216+
GumboNode* body;
1217+
GetAndAssertBody(root_, &body);
1218+
ASSERT_EQ(1, GetChildCount(body));
1219+
1220+
GumboNode* select = GetChild(body, 0);
1221+
ASSERT_EQ(GUMBO_NODE_ELEMENT, select->type);
1222+
EXPECT_EQ(GUMBO_TAG_SELECT, GetTag(select));
1223+
ASSERT_EQ(3, GetChildCount(select));
1224+
1225+
GumboNode* button = GetChild(select, 0);
1226+
ASSERT_EQ(GUMBO_NODE_ELEMENT, button->type);
1227+
EXPECT_EQ(GUMBO_TAG_BUTTON, GetTag(button));
1228+
ASSERT_EQ(1, GetChildCount(button));
1229+
1230+
GumboNode* selectedcontent = GetChild(button, 0);
1231+
ASSERT_EQ(GUMBO_NODE_ELEMENT, selectedcontent->type);
1232+
EXPECT_EQ(GUMBO_TAG_SELECTEDCONTENT, GetTag(selectedcontent));
1233+
ASSERT_EQ(1, GetChildCount(selectedcontent));
1234+
1235+
GumboNode* selectedtext = GetChild(selectedcontent, 0);
1236+
ASSERT_EQ(GUMBO_NODE_TEXT, selectedtext->type);
1237+
EXPECT_STREQ("world", selectedtext->v.text.text);
1238+
1239+
GumboNode* option1 = GetChild(select, 1);
1240+
ASSERT_EQ(GUMBO_NODE_ELEMENT, option1->type);
1241+
EXPECT_EQ(GUMBO_TAG_OPTION, GetTag(option1));
1242+
ASSERT_EQ(1, GetChildCount(option1));
1243+
1244+
GumboNode* text1 = GetChild(option1, 0);
1245+
ASSERT_EQ(GUMBO_NODE_TEXT, text1->type);
1246+
EXPECT_STREQ("hello", text1->v.text.text);
1247+
1248+
GumboNode* option2 = GetChild(select, 2);
1249+
ASSERT_EQ(GUMBO_NODE_ELEMENT, option2->type);
1250+
EXPECT_EQ(GUMBO_TAG_OPTION, GetTag(option2));
1251+
ASSERT_EQ(1, GetChildCount(option2));
1252+
1253+
GumboNode* text2 = GetChild(option2, 0);
1254+
ASSERT_EQ(GUMBO_NODE_TEXT, text2->type);
1255+
EXPECT_STREQ("world", text2->v.text.text);
1256+
}
1257+
11681258
TEST_F(GumboParserTest, ImplicitColgroup) {
11691259
Parse("<table><col /><col /></table>");
11701260

0 commit comments

Comments
 (0)