#![allow(clippy::new_without_default)]
#![allow(clippy::should_implement_trait)]
use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::Hash;
use crate::node::{Attributes, Children, Node, Value};
pub mod path;
pub mod tag;
#[derive(Clone, Debug)]
pub struct Element {
name: String,
attributes: Attributes,
children: Children,
}
impl Element {
pub fn new<T>(name: T) -> Self
where
T: Into<String>,
{
Element {
name: name.into(),
attributes: Attributes::new(),
children: Children::new(),
}
}
#[inline]
pub fn get_name(&self) -> &String {
&self.name
}
#[inline]
pub fn get_attributes(&self) -> &Attributes {
&self.attributes
}
#[inline]
pub fn get_attributes_mut(&mut self) -> &mut Attributes {
&mut self.attributes
}
#[inline]
pub fn get_children(&self) -> &Children {
&self.children
}
#[inline]
pub fn get_children_mut(&mut self) -> &mut Children {
&mut self.children
}
}
impl fmt::Display for Element {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "<{}", self.name)?;
let mut attributes = self.attributes.iter().collect::<Vec<_>>();
attributes.sort_by_key(|pair| pair.0.as_str());
for (name, value) in attributes {
match (value.contains('\''), value.contains('"')) {
(true, false) | (false, false) => {
write!(formatter, r#" {}="{}""#, name, value)?;
}
(false, true) => {
write!(formatter, r#" {}='{}'"#, name, value)?;
}
_ => {}
}
}
if self.children.is_empty() {
return write!(formatter, "/>");
}
write!(formatter, ">")?;
for child in self.children.iter() {
write!(formatter, "\n{}", child)?;
}
write!(formatter, "\n</{}>", self.name)
}
}
impl Node for Element {
#[inline]
fn append<T>(&mut self, node: T)
where
T: Into<Box<dyn Node>>,
{
self.children.push(node.into());
}
#[inline]
fn assign<T, U>(&mut self, name: T, value: U)
where
T: Into<String>,
U: Into<Value>,
{
self.attributes.insert(name.into(), value.into());
}
}
macro_rules! implement {
($(#[$doc:meta] struct $struct_name:ident)*) => ($(
#[$doc]
#[derive(Clone, Debug)]
pub struct $struct_name {
inner: Element,
}
impl $struct_name {
#[inline]
pub fn new() -> Self {
$struct_name {
inner: Element::new(tag::$struct_name),
}
}
}
impl Default for $struct_name {
fn default() -> Self {
Self::new()
}
}
impl super::NodeDefaultHash for $struct_name {
#[inline]
fn default_hash(&self, state: &mut DefaultHasher) {
self.inner.default_hash(state);
}
}
node! { $struct_name::inner }
)*);
}
impl super::NodeDefaultHash for Element {
fn default_hash(&self, state: &mut DefaultHasher) {
self.name.hash(state);
self.attributes.iter().for_each(|(key, value)| {
key.hash(state);
value.hash(state)
});
self.children
.iter()
.for_each(|child| child.default_hash(state));
}
}
implement! {
#[doc = "An [`animate`](https://www.w3.org/TR/SVG/animate.html#AnimateElement) element."]
struct Animate
#[doc = "An [`animateColor`](https:) element."]
struct AnimateColor
#[doc = "An [`animateMotion`](https: struct AnimateMotion
#[doc = "An [`animateTransform`](https:) element."]
struct AnimateTransform
#[doc = "A [`circle`](https: struct Circle
#[doc = "A [`clipPath`](https:) element."]
struct ClipPath
#[doc = "A [`defs`](https: struct Definitions
#[doc = "A [`desc`](https:) element."]
struct Description
#[doc = "An [`ellipse`](https: struct Ellipse
#[doc = "A [`filter`](https:) element."]
struct Filter
#[doc = "A [`foreignObject`](https: struct ForeignObject
#[doc = "A [`g`](https:) element."]
struct Group
#[doc = "An [`image`](https: struct Image
#[doc = "A [`line`](https:) element."]
struct Line
#[doc = "A [`linearGradient`](https: struct LinearGradient
#[doc = "An [`a`](https:) element."]
struct Link
#[doc = "A [`marker`](https: struct Marker
#[doc = "A [`mask`](https:) element."]
struct Mask
#[doc = "An [`mpath`](https: struct MotionPath
#[doc = "A [`path`](https:) element."]
struct Path
#[doc = "A [`pattern`](https: struct Pattern
#[doc = "A [`polygon`](https:) element."]
struct Polygon
#[doc = "A [`polyline`](https: struct Polyline
#[doc = "A [`radialGradient`](https:) element."]
struct RadialGradient
#[doc = "A [`rect`](https: struct Rectangle
#[doc = "A [`stop`](https:) element."]
struct Stop
#[doc = "A [`symbol`](https: struct Symbol
#[doc = "A [`text`](https:) element."]
struct Text
#[doc = "A [`textPath`](https: struct TextPath
#[doc = "A [`title`](https:) element."]
struct Title
#[doc = "A [`tspan`](https: struct TSpan
#[doc = "A [`use`](https:) element."]
struct Use
}
macro_rules! implement {
(@itemize $i:item) => ($i);
($(
#[$doc:meta]
struct $struct_name:ident
[$($pn:ident: $($pt:tt)*),*] [$inner:ident $(,$an:ident: $at:ty)*] $body:block
)*) => ($(
#[$doc]
#[derive(Clone, Debug)]
pub struct $struct_name {
inner: Element,
}
implement! { @itemize
impl $struct_name {
/// Create a node.
#[inline]
pub fn new<$($pn: $($pt)*),*>($($an: $at),*) -> Self {
#[inline(always)]
fn initialize<$($pn: $($pt)*),*>($inner: &mut Element $(, $an: $at)*) $body
let mut inner = Element::new(tag::$struct_name);
initialize(&mut inner $(, $an)*);
$struct_name {
inner,
}
}
}
}
impl super::NodeDefaultHash for $struct_name {
fn default_hash(&self, state: &mut DefaultHasher) {
self.inner.default_hash(state);
}
}
node! { $struct_name::inner }
)*);
}
implement! {
#[doc = "An [`svg`](https: struct SVG [] [inner] {
inner.assign("xmlns", "http: }
#[doc = "A [`script`](https://www.w3.org/TR/SVG/script.html#ScriptElement) element."]
struct Script [T: Into<String>] [inner, content: T] {
inner.append(crate::node::Text::new(content));
}
#[doc = "A [`style`](https://www.w3.org/TR/SVG/styling.html#StyleElement) element."]
struct Style [T: Into<String>] [inner, content: T] {
inner.append(crate::node::Text::new(content));
}
}
#[cfg(test)]
mod tests {
use super::{Element, Style};
use crate::node::{self, element, Node};
#[test]
fn element_children() {
let mut one = element::Group::new()
.add(element::Text::new().add(node::Text::new("foo")))
.add(element::Text::new().add(node::Text::new("bar")))
.add(element::Text::new().add(node::Text::new("buz")));
let two = element::Group::new()
.add(one.get_children()[0].clone())
.add(one.get_children_mut().pop().unwrap());
assert_eq!(
one.to_string(),
"<g>\n<text>\nfoo\n</text>\n<text>\nbar\n</text>\n</g>",
);
assert_eq!(
two.to_string(),
"<g>\n<text>\nfoo\n</text>\n<text>\nbuz\n</text>\n</g>",
);
}
#[test]
fn element_display() {
let mut element = Element::new("foo");
element.assign("x", -10);
element.assign("y", "10px");
element.assign("s", (12.5, 13.0));
element.assign("c", "green");
element.append(Element::new("bar"));
assert_eq!(
element.to_string(),
"<foo c=\"green\" s=\"12.5 13\" x=\"-10\" y=\"10px\">\n\
<bar/>\n\
</foo>\
"
);
}
#[test]
fn element_display_quotes() {
let mut element = Element::new("foo");
element.assign("s", "'single'");
element.assign("d", r#""double""#);
element.assign("m", r#""mixed'"#);
assert_eq!(element.to_string(), r#"<foo d='"double"' s="'single'"/>"#);
}
#[test]
fn style_display() {
let element = Style::new("* { font-family: foo; }");
assert_eq!(
element.to_string(),
"<style>\n\
* { font-family: foo; }\n\
</style>\
"
);
}
}