#[cfg(feature = "npm")]
use napi_derive::napi;
use serde::Serialize;
#[cfg_attr(feature = "npm", napi(object))]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
pub struct Attributes {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub classes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<Vec<(String, String)>>,
}
impl Attributes {
pub fn to_html_attr_string(&self) -> String {
let mut parts = Vec::new();
if let Some(ref id) = self.id {
parts.push(format!("id=\"{}\"", escape_attr(id)));
}
if let Some(ref classes) = self.classes {
parts.push(format!("class=\"{}\"", escape_attr(&classes.join(" "))));
}
if let Some(ref attrs) = self.attributes {
for (k, v) in attrs {
parts.push(format!("{}=\"{}\"", escape_attr(k), escape_attr(v)));
}
}
parts.join(" ")
}
}
fn escape_attr(s: &str) -> String {
s.replace('&', "&")
.replace('"', """)
.replace('<', "<")
.replace('>', ">")
}
#[cfg(test)]
mod tests {
use super::Attributes;
#[test]
fn default_is_empty_string() {
assert_eq!(Attributes::default().to_html_attr_string(), "");
}
#[test]
fn id_renders_first() {
let attrs = Attributes {
id: Some("intro".into()),
classes: None,
attributes: None,
};
assert_eq!(attrs.to_html_attr_string(), "id=\"intro\"");
}
#[test]
fn classes_join_with_spaces() {
let attrs = Attributes {
id: None,
classes: Some(vec!["a".into(), "b".into()]),
attributes: None,
};
assert_eq!(attrs.to_html_attr_string(), "class=\"a b\"");
}
#[test]
fn key_value_pairs_render_after_id_and_class() {
let attrs = Attributes {
id: Some("x".into()),
classes: Some(vec!["y".into()]),
attributes: Some(vec![("data-role".into(), "note".into())]),
};
assert_eq!(
attrs.to_html_attr_string(),
"id=\"x\" class=\"y\" data-role=\"note\""
);
}
#[test]
fn special_characters_are_escaped() {
let attrs = Attributes {
id: Some("a<b".into()),
classes: Some(vec!["c&d".into()]),
attributes: Some(vec![("k".into(), "v\"1".into())]),
};
assert_eq!(
attrs.to_html_attr_string(),
"id=\"a<b\" class=\"c&d\" k=\"v"1\""
);
}
}