autosar_data/
parser.rs

1use autosar_data_specification::{
2    AttributeName, AttributeSpec, AutosarVersion, CharacterDataSpec, ContentMode, ElementMultiplicity, ElementName,
3    ElementType, EnumItem,
4};
5use smallvec::SmallVec;
6use std::borrow::Cow;
7use std::collections::HashSet;
8use std::path::PathBuf;
9use std::str::FromStr;
10use std::str::Utf8Error;
11use thiserror::Error;
12
13use crate::lexer::{ArxmlEvent, ArxmlLexer};
14use crate::{
15    Attribute, AutosarDataError, CharacterData, Element, ElementContent, ElementOrModel, ElementRaw, WeakElement,
16};
17
18#[derive(Debug, Error)]
19#[non_exhaustive]
20/// `ArxmlParserError` contains all the errors that can occur while parsing a file
21pub enum ArxmlParserError {
22    /// The arxml file header is invalid
23    #[error("Invalid arxml file: bad file header")]
24    InvalidArxmlFileHeader,
25
26    /// An XML file header was unexpectedly found inside the ARXML data
27    #[error("Unexpeded XML file header found inside ARXML data")]
28    UnexpectedXmlFileHeader {
29        /// The element that was open when the unexpected XML file header was found
30        element: ElementName,
31    },
32
33    /// The file version in the xsi:schemaLocation attribute is unknown
34    #[error("Unknown Autosar xsd file {input_verstring} referenced in the file header")]
35    UnknownAutosarVersion {
36        /// The version string that was found in the file header
37        input_verstring: String,
38    },
39
40    /// The file version in the xsi:schemaLocation attribute is invalid, but can be corrected
41    #[error("Invalid Autosar xsd file {input_verstring} referenced in the file header should be replaced with {}", .replacement.filename())]
42    InvalidAutosarVersion {
43        /// The version string that was found in the file header
44        input_verstring: String,
45        /// The corrected version
46        replacement: AutosarVersion,
47    },
48
49    /// A valid name of an Autosar element was found, but it is not allowed in the current context
50    #[error("Encountered unexpected child element {sub_element} inside element {element}")]
51    IncorrectBeginElement {
52        /// The parent element where the error occurred
53        element: ElementName,
54        /// The unexpected child element
55        sub_element: ElementName,
56    },
57
58    /// An xml element was found, but it is not a valid Autosar element
59    #[error(
60        "Encountered invalid child element {invalid_element} inside parent element {element}. {invalid_element} is not a known Autosar element."
61    )]
62    InvalidBeginElement {
63        /// The parent element where the error occurred
64        element: ElementName,
65        /// The name of the invalid child element
66        invalid_element: String,
67    },
68
69    /// An element was opened, but the closing tag for a different element was found
70    #[error("Encountered the closing tag for element {other_element}, but element {element} was open.")]
71    IncorrectEndElement {
72        /// The element that was open when the incorrect closing tag was found
73        element: ElementName,
74        /// The name of the element that was closed
75        other_element: ElementName,
76    },
77
78    /// An xml element was closed, but it is not a valid Autosar element
79    #[error(
80        "Encountered invalid end tag for element {invalid_element} inside parent element {parent_element}. {invalid_element} is not a known Autosar element."
81    )]
82    InvalidEndElement {
83        /// The parent element where the error occurred
84        parent_element: ElementName,
85        /// The name of the invalid element that was closed
86        invalid_element: String,
87    },
88
89    /// A parent element contains multiple sub elements which are mutually exclusive
90    #[error("Multiple conflicting sub elements have been added to element {element}. The latest was {sub_element}.")]
91    ElementChoiceConflict {
92        /// The parent element where the error occurred
93        element: ElementName,
94        /// The name of the conflicting sub element
95        sub_element: ElementName,
96    },
97
98    /// The element contains a sub element that is not allowed in the current Autosar version
99    #[error("Element {sub_element} exists in {element}, but is not allowed in {version}")]
100    ElementVersionError {
101        /// The parent element where the error occurred
102        element: ElementName,
103        /// The sub element that is not allowed
104        sub_element: ElementName,
105        /// The Autosar version in which the sub element is not allowed
106        version: AutosarVersion,
107    },
108
109    /// A sub element is only allowed to be present once inside a parent element, but another occurrence was found
110    #[error("Only one {sub_element} is allowed inside {element}, but another occurrence was found")]
111    TooManySubElements {
112        /// The parent element where the error occurred
113        element: ElementName,
114        /// The name of the sub element that was found multiple times
115        sub_element: ElementName,
116    },
117
118    /// A required sub element is missing from a parent element
119    #[error("The required sub element {sub_element} was not found in element {element}")]
120    RequiredSubelementMissing {
121        /// The parent element where the error occurred
122        element: ElementName,
123        /// The name of the missing sub element
124        sub_element: ElementName,
125    },
126
127    /// An attribute value yould not be parsed
128    #[error("Could not parse the attribute text \"{attribute_text}\" in element {element}")]
129    AttributeValueError {
130        /// The element where the error occurred
131        element: ElementName,
132        /// The attribute text that could not be parsed
133        attribute_text: String,
134    },
135
136    /// An unknown attribute was found in an element
137    #[error("Element {element} contains unknown attribute {attribute}")]
138    UnknownAttributeError {
139        /// The element where the error occurred
140        element: ElementName,
141        /// The name of the unknown attribute
142        attribute: String,
143    },
144
145    /// A known attribute was found, but it is not allowed in the current Autosar version
146    #[error("Attribute {attribute} exists in element {element}, but is not allowed in {version}")]
147    AttributeVersionError {
148        /// The element where the error occurred
149        element: ElementName,
150        /// The name of the attribute that is not allowed
151        attribute: AttributeName,
152        /// The Autosar version in which the attribute is not allowed
153        version: AutosarVersion,
154    },
155
156    /// An attribute is required in an element, but it was not found
157    #[error("Attribute {attribute} is required in element {element}, but was not found")]
158    RequiredAttributeMissing {
159        /// The element where the error occurred
160        element: ElementName,
161        /// The name of the missing attribute
162        attribute: AttributeName,
163    },
164
165    /// Character content was found inside an element that does not allow it
166    #[error("Character content found, which is not allowed inside element {element}")]
167    CharacterContentForbidden {
168        /// The element where the error occurred
169        element: ElementName,
170    },
171
172    /// A valid enum item was found, but it is not allowed in the current autosar version
173    #[error("enum item {enum_item} is a valid value in element {element}, but is not allowed in {version}")]
174    EnumItemVersionError {
175        /// The element where the error occurred
176        element: ElementName,
177        /// The enum item that is not allowed
178        enum_item: EnumItem,
179        /// The Autosar version in which the enum item is not allowed
180        version: AutosarVersion,
181    },
182
183    /// A string could not be parsed as a valid enum item
184    #[error("string {value} is not a valid enum item")]
185    UnknownEnumItem {
186        /// The string that could not be parsed as an enum item
187        value: String,
188    },
189
190    /// Parsed a valid enum item, but it is not part of the enum in the current context
191    #[error("enum item {item} is not valid in element {element}")]
192    InvalidEnumItem {
193        /// The element where the error occurred
194        element: ElementName,
195        /// The invalid enum item
196        item: EnumItem,
197    },
198
199    /// The string value is too long
200    #[error("string value {value} is too long: max length is {length}")]
201    StringValueTooLong {
202        /// The string that is too long
203        value: String,
204        /// The maximum allowed length
205        length: usize,
206    },
207
208    /// The string value does not match the validation regex
209    #[error("string value {value} is not matched by the validation regex {regex}")]
210    RegexMatchError {
211        /// The string that does not match the regex
212        value: String,
213        /// The regex that the string should match
214        regex: String,
215    },
216
217    /// Some bytes from the input could not be converted to a utf-8 string
218    #[error("could not convert value to utf-8: {source}")]
219    Utf8Error {
220        /// The original error returned by std::str::from_utf8
221        source: Utf8Error,
222    },
223
224    /// The end of the input was reached unexpectedly while parsing an element
225    #[error("Unexpected end of file while parsing element {element}")]
226    UnexpectedEndOfFile {
227        /// The element that was open when the end of the file was reached
228        element: ElementName,
229    },
230
231    /// A number was expected, but the input could not be parsed as a number
232    #[error("Failed to parse {input} as a number")]
233    InvalidNumber {
234        /// The input that could not be parsed as a number
235        input: String,
236    },
237
238    /// The input contains additional data after the final `</AUTOSAR>` element
239    #[error("Additional data found in the input after the final </AUTOSAR> element")]
240    AdditionalDataError,
241
242    /// The input contains an invalid XML entity
243    #[error("Invalid XML entity in {input}")]
244    InvalidXmlEntity {
245        /// The invalid XML entity
246        input: String,
247    },
248}
249
250pub(crate) struct ArxmlParser<'a> {
251    filename: PathBuf,
252    line: usize,
253    buffer: &'a [u8],
254    fileversion: AutosarVersion,
255    current_element: ElementName,
256    strict: bool,
257    version_compatibility: u32,
258    pub(crate) identifiables: Vec<(String, WeakElement)>,
259    pub(crate) references: Vec<(String, WeakElement)>,
260    pub(crate) warnings: Vec<AutosarDataError>,
261    standalone: Option<bool>,
262}
263
264impl<'a> ArxmlParser<'a> {
265    pub(crate) fn new(filename: PathBuf, buffer: &'a [u8], strict: bool) -> Self {
266        Self {
267            filename,
268            line: 1,
269            buffer,
270            fileversion: AutosarVersion::Autosar_4_0_1, // this is temporary and gets replaced as soon as the xsd declaration in the top-level AUTOSAR element is read
271            current_element: ElementName::Autosar,
272            strict,
273            version_compatibility: u32::MAX,
274            identifiables: Vec::new(),
275            references: Vec::new(),
276            warnings: Vec::new(),
277            standalone: None,
278        }
279    }
280
281    fn next<'b>(&mut self, lexer: &'b mut ArxmlLexer) -> Result<ArxmlEvent<'b>, AutosarDataError> {
282        let (line, event) = lexer.next()?;
283        self.line = line;
284        Ok(event)
285    }
286
287    pub(crate) fn error(&self, err: ArxmlParserError) -> AutosarDataError {
288        AutosarDataError::ParserError {
289            filename: self.filename.clone(),
290            line: self.line,
291            source: err,
292        }
293    }
294
295    pub(crate) fn optional_error(&mut self, err: ArxmlParserError) -> Result<(), AutosarDataError> {
296        let wrapped_err = AutosarDataError::ParserError {
297            filename: self.filename.clone(),
298            line: self.line,
299            source: err,
300        };
301        if self.strict {
302            Err(wrapped_err)
303        } else {
304            self.warnings.push(wrapped_err);
305            Ok(())
306        }
307    }
308
309    fn check_version(&mut self, item_version: u32, error: ArxmlParserError) -> Result<(), AutosarDataError> {
310        self.version_compatibility &= item_version;
311        if (self.fileversion as u32) & item_version == 0 {
312            self.optional_error(error)
313        } else {
314            Ok(())
315        }
316    }
317
318    /// parse an arxml file and return the root element of the parsed hierarchy
319    pub(crate) fn parse_arxml(&mut self) -> Result<Element, AutosarDataError> {
320        let mut lexer = ArxmlLexer::new(self.buffer, self.filename.clone());
321
322        if let ArxmlEvent::ArxmlHeader(standalone) = self.next(&mut lexer)? {
323            self.standalone = standalone;
324        } else {
325            return Err(self.error(ArxmlParserError::InvalidArxmlFileHeader));
326        }
327
328        let mut stored_comment = None;
329        let mut token = self.next(&mut lexer)?;
330        while let ArxmlEvent::Comment(comment_bytes) = token {
331            stored_comment = Some(String::from_utf8_lossy(comment_bytes).into());
332            token = self.next(&mut lexer)?;
333        }
334
335        if let ArxmlEvent::BeginElement(elemname, attributes_text) = token
336            && let Ok(ElementName::Autosar) = ElementName::from_bytes(elemname)
337        {
338            let attributes = self.parse_attribute_text(ElementType::ROOT, attributes_text)?;
339            self.parse_file_header(&attributes)?;
340
341            let new_element = ElementRaw {
342                parent: ElementOrModel::None,
343                elemname: ElementName::Autosar,
344                elemtype: ElementType::ROOT,
345                content: SmallVec::new(),
346                attributes,
347                file_membership: HashSet::with_capacity(0),
348                comment: stored_comment,
349            };
350            let path = Cow::from("");
351            let autosar_root_element = self.parse_element(new_element, path, &mut lexer)?;
352            self.verify_end_of_input(&mut lexer)?;
353
354            return Ok(autosar_root_element);
355        }
356        Err(self.error(ArxmlParserError::InvalidArxmlFileHeader))
357    }
358
359    /// parse the arxml file header
360    fn parse_file_header(&mut self, attributes: &SmallVec<[Attribute; 1]>) -> Result<(), AutosarDataError> {
361        let attr_xmlns = attributes.iter().find(|attr| attr.attrname == AttributeName::xmlns);
362        let attr_xsi = attributes.iter().find(|attr| attr.attrname == AttributeName::xmlnsXsi);
363        let attr_schema = attributes
364            .iter()
365            .find(|attr| attr.attrname == AttributeName::xsiSchemalocation);
366        if let (
367            Some(Attribute {
368                content: CharacterData::String(xmlns),
369                ..
370            }),
371            Some(Attribute {
372                content: CharacterData::String(xsi),
373                ..
374            }),
375            Some(Attribute {
376                content: CharacterData::String(schema),
377                ..
378            }),
379        ) = (attr_xmlns, attr_xsi, attr_schema)
380        {
381            if xmlns != "http://autosar.org/schema/r4.0" || xsi != "http://www.w3.org/2001/XMLSchema-instance" {
382                return Err(self.error(ArxmlParserError::InvalidArxmlFileHeader));
383            }
384            self.fileversion = self.parse_file_version(schema)?;
385
386            Ok(())
387        } else {
388            Err(self.error(ArxmlParserError::InvalidArxmlFileHeader))
389        }
390    }
391
392    /// get the file version from the value of the xsi:schemaLocation attribute
393    fn parse_file_version(&mut self, schema: &str) -> Result<AutosarVersion, AutosarDataError> {
394        let mut schema_parts = schema.split(' ');
395        let schema_base = schema_parts.next().unwrap_or("");
396        if schema_base != "http://autosar.org/schema/r4.0" {
397            return Err(self.error(ArxmlParserError::InvalidArxmlFileHeader));
398        }
399        let xsd_file_raw = schema_parts.next().unwrap_or("");
400        let xsd_file: String = if xsd_file_raw.starts_with("autosar") {
401            format!("AUTOSAR{}", xsd_file_raw.strip_prefix("autosar").unwrap())
402        } else {
403            xsd_file_raw.to_owned()
404        };
405        let version = if let Ok(autosar_version) = AutosarVersion::from_str(&xsd_file) {
406            autosar_version
407        } else if xsd_file == "AUTOSAR_4-3-1.xsd" {
408            // compat helper - a manually edited file might have a plausible but invalid version which can be corrected
409            // AUTOSAR_4-3-1.xsd -> AUTOSAR_00044.xsd
410            self.optional_error(ArxmlParserError::InvalidAutosarVersion {
411                input_verstring: xsd_file.to_string(),
412                replacement: AutosarVersion::Autosar_00044,
413            })?;
414            AutosarVersion::Autosar_00044
415        } else if xsd_file == "AUTOSAR_4-4-0.xsd" {
416            // compat helper - a manually edited file might have a plausible but invalid version which can be corrected
417            // AUTOSAR_4-4-0.xsd -> AUTOSAR_00046.xsd
418            self.optional_error(ArxmlParserError::InvalidAutosarVersion {
419                input_verstring: xsd_file.to_string(),
420                replacement: AutosarVersion::Autosar_00046,
421            })?;
422            AutosarVersion::Autosar_00046
423        } else if xsd_file == "AUTOSAR_4-5-0.xsd" {
424            // compat helper - a manually edited file might have a plausible but invalid version which can be corrected
425            // AUTOSAR_4-5-0.xsd -> AUTOSAR_00048.xsd
426            self.optional_error(ArxmlParserError::InvalidAutosarVersion {
427                input_verstring: xsd_file.to_string(),
428                replacement: AutosarVersion::Autosar_00048,
429            })?;
430            AutosarVersion::Autosar_00048
431        } else {
432            self.optional_error(ArxmlParserError::UnknownAutosarVersion {
433                input_verstring: xsd_file.to_string(),
434            })?;
435            AutosarVersion::LATEST
436        };
437        Ok(version)
438    }
439
440    /// return the standalone attribute from the xml header
441    pub(crate) fn get_standalone(&self) -> Option<bool> {
442        self.standalone
443    }
444
445    /// parse a single element of an arxml file
446    fn parse_element(
447        &mut self,
448        raw_element: ElementRaw,
449        mut path: Cow<str>,
450        lexer: &mut ArxmlLexer,
451    ) -> Result<Element, AutosarDataError> {
452        let wrapped_element = raw_element.wrap();
453        let mut element = wrapped_element.0.write();
454
455        let mut elem_idx: Vec<usize> = Vec::new();
456        let mut short_name_found = false;
457
458        let mut stored_comment = None;
459        loop {
460            // track the current element name in the parser for error messages - set this in every loop iteration, since it gets overwritten during the recursive calls
461            self.current_element = element.elemname;
462            let arxmlevent = self.next(lexer)?;
463            match arxmlevent {
464                ArxmlEvent::BeginElement(elem_text, attr_text) => {
465                    if let Ok(name) = ElementName::from_bytes(elem_text) {
466                        let (sub_elemtype, idx) = self.find_element_in_spec_checked(name, element.elemtype)?;
467                        self.check_element_conflict(name, element.elemtype, &elem_idx, &idx)?;
468                        elem_idx = idx;
469
470                        // make sure there aren't too many of this kind of element
471                        if !element.content.is_empty() {
472                            self.check_multiplicity(name, element.elemtype, &elem_idx, &element)?;
473                        }
474
475                        // recursively parse the sub element and its sub sub elements
476                        let new_element = ElementRaw {
477                            parent: ElementOrModel::Element(wrapped_element.downgrade()),
478                            elemname: name,
479                            elemtype: sub_elemtype,
480                            content: SmallVec::new(),
481                            attributes: self.parse_attribute_text(sub_elemtype, attr_text)?,
482                            file_membership: HashSet::with_capacity(0),
483                            comment: stored_comment,
484                        };
485                        let sub_element = self.parse_element(new_element, Cow::from(path.as_ref()), lexer)?;
486                        stored_comment = None;
487                        // if this sub element was a short name, then Autosar path handling is needed
488                        if name == ElementName::ShortName {
489                            short_name_found = true;
490                            let sub_element_inner = sub_element.0.read();
491                            if let Some(ElementContent::CharacterData(CharacterData::String(name_string))) =
492                                sub_element_inner.content.first()
493                            {
494                                let mut new_path = String::with_capacity(path.len() + name_string.len() + 1);
495                                new_path.push_str(&path);
496                                new_path.push('/');
497                                new_path.push_str(name_string);
498                                path = Cow::from(new_path.clone());
499                                self.identifiables.push((new_path, wrapped_element.downgrade()));
500                            }
501                        }
502                        element.content.push(ElementContent::Element(sub_element));
503                    } else {
504                        return Err(self.error(ArxmlParserError::InvalidBeginElement {
505                            element: element.elemname,
506                            invalid_element: String::from_utf8_lossy(elem_text).to_string(),
507                        }));
508                    }
509                }
510                ArxmlEvent::EndElement(elem_text) => {
511                    if let Ok(name) = ElementName::from_bytes(elem_text) {
512                        if name == element.elemname {
513                            break;
514                        }
515                        return Err(self.error(ArxmlParserError::IncorrectEndElement {
516                            element: element.elemname,
517                            other_element: name,
518                        }));
519                    }
520                    return Err(self.error(ArxmlParserError::InvalidEndElement {
521                        parent_element: element.elemname,
522                        invalid_element: String::from_utf8_lossy(elem_text).to_string(),
523                    }));
524                }
525                ArxmlEvent::Characters(text_content) => {
526                    if let Some(character_data_spec) = element.elemtype.chardata_spec() {
527                        let value = self.parse_character_data(text_content, character_data_spec)?;
528                        if element.elemtype.is_ref()
529                            && let CharacterData::String(refpath) = &value
530                        {
531                            self.references.push((refpath.to_owned(), wrapped_element.downgrade()));
532                        }
533                        element.content.push(ElementContent::CharacterData(value));
534                    } else {
535                        self.optional_error(ArxmlParserError::CharacterContentForbidden {
536                            element: element.elemname,
537                        })?;
538                    }
539                }
540                ArxmlEvent::ArxmlHeader(_) => self.optional_error(ArxmlParserError::UnexpectedXmlFileHeader {
541                    element: element.elemname,
542                })?,
543                ArxmlEvent::EndOfFile => {
544                    return Err(self.error(ArxmlParserError::UnexpectedEndOfFile {
545                        element: element.elemname,
546                    }));
547                }
548                ArxmlEvent::Comment(comment_bytes) => {
549                    stored_comment = Some(String::from_utf8_lossy(comment_bytes).into());
550                }
551            }
552        }
553
554        if short_name_found {
555        } else if element.elemtype.is_named_in_version(self.fileversion) {
556            self.optional_error(ArxmlParserError::RequiredSubelementMissing {
557                element: element.elemname,
558                sub_element: ElementName::ShortName,
559            })?;
560        }
561
562        Ok(wrapped_element.clone())
563    }
564
565    fn find_element_in_spec_checked(
566        &mut self,
567        name: ElementName,
568        elemtype: ElementType,
569    ) -> Result<(ElementType, Vec<usize>), AutosarDataError> {
570        // Some elements have multiple entries, and the correct one must be chosen based on the autosar version
571        // First try to find the sub element using the current file version. If that fails then search again
572        // allowing elements from all autosar versions. This is useful in order to give better diagnostics.
573        let (sub_elem_type, new_elem_indices) =
574            if let Some(result) = elemtype.find_sub_element(name, self.fileversion as u32) {
575                // normal case: the element was found in the spec, while restricted to only the current version
576                result
577            } else {
578                // fallback: the search is retried, while allowing matching sub-elements from any AutosarVersion
579                let (sub_elemtype, elem_idx) = elemtype.find_sub_element(name, u32::MAX).ok_or_else(|| {
580                    self.error(ArxmlParserError::IncorrectBeginElement {
581                        element: self.current_element,
582                        sub_element: name,
583                    })
584                })?;
585                // now we need to get the version mask that tells us in what versions this element was actually allowed in
586                // unwrap() is ok here since this can't fail: elem_idx just came from find_sub_element
587                let version_mask = elemtype.get_sub_element_version_mask(&elem_idx).unwrap();
588                // check_version will return an ElementVersionError is strict parsing is on, otherwise it's a warning
589                self.check_version(
590                    version_mask,
591                    ArxmlParserError::ElementVersionError {
592                        element: self.current_element,
593                        sub_element: name,
594                        version: self.fileversion,
595                    },
596                )?;
597                (sub_elemtype, elem_idx)
598            };
599
600        Ok((sub_elem_type, new_elem_indices))
601    }
602
603    fn check_element_conflict(
604        &mut self,
605        name: ElementName,
606        elemtype: ElementType,
607        elem_indices: &[usize],
608        new_elem_indices: &Vec<usize>,
609    ) -> Result<(), AutosarDataError> {
610        if elem_indices.is_empty() || (elem_indices == new_elem_indices) {
611            // when elem_indices is empty, that means that this is the first sub-element or found the exact same element as last time
612            // no ordering checks are possible
613        } else {
614            let group_type = elemtype.find_common_group(elem_indices, new_elem_indices);
615            let mode = group_type.content_mode();
616
617            match mode {
618                ContentMode::Sequence => {
619                    // We could check if the elements are in the specified order.
620                    // Unfortunaltely the tool used by the Autosar organisation to derive the xsd files from the meta model seems to be buggy.
621                    // For example, VARIATION-POINT should always be last according to the meta model, but some of the xsd files do not place it there.
622                    // Since other tools seem to skip this check, lets also ignore ordering.
623                }
624                ContentMode::Choice => {
625                    self.optional_error(ArxmlParserError::ElementChoiceConflict {
626                        element: self.current_element,
627                        sub_element: name,
628                    })?;
629                }
630                ContentMode::Characters => {
631                    // an element with ContentMode::Characters has no sub elements, so the outer "if let Some(new_elem_indices)" is never true
632                    panic!("accepted a sub-element inside a character-only element");
633                }
634                _ => {}
635            }
636        }
637        Ok(())
638    }
639
640    fn check_multiplicity(
641        &mut self,
642        name: ElementName,
643        elemtype: ElementType,
644        elem_idx: &[usize],
645        element: &ElementRaw,
646    ) -> Result<(), AutosarDataError> {
647        // get the parent type id, i.e. the type of the containing element or group
648        let datatype_mode = elemtype.get_sub_element_container_mode(elem_idx);
649        // multiplicity only matters if the mode is Choice or Sequence - modes Mixed and Bag allow arbitrary amounts of all elements
650        if (datatype_mode == ContentMode::Sequence || datatype_mode == ContentMode::Choice)
651            && let Some(multiplicity) = elemtype.get_sub_element_multiplicity(elem_idx)
652        {
653            // multiplicity only needs to be checked if it is not Any - i.e. One / ZeroOrOne
654            if multiplicity != ElementMultiplicity::Any {
655                // there is a conflict if there is already a subelement with the same ElementName
656                if element.content.iter().any(|ec| {
657                    ec.unwrap_element()
658                        .is_some_and(|subelem| subelem.element_name() == name)
659                }) {
660                    self.optional_error(ArxmlParserError::TooManySubElements {
661                        element: self.current_element,
662                        sub_element: name,
663                    })?;
664                }
665            }
666        }
667        Ok(())
668    }
669
670    fn parse_attribute_text(
671        &mut self,
672        elemtype: ElementType,
673        attributes_text: &[u8],
674    ) -> Result<SmallVec<[Attribute; 1]>, AutosarDataError> {
675        let mut attributes = SmallVec::new();
676        // attributes_text is a byte string containig all the attributes of an element
677        // for example: xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-2-2.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
678        let startpos = attributes_text
679            .iter()
680            .position(|c| !c.is_ascii_whitespace())
681            .unwrap_or(0);
682        let mut rem = &attributes_text[startpos..];
683        while let Some(equals_pos) = rem.iter().position(|c| *c == b'=') {
684            let attr_name_part = &rem[..equals_pos];
685            if rem.len() - equals_pos < 3 {
686                // minimally the attribute name should be followed by an equals sign and two quotes (empty string)
687                break;
688            }
689            let quote_char = rem[equals_pos + 1];
690            if quote_char != b'"' && quote_char != b'\'' {
691                // the attribute value should be enclosed in quotes
692                break;
693            }
694            rem = &rem[equals_pos + 2..];
695            let Some(endquote_pos) = rem.iter().position(|c| c == &quote_char) else {
696                // failed to find the end of the attribute value
697                break;
698            };
699            let attr_value_part = &rem[..endquote_pos];
700
701            if let Ok(attr_name) = AttributeName::from_bytes(attr_name_part) {
702                if let Some(AttributeSpec {
703                    spec: ctype,
704                    version: version_mask,
705                    ..
706                }) = elemtype.find_attribute_spec(attr_name)
707                {
708                    self.check_version(
709                        version_mask,
710                        ArxmlParserError::AttributeVersionError {
711                            element: self.current_element,
712                            attribute: attr_name,
713                            version: self.fileversion,
714                        },
715                    )?;
716                    let attr_value = self.parse_character_data(attr_value_part, ctype)?;
717                    attributes.push(Attribute {
718                        attrname: attr_name,
719                        content: attr_value,
720                    });
721                } else {
722                    self.optional_error(ArxmlParserError::UnknownAttributeError {
723                        element: self.current_element,
724                        attribute: attr_name.to_string(),
725                    })?;
726                }
727            } else {
728                self.optional_error(ArxmlParserError::UnknownAttributeError {
729                    element: self.current_element,
730                    attribute: String::from_utf8_lossy(attr_name_part).to_string(),
731                })?;
732            }
733
734            // skip whitespace and move to the next attribute
735            let mut nextattr_start = endquote_pos + 1;
736            while nextattr_start < rem.len() && rem[nextattr_start].is_ascii_whitespace() {
737                nextattr_start += 1;
738            }
739
740            // verify that there was at least one whitespace character after the end quote
741            if nextattr_start < rem.len() && nextattr_start == endquote_pos + 1 {
742                // the attributes should be separated by whitespace; if not then there is a problem in the file
743                break;
744            }
745            rem = &rem[nextattr_start..];
746        }
747
748        if !rem.is_empty() && !rem.iter().all(|c| c.is_ascii_whitespace()) {
749            self.optional_error(ArxmlParserError::AttributeValueError {
750                element: self.current_element,
751                attribute_text: String::from_utf8_lossy(attributes_text).into_owned(),
752            })?;
753        }
754
755        for (name, _ctype, required) in elemtype.attribute_spec_iter() {
756            if required && !attributes.iter().any(|attr: &Attribute| attr.attrname == name) {
757                self.optional_error(ArxmlParserError::RequiredAttributeMissing {
758                    element: self.current_element,
759                    attribute: name,
760                })?;
761            }
762        }
763
764        Ok(attributes)
765    }
766
767    fn parse_character_data(
768        &mut self,
769        input: &[u8],
770        character_data_spec: &CharacterDataSpec,
771    ) -> Result<CharacterData, AutosarDataError> {
772        let trimmed_input = trim_byte_string(input);
773        match character_data_spec {
774            CharacterDataSpec::Enum { items } => {
775                let value = EnumItem::from_bytes(trimmed_input).map_err(|_| {
776                    self.error(ArxmlParserError::UnknownEnumItem {
777                        value: String::from_utf8_lossy(trimmed_input).to_string(),
778                    })
779                })?;
780                let (_, version) = items.iter().find(|(item, _)| *item == value).ok_or_else(|| {
781                    self.error(ArxmlParserError::InvalidEnumItem {
782                        element: self.current_element,
783                        item: value,
784                    })
785                })?;
786                self.check_version(
787                    *version,
788                    ArxmlParserError::EnumItemVersionError {
789                        element: self.current_element,
790                        enum_item: value,
791                        version: self.fileversion,
792                    },
793                )?;
794                Ok(CharacterData::Enum(value))
795            }
796            CharacterDataSpec::Pattern {
797                check_fn,
798                regex,
799                max_length,
800            } => {
801                if max_length.is_some() && trimmed_input.len() > max_length.unwrap() {
802                    self.optional_error(ArxmlParserError::StringValueTooLong {
803                        value: String::from_utf8_lossy(trimmed_input).to_string(),
804                        length: max_length.unwrap(),
805                    })?;
806                }
807                if !check_fn(trimmed_input) {
808                    self.optional_error(ArxmlParserError::RegexMatchError {
809                        value: String::from_utf8_lossy(trimmed_input).to_string(),
810                        regex: (*regex).to_string(),
811                    })?;
812                }
813                // text with regex pattern validation doesn't need unescaping - none of the regexes will allow any of the the escaped chars
814                match std::str::from_utf8(trimmed_input) {
815                    Ok(utf8string) => Ok(CharacterData::String(utf8string.to_owned())),
816                    Err(err) => {
817                        self.optional_error(ArxmlParserError::Utf8Error { source: err })?;
818                        Ok(CharacterData::String(
819                            String::from_utf8_lossy(trimmed_input).into_owned(),
820                        ))
821                    }
822                }
823            }
824            CharacterDataSpec::String {
825                preserve_whitespace,
826                max_length,
827            } => {
828                let raw_text = if *preserve_whitespace { input } else { trimmed_input };
829                if max_length.is_some() && raw_text.len() > max_length.unwrap() {
830                    self.optional_error(ArxmlParserError::StringValueTooLong {
831                        value: String::from_utf8_lossy(trimmed_input).to_string(),
832                        length: max_length.unwrap(),
833                    })?;
834                }
835                let text = match std::str::from_utf8(raw_text) {
836                    Ok(utf8string) => Cow::from(utf8string),
837                    Err(err) => {
838                        self.optional_error(ArxmlParserError::Utf8Error { source: err })?;
839                        String::from_utf8_lossy(raw_text)
840                    }
841                };
842                let unescaped_text = self.unescape_string(&text)?.into_owned();
843                Ok(CharacterData::String(unescaped_text))
844            }
845            CharacterDataSpec::UnsignedInteger => {
846                let strval = std::str::from_utf8(trimmed_input)
847                    .map_err(|err| self.error(ArxmlParserError::Utf8Error { source: err }))?;
848                let value = match strval.parse::<u64>() {
849                    Ok(parsed) => parsed,
850                    Err(_) => {
851                        self.optional_error(ArxmlParserError::InvalidNumber {
852                            input: strval.to_owned(),
853                        })?;
854                        0
855                    }
856                };
857                Ok(CharacterData::UnsignedInteger(value))
858            }
859            CharacterDataSpec::Float => {
860                let strval = std::str::from_utf8(trimmed_input)
861                    .map_err(|err| self.error(ArxmlParserError::Utf8Error { source: err }))?;
862                let value = match strval.parse::<f64>() {
863                    Ok(parsed) => parsed,
864                    Err(_) => {
865                        self.optional_error(ArxmlParserError::InvalidNumber {
866                            input: strval.to_owned(),
867                        })?;
868                        0.0
869                    }
870                };
871                Ok(CharacterData::Float(value))
872            }
873        }
874    }
875
876    fn unescape_string<'b>(&mut self, input: &'b str) -> Result<Cow<'b, str>, AutosarDataError> {
877        if input.contains('&') {
878            let mut unescaped = String::with_capacity(input.len());
879            let mut rem = input;
880            while let Some(pos) = rem.find('&') {
881                unescaped.push_str(&rem[..pos]);
882                rem = &rem[pos..];
883                if rem.starts_with("&lt;") {
884                    unescaped.push('<');
885                    rem = &rem[4..];
886                } else if rem.starts_with("&gt;") {
887                    unescaped.push('>');
888                    rem = &rem[4..];
889                } else if rem.starts_with("&amp;") {
890                    unescaped.push('&');
891                    rem = &rem[5..];
892                } else if rem.starts_with("&apos;") {
893                    unescaped.push('\'');
894                    rem = &rem[6..];
895                } else if rem.starts_with("&quot;") {
896                    unescaped.push('"');
897                    rem = &rem[6..];
898                } else if rem.starts_with("&#x") {
899                    // hexadecimal character reference
900                    let mut valid = false;
901                    if let Some(endpos) = rem.find(';') {
902                        let hextxt = &rem[3..endpos];
903                        if let Ok(hexval) = u32::from_str_radix(hextxt, 16)
904                            && let Some(ch) = char::from_u32(hexval)
905                        {
906                            unescaped.push(ch);
907                            rem = &rem[endpos + 1..];
908                            valid = true;
909                        }
910                    }
911                    if !valid {
912                        self.optional_error(ArxmlParserError::InvalidXmlEntity {
913                            input: input.to_owned(),
914                        })?;
915                        unescaped.push('&');
916                        rem = &rem[1..];
917                    }
918                } else if rem.starts_with("&#") {
919                    // decimal character reference
920                    let mut valid = false;
921                    if let Some(endpos) = rem.find(';') {
922                        let numtxt = &rem[2..endpos];
923                        if let Ok(val) = u32::from_str(numtxt)
924                            && let Some(ch) = char::from_u32(val)
925                        {
926                            unescaped.push(ch);
927                            rem = &rem[endpos + 1..];
928                            valid = true;
929                        }
930                    }
931                    if !valid {
932                        self.optional_error(ArxmlParserError::InvalidXmlEntity {
933                            input: input.to_owned(),
934                        })?;
935                        unescaped.push('&');
936                        rem = &rem[1..];
937                    }
938                } else {
939                    self.optional_error(ArxmlParserError::InvalidXmlEntity {
940                        input: input.to_owned(),
941                    })?;
942                    unescaped.push('&');
943                    rem = &rem[1..];
944                }
945            }
946            unescaped.push_str(rem);
947
948            Ok(Cow::Owned(unescaped))
949        } else {
950            Ok(Cow::Borrowed(input))
951        }
952    }
953
954    pub(crate) fn get_fileversion(&self) -> AutosarVersion {
955        self.fileversion
956    }
957
958    fn verify_end_of_input(&mut self, lexer: &mut ArxmlLexer) -> Result<(), AutosarDataError> {
959        let (_, next_event) = lexer.next()?;
960        if let ArxmlEvent::EndOfFile = next_event {
961            Ok(())
962        } else {
963            self.optional_error(ArxmlParserError::AdditionalDataError)?;
964            Ok(())
965        }
966    }
967
968    /// parse an arxml file and return the root element of the parsed hierarchy
969    pub(crate) fn check_arxml_header(&mut self) -> bool {
970        let mut lexer = ArxmlLexer::new(self.buffer, self.filename.clone());
971
972        if let Ok(ArxmlEvent::ArxmlHeader(_)) = self.next(&mut lexer) {
973            // skip any comments
974            let mut arxmlevent = self.next(&mut lexer);
975            while let Ok(ArxmlEvent::Comment(..)) = arxmlevent {
976                arxmlevent = self.next(&mut lexer);
977            }
978            if let Ok(ArxmlEvent::BeginElement(elemname, attributes_text)) = arxmlevent
979                && let Ok(ElementName::Autosar) = ElementName::from_bytes(elemname)
980                && let Ok(attributes) = self.parse_attribute_text(ElementType::ROOT, attributes_text)
981                && self.parse_file_header(&attributes).is_ok()
982            {
983                // no errors after parsing the header - this looks like an arxml file
984                return true;
985            }
986        }
987
988        false
989    }
990}
991
992fn trim_byte_string(input: &[u8]) -> &[u8] {
993    let mut len = input.len();
994    if len > 0 {
995        while input[len - 1].is_ascii_whitespace() {
996            len -= 1;
997        }
998        let start = input.iter().position(|c| !c.is_ascii_whitespace()).unwrap_or(len);
999        &input[start..len]
1000    } else {
1001        input
1002    }
1003}
1004
1005#[cfg(test)]
1006mod test {
1007    use crate::parser::*;
1008    use crate::*;
1009
1010    fn test_helper(buffer: &[u8], target_error: std::mem::Discriminant<ArxmlParserError>, optional: bool) {
1011        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), buffer, true);
1012        let result = parser.parse_arxml();
1013        if let Err(AutosarDataError::ParserError { source, .. }) = result {
1014            println!("Error result: {source:?}");
1015            assert_eq!(
1016                std::mem::discriminant(&source),
1017                target_error,
1018                "Did not get the expected parser error"
1019            );
1020        } else {
1021            panic!("Did not get any parser error when one was expected");
1022        }
1023
1024        if optional {
1025            let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), buffer, false);
1026            let _result = parser.parse_arxml();
1027            if let Some(AutosarDataError::ParserError { source, .. }) = parser.warnings.first() {
1028                println!("Warnings result: {source:?}");
1029                assert_eq!(
1030                    std::mem::discriminant(source),
1031                    target_error,
1032                    "Did not get the expected parser error"
1033                );
1034            } else {
1035                panic!("Did not get a parser warning");
1036            }
1037        }
1038    }
1039
1040    const INVALID_HEADER_1: &str = "BLA BLA bla";
1041    const INVALID_HEADER_2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1042    <something>"#;
1043    const INVALID_HEADER_3: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1044    <AUTOSAR xsi:schemaLocation="nonsense" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#;
1045    const INVALID_HEADER_4: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1046    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00049.xsd" xmlns="http://other" xmlns:xsi="http://other">"#;
1047
1048    #[test]
1049    fn test_invalid_header() {
1050        test_helper(
1051            INVALID_HEADER_1.as_bytes(),
1052            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1053            false,
1054        );
1055        test_helper(
1056            INVALID_HEADER_2.as_bytes(),
1057            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1058            false,
1059        );
1060        test_helper(
1061            INVALID_HEADER_3.as_bytes(),
1062            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1063            false,
1064        );
1065        test_helper(
1066            INVALID_HEADER_4.as_bytes(),
1067            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1068            false,
1069        );
1070    }
1071
1072    const HDR_SINGLE_QUOTE: &str = r#"<?xml version='1.0' encoding='utf-8'?>
1073    <AUTOSAR xsi:schemaLocation='http://autosar.org/schema/r4.0 autosar_4-3-0.xsd' xmlns='http://autosar.org/schema/r4.0' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
1074    </AUTOSAR>"#;
1075
1076    #[test]
1077    fn single_quotes_in_header() {
1078        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), HDR_SINGLE_QUOTE.as_bytes(), true);
1079        let result = parser.parse_arxml();
1080        assert!(result.is_ok());
1081    }
1082
1083    const SCHEMA_VERSION_LC: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1084    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 autosar_4-3-0.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1085    </AUTOSAR>"#;
1086    const INVALID_VERSION_4_3_1: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1087    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-3-1.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1088    </AUTOSAR>"#;
1089    const INVALID_VERSION_4_4_0: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1090    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-4-0.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1091    </AUTOSAR>"#;
1092    const INVALID_VERSION_4_5_0: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1093    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-5-0.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1094    </AUTOSAR>"#;
1095
1096    #[test]
1097    fn test_invalid_version() {
1098        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), SCHEMA_VERSION_LC.as_bytes(), true);
1099        let result = parser.parse_arxml();
1100        assert!(result.is_ok());
1101
1102        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidAutosarVersion {
1103            input_verstring: "".to_string(),
1104            replacement: AutosarVersion::Autosar_00044,
1105        });
1106        test_helper(INVALID_VERSION_4_3_1.as_bytes(), discriminant, true);
1107        test_helper(INVALID_VERSION_4_4_0.as_bytes(), discriminant, true);
1108        test_helper(INVALID_VERSION_4_5_0.as_bytes(), discriminant, true);
1109    }
1110
1111    const UNKNOWN_VERSION: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1112    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_something_else.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1113    </AUTOSAR>"#;
1114
1115    #[test]
1116    fn test_unknown_version() {
1117        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownAutosarVersion {
1118            input_verstring: "".to_string(),
1119        });
1120        test_helper(UNKNOWN_VERSION.as_bytes(), discriminant, true);
1121    }
1122
1123    const UNEXPECTED_XML_FILE_HEADER: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1124    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1125    <?xml version="1.0" encoding="utf-8"?>
1126    </AUTOSAR>"#;
1127
1128    #[test]
1129    fn test_unexpected_xml_file_header() {
1130        let discriminant = std::mem::discriminant(&ArxmlParserError::UnexpectedXmlFileHeader {
1131            element: ElementName::Autosar,
1132        });
1133        test_helper(UNEXPECTED_XML_FILE_HEADER.as_bytes(), discriminant, true);
1134    }
1135
1136    const INCORRECT_BEGIN_ELEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1137    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1138    <ELEMENT>"#;
1139
1140    #[test]
1141    fn test_incorrect_begin_element() {
1142        let discriminant = std::mem::discriminant(&ArxmlParserError::IncorrectBeginElement {
1143            element: ElementName::Autosar,
1144            sub_element: ElementName::Autosar,
1145        });
1146        test_helper(INCORRECT_BEGIN_ELEMENT.as_bytes(), discriminant, false);
1147    }
1148
1149    const INVALID_BEGIN_ELEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1150    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1151    <NOT_AN_AUTOSAR_ELEMENT>"#;
1152
1153    #[test]
1154    fn test_invalid_begin_element() {
1155        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidBeginElement {
1156            element: ElementName::Autosar,
1157            invalid_element: "".to_string(),
1158        });
1159        test_helper(INVALID_BEGIN_ELEMENT.as_bytes(), discriminant, false);
1160    }
1161
1162    const INCORRECT_END_ELEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1163    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1164    <AR-PACKAGES></AUTOSAR>"#;
1165
1166    #[test]
1167    fn test_incorrect_end_element() {
1168        let discriminant = std::mem::discriminant(&ArxmlParserError::IncorrectEndElement {
1169            element: ElementName::Autosar,
1170            other_element: ElementName::Autosar,
1171        });
1172        test_helper(INCORRECT_END_ELEMENT.as_bytes(), discriminant, false);
1173    }
1174
1175    const INVALID_END_ELEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1176    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1177    <AR-PACKAGES></NOT_AN_AUTOSAR_ELEMENT>"#;
1178
1179    #[test]
1180    fn test_invalid_end_element() {
1181        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidEndElement {
1182            parent_element: ElementName::Autosar,
1183            invalid_element: "".to_string(),
1184        });
1185        test_helper(INVALID_END_ELEMENT.as_bytes(), discriminant, false);
1186    }
1187
1188    const ELEMENT_VERSION_ERROR: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1189    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-0-1.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1190    <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>TestPackage</SHORT-NAME><ELEMENTS><DIAGNOSTIC-ACCESS-PERMISSION>"#;
1191
1192    #[test]
1193    fn test_element_version_error() {
1194        let discriminant = std::mem::discriminant(&ArxmlParserError::ElementVersionError {
1195            element: ElementName::Autosar,
1196            sub_element: ElementName::Autosar,
1197            version: AutosarVersion::Autosar_00050,
1198        });
1199        test_helper(ELEMENT_VERSION_ERROR.as_bytes(), discriminant, false);
1200    }
1201
1202    const CHOICE_CONFLICT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1203    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1204        <AR-PACKAGES>
1205            <AR-PACKAGE>
1206                <SHORT-NAME>base</SHORT-NAME>
1207                <ELEMENTS>
1208                    <DIAGNOSTIC-CONTRIBUTION-SET>
1209                        <SHORT-NAME>dcs</SHORT-NAME>
1210                        <COMMON-PROPERTIES>
1211                            <DIAGNOSTIC-COMMON-PROPS-VARIANTS>
1212                                <DIAGNOSTIC-COMMON-PROPS-CONDITIONAL>
1213                                    <DEBOUNCE-ALGORITHM-PROPSS>
1214                                        <DIAGNOSTIC-DEBOUNCE-ALGORITHM-PROPS>
1215                                            <SHORT-NAME>props</SHORT-NAME>
1216                                            <DEBOUNCE-ALGORITHM>
1217                                                <DIAG-EVENT-DEBOUNCE-COUNTER-BASED>
1218                                                    <SHORT-NAME>abc</SHORT-NAME>
1219                                                </DIAG-EVENT-DEBOUNCE-COUNTER-BASED>
1220                                                <DIAG-EVENT-DEBOUNCE-TIME-BASED>
1221                                                    <SHORT-NAME>def</SHORT-NAME>
1222                                                </DIAG-EVENT-DEBOUNCE-TIME-BASED>"#;
1223
1224    #[test]
1225    fn test_choice_conflict() {
1226        let discriminant = std::mem::discriminant(&ArxmlParserError::ElementChoiceConflict {
1227            element: ElementName::Autosar,
1228            sub_element: ElementName::Autosar,
1229        });
1230        test_helper(CHOICE_CONFLICT.as_bytes(), discriminant, true);
1231    }
1232
1233    const TOO_MANY_SUBELEMENTS: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1234    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1235        <AR-PACKAGES>
1236            <AR-PACKAGE>
1237                <SHORT-NAME>base</SHORT-NAME>
1238                <SHORT-NAME>base</SHORT-NAME>"#;
1239
1240    #[test]
1241    fn test_too_many_sub_elements() {
1242        let discriminant = std::mem::discriminant(&ArxmlParserError::TooManySubElements {
1243            element: ElementName::Autosar,
1244            sub_element: ElementName::Autosar,
1245        });
1246        test_helper(TOO_MANY_SUBELEMENTS.as_bytes(), discriminant, true);
1247    }
1248
1249    const REQUIRED_SUB_ELEMENT_MISSING: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1250    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1251        <AR-PACKAGES>
1252            <AR-PACKAGE></AR-PACKAGE>"#;
1253
1254    #[test]
1255    fn test_required_sub_element_missing() {
1256        let discriminant = std::mem::discriminant(&ArxmlParserError::RequiredSubelementMissing {
1257            element: ElementName::Autosar,
1258            sub_element: ElementName::Autosar,
1259        });
1260        test_helper(REQUIRED_SUB_ELEMENT_MISSING.as_bytes(), discriminant, false);
1261    }
1262
1263    const UNKNOWN_ATTRIBUTE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1264    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1265    <AR-PACKAGES UnknownAttribute="value">
1266    </AR-PACKAGES></AUTOSAR>"#;
1267    const UNKNOWN_ATTRIBUTE_2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1268    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1269    <AR-PACKAGES DEST="value">
1270    </AR-PACKAGES></AUTOSAR>"#;
1271
1272    #[test]
1273    fn test_unknown_attribute() {
1274        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownAttributeError {
1275            element: ElementName::Autosar,
1276            attribute: "".to_string(),
1277        });
1278        test_helper(UNKNOWN_ATTRIBUTE.as_bytes(), discriminant, true);
1279        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownAttributeError {
1280            element: ElementName::Autosar,
1281            attribute: "DEST".to_string(),
1282        });
1283        test_helper(UNKNOWN_ATTRIBUTE_2.as_bytes(), discriminant, true);
1284    }
1285
1286    const REQUIRED_ATTRIBUTE_MISSING: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1287    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0">
1288    </AUTOSAR>"#;
1289
1290    #[test]
1291    fn test_required_attribute_missing() {
1292        let discriminant = std::mem::discriminant(&ArxmlParserError::RequiredAttributeMissing {
1293            element: ElementName::Autosar,
1294            attribute: AttributeName::Accesskey,
1295        });
1296        test_helper(REQUIRED_ATTRIBUTE_MISSING.as_bytes(), discriminant, true);
1297    }
1298
1299    const CHARACTER_CONTENT_FORBIDDEN: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1300    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1301    abcdef"#;
1302
1303    #[test]
1304    fn test_character_content_forbidden() {
1305        let discriminant = std::mem::discriminant(&ArxmlParserError::CharacterContentForbidden {
1306            element: ElementName::Autosar,
1307        });
1308        test_helper(CHARACTER_CONTENT_FORBIDDEN.as_bytes(), discriminant, false);
1309    }
1310
1311    const WRONG_ENUM_ITEM_VERSION: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1312    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00044.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1313        <AR-PACKAGES>
1314            <AR-PACKAGE>
1315                <SHORT-NAME>base</SHORT-NAME>
1316                <ELEMENTS>
1317                    <SYSTEM>
1318                        <SHORT-NAME>System</SHORT-NAME>
1319                        <FIBEX-ELEMENTS>
1320                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1321                                <FIBEX-ELEMENT-REF DEST="SERVICE-INSTANCE-COLLECTION-SET">"#;
1322
1323    #[test]
1324    fn test_enum_item_version() {
1325        let discriminant = std::mem::discriminant(&ArxmlParserError::EnumItemVersionError {
1326            element: ElementName::Autosar,
1327            enum_item: EnumItem::Aa,
1328            version: AutosarVersion::Autosar_00050,
1329        });
1330        test_helper(WRONG_ENUM_ITEM_VERSION.as_bytes(), discriminant, false);
1331    }
1332
1333    const UNKNOWN_ENUM_ITEM: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1334    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1335        <AR-PACKAGES>
1336            <AR-PACKAGE>
1337                <SHORT-NAME>base</SHORT-NAME>
1338                <ELEMENTS>
1339                    <SYSTEM>
1340                        <SHORT-NAME>System</SHORT-NAME>
1341                        <FIBEX-ELEMENTS>
1342                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1343                                <FIBEX-ELEMENT-REF DEST="invalid_value_for_the_test">"#;
1344
1345    #[test]
1346    fn test_unknown_enum_item() {
1347        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownEnumItem { value: "".to_string() });
1348        test_helper(UNKNOWN_ENUM_ITEM.as_bytes(), discriminant, false);
1349    }
1350
1351    const INVALID_ENUM_ITEM: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1352    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1353        <AR-PACKAGES>
1354            <AR-PACKAGE>
1355                <SHORT-NAME>base</SHORT-NAME>
1356                <ELEMENTS>
1357                    <SYSTEM>
1358                        <SHORT-NAME>System</SHORT-NAME>
1359                        <FIBEX-ELEMENTS>
1360                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1361                                <FIBEX-ELEMENT-REF DEST="default">"#;
1362
1363    #[test]
1364    fn test_invalid_enum_item() {
1365        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidEnumItem {
1366            element: ElementName::Abs,
1367            item: EnumItem::Aa,
1368        });
1369        test_helper(INVALID_ENUM_ITEM.as_bytes(), discriminant, false);
1370    }
1371
1372    const STRING_VALUE_TOO_LONG: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1373    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1374        <AR-PACKAGES><AR-PACKAGE>
1375            <SHORT-NAME>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</SHORT-NAME>
1376        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1377
1378    #[test]
1379    fn test_string_value_too_long() {
1380        let discriminant = std::mem::discriminant(&ArxmlParserError::StringValueTooLong {
1381            value: "".to_string(),
1382            length: 1,
1383        });
1384        test_helper(STRING_VALUE_TOO_LONG.as_bytes(), discriminant, true);
1385    }
1386
1387    const REGEX_MATCH_ERROR: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1388    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1389        <AR-PACKAGES><AR-PACKAGE>
1390            <SHORT-NAME>0a</SHORT-NAME>
1391        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1392
1393    #[test]
1394    fn test_regex_match_error() {
1395        let discriminant = std::mem::discriminant(&ArxmlParserError::RegexMatchError {
1396            value: "".to_string(),
1397            regex: "".to_string(),
1398        });
1399        test_helper(REGEX_MATCH_ERROR.as_bytes(), discriminant, true);
1400    }
1401
1402    const UTF8_ERROR: &[u8] = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>
1403    <AUTOSAR xsi:schemaLocation=\"http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd\" xmlns=\"http://autosar.org/schema/r4.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
1404        <AR-PACKAGES><AR-PACKAGE S=\"\xff\xff\">";
1405
1406    #[test]
1407    fn test_utf8_error() {
1408        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), UTF8_ERROR, true);
1409        let result = parser.parse_arxml();
1410        assert!(
1411            matches!(
1412                result,
1413                Err(AutosarDataError::ParserError {
1414                    source: ArxmlParserError::Utf8Error { .. },
1415                    ..
1416                })
1417            ),
1418            "Did not get the expected parser error"
1419        );
1420
1421        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), UTF8_ERROR, false);
1422        let _ = parser.parse_arxml();
1423        let warning = parser.warnings.first();
1424        assert!(
1425            matches!(
1426                warning,
1427                Some(AutosarDataError::ParserError {
1428                    source: ArxmlParserError::Utf8Error { .. },
1429                    ..
1430                })
1431            ),
1432            "Did not get the expected parser warning"
1433        );
1434    }
1435
1436    const UNEXPECTED_END_OF_FILE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1437    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#;
1438
1439    #[test]
1440    fn test_unexpected_end_of_file() {
1441        let discriminant = std::mem::discriminant(&ArxmlParserError::UnexpectedEndOfFile {
1442            element: ElementName::Autosar,
1443        });
1444        test_helper(UNEXPECTED_END_OF_FILE.as_bytes(), discriminant, false);
1445    }
1446
1447    const INVALID_NUMBER: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1448    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1449    <AR-PACKAGES><AR-PACKAGE>
1450        <SHORT-NAME>base</SHORT-NAME>
1451        <ELEMENTS><I-SIGNAL-I-PDU>
1452            <SHORT-NAME>Pdu</SHORT-NAME>
1453            <I-PDU-TIMING-SPECIFICATIONS><I-PDU-TIMING><TRANSMISSION-MODE-DECLARATION><TRANSMISSION-MODE-TRUE-TIMING><CYCLIC-TIMING>
1454            <TIME-PERIOD><TOLERANCE><ABSOLUTE-TOLERANCE><ABSOLUTE>not a number</ABSOLUTE>"#;
1455
1456    #[test]
1457    fn test_invalid_number() {
1458        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidNumber { input: "".to_string() });
1459        test_helper(INVALID_NUMBER.as_bytes(), discriminant, true);
1460    }
1461
1462    const ADDITIONAL_DATA: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1463    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1464    </AUTOSAR>
1465    <extra>"#;
1466
1467    #[test]
1468    fn test_additional_data_error() {
1469        let discriminant = std::mem::discriminant(&ArxmlParserError::AdditionalDataError);
1470        test_helper(ADDITIONAL_DATA.as_bytes(), discriminant, true);
1471    }
1472
1473    #[test]
1474    fn unescape_entities() {
1475        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), &[], true);
1476        let result = parser
1477            .unescape_string("&amp;&amp;&lt;FOO&gt;&quot;&quot;&apos;&#32;&#x20;end")
1478            .unwrap();
1479        assert_eq!(&result, r#"&&<FOO>""'  end"#);
1480        let result = parser.unescape_string("&amp;&amp;&gt;FOO&lt;&quot&quot;&apos;end");
1481        assert!(result.is_err());
1482        // numeric character entity does not accept hex values
1483        let result = parser.unescape_string("&#abcde;");
1484        assert!(result.is_err());
1485        // values from 0x110000 to 0x1FFFFF are not valid unicode code points -> 0x110000 = 1114112
1486        let result = parser.unescape_string("&#1114112;");
1487        assert!(result.is_err());
1488    }
1489
1490    const PARSER_TEST_DATA: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1491    <!--comment-->
1492    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1493        <!--comment-->
1494        <AR-PACKAGES>
1495            <AR-PACKAGE>
1496                <SHORT-NAME>base</SHORT-NAME>
1497                <ELEMENTS>
1498                    <SYSTEM UUID="12345678" S="some string" T="2022-01-31T13:00:59Z">
1499                        <SHORT-NAME>System</SHORT-NAME>
1500                        <FIBEX-ELEMENTS>
1501                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1502                                <FIBEX-ELEMENT-REF DEST="I-SIGNAL-I-PDU">/base/Pdu</FIBEX-ELEMENT-REF>
1503                            </FIBEX-ELEMENT-REF-CONDITIONAL>
1504                        </FIBEX-ELEMENTS>
1505                    </SYSTEM>
1506                    <I-SIGNAL-I-PDU>
1507                        <SHORT-NAME>Pdu</SHORT-NAME>
1508                        <I-PDU-TIMING-SPECIFICATIONS>
1509                            <I-PDU-TIMING>
1510                                <TRANSMISSION-MODE-DECLARATION>
1511                                    <TRANSMISSION-MODE-TRUE-TIMING>
1512                                        <CYCLIC-TIMING>
1513                                            <TIME-PERIOD>
1514                                                <TOLERANCE>
1515                                                    <ABSOLUTE-TOLERANCE>
1516                                                        <ABSOLUTE>1.0</ABSOLUTE>
1517                                                    </ABSOLUTE-TOLERANCE>
1518                                                </TOLERANCE>
1519                                            </TIME-PERIOD>
1520                                        </CYCLIC-TIMING>
1521                                    </TRANSMISSION-MODE-TRUE-TIMING>
1522                                </TRANSMISSION-MODE-DECLARATION>
1523                            </I-PDU-TIMING>
1524                        </I-PDU-TIMING-SPECIFICATIONS>
1525                    </I-SIGNAL-I-PDU>
1526                </ELEMENTS>
1527            </AR-PACKAGE>
1528        </AR-PACKAGES>
1529    </AUTOSAR>
1530    "#;
1531
1532    #[test]
1533    fn test_basic_functionality() {
1534        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), PARSER_TEST_DATA.as_bytes(), true);
1535        let result = parser.parse_arxml();
1536        assert!(result.is_ok());
1537    }
1538
1539    const EMPTY_CHARACTER_DATA: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1540    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1541        <AR-PACKAGES>
1542            <AR-PACKAGE UUID="">
1543                <SHORT-NAME>x</SHORT-NAME>
1544            </AR-PACKAGE>
1545        </AR-PACKAGES>
1546    </AUTOSAR>
1547    "#;
1548
1549    #[test]
1550    fn test_empty_character_data() {
1551        let mut parser = ArxmlParser::new(
1552            PathBuf::from("test_buffer.arxml"),
1553            EMPTY_CHARACTER_DATA.as_bytes(),
1554            true,
1555        );
1556        let result = parser.parse_arxml();
1557        assert!(result.is_ok());
1558    }
1559
1560    #[test]
1561    fn chardata_utf8_error() {
1562        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), b"", true);
1563        let mut parser_permissive = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), b"", false);
1564
1565        // correct input for CharacterDataSpec::Pattern
1566        let pattern_spec = CharacterDataSpec::Pattern {
1567            check_fn: |_| true,
1568            regex: "",
1569            max_length: Some(2),
1570        };
1571        let result = parser.parse_character_data(b"ab", &pattern_spec);
1572        assert!(result.is_ok());
1573
1574        // trigger the invalid utf-8 error for CharacterDataSpec::Pattern
1575        let result = parser.parse_character_data(&[0xff], &pattern_spec);
1576        assert!(result.is_err());
1577
1578        // permissive parsing allows invalid utf-8 in CharacterDataSpec::Pattern
1579        let result = parser_permissive.parse_character_data(&[0xff], &pattern_spec);
1580        assert!(result.is_ok());
1581
1582        // correct input for CharacterDataSpec::String
1583        let string_spec = CharacterDataSpec::String {
1584            max_length: Some(2),
1585            preserve_whitespace: false,
1586        };
1587        let result = parser.parse_character_data(b"ab", &string_spec);
1588        assert!(result.is_ok());
1589
1590        // input too long for CharacterDataSpec::String
1591        let result = parser.parse_character_data(b"abc", &string_spec);
1592        assert!(result.is_err());
1593
1594        // trigger the invalid utf-8 error for CharacterDataSpec::String
1595        let result = parser.parse_character_data(&[0xff], &string_spec);
1596        assert!(result.is_err());
1597
1598        // correct conversion for CharacterDataSpec::UnsignedInteger
1599        let int_spec = CharacterDataSpec::UnsignedInteger;
1600        let result = parser.parse_character_data(b"123", &int_spec);
1601        assert!(result.is_ok());
1602
1603        // conversion error for CharacterDataSpec::UnsignedInteger: valid utf-8, but not a number
1604        let result = parser.parse_character_data(b"abc", &int_spec);
1605        assert!(result.is_err());
1606
1607        // conversion error for CharacterDataSpec::UnsignedInteger: invalid utf-8
1608        let result = parser.parse_character_data(&[0xff], &int_spec);
1609        assert!(result.is_err());
1610
1611        // correct conversion for CharacterDataSpec::Float
1612        let float_spec = CharacterDataSpec::Float;
1613        let result = parser.parse_character_data(b"1.0", &float_spec);
1614        assert!(result.is_ok());
1615
1616        // conversion error for CharacterDataSpec::Float: valid utf-8, but not a number
1617        let result = parser.parse_character_data(b"abc", &float_spec);
1618        assert!(result.is_err());
1619
1620        // conversion error for CharacterDataSpec::Float: invalid utf-8
1621        let result = parser.parse_character_data(&[0xff], &float_spec);
1622        assert!(result.is_err());
1623    }
1624
1625    #[test]
1626    fn test_check_arxml_header() {
1627        let buffer = "abcde".as_bytes();
1628        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1629        assert!(!parser.check_arxml_header());
1630
1631        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>abcde"#.as_bytes();
1632        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1633        assert!(!parser.check_arxml_header());
1634
1635        let buffer = r#"<?xml version="1.0" encoding="utf-8"?><abcde>"#.as_bytes();
1636        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1637        assert!(!parser.check_arxml_header());
1638
1639        let buffer = r#"<?xml version="1.0" encoding="utf-8"?><AUTOSAR abcde="abcde">"#.as_bytes();
1640        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1641        assert!(!parser.check_arxml_header());
1642
1643        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>
1644<AUTOSAR>"#
1645            .as_bytes();
1646        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1647        assert!(!parser.check_arxml_header());
1648
1649        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>
1650<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 abcdef" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#.as_bytes();
1651        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1652        assert!(!parser.check_arxml_header());
1653
1654        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>
1655<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#.as_bytes();
1656        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1657        assert!(parser.check_arxml_header());
1658    }
1659
1660    #[test]
1661    fn parse_attribute_text() {
1662        let mut parser = ArxmlParser::new(PathBuf::from("test"), &[], true);
1663        // find the element type of AR-PACKAGE
1664        let etype_arpackage = ElementType::ROOT
1665            .find_sub_element(ElementName::ArPackages, u32::MAX)
1666            .unwrap()
1667            .0
1668            .find_sub_element(ElementName::ArPackage, u32::MAX)
1669            .unwrap()
1670            .0;
1671
1672        // whitespace only, should not return an error
1673        let result = parser.parse_attribute_text(etype_arpackage, br#"   "#);
1674        assert!(result.is_ok());
1675        let value = result.unwrap();
1676        assert!(value.is_empty());
1677
1678        // invalid string
1679        let result = parser.parse_attribute_text(etype_arpackage, br#"   abc   "#);
1680        assert!(result.is_err());
1681
1682        // invalid attribute name, and no value after the '='
1683        let result = parser.parse_attribute_text(etype_arpackage, br#"abc="#);
1684        assert!(result.is_err());
1685
1686        // Attribute name without a value after the '='
1687        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID="#);
1688        assert!(result.is_err());
1689
1690        // valid UUID attribute
1691        let value = parser
1692            .parse_attribute_text(etype_arpackage, br#"UUID="12345678""#)
1693            .unwrap();
1694        assert_eq!(value.len(), 1);
1695        assert_eq!(value[0].attrname, AttributeName::Uuid);
1696
1697        // whitespace after the attribute value
1698        let value = parser
1699            .parse_attribute_text(etype_arpackage, br#"UUID="12345678"   "#)
1700            .unwrap();
1701        assert_eq!(value.len(), 1);
1702
1703        // junk after the attribute value
1704        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID="12345678"   abc   "#);
1705        assert!(result.is_err());
1706
1707        // attribute enclosed in single quotes
1708        let value = parser
1709            .parse_attribute_text(etype_arpackage, br#"UUID='12345678'"#)
1710            .unwrap();
1711        assert_eq!(value.len(), 1);
1712
1713        // missing final quote
1714        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID='12345678"#);
1715        assert!(result.is_err());
1716
1717        // mixed quotes ' -> ", error
1718        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID='12345678""#);
1719        assert!(result.is_err());
1720
1721        // two attributes without whitespace between them
1722        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID="12345678"T="2024-01-01""#);
1723        assert!(result.is_err());
1724
1725        // two attributes with whitespace between them
1726        let value = parser
1727            .parse_attribute_text(etype_arpackage, br#"UUID="12345678" T="2024-01-01""#)
1728            .unwrap();
1729        assert_eq!(value.len(), 2);
1730
1731        // two attributes with extra whitespace between them
1732        let value = parser
1733            .parse_attribute_text(etype_arpackage, br#"UUID="12345678"  T="2024-01-01""#)
1734            .unwrap();
1735        assert_eq!(value.len(), 2);
1736
1737        // two attributes with leading whitespace and extra whitespace between them
1738        let value = parser
1739            .parse_attribute_text(etype_arpackage, br#"  UUID="12345678"  T="2024-01-01""#)
1740            .unwrap();
1741        assert_eq!(value.len(), 2);
1742    }
1743}