1#![doc = include_str!("../README.md")]
2#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
3#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
4
5use convert_case::{Case, Casing};
6use dioxus_html::{map_html_attribute_to_rsx, map_html_element_to_rsx};
7use dioxus_rsx::{
8 AttributeType, BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed,
9 ElementName, IfmtInput,
10};
11pub use html_parser::{Dom, Node};
12use proc_macro2::{Ident, Span};
13use syn::{punctuated::Punctuated, LitStr};
14
15pub fn rsx_from_html(dom: &Dom) -> CallBody {
17 CallBody {
18 roots: dom.children.iter().filter_map(rsx_node_from_html).collect(),
19 }
20}
21
22pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
26 match node {
27 Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
28 Node::Element(el) => {
29 let el_name = if let Some(name) = map_html_element_to_rsx(&el.name) {
30 ElementName::Ident(Ident::new(name, Span::call_site()))
31 } else {
32 if el.name.contains('-') {
34 ElementName::Custom(LitStr::new(&el.name, Span::call_site()))
35 } else {
36 ElementName::Ident(Ident::new(&el.name.to_case(Case::Snake), Span::call_site()))
38 }
39 };
40
41 let mut attributes: Vec<_> = el
42 .attributes
43 .iter()
44 .map(|(name, value)| {
45 let value = ifmt_from_text(value.as_deref().unwrap_or("false"));
46 let attr = if let Some(name) = map_html_attribute_to_rsx(name) {
47 let ident = if let Some(name) = name.strip_prefix("r#") {
48 Ident::new_raw(name, Span::call_site())
49 } else {
50 Ident::new(name, Span::call_site())
51 };
52 ElementAttr {
53 value: dioxus_rsx::ElementAttrValue::AttrLiteral(value),
54 name: dioxus_rsx::ElementAttrName::BuiltIn(ident),
55 }
56 } else {
57 ElementAttr {
59 value: dioxus_rsx::ElementAttrValue::AttrLiteral(value),
60 name: dioxus_rsx::ElementAttrName::Custom(LitStr::new(
61 name,
62 Span::call_site(),
63 )),
64 }
65 };
66
67 AttributeType::Named(ElementAttrNamed {
68 el_name: el_name.clone(),
69 attr,
70 })
71 })
72 .collect();
73
74 let class = el.classes.join(" ");
75 if !class.is_empty() {
76 attributes.push(AttributeType::Named(ElementAttrNamed {
77 el_name: el_name.clone(),
78 attr: ElementAttr {
79 name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
80 "class",
81 Span::call_site(),
82 )),
83 value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)),
84 },
85 }));
86 }
87
88 if let Some(id) = &el.id {
89 attributes.push(AttributeType::Named(ElementAttrNamed {
90 el_name: el_name.clone(),
91 attr: ElementAttr {
92 name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
93 "id",
94 Span::call_site(),
95 )),
96 value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)),
97 },
98 }));
99 }
100
101 let children = el.children.iter().filter_map(rsx_node_from_html).collect();
102
103 Some(BodyNode::Element(Element {
104 name: el_name,
105 children,
106 attributes,
107 merged_attributes: Default::default(),
108 key: None,
109 brace: Default::default(),
110 }))
111 }
112
113 Node::Comment(_) => None,
115 }
116}
117
118pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec<BodyNode>) {
120 for child in children {
121 match child {
122 BodyNode::Component(comp) => collect_svgs(&mut comp.children, out),
123
124 BodyNode::Element(el) if el.name == "svg" => {
125 let mut segments = Punctuated::new();
127
128 segments.push(Ident::new("icons", Span::call_site()).into());
129
130 let new_name: Ident = Ident::new(&format!("icon_{}", out.len()), Span::call_site());
131
132 segments.push(new_name.clone().into());
133
134 let mut new_comp = BodyNode::Component(Component {
136 name: syn::Path {
137 leading_colon: None,
138 segments,
139 },
140 prop_gen_args: None,
141 fields: vec![],
142 children: vec![],
143 manual_props: None,
144 key: None,
145 brace: Default::default(),
146 location: Default::default(),
147 });
148
149 std::mem::swap(child, &mut new_comp);
150
151 out.push(new_comp);
153 }
154
155 BodyNode::Element(el) => collect_svgs(&mut el.children, out),
156
157 _ => {}
158 }
159 }
160}
161
162fn ifmt_from_text(text: &str) -> IfmtInput {
163 IfmtInput {
164 source: Some(LitStr::new(text, Span::call_site())),
165 segments: vec![],
166 }
167}