diff options
Diffstat (limited to 'src/lowlevel/property.rs')
-rw-r--r-- | src/lowlevel/property.rs | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/src/lowlevel/property.rs b/src/lowlevel/property.rs new file mode 100644 index 0000000..26dcedd --- /dev/null +++ b/src/lowlevel/property.rs @@ -0,0 +1,201 @@ +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<Property> { + 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<Property>, +} + +impl Properties { + fn from_xml(node: roxmltree::Node) -> anyhow::Result<Properties> { + 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##"<property name="enemyTint" type="color" value="#ffa33636"/>"##, + ) + .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##"<property name="enemyText" type="string" value="hello world"/>"##, + ) + .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##"<property name="enemyText" type="string">hello world</property>"##, + ) + .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() + } + ); + } +} |