autosar_data/
chardata.rs

1use std::borrow::Cow;
2use std::fmt::Display;
3use std::str::FromStr;
4
5use super::{AutosarVersion, CharacterData, CharacterDataSpec, EnumItem};
6
7impl CharacterData {
8    pub(crate) fn check_value(value: &CharacterData, spec: &CharacterDataSpec, file_version: AutosarVersion) -> bool {
9        match spec {
10            // the specification must call for an enum
11            CharacterDataSpec::Enum { items } => {
12                // the actual value must be an enum item
13                if let CharacterData::Enum(enumitem) = &value {
14                    // find the given enum item in the specified list of values
15                    if let Some((_, version_mask)) = items.iter().find(|(name, _)| *name == *enumitem) {
16                        // and finally check that this value is allowed in the active autosar version
17                        if *version_mask & (file_version as u32) != 0 {
18                            return true;
19                        }
20                    }
21                }
22            }
23            CharacterDataSpec::Pattern {
24                check_fn, max_length, ..
25            } => {
26                if let CharacterData::String(stringval) = &value {
27                    if stringval.len() <= max_length.unwrap_or(usize::MAX) && check_fn(stringval.as_bytes()) {
28                        return true;
29                    }
30                }
31            }
32            CharacterDataSpec::String { max_length, .. } => {
33                if let CharacterData::String(stringval) = &value {
34                    if stringval.len() <= max_length.unwrap_or(usize::MAX) {
35                        return true;
36                    }
37                }
38            }
39            CharacterDataSpec::UnsignedInteger => {
40                if let CharacterData::UnsignedInteger(_) = &value {
41                    return true;
42                }
43            }
44            CharacterDataSpec::Float => {
45                if let CharacterData::Float(_) = &value {
46                    return true;
47                }
48            }
49        }
50        false
51    }
52
53    pub(crate) fn check_version_compatibility(
54        &self,
55        data_spec: &CharacterDataSpec,
56        target_version: AutosarVersion,
57    ) -> (bool, u32) {
58        if let CharacterDataSpec::Enum { items } = data_spec {
59            if let CharacterData::Enum(attrval) = self {
60                if let Some((_, enumitem_version_mask)) = items.iter().find(|(item, _)| *item == *attrval) {
61                    if target_version.compatible(*enumitem_version_mask) {
62                        (true, *enumitem_version_mask)
63                    } else {
64                        (false, *enumitem_version_mask)
65                    }
66                } else {
67                    // no spec for this item -> not allowed in any version
68                    (false, 0)
69                }
70            } else {
71                // content is not an enum item, but an enum item is expected
72                (false, u32::MAX)
73            }
74        } else {
75            (true, u32::MAX)
76        }
77    }
78
79    pub(crate) fn parse(input: &str, character_data_spec: &CharacterDataSpec, version: AutosarVersion) -> Option<Self> {
80        match character_data_spec {
81            CharacterDataSpec::Enum { items } => {
82                if let Ok(enumitem) = EnumItem::from_str(input) {
83                    if let Some((_, version_mask)) = items.iter().find(|(item, _)| *item == enumitem) {
84                        if version as u32 & version_mask != 0 {
85                            return Some(CharacterData::Enum(enumitem));
86                        }
87                    }
88                }
89            }
90            CharacterDataSpec::Pattern {
91                check_fn, max_length, ..
92            } => {
93                if input.len() <= max_length.unwrap_or(usize::MAX) && check_fn(input.as_bytes()) {
94                    return Some(CharacterData::String(input.to_owned()));
95                }
96            }
97            CharacterDataSpec::String { max_length, .. } => {
98                if input.len() <= max_length.unwrap_or(usize::MAX) {
99                    return Some(CharacterData::String(input.to_owned()));
100                }
101            }
102            CharacterDataSpec::UnsignedInteger => {
103                if let Ok(value) = input.parse() {
104                    return Some(CharacterData::UnsignedInteger(value));
105                }
106            }
107            CharacterDataSpec::Float => {
108                if let Ok(value) = input.parse() {
109                    return Some(CharacterData::Float(value));
110                }
111            }
112        }
113        None
114    }
115
116    pub(crate) fn serialize_internal(&self, outstring: &mut String) {
117        match self {
118            CharacterData::Enum(enumval) => outstring.push_str(enumval.to_str()),
119            CharacterData::String(strval) => outstring.push_str(&escape_text(strval)),
120            CharacterData::UnsignedInteger(intval) => outstring.push_str(&intval.to_string()),
121            CharacterData::Float(floatval) => outstring.push_str(&floatval.to_string()),
122        }
123    }
124
125    /// Get the contained enum value
126    ///
127    /// Returns the enum value if the content is an enum, or None otherwise
128    #[must_use]
129    pub fn enum_value(&self) -> Option<EnumItem> {
130        if let CharacterData::Enum(item) = self {
131            Some(*item)
132        } else {
133            None
134        }
135    }
136
137    /// Get the contained string
138    ///
139    /// Returns the string if the content is a string, or None otherwise
140    #[must_use]
141    pub fn string_value(&self) -> Option<String> {
142        if let CharacterData::String(value) = self {
143            Some(value.to_owned())
144        } else {
145            None
146        }
147    }
148
149    /// Get the contained unsigned integer
150    ///
151    /// Returns the string if the content is a string, or None otherwise
152    #[must_use]
153    pub fn unsigned_integer_value(&self) -> Option<u64> {
154        if let CharacterData::UnsignedInteger(uintval) = self {
155            Some(*uintval)
156        } else {
157            None
158        }
159    }
160
161    /// Get the contained floating point value
162    ///
163    /// Returns the value if the content is a float, or None otherwise
164    #[must_use]
165    pub fn float_value(&self) -> Option<f64> {
166        if let CharacterData::Float(value) = self {
167            Some(*value)
168        } else {
169            None
170        }
171    }
172
173    /// parse the stored charcter data value as an integer
174    ///
175    /// Many numbers are stored as strings in order to allow hexadecimal, octal, and binary encoding.
176    /// This function handles the conversion from text to integer.
177    /// If the stored value is already an integer, it will be converted to the output type.
178    ///
179    /// Returns the value if the conversion succeeds, or None otherwise
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// # use autosar_data::CharacterData;
185    /// let data = CharacterData::String("0x1234".to_string());
186    /// let value = data.parse_integer::<u32>().unwrap();
187    /// assert_eq!(value, 0x1234);
188    /// ```
189    #[must_use]
190    pub fn parse_integer<T: num_traits::Num + TryFrom<u64>>(&self) -> Option<T> {
191        if let CharacterData::String(text) = self {
192            if text == "0" {
193                // handle this first to avoid hitting the octal case
194                T::try_from(0u64).ok()
195            } else if let Some(hexstr) = text.strip_prefix("0x") {
196                T::from_str_radix(hexstr, 16).ok()
197            } else if let Some(hexstr) = text.strip_prefix("0X") {
198                T::from_str_radix(hexstr, 16).ok()
199            } else if let Some(binstr) = text.strip_prefix("0b") {
200                T::from_str_radix(binstr, 2).ok()
201            } else if let Some(binstr) = text.strip_prefix("0B") {
202                T::from_str_radix(binstr, 2).ok()
203            } else if let Some(octstr) = text.strip_prefix('0') {
204                T::from_str_radix(octstr, 8).ok()
205            } else {
206                T::from_str_radix(text, 10).ok()
207            }
208        } else if let CharacterData::UnsignedInteger(value) = self {
209            T::try_from(*value).ok()
210        } else {
211            None
212        }
213    }
214
215    /// parse the stored character data value as a floating point number
216    ///
217    /// When the meta model declares that a value is of class "Numerical", this means it is stored as a string.
218    /// The regex associated with "Numerical" allows signed integers, floating point values (including scientific
219    /// notation, as well as INF and NaN), hexadecimal, octal, and binary numbers.
220    /// This function handles the conversion from text to floating point.
221    ///
222    /// Returns the value if the conversion succeeds, or None otherwise
223    ///
224    /// # Example
225    ///
226    /// ```
227    /// # use autosar_data::CharacterData;
228    /// let data = CharacterData::String("0x1234".to_string());
229    /// let value = data.parse_float().unwrap();
230    /// assert_eq!(value, 0x1234 as f64);
231    ///
232    /// let data = CharacterData::String("1.234e5".to_string());
233    /// let value = data.parse_float().unwrap();
234    /// assert_eq!(value, 1.234e5);
235    /// ```
236    #[must_use]
237    pub fn parse_float(&self) -> Option<f64> {
238        if let CharacterData::String(text) = self {
239            if text == "0" {
240                // handle this first to avoid hitting the octal case
241                Some(0f64)
242            } else if let Some(hexval) = text
243                .strip_prefix("0x")
244                .and_then(|hextxt| u64::from_str_radix(hextxt, 16).ok())
245            {
246                Some(hexval as f64)
247            } else if let Some(hexval) = text
248                .strip_prefix("0X")
249                .and_then(|hextxt| u64::from_str_radix(hextxt, 16).ok())
250            {
251                Some(hexval as f64)
252            } else if let Some(binval) = text
253                .strip_prefix("0b")
254                .and_then(|bintxt| u64::from_str_radix(bintxt, 2).ok())
255            {
256                Some(binval as f64)
257            } else if let Some(binval) = text
258                .strip_prefix("0B")
259                .and_then(|bintxt| u64::from_str_radix(bintxt, 2).ok())
260            {
261                Some(binval as f64)
262            } else if let Some(octval) = text
263                .strip_prefix('0')
264                .and_then(|octtxt| u64::from_str_radix(octtxt, 8).ok())
265            {
266                Some(octval as f64)
267            } else {
268                // normal float conversion
269                text.parse().ok()
270            }
271        } else if let CharacterData::Float(value) = self {
272            Some(*value)
273        } else if let CharacterData::UnsignedInteger(value) = self {
274            Some(*value as f64)
275        } else {
276            None
277        }
278    }
279
280    /// parse the stored character data value as a boolean
281    ///
282    /// `CharacterData` doesn't store a boolean type natively: Autosar describes booleans as strings with the values
283    /// "true" or "1" for true, and "false" or "0" for false.
284    ///
285    /// Returns the value if the conversion succeeds, or None otherwise
286    ///
287    /// # Example
288    ///
289    /// ```
290    /// # use autosar_data::CharacterData;
291    /// let data = CharacterData::String("true".to_string());
292    /// let value = data.parse_bool().unwrap();
293    /// assert_eq!(value, true);
294    /// ```
295    #[must_use]
296    pub fn parse_bool(&self) -> Option<bool> {
297        if let CharacterData::String(text) = self {
298            match text.as_str() {
299                "true" | "1" => Some(true),
300                "false" | "0" => Some(false),
301                _ => None,
302            }
303        } else {
304            None
305        }
306    }
307}
308
309impl From<String> for CharacterData {
310    fn from(value: String) -> Self {
311        Self::String(value)
312    }
313}
314
315impl From<&str> for CharacterData {
316    fn from(value: &str) -> Self {
317        Self::String(value.to_string())
318    }
319}
320
321impl From<EnumItem> for CharacterData {
322    fn from(value: EnumItem) -> Self {
323        Self::Enum(value)
324    }
325}
326
327impl From<u64> for CharacterData {
328    fn from(value: u64) -> Self {
329        Self::UnsignedInteger(value)
330    }
331}
332
333impl From<f64> for CharacterData {
334    fn from(value: f64) -> Self {
335        Self::Float(value)
336    }
337}
338
339impl From<bool> for CharacterData {
340    fn from(value: bool) -> Self {
341        Self::String(value.to_string())
342    }
343}
344
345impl Display for CharacterData {
346    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347        match self {
348            CharacterData::Enum(enumitem) => f.write_str(enumitem.to_str()),
349            CharacterData::String(stringval) => f.write_str(stringval),
350            CharacterData::UnsignedInteger(uintval) => f.write_str(&uintval.to_string()),
351            CharacterData::Float(f64val) => f.write_str(&f64val.to_string()),
352        }
353    }
354}
355
356impl Ord for CharacterData {
357    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
358        // compare two CharacterData values
359        // if the types are different, the order is determined by the type so that there is a consistent order
360        // sort order: enum < string < unsigned integer < float - this is arbitrary
361        match (self, other) {
362            (CharacterData::Enum(a), CharacterData::Enum(b)) => a.to_str().cmp(b.to_str()),
363            (CharacterData::String(a), CharacterData::String(b)) => a.cmp(b),
364            (CharacterData::UnsignedInteger(a), CharacterData::UnsignedInteger(b)) => a.cmp(b),
365            (CharacterData::Float(a), CharacterData::Float(b)) => a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal),
366            (CharacterData::Enum(_), _) => std::cmp::Ordering::Less,
367            (CharacterData::String(_), CharacterData::Enum(_)) => std::cmp::Ordering::Greater,
368            (CharacterData::String(_), _) => std::cmp::Ordering::Less,
369            (CharacterData::UnsignedInteger(_), CharacterData::Enum(_)) => std::cmp::Ordering::Greater,
370            (CharacterData::UnsignedInteger(_), CharacterData::String(_)) => std::cmp::Ordering::Greater,
371            (CharacterData::UnsignedInteger(_), _) => std::cmp::Ordering::Less,
372            (CharacterData::Float(_), _) => std::cmp::Ordering::Greater,
373        }
374    }
375}
376
377impl PartialOrd for CharacterData {
378    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
379        Some(self.cmp(other))
380    }
381}
382
383impl Eq for CharacterData {}
384
385fn escape_text(input: &str) -> Cow<str> {
386    if input.contains(['&', '>', '<', '\'', '"']) {
387        let mut escaped = String::with_capacity(input.len() + 6);
388
389        for c in input.chars() {
390            match c {
391                '<' => escaped.push_str("&lt;"),
392                '>' => escaped.push_str("&gt;"),
393                '&' => escaped.push_str("&amp;"),
394                '"' => escaped.push_str("&quot;"),
395                '\'' => escaped.push_str("&apos;"),
396                other => escaped.push(other),
397            }
398        }
399
400        Cow::Owned(escaped)
401    } else {
402        Cow::Borrowed(input)
403    }
404}
405
406#[cfg(test)]
407mod test {
408    use super::*;
409
410    fn dummy_validate(s: &[u8]) -> bool {
411        s.len() == 1 && (s[0] == b'0' || s[0] == b'1')
412    }
413
414    #[test]
415    fn check_value() {
416        let spec_enum = CharacterDataSpec::Enum {
417            items: &[(EnumItem::default, 0x3ffff), (EnumItem::preserve, 0x0ffff)],
418        };
419        let data = CharacterData::Enum(EnumItem::default);
420        assert!(CharacterData::check_value(
421            &data,
422            &spec_enum,
423            AutosarVersion::Autosar_00050
424        ));
425        let data = CharacterData::Enum(EnumItem::preserve);
426        assert!(!CharacterData::check_value(
427            &data,
428            &spec_enum,
429            AutosarVersion::Autosar_00050
430        ));
431        let data = CharacterData::Enum(EnumItem::Abstract);
432        assert!(!CharacterData::check_value(
433            &data,
434            &spec_enum,
435            AutosarVersion::Autosar_00050
436        ));
437        let data = CharacterData::Float(1.23);
438        assert!(!CharacterData::check_value(
439            &data,
440            &spec_enum,
441            AutosarVersion::Autosar_00050
442        ));
443
444        let spec_double = CharacterDataSpec::Float;
445        let data = CharacterData::Float(1.23);
446        assert!(CharacterData::check_value(
447            &data,
448            &spec_double,
449            AutosarVersion::Autosar_00050
450        ));
451        let data = CharacterData::String("1.23".to_string());
452        assert!(!CharacterData::check_value(
453            &data,
454            &spec_double,
455            AutosarVersion::Autosar_00050
456        ));
457
458        let spec_uint = CharacterDataSpec::UnsignedInteger;
459        let data = CharacterData::UnsignedInteger(123);
460        assert!(CharacterData::check_value(
461            &data,
462            &spec_uint,
463            AutosarVersion::Autosar_00050
464        ));
465        let data = CharacterData::String("123".to_string());
466        assert!(!CharacterData::check_value(
467            &data,
468            &spec_uint,
469            AutosarVersion::Autosar_00050
470        ));
471
472        let spec_string = CharacterDataSpec::String {
473            preserve_whitespace: false,
474            max_length: Some(10),
475        };
476        let data = CharacterData::String("123".to_string());
477        assert!(CharacterData::check_value(
478            &data,
479            &spec_string,
480            AutosarVersion::Autosar_00050
481        ));
482        let data = CharacterData::String("12345678901".to_string());
483        assert!(!CharacterData::check_value(
484            &data,
485            &spec_string,
486            AutosarVersion::Autosar_00050
487        ));
488        let data = CharacterData::UnsignedInteger(1);
489        assert!(!CharacterData::check_value(
490            &data,
491            &spec_string,
492            AutosarVersion::Autosar_00050
493        ));
494
495        let spec_pattern = CharacterDataSpec::Pattern {
496            check_fn: dummy_validate,
497            regex: r"0|1",
498            max_length: Some(1),
499        };
500        let data = CharacterData::String("0".to_string());
501        assert!(CharacterData::check_value(
502            &data,
503            &spec_pattern,
504            AutosarVersion::Autosar_00050
505        ));
506        let data = CharacterData::String("2".to_string());
507        assert!(!CharacterData::check_value(
508            &data,
509            &spec_pattern,
510            AutosarVersion::Autosar_00050
511        ));
512    }
513
514    #[test]
515    fn check_version_compatibility() {
516        let spec_enum = CharacterDataSpec::Enum {
517            items: &[(EnumItem::default, 0x0001), (EnumItem::preserve, 0x0002)],
518        };
519        let data = CharacterData::Enum(EnumItem::default);
520        let (result, _) = data.check_version_compatibility(&spec_enum, AutosarVersion::Autosar_4_0_1);
521        assert!(result);
522        let (result, _) = data.check_version_compatibility(&spec_enum, AutosarVersion::Autosar_00050);
523        assert!(!result);
524        let data = CharacterData::Enum(EnumItem::Abstract);
525        let (result, _) = data.check_version_compatibility(&spec_enum, AutosarVersion::Autosar_00050);
526        assert!(!result);
527        let data = CharacterData::UnsignedInteger(0);
528        let (result, _) = data.check_version_compatibility(&spec_enum, AutosarVersion::Autosar_00050);
529        assert!(!result);
530    }
531
532    #[test]
533    fn parse() {
534        let spec_enum = CharacterDataSpec::Enum {
535            items: &[(EnumItem::default, 0x3ffff), (EnumItem::preserve, 0x0ffff)],
536        };
537        let data = CharacterData::parse("default", &spec_enum, AutosarVersion::Autosar_00050).unwrap();
538        assert_eq!(data.enum_value().unwrap(), EnumItem::default);
539        let data = CharacterData::parse("preserve", &spec_enum, AutosarVersion::Autosar_00050);
540        assert!(data.is_none());
541        let data = CharacterData::parse("ABSTRACT", &spec_enum, AutosarVersion::Autosar_00050);
542        assert!(data.is_none());
543        let data = CharacterData::parse("", &spec_enum, AutosarVersion::Autosar_00050);
544        assert!(data.is_none());
545
546        let spec_double = CharacterDataSpec::Float;
547        let data = CharacterData::parse("2.0", &spec_double, AutosarVersion::Autosar_00050).unwrap();
548        assert_eq!(data.float_value().unwrap(), 2.0);
549        let data = CharacterData::parse("text", &spec_double, AutosarVersion::Autosar_00050);
550        assert!(data.is_none());
551
552        let spec_uint = CharacterDataSpec::UnsignedInteger;
553        let data = CharacterData::parse("2", &spec_uint, AutosarVersion::Autosar_00050).unwrap();
554        assert_eq!(data.unsigned_integer_value().unwrap(), 2);
555        let data = CharacterData::parse("text", &spec_uint, AutosarVersion::Autosar_00050);
556        assert!(data.is_none());
557
558        let spec_string = CharacterDataSpec::String {
559            preserve_whitespace: false,
560            max_length: Some(10),
561        };
562        let data = CharacterData::parse("text", &spec_string, AutosarVersion::Autosar_00050).unwrap();
563        assert_eq!(data.string_value().unwrap(), "text");
564        let data = CharacterData::parse("text text text", &spec_string, AutosarVersion::Autosar_00050);
565        assert!(data.is_none());
566
567        let spec_pattern = CharacterDataSpec::Pattern {
568            check_fn: dummy_validate,
569            regex: r"0|1",
570            max_length: Some(1),
571        };
572        let data = CharacterData::parse("0", &spec_pattern, AutosarVersion::Autosar_00050).unwrap();
573        assert_eq!(data.string_value().unwrap(), "0");
574        let data = CharacterData::parse("2", &spec_pattern, AutosarVersion::Autosar_00050);
575        assert!(data.is_none());
576    }
577
578    #[test]
579    fn serialize() {
580        let mut out = "".to_string();
581        let data = CharacterData::Enum(EnumItem::Abstract);
582        data.serialize_internal(&mut out);
583        assert_eq!(out, "ABSTRACT");
584        assert_eq!(format!("{data}"), "ABSTRACT");
585
586        let mut out = "".to_string();
587        let data = CharacterData::Float(1.23);
588        data.serialize_internal(&mut out);
589        assert_eq!(out, "1.23");
590        assert_eq!(format!("{data}"), "1.23");
591
592        let mut out = "".to_string();
593        let data = CharacterData::UnsignedInteger(0);
594        data.serialize_internal(&mut out);
595        assert_eq!(out, "0");
596        assert_eq!(format!("{data}"), "0");
597
598        let mut out = "".to_string();
599        let data = CharacterData::String("text".to_string());
600        data.serialize_internal(&mut out);
601        assert_eq!(out, "text");
602        assert_eq!(format!("{data}"), "text");
603
604        let mut out = "".to_string();
605        let data = CharacterData::String("special chars: <, >, &, \', \"".to_string());
606        data.serialize_internal(&mut out);
607        assert_eq!(out, "special chars: &lt;, &gt;, &amp;, &apos;, &quot;");
608        assert_eq!(format!("{data}"), "special chars: <, >, &, \', \"");
609    }
610
611    #[test]
612    fn get_value() {
613        assert!(CharacterData::Enum(EnumItem::Abstract).enum_value().is_some());
614        assert!(CharacterData::Enum(EnumItem::Abstract).string_value().is_none());
615
616        assert!(CharacterData::Float(1.23).float_value().is_some());
617        assert!(CharacterData::Float(1.23).unsigned_integer_value().is_none());
618
619        assert!(CharacterData::String("x".to_string()).string_value().is_some());
620        assert!(CharacterData::String("x".to_string()).float_value().is_none());
621
622        assert!(CharacterData::UnsignedInteger(1).unsigned_integer_value().is_some());
623        assert!(CharacterData::UnsignedInteger(1).enum_value().is_none());
624    }
625
626    #[test]
627    fn parse_integer() {
628        let data = CharacterData::String("text".to_string());
629        let result = data.parse_integer::<u32>();
630        assert!(result.is_none());
631
632        let data = CharacterData::String("0x123412341234".to_string());
633        let result = data.parse_integer::<u32>();
634        assert!(result.is_none());
635
636        let data = CharacterData::String("0x1234".to_string());
637        let result = data.parse_integer::<u32>().unwrap();
638        assert_eq!(result, 0x1234);
639
640        let data = CharacterData::String("0X123456".to_string());
641        let result = data.parse_integer::<u32>().unwrap();
642        assert_eq!(result, 0x123456);
643
644        let data = CharacterData::String("0b1010".to_string());
645        let result = data.parse_integer::<u32>().unwrap();
646        assert_eq!(result, 10);
647
648        let data = CharacterData::String("0B101010".to_string());
649        let result = data.parse_integer::<u32>().unwrap();
650        assert_eq!(result, 42);
651
652        let data = CharacterData::String("0733".to_string());
653        let result = data.parse_integer::<u32>().unwrap();
654        assert_eq!(result, 475);
655
656        let data = CharacterData::String("0".to_string());
657        let result = data.parse_integer::<u32>().unwrap();
658        assert_eq!(result, 0);
659
660        let data = CharacterData::String("-55".to_string());
661        let result = data.parse_integer::<i32>().unwrap();
662        assert_eq!(result, -55);
663
664        let data = CharacterData::UnsignedInteger(0);
665        let result = data.parse_integer::<u32>().unwrap();
666        assert_eq!(result, 0);
667
668        let data = CharacterData::Float(0.0);
669        let result = data.parse_integer::<u32>();
670        assert!(result.is_none());
671    }
672
673    #[test]
674    fn parse_float() {
675        // not a number
676        let data = CharacterData::String("text".to_string());
677        let result = data.parse_float();
678        assert!(result.is_none());
679
680        // zero
681        let data = CharacterData::String("0".to_string());
682        let result = data.parse_float().unwrap();
683        assert_eq!(result, 0.0);
684
685        // hex (> 32 bits)
686        let data = CharacterData::String("0xFFFFFFFFF".to_string());
687        let result = data.parse_float().unwrap();
688        assert_eq!(result, 68719476735.0);
689
690        // invalid format, hex and float can't be mixed like this
691        let data = CharacterData::String("0x1.234".to_string());
692        let result = data.parse_float();
693        assert!(result.is_none());
694
695        // float: normal number
696        let data = CharacterData::String("0.0001".to_string());
697        let result = data.parse_float().unwrap();
698        assert_eq!(result, 0.0001);
699
700        // float: normal number
701        let data = CharacterData::String("1.234e5".to_string());
702        let result = data.parse_float().unwrap();
703        assert_eq!(result, 1.234e5);
704
705        // float: 0.12 - not octal, note the leading zero!
706        let data = CharacterData::String("00.12".to_string());
707        let result = data.parse_float().unwrap();
708        assert_eq!(result, 0.12);
709
710        // float: NaN
711        let data = CharacterData::String("NaN".to_string());
712        let result = data.parse_float().unwrap();
713        assert!(result.is_nan());
714
715        // float: infinity
716        let data = CharacterData::String("INF".to_string());
717        let result = data.parse_float().unwrap();
718        assert!(result.is_infinite());
719
720        // hex
721        let data = CharacterData::String("0x1234".to_string());
722        let result = data.parse_float().unwrap();
723        assert_eq!(result, 4660.0);
724
725        // hex
726        let data = CharacterData::String("0X1234".to_string());
727        let result = data.parse_float().unwrap();
728        assert_eq!(result, 4660.0);
729
730        // binary
731        let data = CharacterData::String("0b1101".to_string());
732        let result = data.parse_float().unwrap();
733        assert_eq!(result, 13.0);
734
735        // binary
736        let data = CharacterData::String("0B1101".to_string());
737        let result = data.parse_float().unwrap();
738        assert_eq!(result, 13.0);
739
740        // octal
741        let data = CharacterData::String("0777".to_string());
742        let result = data.parse_float().unwrap();
743        assert_eq!(result, 511.0);
744
745        // integer value
746        let data = CharacterData::UnsignedInteger(0);
747        let result = data.parse_float().unwrap();
748        assert_eq!(result, 0.0);
749
750        // float value
751        let data = CharacterData::Float(5.0);
752        let result = data.parse_float().unwrap();
753        assert_eq!(result, 5.0);
754
755        // enum value
756        let data = CharacterData::Enum(EnumItem::Abstract);
757        let result = data.parse_float();
758        assert!(result.is_none());
759    }
760
761    #[test]
762    fn parse_bool() {
763        let data = CharacterData::String("true".to_string());
764        let result = data.parse_bool().unwrap();
765        assert!(result);
766
767        let data = CharacterData::String("1".to_string());
768        let result = data.parse_bool().unwrap();
769        assert!(result);
770
771        let data = CharacterData::String("false".to_string());
772        let result = data.parse_bool().unwrap();
773        assert!(!result);
774
775        let data = CharacterData::String("0".to_string());
776        let result = data.parse_bool().unwrap();
777        assert!(!result);
778
779        let data = CharacterData::String("text".to_string());
780        let result = data.parse_bool();
781        assert!(result.is_none());
782
783        let data = CharacterData::UnsignedInteger(0);
784        let result = data.parse_bool();
785        assert!(result.is_none());
786    }
787
788    #[test]
789    fn ordering() {
790        let enum1 = CharacterData::Enum(EnumItem::Abstract);
791        let enum2 = CharacterData::Enum(EnumItem::default);
792        assert!(enum1 < enum2);
793
794        let string1 = CharacterData::String("abcdef".to_string());
795        let string2 = CharacterData::String("text".to_string());
796        assert!(string1 < string2);
797
798        let integer1 = CharacterData::UnsignedInteger(123);
799        let integer2 = CharacterData::UnsignedInteger(456);
800        assert!(integer1 < integer2);
801
802        let float1 = CharacterData::Float(1.23);
803        let float2 = CharacterData::Float(4.56);
804        assert!(float1 < float2);
805
806        // for unequal data types, the order is (arbitrarily) defined as enum < string < unsigned integer < float
807        assert!(enum1 < string1);
808        assert!(enum1 < integer1);
809        assert!(enum1 < float1);
810        assert!(string1 > enum1);
811        assert!(string1 < integer1);
812        assert!(string1 < float1);
813        assert!(integer1 > enum1);
814        assert!(integer1 > string1);
815        assert!(integer1 < float1);
816        assert!(float1 > enum1);
817        assert!(float1 > string1);
818        assert!(float1 > integer1);
819
820        // PartialOrd
821        assert!(enum1.partial_cmp(&enum2).unwrap() == std::cmp::Ordering::Less);
822    }
823
824    #[test]
825    fn cdata_conversion() {
826        let cdata: CharacterData = 0.into();
827        assert_eq!(cdata, CharacterData::UnsignedInteger(0));
828
829        let cdata: CharacterData = (1.0).into();
830        assert_eq!(cdata, CharacterData::Float(1.0));
831
832        let cdata: CharacterData = "text".into();
833        assert_eq!(cdata, CharacterData::String("text".to_string()));
834
835        let cdata: CharacterData = String::from("text").into();
836        assert_eq!(cdata, CharacterData::String("text".to_string()));
837
838        let cdata: CharacterData = EnumItem::Abstract.into();
839        assert_eq!(cdata, CharacterData::Enum(EnumItem::Abstract));
840
841        let cdata: CharacterData = true.into();
842        assert_eq!(cdata, CharacterData::String("true".to_string()));
843    }
844}