use crate::xml::Element;
pub trait FromElement: Sized {
fn from_element(element: &Element) -> Self;
}
pub trait FromElementValue: Sized {
fn from_attribute(value: Option<&str>) -> Self;
fn from_child_text(text: Option<&str>) -> Self;
}
impl FromElementValue for String {
fn from_attribute(value: Option<&str>) -> String {
value.unwrap_or_default().to_string()
}
fn from_child_text(text: Option<&str>) -> String {
text.unwrap_or_default().to_string()
}
}
impl FromElementValue for Option<String> {
fn from_attribute(value: Option<&str>) -> Option<String> {
value.map(str::to_string)
}
fn from_child_text(text: Option<&str>) -> Option<String> {
text.map(str::to_string)
}
}
impl FromElementValue for bool {
fn from_attribute(value: Option<&str>) -> bool {
value == Some("true")
}
fn from_child_text(text: Option<&str>) -> bool {
text == Some("true")
}
}
macro_rules! numbers {
($($type:ty),*) => {$(
impl FromElementValue for $type {
fn from_attribute(value: Option<&str>) -> $type {
value.and_then(|v| v.trim().parse().ok()).unwrap_or_default()
}
fn from_child_text(text: Option<&str>) -> $type {
text.and_then(|v| v.trim().parse().ok()).unwrap_or_default()
}
}
)*};
}
numbers!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);
impl FromElementValue for Option<crate::vocabulary::NullFlavor> {
fn from_attribute(value: Option<&str>) -> Option<crate::vocabulary::NullFlavor> {
value.map(crate::vocabulary::NullFlavor::parse)
}
fn from_child_text(text: Option<&str>) -> Option<crate::vocabulary::NullFlavor> {
text.map(crate::vocabulary::NullFlavor::parse)
}
}
impl FromElement for crate::vocabulary::Ivl {
fn from_element(element: &Element) -> Self {
crate::vocabulary::Ivl::from_element(element)
}
}
impl FromElement for crate::vocabulary::Pq {
fn from_element(element: &Element) -> Self {
crate::vocabulary::Pq::from_element(element)
}
}
impl FromElement for crate::vocabulary::Ed {
fn from_element(element: &Element) -> Self {
crate::vocabulary::Ed::from_element(element)
}
}
impl FromElement for crate::rim::Act {
fn from_element(element: &Element) -> Self {
crate::rim::Act::from_element(element)
}
}
impl FromElement for crate::rim::Entity {
fn from_element(element: &Element) -> Self {
crate::rim::Entity::from_element(element)
}
}
impl FromElement for crate::rim::Role {
fn from_element(element: &Element) -> Self {
crate::rim::Role::from_element(element)
}
}
impl FromElement for crate::rim::Participation {
fn from_element(element: &Element) -> Self {
crate::rim::Participation::from_element(element)
}
}
impl FromElement for crate::rim::ActRelationship {
fn from_element(element: &Element) -> Self {
crate::rim::ActRelationship::from_element(element)
}
}
impl FromElement for crate::rim::RoleLink {
fn from_element(element: &Element) -> Self {
crate::rim::RoleLink::from_element(element)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rim::Act;
#[derive(Debug, Default)]
struct Observation {
class_code: String,
mood_code: String,
note: Option<String>,
component: Act,
raw: Element,
}
impl FromElement for Observation {
fn from_element(element: &Element) -> Observation {
Observation {
class_code: FromElementValue::from_attribute(element.attribute("classCode")),
mood_code: FromElementValue::from_attribute(element.attribute("moodCode")),
note: FromElementValue::from_child_text(
element.child("note").and_then(Element::text_opt),
),
component: element
.child("component")
.map(Act::from_element)
.unwrap_or_default(),
raw: element.clone(),
}
}
}
#[test]
fn reads_attributes_child_text_and_nested_types() {
let element = crate::xml::parse(
r#"<observation classCode="OBS" moodCode="EVN">
<note>elevated</note>
<component classCode="ACT" moodCode="EVN"/>
</observation>"#,
)
.unwrap();
let observation = Observation::from_element(&element);
assert_eq!(observation.class_code, "OBS");
assert_eq!(observation.mood_code, "EVN");
assert_eq!(observation.note.as_deref(), Some("elevated"));
assert_eq!(observation.component.class_code, "ACT");
assert_eq!(observation.raw.local_name(), "observation");
}
#[test]
fn missing_attributes_and_children_degrade_to_defaults() {
let element = crate::xml::parse(r"<observation/>").unwrap();
let observation = Observation::from_element(&element);
assert_eq!(observation.class_code, "");
assert_eq!(observation.note, None);
assert_eq!(observation.component, Act::default());
}
#[test]
fn numbers_and_bool_default_silently_on_bad_input() {
assert_eq!(u32::from_attribute(Some("7")), 7);
assert_eq!(u32::from_attribute(Some("not a number")), 0);
assert_eq!(u32::from_attribute(None), 0);
assert!(bool::from_attribute(Some("true")));
assert!(!bool::from_attribute(Some("yes")));
assert!(!bool::from_attribute(None));
}
}