use anyhow::anyhow; use rgb::RGBA8; // https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#property // name: The name of the property. // type: The type of the property. Can be string (default), int, float, bool, color, file or object (since 0.16, with color and file added in 0.17, and object added in 1.4). // value: The value of the property. (default string is “”, default number is 0, default boolean is “false”, default color is #00000000, default file is “.” (the current file’s parent directory)) #[derive(Debug, PartialEq)] pub enum Property { String { name: String, value: String }, Int { name: String, value: isize }, Float { name: String, value: f64 }, Bool { name: String, value: bool }, Color { name: String, value: RGBA8 }, File { name: String, value: String }, Object { name: String, value: usize }, } impl Property { pub fn from_xml(node: roxmltree::Node) -> anyhow::Result { if node.node_type() != roxmltree::NodeType::Element { return Err(anyhow!("xml -> node is not an element")); } let name = node .attribute("name") .ok_or(anyhow!("property name missing"))? .to_owned(); let property_type = node .attribute("type") .ok_or(anyhow!("property type missing"))?; // handle the case that 'string' value is stored in element content instead of value atribute if property_type == "string" { if let Some(child) = node.first_child() { if let Some(text) = child.text() { return Ok(Property::String { name, value: text.to_owned(), }); } else { return Err(anyhow!("property element content is not of NodeType::Text")); } } } let raw_value = node .attribute("value") .ok_or(anyhow!("property value missing"))?; match property_type { "string" => Ok(Property::String { name, value: raw_value.to_owned(), }), "int" => Ok(Property::Int { name, value: raw_value.parse()?, }), "float" => Ok(Property::Float { name, value: raw_value.parse()?, }), "bool" => { let value = match raw_value { "true" => Ok(true), "false" => Ok(false), _ => Err(anyhow!( "boolean property has unexpected value: {}", raw_value )), }; Ok(Property::Bool { name, value: value?, }) } "color" => { let mut chars = raw_value.chars(); chars.next(); // remove the `#` if let Some(color) = read_color::rgba(&mut chars) { Ok(Property::Color { name, value: RGBA8 { r: color[0], g: color[1], b: color[2], a: color[3], }, }) } else { Err(anyhow!("could not parse color")) } } "file" => Ok(Property::File { name, value: raw_value.to_owned(), }), "object" => Ok(Property::Object { name, value: raw_value.parse()?, }), _ => Err(anyhow!("unknown type {}", property_type)), } } pub fn value_to_string(self) -> String { match self { Property::String { value, .. } => value, Property::Int { value, .. } => format!("{}", value), Property::Float { value, .. } => format!("{}", value), Property::Bool { value, .. } => match value { true => "true", false => "false", } .to_owned(), Property::Color { value, .. } => { format!("#{:x}{:x}{:x}{:x}", value.r, value.g, value.b, value.a) } Property::File { value, .. } => value, Property::Object { value, .. } => format!("{}", value), } } } // Wraps any number of custom properties #[derive(Debug)] pub struct Properties { pub properties: Vec, } impl Properties { fn from_xml(node: roxmltree::Node) -> anyhow::Result { Err(anyhow!("not implemented")) } } #[cfg(test)] mod tests { use crate::lowlevel::property::Property; use rgb::RGBA8; #[test] fn parse_color_property() { // Find element by id let doc = roxmltree::Document::parse( r##""##, ) .unwrap(); let elem = doc.root_element(); assert_eq!( Property::from_xml(elem).unwrap(), Property::Color { name: "enemyTint".to_owned(), value: RGBA8 { r: 255, g: 163, b: 54, a: 54 } } ); } #[test] fn parse_string_property_inside_attribute() { // Find element by id let doc = roxmltree::Document::parse( r##""##, ) .unwrap(); let elem = doc.root_element(); assert_eq!( Property::from_xml(elem).unwrap(), Property::String { name: "enemyText".to_owned(), value: "hello world".to_owned() } ); } #[test] fn parse_string_property_inside_element() { // Find element by id let doc = roxmltree::Document::parse( r##"hello world"##, ) .unwrap(); let elem = doc.root_element(); println!("{:?}", elem.first_child()); assert_eq!( Property::from_xml(elem).unwrap(), Property::String { name: "enemyText".to_owned(), value: "hello world".to_owned() } ); } }