@@ -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+
916994static 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
12501332static 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 );
0 commit comments