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()
}
);
}
}