use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
};
use html5gum::Span;
use string_cache::DefaultAtom as Atom;
#[derive(Debug)]
pub enum NodeData {
Document,
Doctype,
Text { contents: String, span: Span },
Comment,
Element {
name: Atom,
attrs: RefCell<Vec<Attribute>>,
span: Span,
},
}
#[derive(Debug, Clone)]
pub struct Attribute {
pub name: Atom,
pub value: String,
pub span: Span,
}
pub struct Node {
pub parent: Cell<Option<WeakHandle>>,
pub children: RefCell<Vec<Handle>>,
pub data: NodeData,
}
impl std::fmt::Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Node")
.field("data", &self.data)
.field("parent", &"<parent>")
.field("children", &self.children)
.finish()
}
}
impl Node {
pub fn new(data: NodeData) -> Rc<Self> {
Rc::new(Node { data, parent: Cell::new(None), children: RefCell::new(Vec::new()) })
}
}
pub type Handle = Rc<Node>;
pub type WeakHandle = Weak<Node>;
pub fn append(new_parent: &Handle, child: Handle) {
let previous_parent = child.parent.replace(Some(Rc::downgrade(new_parent)));
assert!(previous_parent.is_none());
new_parent.children.borrow_mut().push(child);
}
#[derive(Debug)]
pub struct RcDom {
pub document: Handle,
pub errors: RefCell<Vec<&'static str>>,
}
impl Default for RcDom {
fn default() -> RcDom {
RcDom { document: Node::new(NodeData::Document), errors: RefCell::default() }
}
}
pub struct RcDomEmitter {
pub dom: RcDom,
current_node: Handle,
open_elements: Vec<Handle>,
}
impl RcDomEmitter {
pub fn new() -> Self {
let dom = RcDom::default();
let document = Rc::clone(&dom.document);
Self { dom, current_node: Rc::clone(&document), open_elements: vec![document] }
}
pub fn finish(self) -> RcDom {
self.dom
}
pub fn add_element(&mut self, name: Atom, attrs: Vec<Attribute>, span: Span, self_closing: bool) {
let element = Node::new(NodeData::Element { name, attrs: RefCell::new(attrs), span });
append(&self.current_node, Rc::clone(&element));
if !self_closing {
self.open_elements.push(Rc::clone(&element));
self.current_node = element;
}
}
pub fn close_element(&mut self, name: &str) {
for i in (0..self.open_elements.len()).rev() {
if let NodeData::Element { name: ref elem_name, .. } = self.open_elements[i].data {
if elem_name == name {
self.open_elements.truncate(i);
if !self.open_elements.is_empty() {
self.current_node = Rc::clone(self.open_elements.last().unwrap());
}
break;
}
}
}
}
pub fn add_text(&self, contents: String, span: Span) {
let text_node = Node::new(NodeData::Text { contents, span });
append(&self.current_node, text_node);
}
pub fn add_comment(&self) {
let comment_node = Node::new(NodeData::Comment);
append(&self.current_node, comment_node);
}
pub fn add_doctype(&self) {
let doctype_node = Node::new(NodeData::Doctype);
append(&self.current_node, doctype_node);
}
pub fn add_parse_error(&self, error: &'static str) {
self.dom.errors.borrow_mut().push(error);
}
}