use crate::{find_in_xml, utils, Error, Result};
use roxmltree::Node;
use std::{fmt, ops::RangeInclusive};
#[derive(Debug)]
pub struct StateVariable {
name: String,
default: Option<String>,
kind: StateVariableKind,
send_events: bool,
multicast: bool,
optional: bool,
}
#[derive(Debug)]
pub enum StateVariableKind {
Simple(DataType),
Enum(Vec<String>),
Range(RangeInclusive<i64>, i64),
}
impl fmt::Display for StateVariable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
impl StateVariable {
pub(crate) fn from_xml(node: Node<'_, '_>) -> Result<Self> {
#[allow(non_snake_case)]
let (name, datatype, default, variants, range, optional) = find_in_xml! { node => name, dataType, ?defaultValue, ?allowedValueList, ?allowedValueRange, ?optional };
let variants = variants
.map(|a| {
a.children()
.filter(Node::is_element)
.map(utils::parse_node_text)
.collect()
})
.transpose()?;
let default = default.map(utils::parse_node_text).transpose()?;
let range = range.map(range_from_xml).transpose()?;
let name = utils::parse_node_text(name)?;
let datatype = utils::parse_node_text(datatype)?;
let optional = optional.is_some();
let send_events = utils::find_node_attribute(node, "sendEvents")
.map(|val| val == "yes")
.unwrap_or(true);
let multicast = utils::find_node_attribute(node, "multicast")
.map(|val| val == "yes")
.unwrap_or(false);
let kind = match (variants, range) {
(None, None) => Ok(StateVariableKind::Simple(datatype)),
(Some(variants), None) => Ok(StateVariableKind::Enum(variants)),
(None, Some((range, step))) => Ok(StateVariableKind::Range(range, step)),
(Some(_), Some(_)) => Err(Error::ParseError(
"both `AllowedValues` and `AllowedValueRange` is set",
)),
}?;
Ok(StateVariable {
name,
kind,
default,
optional,
send_events,
multicast,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn default(&self) -> Option<&str> {
self.default.as_deref()
}
pub fn optional(&self) -> bool {
self.optional
}
pub fn sends_events(&self) -> bool {
self.send_events
}
pub fn is_multicast(&self) -> bool {
self.multicast
}
pub fn kind(&self) -> &StateVariableKind {
&self.kind
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[allow(non_camel_case_types)]
pub enum DataType {
ui1,
ui2,
ui4,
ui8,
i1,
i2,
i4,
int,
r4,
r8,
Number,
Float,
Fixed14_4,
Char,
String,
Date,
DateTime,
DateTimeTz,
Time,
TimeTz,
Boolean,
BinBase64,
BinHex,
Uri,
}
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}
#[derive(Debug)]
pub struct ParseDataTypeErr(String);
impl fmt::Display for ParseDataTypeErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid data type: {}", self.0)
}
}
impl std::error::Error for ParseDataTypeErr {}
impl std::str::FromStr for DataType {
type Err = ParseDataTypeErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ui1" => Ok(DataType::ui1),
"ui2" => Ok(DataType::ui2),
"ui4" => Ok(DataType::ui4),
"ui8" => Ok(DataType::ui8),
"i1" => Ok(DataType::i1),
"i2" => Ok(DataType::i2),
"i4" => Ok(DataType::i4),
"int" => Ok(DataType::int),
"r4" => Ok(DataType::r4),
"r8" => Ok(DataType::r8),
"number" => Ok(DataType::Number),
"float" => Ok(DataType::Float),
"fixed14_4" => Ok(DataType::Fixed14_4),
"char" => Ok(DataType::Char),
"string" => Ok(DataType::String),
"date" => Ok(DataType::Date),
"dateTime" => Ok(DataType::DateTime),
"dateTimeTz" => Ok(DataType::DateTimeTz),
"time" => Ok(DataType::Time),
"timeTz" => Ok(DataType::TimeTz),
"boolean" => Ok(DataType::Boolean),
"bin.base64" => Ok(DataType::BinBase64),
"bin.hex" => Ok(DataType::BinHex),
"uri" => Ok(DataType::Uri),
_ => Err(ParseDataTypeErr(s.to_string())),
}
}
}
fn range_from_xml(node: Node<'_, '_>) -> Result<(RangeInclusive<i64>, i64)> {
#[allow(non_snake_case)]
let (minimum, maximum, step) = find_in_xml! { node => minimum, maximum, ?step };
let step = step.map(utils::parse_node_text).transpose()?.unwrap_or(1);
let minimum = utils::parse_node_text(minimum)?;
let maximum = utils::parse_node_text(maximum)?;
Ok((minimum..=maximum, step))
}