use convert_case::{Case, Casing}; use dioxus_rsx::{ BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput, }; pub use html_parser::{Dom, Node}; use proc_macro2::{Ident, Span}; use syn::{punctuated::Punctuated, LitStr}; /// Convert an HTML DOM tree into an RSX CallBody pub fn rsx_from_html(dom: &Dom) -> CallBody { CallBody { roots: dom.children.iter().filter_map(rsx_node_from_html).collect(), } } /// Convert an HTML Node into an RSX BodyNode /// /// If the node is a comment, it will be ignored since RSX doesn't support comments pub fn rsx_node_from_html(node: &Node) -> Option { match node { Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))), Node::Element(el) => { let el_name = el.name.to_case(Case::Snake); let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site())); let mut attributes: Vec<_> = el .attributes .iter() .map(|(name, value)| { let ident = if matches!(name.as_str(), "for" | "async" | "type" | "as") { Ident::new_raw(name.as_str(), Span::call_site()) } else { let new_name = name.to_case(Case::Snake); Ident::new(new_name.as_str(), Span::call_site()) }; ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr::AttrText { value: ifmt_from_text(value.as_deref().unwrap_or("false")), name: ident, }, } }) .collect(); let class = el.classes.join(" "); if !class.is_empty() { attributes.push(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr::AttrText { name: Ident::new("class", Span::call_site()), value: ifmt_from_text(&class), }, }); } if let Some(id) = &el.id { attributes.push(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr::AttrText { name: Ident::new("id", Span::call_site()), value: ifmt_from_text(id), }, }); } let children = el.children.iter().filter_map(rsx_node_from_html).collect(); Some(BodyNode::Element(Element { name: el_name, children, attributes, key: None, brace: Default::default(), })) } // We ignore comments Node::Comment(_) => None, } } /// Pull out all the svgs from the body and replace them with components of the same name pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec) { for child in children { match child { BodyNode::Component(comp) => collect_svgs(&mut comp.children, out), BodyNode::Element(el) if el.name == "svg" => { // we want to replace this instance with a component let mut segments = Punctuated::new(); segments.push(Ident::new("icons", Span::call_site()).into()); let new_name: Ident = Ident::new(&format!("icon_{}", out.len()), Span::call_site()); segments.push(new_name.clone().into()); // Replace this instance with a component let mut new_comp = BodyNode::Component(Component { name: syn::Path { leading_colon: None, segments, }, prop_gen_args: None, fields: vec![], children: vec![], manual_props: None, brace: Default::default(), }); std::mem::swap(child, &mut new_comp); // And push the original svg into the svg list out.push(new_comp); } BodyNode::Element(el) => collect_svgs(&mut el.children, out), _ => {} } } } fn ifmt_from_text(text: &str) -> IfmtInput { IfmtInput { source: Some(LitStr::new(text, Span::call_site())), segments: vec![], } }