diff --git a/html5ever/examples/arena.rs b/html5ever/examples/arena.rs index 36854caf..08ec7328 100644 --- a/html5ever/examples/arena.rs +++ b/html5ever/examples/arena.rs @@ -55,6 +55,7 @@ pub struct Node<'arena> { } /// HTML node data which can be an element, a comment, a string, a DOCTYPE, etc... +#[derive(Clone)] pub enum NodeData<'arena> { Document, Doctype { @@ -338,6 +339,21 @@ impl<'arena> TreeSink for Sink<'arena> { new_parent.append(child) } } + + fn clone_subtree(&self, node: &Self::Handle) -> Self::Handle { + // Allocate the new node in the arena using Clone + let cloned_node = self.arena.alloc(Node::new(node.data.clone())); + + // Clone all children and append them + let mut child = node.first_child.get(); + while let Some(current_child) = child { + let cloned_child = self.clone_subtree(¤t_child); + cloned_node.append(cloned_child); + child = current_child.next_sibling.get(); + } + + cloned_node + } } /// In this example an "arena" is created and filled with the DOM nodes. @@ -352,5 +368,33 @@ fn main() { io::stdin().read_to_end(&mut bytes).unwrap(); let arena = typed_arena::Arena::new(); - html5ever_parse_slice_into_arena(&bytes, &arena); + let dom = html5ever_parse_slice_into_arena(&bytes, &arena); + + // Print the DOM structure + print_node(dom, 0); +} + +fn print_node<'arena>(node: &Node<'arena>, depth: usize) { + let indent = " ".repeat(depth); + + match &node.data { + NodeData::Document => println!("{}Document", indent), + NodeData::Doctype { name, .. } => println!("{}", indent, name), + NodeData::Text { contents } => { + let text = contents.borrow(); + if !text.trim().is_empty() { + println!("{}\"{}\"", indent, text.trim()); + } + }, + NodeData::Comment { contents } => println!("{}", indent, contents), + NodeData::Element { name, .. } => println!("{}<{}>", indent, name.local), + NodeData::ProcessingInstruction { target, .. } => println!("{}", indent, target), + } + + // Print all children + let mut child = node.first_child.get(); + while let Some(current_child) = child { + print_node(current_child, depth + 1); + child = current_child.next_sibling.get(); + } } diff --git a/html5ever/examples/noop-tree-builder.rs b/html5ever/examples/noop-tree-builder.rs index f0673462..fc04e2b3 100644 --- a/html5ever/examples/noop-tree-builder.rs +++ b/html5ever/examples/noop-tree-builder.rs @@ -113,6 +113,11 @@ impl TreeSink for Sink { fn remove_from_parent(&self, _target: &usize) {} fn reparent_children(&self, _node: &usize, _new_parent: &usize) {} fn mark_script_already_started(&self, _node: &usize) {} + + fn clone_subtree(&self, _node: &Self::Handle) -> Self::Handle { + // For this noop example, just return a new placeholder ID + self.get_id() + } } /// In this example we implement the TreeSink trait which takes each parsed elements and insert diff --git a/html5ever/examples/print-tree-actions.rs b/html5ever/examples/print-tree-actions.rs index dfa0aedd..3da79ea9 100644 --- a/html5ever/examples/print-tree-actions.rs +++ b/html5ever/examples/print-tree-actions.rs @@ -167,6 +167,12 @@ impl TreeSink for Sink { fn pop(&self, elem: &usize) { println!("Popped element {elem}"); } + + fn clone_subtree(&self, node: &Self::Handle) -> Self::Handle { + println!("Clone subtree for node {node}"); + // For this example, just return a new placeholder ID + self.get_id() + } } /// Same example as the "noop-tree-builder", but this time every function implemented in our diff --git a/html5ever/src/tree_builder/mod.rs b/html5ever/src/tree_builder/mod.rs index f0d89b92..6a29478d 100644 --- a/html5ever/src/tree_builder/mod.rs +++ b/html5ever/src/tree_builder/mod.rs @@ -118,6 +118,9 @@ pub struct TreeBuilder { /// Form element pointer. form_elem: RefCell>, + + /// selectedcontent element pointer. + selectedcontent_elem: RefCell>, //§ END /// Frameset-ok flag. frameset_ok: Cell, @@ -163,6 +166,7 @@ where active_formatting: Default::default(), head_elem: Default::default(), form_elem: Default::default(), + selectedcontent_elem: Default::default(), frameset_ok: Cell::new(true), ignore_lf: Default::default(), foster_parenting: Default::default(), @@ -203,6 +207,7 @@ where active_formatting: Default::default(), head_elem: Default::default(), form_elem: RefCell::new(form_elem), + selectedcontent_elem: Default::default(), frameset_ok: Cell::new(true), ignore_lf: Default::default(), foster_parenting: Default::default(), @@ -285,6 +290,10 @@ where tracer.trace_handle(form_elem); } + if let Some(selectedcontent_elem) = self.selectedcontent_elem.borrow().as_ref() { + tracer.trace_handle(selectedcontent_elem); + } + if let Some(context_elem) = self.context_elem.borrow().as_ref() { tracer.trace_handle(context_elem); } @@ -923,6 +932,7 @@ where .borrow_mut() .pop() .expect("no current element"); + self.sink.pop(&elem); elem } @@ -1183,6 +1193,7 @@ where n } + /// Pop element until an element with the given name has been popped. fn pop_until_named(&self, name: LocalName) -> usize { self.pop_until(|p| *p.ns == ns!(html) && *p.local == name) } @@ -1269,16 +1280,6 @@ where _ => continue, }; match *name { - local_name!("select") => { - for ancestor in self.open_elems.borrow()[0..i].iter().rev() { - if self.html_elem_named(ancestor, local_name!("template")) { - return InsertionMode::InSelect; - } else if self.html_elem_named(ancestor, local_name!("table")) { - return InsertionMode::InSelectInTable; - } - } - return InsertionMode::InSelect; - }, local_name!("td") | local_name!("th") => { if !last { return InsertionMode::InCell; @@ -1401,6 +1402,12 @@ where self.insert_at(insertion_point, AppendNode(elem.clone())); + if qname.local == local_name!("selectedcontent") + && self.selectedcontent_elem.borrow().is_none() + { + *self.selectedcontent_elem.borrow_mut() = Some(elem.clone()); + } + match push { PushFlag::Push => self.push(&elem), PushFlag::NoPush => (), @@ -1585,6 +1592,19 @@ where self.remove_from_stack(&node); } + fn maybe_clone_option_into_selectedcontent(&self, option: &Handle) { + if let Some(selectedcontent) = self.selectedcontent_elem.borrow().as_ref().cloned() { + self.clone_option_into_selectedcontent(option, &selectedcontent); + } + } + + fn clone_option_into_selectedcontent(&self, option: &Handle, selectedcontent: &Handle) { + self.sink + .reparent_children(selectedcontent, &self.sink.get_document()); + let cloned_option = self.sink.clone_subtree(option); + self.sink.reparent_children(&cloned_option, selectedcontent); + } + //§ tree-construction fn is_foreign(&self, token: &Token) -> bool { if let Token::Eof = *token { diff --git a/html5ever/src/tree_builder/rules.rs b/html5ever/src/tree_builder/rules.rs index e0ddff45..fcbaf1f3 100644 --- a/html5ever/src/tree_builder/rules.rs +++ b/html5ever/src/tree_builder/rules.rs @@ -592,7 +592,7 @@ where tag!( | | | | | | | | | | | | | | | | | | | | - | | | | | | ), + | | | | | | | ), ) => { if !self.in_scope_named(default_scope, tag.name.clone()) { self.unexpected(&tag); @@ -638,6 +638,30 @@ where ProcessResult::Done }, + Token::Tag(tag @ tag!()) => { + let option_in_stack = self + .open_elems + .borrow() + .iter() + .find(|elem| self.html_elem_named(elem, local_name!("option"))) + .cloned(); + + self.process_end_tag_in_body(tag); + + if let Some(option) = option_in_stack { + if !self + .open_elems + .borrow() + .iter() + .any(|elem| self.sink.same_node(elem, &option)) + { + self.maybe_clone_option_into_selectedcontent(&option); + } + } + + ProcessResult::Done + }, + Token::Tag(tag!(

)) => { if !self.in_scope_named(button_scope, local_name!("p")) { self.sink.parse_error(Borrowed("No

tag to close")); @@ -753,18 +777,37 @@ where ) }, - Token::Tag( - tag @ tag!( |
| | | | | ), - ) => { - let keep_frameset_ok = match tag.name { - local_name!("input") => self.is_type_hidden(&tag), - _ => false, - }; + Token::Tag(tag @ tag!( |
| | | | )) => { self.reconstruct_active_formatting_elements(); self.insert_and_pop_element_for(tag); - if !keep_frameset_ok { + self.frameset_ok.set(false); + ProcessResult::DoneAckSelfClosing + }, + + Token::Tag(tag @ tag!()) => { + if self.is_fragment() + && self.html_elem_named( + self.context_elem.borrow().as_ref().unwrap(), + local_name!("select"), + ) + { + self.unexpected(&tag); + } + + if self.in_scope_named(default_scope, local_name!("select")) { + self.unexpected(&tag); + self.pop_until_named(local_name!("select")); + } + + let is_type_hidden = self.is_type_hidden(&tag); + + self.reconstruct_active_formatting_elements(); + self.insert_and_pop_element_for(tag); + + if !is_type_hidden { self.frameset_ok.set(false); } + ProcessResult::DoneAckSelfClosing }, @@ -775,6 +818,15 @@ where Token::Tag(tag @ tag!(


)) => { self.close_p_element_in_button_scope(); + if self.in_scope_named(default_scope, local_name!("select")) { + self.generate_implied_end_tags(cursory_implied_end); + if self.in_scope_named(default_scope, local_name!("option")) + || self.in_scope_named(default_scope, local_name!("optgroup")) + { + self.sink.parse_error(Borrowed("hr in option")); + } + } + self.insert_and_pop_element_for(tag); self.frameset_ok.set(false); ProcessResult::DoneAckSelfClosing @@ -813,26 +865,52 @@ where //