paramdex-rs 0.1.0+build.2

Utilities for handling and deserializing a Paramdex / individual Paramdef XMLs for modifying Souls games
Documentation
use std::collections::HashMap;
use std::num::{ParseFloatError, ParseIntError};
use std::str::{FromStr, ParseBoolError};
use roxmltree::Node;
use thiserror::Error;
use crate::{EditFlags, ParamDef, ParamdefEndian, ParamdefFormat, ParamField, ParamFieldDef};

mod field_def_parse;

pub use field_def_parse::DefParseError;

/// Root element name of a ParamDef in the XML
const PARAM_DEF_ROOT: &'static str = "PARAMDEF";

impl FromStr for ParamDef {
    type Err = ParamdefDeserializeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        deserialize_def(s)
    }
}

/// Deserialize a ParamDef from a provided XML string
pub fn deserialize_def<S: AsRef<str>>(input: S) -> Result<ParamDef, ParamdefDeserializeError> {
    let input = input.as_ref();

    let doc = roxmltree::Document::parse(input)?;

    let root = doc.root_element();
    if root.tag_name().name() != PARAM_DEF_ROOT {
        return Err(ParamdefDeserializeError::MissingParamData("Invalid root element".into()));
    }

    let mut root_config: HashMap<String, String> = HashMap::new();

    let mut fields: Option<Node> = None;

    for child in root.children() {
        match child.tag_name().name() {
            "Fields" => {
                fields.replace(child);
            }
            name => {
                root_config.insert(name.into(), child.text().ok_or(ParamdefDeserializeError::XmlBlankElement(name.into()))?.into());
            }
        }
    }

    let fields_node = fields.ok_or(ParamdefDeserializeError::MissingParamData("Fields".into()))?;

    let mut paramdef = ParamDef {
        param_type: get_or_error(&root_config, "ParamType").cloned()?,
        data_version: u32::from_str(get_or_error(&root_config, "DataVersion")?)?,
        endian: ParamdefEndian::from_str(get_or_error(&root_config, "BigEndian")?)?,
        string_format: ParamdefFormat::from_str(get_or_error(&root_config, "BigEndian")?)?,
        format_version: u32::from_str(get_or_error(&root_config, "FormatVersion")?)?,
        fields: Vec::new()
    };

    let fields = &mut paramdef.fields;

    for node in fields_node.children() {
        fields.push(parse_field_node(node)?);
    }

    Ok(paramdef)
}

/// Wrapper function to return value from a map or an error
fn get_or_error<'a>(map: &'a HashMap<String, String>, key: &str) -> Result<&'a String, ParamdefDeserializeError> {
    map.get(key).ok_or(ParamdefDeserializeError::MissingParamData(format!("{}", key)))
}

fn parse_field_node(field_node: Node) -> Result<ParamField, ParamdefDeserializeError> {
    let attr = field_node.attribute("Def").ok_or(ParamdefDeserializeError::MissingParamData("Field Def".into()))?;

    let mut field_config: HashMap<String, String> = HashMap::new();

    for child in field_node.children() {
        if let Some(text) = child.text() {
            field_config.insert(child.tag_name().name().into(), text.into());
        }
    }


    Ok(ParamField {

        field_def: ParamFieldDef::from_str(attr)?,

        display_name: field_config.get("DisplayName").cloned(),
        enum_tdf: field_config.get("Enum").cloned(),
        description: field_config.get("Description").cloned(),
        printf_format: field_config.get("DisplayFormat").cloned(),

        edit_flags: field_config.get("EditFlags").map(|a| EditFlags::from_str(a)).swap()?, // TODO

        minimum: field_config.get("Minimum").map(|a| f64::from_str(a.as_str())).swap()?,
        maximum: field_config.get("Maximum").map(|a| f64::from_str(a.as_str())).swap()?,
        increment: field_config.get("Increment").map(|a| f64::from_str(a.as_str())).swap()?,
        sort_id: field_config.get("SortID").map(|a| usize::from_str(a.as_str())).swap()?,
    })

}

impl FromStr for EditFlags {
    type Err = ParamdefDeserializeError;

    fn from_str(text: &str) -> Result<Self, Self::Err> {
        Ok(EditFlags {
            wrap: text.contains("Wrap"),
            lock: text.contains("Lock"),
        })
    }
}

impl FromStr for ParamFieldDef {
    type Err = DefParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        field_def_parse::parse_param_field_def(s)
    }
}

trait OptionResultExt<V, E> {
    fn swap(self) -> Result<Option<V>, E>;
}

impl<V, E> OptionResultExt<V, E> for Option<Result<V, E>> {
    fn swap(self) -> Result<Option<V>, E> {
        match self {
            Some(Ok(v)) => Ok(Some(v)),
            Some(Err(e)) => Err(e),
            None => Ok(None)
        }
    }
}

impl FromStr for ParamdefEndian {
    type Err = ParseBoolError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
          bool::from_str(s.to_lowercase().as_str()).map(|a| a.into())
    }
}

impl From<bool> for ParamdefEndian {
    fn from(a: bool) -> Self {
        if a {
            Self::Big
        } else {
            Self::Little
        }
    }
}

impl FromStr for ParamdefFormat {
    type Err = ParseBoolError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        bool::from_str(s).map(|a| a.into())
    }
}

impl From<bool> for ParamdefFormat {
    fn from(a: bool) -> Self {
        if a {
            Self::UTF16
        } else {
            Self::ShiftJIS
        }
    }
}

#[derive(Error, Debug)]
pub enum ParamdefDeserializeError {
    #[error("XML parsing failed: {0}")]
    XmlParsing(#[from] roxmltree::Error),

    #[error("XML blank element: {0}")]
    XmlBlankElement(String),

    #[error("Parsing number from XML: {0}")]
    XmlParsingNumber(#[from] ParseIntError),

    #[error("Parsing bool from XML: {0}")]
    XmlParsingBool(#[from] ParseBoolError),

    #[error("Parsing float from XML: {0}")]
    XmlParsingFloat(#[from] ParseFloatError),

    #[error("A required field in the XML was missing")]
    MissingParamData(String),

    #[error("Failed to parse field def string")]
    ParsingDefString(#[from] DefParseError)
}