1use crate::xml::Element;
40
41pub trait FromElement: Sized {
54 fn from_element(element: &Element) -> Self;
57}
58
59pub trait FromElementValue: Sized {
70 fn from_attribute(value: Option<&str>) -> Self;
73 fn from_child_text(text: Option<&str>) -> Self;
76}
77
78impl FromElementValue for String {
79 fn from_attribute(value: Option<&str>) -> String {
80 value.unwrap_or_default().to_string()
81 }
82
83 fn from_child_text(text: Option<&str>) -> String {
84 text.unwrap_or_default().to_string()
85 }
86}
87
88impl FromElementValue for Option<String> {
89 fn from_attribute(value: Option<&str>) -> Option<String> {
90 value.map(str::to_string)
91 }
92
93 fn from_child_text(text: Option<&str>) -> Option<String> {
94 text.map(str::to_string)
95 }
96}
97
98impl FromElementValue for bool {
101 fn from_attribute(value: Option<&str>) -> bool {
102 value == Some("true")
103 }
104
105 fn from_child_text(text: Option<&str>) -> bool {
106 text == Some("true")
107 }
108}
109
110macro_rules! numbers {
113 ($($type:ty),*) => {$(
114 impl FromElementValue for $type {
115 fn from_attribute(value: Option<&str>) -> $type {
116 value.and_then(|v| v.trim().parse().ok()).unwrap_or_default()
117 }
118
119 fn from_child_text(text: Option<&str>) -> $type {
120 text.and_then(|v| v.trim().parse().ok()).unwrap_or_default()
121 }
122 }
123 )*};
124}
125
126numbers!(
127 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
128);
129
130impl FromElementValue for Option<crate::vocabulary::NullFlavor> {
138 fn from_attribute(value: Option<&str>) -> Option<crate::vocabulary::NullFlavor> {
139 value.map(crate::vocabulary::NullFlavor::parse)
140 }
141
142 fn from_child_text(text: Option<&str>) -> Option<crate::vocabulary::NullFlavor> {
143 text.map(crate::vocabulary::NullFlavor::parse)
144 }
145}
146
147impl FromElement for crate::vocabulary::Ivl {
148 fn from_element(element: &Element) -> Self {
149 crate::vocabulary::Ivl::from_element(element)
150 }
151}
152
153impl FromElement for crate::vocabulary::Pq {
154 fn from_element(element: &Element) -> Self {
155 crate::vocabulary::Pq::from_element(element)
156 }
157}
158
159impl FromElement for crate::vocabulary::Ed {
160 fn from_element(element: &Element) -> Self {
161 crate::vocabulary::Ed::from_element(element)
162 }
163}
164
165impl FromElement for crate::rim::Act {
166 fn from_element(element: &Element) -> Self {
167 crate::rim::Act::from_element(element)
168 }
169}
170
171impl FromElement for crate::rim::Entity {
172 fn from_element(element: &Element) -> Self {
173 crate::rim::Entity::from_element(element)
174 }
175}
176
177impl FromElement for crate::rim::Role {
178 fn from_element(element: &Element) -> Self {
179 crate::rim::Role::from_element(element)
180 }
181}
182
183impl FromElement for crate::rim::Participation {
184 fn from_element(element: &Element) -> Self {
185 crate::rim::Participation::from_element(element)
186 }
187}
188
189impl FromElement for crate::rim::ActRelationship {
190 fn from_element(element: &Element) -> Self {
191 crate::rim::ActRelationship::from_element(element)
192 }
193}
194
195impl FromElement for crate::rim::RoleLink {
196 fn from_element(element: &Element) -> Self {
197 crate::rim::RoleLink::from_element(element)
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::rim::Act;
205
206 #[derive(Debug, Default)]
207 struct Observation {
208 class_code: String,
209 mood_code: String,
210 note: Option<String>,
211 component: Act,
212 raw: Element,
213 }
214
215 impl FromElement for Observation {
218 fn from_element(element: &Element) -> Observation {
219 Observation {
220 class_code: FromElementValue::from_attribute(element.attribute("classCode")),
221 mood_code: FromElementValue::from_attribute(element.attribute("moodCode")),
222 note: FromElementValue::from_child_text(
223 element.child("note").and_then(Element::text_opt),
224 ),
225 component: element
226 .child("component")
227 .map(Act::from_element)
228 .unwrap_or_default(),
229 raw: element.clone(),
230 }
231 }
232 }
233
234 #[test]
235 fn reads_attributes_child_text_and_nested_types() {
236 let element = crate::xml::parse(
237 r#"<observation classCode="OBS" moodCode="EVN">
238 <note>elevated</note>
239 <component classCode="ACT" moodCode="EVN"/>
240 </observation>"#,
241 )
242 .unwrap();
243 let observation = Observation::from_element(&element);
244 assert_eq!(observation.class_code, "OBS");
245 assert_eq!(observation.mood_code, "EVN");
246 assert_eq!(observation.note.as_deref(), Some("elevated"));
247 assert_eq!(observation.component.class_code, "ACT");
248 assert_eq!(observation.raw.local_name(), "observation");
249 }
250
251 #[test]
252 fn missing_attributes_and_children_degrade_to_defaults() {
253 let element = crate::xml::parse(r"<observation/>").unwrap();
254 let observation = Observation::from_element(&element);
255 assert_eq!(observation.class_code, "");
256 assert_eq!(observation.note, None);
257 assert_eq!(observation.component, Act::default());
258 }
259
260 #[test]
261 fn numbers_and_bool_default_silently_on_bad_input() {
262 assert_eq!(u32::from_attribute(Some("7")), 7);
263 assert_eq!(u32::from_attribute(Some("not a number")), 0);
264 assert_eq!(u32::from_attribute(None), 0);
265 assert!(bool::from_attribute(Some("true")));
266 assert!(!bool::from_attribute(Some("yes")));
267 assert!(!bool::from_attribute(None));
268 }
269}