Skip to content
Snippets Groups Projects
properties.rs 5.18 KiB
Newer Older
use std::{collections::HashMap, str::FromStr};
alexdevteam's avatar
alexdevteam committed

use xml::{attribute::OwnedAttribute, reader::XmlEvent};
alexdevteam's avatar
alexdevteam committed

use crate::{
    util::{get_attrs, parse_tag, XmlEventResult},
alexdevteam's avatar
alexdevteam committed
};

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
alexdevteam's avatar
alexdevteam committed
pub struct Color {
alexdevteam's avatar
alexdevteam committed
    pub red: u8,
    pub green: u8,
    pub blue: u8,
}

alexdevteam's avatar
alexdevteam committed
impl FromStr for Color {
alexdevteam's avatar
alexdevteam committed

    fn from_str(s: &str) -> Result<Color, Self::Err> {
alexdevteam's avatar
alexdevteam committed
        let s = if s.starts_with("#") { &s[1..] } else { s };
        match s.len() {
            6 => {
                let r = u8::from_str_radix(&s[0..2], 16);
                let g = u8::from_str_radix(&s[2..4], 16);
                let b = u8::from_str_radix(&s[4..6], 16);
                match (r, g, b) {
                    (Ok(red), Ok(green), Ok(blue)) => Ok(Color {
                        alpha: 0xFF,
                        red,
                        green,
                        blue,
                    }),
                    _ => Err(()),
                }
            }
            8 => {
                let a = u8::from_str_radix(&s[0..2], 16);
                let r = u8::from_str_radix(&s[2..4], 16);
                let g = u8::from_str_radix(&s[4..6], 16);
                let b = u8::from_str_radix(&s[6..8], 16);
                match (a, r, g, b) {
                    (Ok(alpha), Ok(red), Ok(green), Ok(blue)) => Ok(Color {
                        alpha,
                        red,
                        green,
                        blue,
                    }),
                    _ => Err(()),
                }
            }
            _ => Err(()),
alexdevteam's avatar
alexdevteam committed
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum PropertyValue {
    BoolValue(bool),
    FloatValue(f32),
    IntValue(i32),
    ColorValue(u32),
    StringValue(String),
    /// Holds the path relative to the map or tileset
    FileValue(String),
SiegeLord's avatar
SiegeLord committed
    /// Holds the id of a referenced object, or 0 if unset
    ObjectValue(u32),
alexdevteam's avatar
alexdevteam committed
}

impl PropertyValue {
    fn new(property_type: String, value: String) -> Result<PropertyValue, TiledError> {
        // Check the property type against the value.
        match property_type.as_str() {
            "bool" => match value.parse() {
                Ok(val) => Ok(PropertyValue::BoolValue(val)),
Alejandro Perea's avatar
Alejandro Perea committed
                Err(err) => Err(TiledError::InvalidPropertyValue {
                    description: err.to_string(),
                }),
alexdevteam's avatar
alexdevteam committed
            },
            "float" => match value.parse() {
                Ok(val) => Ok(PropertyValue::FloatValue(val)),
Alejandro Perea's avatar
Alejandro Perea committed
                Err(err) => Err(TiledError::InvalidPropertyValue {
                    description: err.to_string(),
                }),
alexdevteam's avatar
alexdevteam committed
            },
            "int" => match value.parse() {
                Ok(val) => Ok(PropertyValue::IntValue(val)),
Alejandro Perea's avatar
Alejandro Perea committed
                Err(err) => Err(TiledError::InvalidPropertyValue {
                    description: err.to_string(),
                }),
alexdevteam's avatar
alexdevteam committed
            },
            "color" if value.len() > 1 => match u32::from_str_radix(&value[1..], 16) {
                Ok(color) => Ok(PropertyValue::ColorValue(color)),
Alejandro Perea's avatar
Alejandro Perea committed
                Err(err) => Err(TiledError::InvalidPropertyValue {
                    description: err.to_string(),
                }),
alexdevteam's avatar
alexdevteam committed
            },
            "string" => Ok(PropertyValue::StringValue(value)),
SiegeLord's avatar
SiegeLord committed
            "object" => match value.parse() {
                Ok(val) => Ok(PropertyValue::ObjectValue(val)),
Alejandro Perea's avatar
Alejandro Perea committed
                Err(err) => Err(TiledError::InvalidPropertyValue {
                    description: err.to_string(),
                }),
SiegeLord's avatar
SiegeLord committed
            },
alexdevteam's avatar
alexdevteam committed
            "file" => Ok(PropertyValue::FileValue(value)),
Alejandro Perea's avatar
Alejandro Perea committed
            _ => Err(TiledError::UnknownPropertyType {
                name: property_type,
            }),
alexdevteam's avatar
alexdevteam committed
        }
    }
}

pub type Properties = HashMap<String, PropertyValue>;

pub(crate) fn parse_properties(
    parser: &mut impl Iterator<Item = XmlEventResult>,
alexdevteam's avatar
alexdevteam committed
) -> Result<Properties, TiledError> {
    let mut p = HashMap::new();
    parse_tag!(parser, "properties", {
        "property" => |attrs:Vec<OwnedAttribute>| {
            let ((t, v_attr), k) = get_attrs!(
alexdevteam's avatar
alexdevteam committed
                attrs,
                optionals: [
                    ("type", property_type, |v| Some(v)),
                    ("value", value, |v| Some(v)),
alexdevteam's avatar
alexdevteam committed
                ],
                required: [
                    ("name", key, |v| Some(v)),
                ],
                TiledError::MalformedAttributes("property must have a name and a value".to_string())
            );
            let t = t.unwrap_or("string".into());
            let v: String = match v_attr {
                Some(val) => val,
                None => {
                    // if the "value" attribute was missing, might be a multiline string
                    match parser.next() {
                        Some(Ok(XmlEvent::Characters(s))) => Ok(s),
                        Some(Err(err)) => Err(TiledError::XmlDecodingError(err)),
                        None => unreachable!(), // EndDocument or error must come first
                        _ => Err(TiledError::MalformedAttributes(format!("property '{}' is missing a value", k))),
                    }?
                }
            };
alexdevteam's avatar
alexdevteam committed

            p.insert(k, PropertyValue::new(t, v)?);
            Ok(())
        },
    });
    Ok(p)
}