use dioxus::prelude::*;
#[derive(Debug, Clone)]
pub enum SvgElement {
Path { d: String },
Circle { cx: String, cy: String, r: String },
Rect { x: String, y: String, width: String, height: String, rx: Option<String>, ry: Option<String> },
Ellipse { cx: String, cy: String, rx: String, ry: String },
Line { x1: String, y1: String, x2: String, y2: String },
Polyline { points: String },
Polygon { points: String },
}
impl SvgElement {
pub fn to_dioxus_element(&self) -> Element {
match self {
SvgElement::Path { d } => rsx! { path { d: "{d}" } },
SvgElement::Circle { cx, cy, r } => rsx! { circle { cx: "{cx}", cy: "{cy}", r: "{r}" } },
SvgElement::Rect { x, y, width, height, rx, ry } => match (rx, ry) {
(Some(rx), Some(ry)) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}", rx: "{rx}", ry: "{ry}" } },
(Some(rx), None) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}", rx: "{rx}" } },
(None, Some(ry)) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}", ry: "{ry}" } },
(None, None) => rsx! { rect { x: "{x}", y: "{y}", width: "{width}", height: "{height}" } },
},
SvgElement::Ellipse { cx, cy, rx, ry } => rsx! { ellipse { cx: "{cx}", cy: "{cy}", rx: "{rx}", ry: "{ry}" } },
SvgElement::Line { x1, y1, x2, y2 } => rsx! { line { x1: "{x1}", y1: "{y1}", x2: "{x2}", y2: "{y2}" } },
SvgElement::Polyline { points } => rsx! { polyline { points: "{points}" } },
SvgElement::Polygon { points } => rsx! { polygon { points: "{points}" } },
}
}
}
pub fn parse_svg_elements(svg_content: &str) -> Vec<SvgElement> {
svg_content
.lines()
.filter_map(|line| {
let line = line.trim();
if line.is_empty() {
return None;
}
if line.starts_with("<path ") {
parse_path_element(line)
} else if line.starts_with("<circle ") {
parse_circle_element(line)
} else if line.starts_with("<rect ") {
parse_rect_element(line)
} else if line.starts_with("<ellipse ") {
parse_ellipse_element(line)
} else if line.starts_with("<line ") {
parse_line_element(line)
} else if line.starts_with("<polyline ") {
parse_polyline_element(line)
} else if line.starts_with("<polygon ") {
parse_polygon_element(line)
} else {
None
}
})
.collect()
}
fn extract_attribute(line: &str, attr: &str) -> Option<String> {
if let Some(start) = line.find(&format!("{}=\"", attr)) {
let start = start + attr.len() + 2; if let Some(end) = line[start..].find('"') {
return Some(line[start..start + end].to_string());
}
}
None
}
fn parse_path_element(line: &str) -> Option<SvgElement> {
extract_attribute(line, "d").map(|d| SvgElement::Path { d })
}
fn parse_circle_element(line: &str) -> Option<SvgElement> {
let cx = extract_attribute(line, "cx")?;
let cy = extract_attribute(line, "cy")?;
let r = extract_attribute(line, "r")?;
Some(SvgElement::Circle { cx, cy, r })
}
fn parse_rect_element(line: &str) -> Option<SvgElement> {
let x = extract_attribute(line, "x")?;
let y = extract_attribute(line, "y")?;
let width = extract_attribute(line, "width")?;
let height = extract_attribute(line, "height")?;
let rx = extract_attribute(line, "rx");
let ry = extract_attribute(line, "ry");
Some(SvgElement::Rect { x, y, width, height, rx, ry })
}
fn parse_ellipse_element(line: &str) -> Option<SvgElement> {
let cx = extract_attribute(line, "cx")?;
let cy = extract_attribute(line, "cy")?;
let rx = extract_attribute(line, "rx")?;
let ry = extract_attribute(line, "ry")?;
Some(SvgElement::Ellipse { cx, cy, rx, ry })
}
fn parse_line_element(line: &str) -> Option<SvgElement> {
let x1 = extract_attribute(line, "x1")?;
let y1 = extract_attribute(line, "y1")?;
let x2 = extract_attribute(line, "x2")?;
let y2 = extract_attribute(line, "y2")?;
Some(SvgElement::Line { x1, y1, x2, y2 })
}
fn parse_polyline_element(line: &str) -> Option<SvgElement> {
let points = extract_attribute(line, "points")?;
Some(SvgElement::Polyline { points })
}
fn parse_polygon_element(line: &str) -> Option<SvgElement> {
let points = extract_attribute(line, "points")?;
Some(SvgElement::Polygon { points })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_svg_element_path_creation() {
let path = SvgElement::Path { d: "M10 10 L20 20".to_string() };
match path {
SvgElement::Path { d } => assert_eq!(d, "M10 10 L20 20"),
_ => panic!("Expected Path variant"),
}
}
#[test]
fn test_svg_element_circle_creation() {
let circle = SvgElement::Circle { cx: "10".to_string(), cy: "20".to_string(), r: "5".to_string() };
match circle {
SvgElement::Circle { cx, cy, r } => {
assert_eq!(cx, "10");
assert_eq!(cy, "20");
assert_eq!(r, "5");
}
_ => panic!("Expected Circle variant"),
}
}
#[test]
fn test_extract_attribute_success() {
let line = r#"<path d="M10 10 L20 20" fill="red" />"#;
assert_eq!(extract_attribute(line, "d"), Some("M10 10 L20 20".to_string()));
assert_eq!(extract_attribute(line, "fill"), Some("red".to_string()));
}
#[test]
fn test_extract_attribute_not_found() {
let line = r#"<path d="M10 10 L20 20" />"#;
assert_eq!(extract_attribute(line, "fill"), None);
assert_eq!(extract_attribute(line, "stroke"), None);
}
#[test]
fn test_extract_attribute_empty_value() {
let line = r#"<path d="" />"#;
assert_eq!(extract_attribute(line, "d"), Some("".to_string()));
}
#[test]
fn test_parse_path_element_success() {
let line = r#"<path d="M10 10 L20 20" />"#;
let result = parse_path_element(line);
assert!(result.is_some());
if let Some(SvgElement::Path { d }) = result {
assert_eq!(d, "M10 10 L20 20");
} else {
panic!("Expected Path element");
}
}
#[test]
fn test_parse_path_element_missing_d() {
let line = r#"<path fill="red" />"#;
let result = parse_path_element(line);
assert!(result.is_none());
}
#[test]
fn test_parse_circle_element_success() {
let line = r#"<circle cx="10" cy="20" r="5" />"#;
let result = parse_circle_element(line);
assert!(result.is_some());
if let Some(SvgElement::Circle { cx, cy, r }) = result {
assert_eq!(cx, "10");
assert_eq!(cy, "20");
assert_eq!(r, "5");
} else {
panic!("Expected Circle element");
}
}
#[test]
fn test_parse_circle_element_missing_attributes() {
let line = r#"<circle cx="10" cy="20" />"#; let result = parse_circle_element(line);
assert!(result.is_none());
}
#[test]
fn test_parse_rect_element_success() {
let line = r#"<rect x="10" y="20" width="30" height="40" />"#;
let result = parse_rect_element(line);
assert!(result.is_some());
if let Some(SvgElement::Rect { x, y, width, height, rx, ry }) = result {
assert_eq!(x, "10");
assert_eq!(y, "20");
assert_eq!(width, "30");
assert_eq!(height, "40");
assert_eq!(rx, None);
assert_eq!(ry, None);
} else {
panic!("Expected Rect element");
}
}
#[test]
fn test_parse_rect_element_with_rx_ry() {
let line = r#"<rect x="10" y="20" width="30" height="40" rx="5" ry="3" />"#;
let result = parse_rect_element(line);
assert!(result.is_some());
if let Some(SvgElement::Rect { x, y, width, height, rx, ry }) = result {
assert_eq!(x, "10");
assert_eq!(y, "20");
assert_eq!(width, "30");
assert_eq!(height, "40");
assert_eq!(rx, Some("5".to_string()));
assert_eq!(ry, Some("3".to_string()));
} else {
panic!("Expected Rect element");
}
}
#[test]
fn test_parse_svg_elements_multiple() {
let svg_content = r#"<path d="M10 10 L20 20" />
<circle cx="10" cy="20" r="5" />
<rect x="0" y="0" width="100" height="50" />"#;
let elements = parse_svg_elements(svg_content);
assert_eq!(elements.len(), 3);
match &elements[0] {
SvgElement::Path { d } => assert_eq!(d, "M10 10 L20 20"),
_ => panic!("Expected first element to be Path"),
}
match &elements[1] {
SvgElement::Circle { cx, cy, r } => {
assert_eq!(cx, "10");
assert_eq!(cy, "20");
assert_eq!(r, "5");
}
_ => panic!("Expected second element to be Circle"),
}
match &elements[2] {
SvgElement::Rect { x, y, width, height, .. } => {
assert_eq!(x, "0");
assert_eq!(y, "0");
assert_eq!(width, "100");
assert_eq!(height, "50");
}
_ => panic!("Expected third element to be Rect"),
}
}
#[test]
fn test_parse_svg_elements_empty_lines() {
let svg_content = r#"
<path d="M10 10 L20 20" />
<circle cx="10" cy="20" r="5" />
"#;
let elements = parse_svg_elements(svg_content);
assert_eq!(elements.len(), 2);
}
#[test]
fn test_parse_svg_elements_unsupported_element() {
let svg_content = r#"<path d="M10 10 L20 20" />
<unsupported attr="value" />
<circle cx="10" cy="20" r="5" />"#;
let elements = parse_svg_elements(svg_content);
assert_eq!(elements.len(), 2); }
}