summaryrefslogblamecommitdiff
path: root/src/lowlevel/property.rs
blob: 26dceddba1f39a25610193d715cb7f45eb74d317 (plain) (tree)








































































































































































































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