use inflections::Inflect;
use scraper::{ElementRef, Html, node::Element};
const TEMPLATE_FILE: &str = include_str!("template.rs");
#[derive(Debug)]
pub struct SvgObject {
pub view_box: Option<String>,
pub xmlns: Option<String>,
pub width: Option<String>,
pub height: Option<String>,
pub fill: Option<String>,
pub stroke: Option<String>,
pub stroke_width: Option<String>,
pub stroke_line_cap: Option<String>,
pub stroke_line_join: Option<String>,
pub children: String,
}
pub fn parse_svg_content(svg_content: &str) -> Option<SvgObject> {
let fragment = Html::parse_fragment(svg_content);
let elements: Vec<_> = fragment
.tree
.nodes()
.filter_map(|node| {
if node.value().is_element()
&& let Some(element) = ElementRef::wrap(node)
{
let element = element.value();
if !element.attrs.is_empty() {
return Some(element);
}
}
None
})
.collect();
if elements.is_empty() {
println!("svg is empty");
return None;
}
let svg_element = &elements[0];
let child_elements = &elements[1..];
let view_box = svg_element.attr("viewBox").map(String::from);
let xmlns = svg_element.attr("xmlns").map(String::from);
let width = svg_element.attr("width").map(String::from);
let height = svg_element.attr("height").map(String::from);
let fill = svg_element.attr("fill").map(String::from);
let stroke = svg_element.attr("stroke").map(String::from);
let stroke_width = svg_element.attr("stroke-width").map(String::from);
let stroke_line_cap = svg_element.attr("stroke-linecap").map(String::from);
let stroke_line_join = svg_element.attr("stroke-linejoin").map(String::from);
let children = extract_svg_child_elements(child_elements);
Some(SvgObject {
view_box,
xmlns,
width,
height,
fill,
stroke,
stroke_width,
stroke_line_cap,
stroke_line_join,
children,
})
}
fn convert_svg_child_name(name: &str) -> Option<String> {
if name.starts_with("data-") {
return None;
}
let skipped_names = &["p-id"];
if skipped_names.contains(&name) {
return None;
}
if name == "in" {
return Some("_in".to_owned());
}
if name == "filterUnits" {
return Some(name.to_owned());
}
Some(name.to_snake_case())
}
fn extract_svg_child_elements(elements: &[&Element]) -> String {
elements
.iter()
.map(|element| {
let tag_name = element.name();
let mut element_attrs = element
.attrs()
.filter_map(|(name, value)| {
convert_svg_child_name(name).map(|name| format!(" {name}: \"{value}\","))
})
.collect::<Vec<_>>();
element_attrs.sort();
let attrs_str = element_attrs.join("\n");
"{TAG_NAME} {\n{ATTRS}\n }"
.replace("{TAG_NAME}", tag_name)
.replace("{ATTRS}", &attrs_str)
})
.collect::<Vec<_>>()
.join("\n")
}
#[must_use]
pub fn generate_svg_component(node_name: &str, title: Option<&str>, svg_obj: &SvgObject) -> String {
let title = title.map_or(String::new(), |t| {
format!(" const TITLE: Option<&'static str> = Some(\"{t}\");\n")
});
let width = svg_obj.width.as_ref().map_or(String::new(), |w| {
format!(" const WIDTH: Option<&'static str> = Some(\"{w}\");\n")
});
let height = svg_obj.height.as_ref().map_or(String::new(), |h| {
format!(" const HEIGHT: Option<&'static str> = Some(\"{h}\");\n")
});
let fill = svg_obj.fill.as_ref().map_or(String::new(), |f| {
format!(" const FILL: Option<&'static str> = Some(\"{f}\");\n")
});
let stroke = svg_obj.stroke.as_ref().map_or(String::new(), |s| {
format!(" const STROKE: Option<&'static str> = Some(\"{s}\");\n")
});
let stroke_width = svg_obj.stroke_width.as_ref().map_or(String::new(), |s| {
format!(" const STROKE_WIDTH: Option<&'static str> = Some(\"{s}\");\n")
});
let stroke_line_cap = svg_obj.stroke_line_cap.as_ref().map_or(String::new(), |s| {
format!(" const STROKE_LINE_CAP: Option<&'static str> = Some(\"{s}\");\n")
});
let stroke_line_join = svg_obj
.stroke_line_join
.as_ref()
.map_or(String::new(), |s| {
format!(" const STROKE_LINE_JOIN: Option<&'static str> = Some(\"{s}\");\n")
});
let view_box = svg_obj.view_box.as_ref().map_or(String::new(), |v| {
format!(" const VIEW_BOX: Option<&'static str> = Some(\"{v}\");\n")
});
let xmlns = svg_obj.xmlns.as_ref().map_or(String::new(), |v| {
format!(" const XMLNS: Option<&'static str> = Some(\"{v}\");\n")
});
let other_props = [
title,
width,
height,
fill,
stroke,
stroke_width,
stroke_line_cap,
stroke_line_join,
view_box,
xmlns,
]
.join("");
TEMPLATE_FILE
.replace("{ICON_NAME}", node_name)
.replace("{ICON_PATH}", &svg_obj.children)
.replace("{OTHER_PROPS}", &other_props)
}