Skip to main content

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