Skip to main content

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::path::PathBuf;
8use std::str::FromStr;
9use std::str::Utf8Error;
10use thiserror::Error;
11
12use crate::WeakAutosarModel;
13use crate::lexer::{ArxmlEvent, ArxmlLexer};
14use crate::{
15    Attribute, AutosarDataError, CharacterData, Element, ElementContent, ElementOrModel, ElementRaw, WeakElement,
16};
17
18#[derive(Debug, Error, PartialEq)]
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("Unexpected 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 could 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    /// An attribute occurs multiple times in the same element, which is not allowed
166    #[error("The attribute {attribute} occurs multiple times in the element {element}")]
167    DuplicateAttributeError {
168        /// The name of the attribute that occurs multiple times
169        attribute: AttributeName,
170        /// The name of the element where the attribute occurs multiple times
171        element: ElementName,
172    },
173
174    /// Character content was found inside an element that does not allow it
175    #[error("Character content found, which is not allowed inside element {element}")]
176    CharacterContentForbidden {
177        /// The element where the error occurred
178        element: ElementName,
179    },
180
181    /// A valid enum item was found, but it is not allowed in the current autosar version
182    #[error("enum item {enum_item} is a valid value in element {element}, but is not allowed in {version}")]
183    EnumItemVersionError {
184        /// The element where the error occurred
185        element: ElementName,
186        /// The enum item that is not allowed
187        enum_item: EnumItem,
188        /// The Autosar version in which the enum item is not allowed
189        version: AutosarVersion,
190    },
191
192    /// A string could not be parsed as a valid enum item
193    #[error("string {value} is not a valid enum item")]
194    UnknownEnumItem {
195        /// The string that could not be parsed as an enum item
196        value: String,
197    },
198
199    /// Parsed a valid enum item, but it is not part of the enum in the current context
200    #[error("enum item {item} is not valid in element {element}")]
201    InvalidEnumItem {
202        /// The element where the error occurred
203        element: ElementName,
204        /// The invalid enum item
205        item: EnumItem,
206    },
207
208    /// The string value is too long
209    #[error("string value {value} is too long: max length is {length}")]
210    StringValueTooLong {
211        /// The string that is too long
212        value: String,
213        /// The maximum allowed length
214        length: usize,
215    },
216
217    /// The string value does not match the validation regex
218    #[error("string value {value} is not matched by the validation regex {regex}")]
219    RegexMatchError {
220        /// The string that does not match the regex
221        value: String,
222        /// The regex that the string should match
223        regex: String,
224    },
225
226    /// Some bytes from the input could not be converted to a utf-8 string
227    #[error("could not convert value to utf-8: {source}")]
228    Utf8Error {
229        /// The original error returned by std::str::from_utf8
230        source: Utf8Error,
231    },
232
233    /// The end of the input was reached unexpectedly while parsing an element
234    #[error("Unexpected end of file while parsing element {element}")]
235    UnexpectedEndOfFile {
236        /// The element that was open when the end of the file was reached
237        element: ElementName,
238    },
239
240    /// A number was expected, but the input could not be parsed as a number
241    #[error("Failed to parse {input} as a number")]
242    InvalidNumber {
243        /// The input that could not be parsed as a number
244        input: String,
245    },
246
247    /// The input contains additional data after the final `</AUTOSAR>` element
248    #[error("Additional data found in the input after the final </AUTOSAR> element")]
249    AdditionalDataError,
250
251    /// The input contains an invalid XML entity
252    #[error("Invalid XML entity in {input}")]
253    InvalidXmlEntity {
254        /// The invalid XML entity
255        input: String,
256    },
257
258    /// An element contains a SHORT-NAME which has no content
259    #[error("The SHORT-NAME of element {element} is empty")]
260    EmptyShortName {
261        /// The element whose SHORT-NAME is empty
262        element: ElementName,
263    },
264}
265
266pub(crate) struct ArxmlParser<'a> {
267    filename: PathBuf,
268    line: usize,
269    buffer: &'a [u8],
270    fileversion: AutosarVersion,
271    current_element: ElementName,
272    strict: bool,
273    version_compatibility: u32,
274    pub(crate) identifiables: Vec<(String, WeakElement)>,
275    pub(crate) references: Vec<(String, WeakElement, Option<String>)>,
276    pub(crate) warnings: Vec<AutosarDataError>,
277    standalone: Option<bool>,
278    pub(crate) model: WeakAutosarModel,
279}
280
281impl<'a> ArxmlParser<'a> {
282    pub(crate) fn new(filename: PathBuf, buffer: &'a [u8], strict: bool) -> Self {
283        Self {
284            filename,
285            line: 1,
286            buffer,
287            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
288            current_element: ElementName::Autosar,
289            strict,
290            version_compatibility: u32::MAX,
291            model: WeakAutosarModel::default(),
292            identifiables: Vec::new(),
293            references: Vec::new(),
294            warnings: Vec::new(),
295            standalone: None,
296        }
297    }
298
299    fn next<'b>(&mut self, lexer: &'b mut ArxmlLexer) -> Result<ArxmlEvent<'b>, AutosarDataError> {
300        let (line, event) = lexer.next()?;
301        self.line = line;
302        Ok(event)
303    }
304
305    pub(crate) fn error(&self, err: ArxmlParserError) -> AutosarDataError {
306        AutosarDataError::ParserError {
307            filename: self.filename.clone(),
308            line: self.line,
309            source: err,
310        }
311    }
312
313    pub(crate) fn optional_error(&mut self, err: ArxmlParserError) -> Result<(), AutosarDataError> {
314        let wrapped_err = AutosarDataError::ParserError {
315            filename: self.filename.clone(),
316            line: self.line,
317            source: err,
318        };
319        if self.strict {
320            Err(wrapped_err)
321        } else {
322            self.warnings.push(wrapped_err);
323            Ok(())
324        }
325    }
326
327    fn check_version(&mut self, item_version: u32, error: ArxmlParserError) -> Result<(), AutosarDataError> {
328        self.version_compatibility &= item_version;
329        if (self.fileversion as u32) & item_version == 0 {
330            self.optional_error(error)
331        } else {
332            Ok(())
333        }
334    }
335
336    /// parse an arxml file and return the root element of the parsed hierarchy
337    pub(crate) fn parse_arxml(&mut self) -> Result<Element, AutosarDataError> {
338        let mut lexer = ArxmlLexer::new(self.buffer, self.filename.clone());
339
340        if let ArxmlEvent::ArxmlHeader(standalone) = self.next(&mut lexer)? {
341            self.standalone = standalone;
342        } else {
343            return Err(self.error(ArxmlParserError::InvalidArxmlFileHeader));
344        }
345
346        let mut stored_comment = None;
347        let mut token = self.next(&mut lexer)?;
348        while let ArxmlEvent::Comment(comment_bytes) = token {
349            stored_comment = Some(String::from_utf8_lossy(comment_bytes).into());
350            token = self.next(&mut lexer)?;
351        }
352
353        if let ArxmlEvent::BeginElement(elemname, attributes_text) = token
354            && let Ok(ElementName::Autosar) = ElementName::from_bytes(elemname)
355        {
356            let attributes = self.parse_attribute_text(ElementType::ROOT, attributes_text)?;
357            self.parse_file_header(&attributes)?;
358
359            let new_element = ElementRaw {
360                parent: ElementOrModel::None,
361                elemname: ElementName::Autosar,
362                elemtype: ElementType::ROOT,
363                content: SmallVec::new(),
364                attributes,
365                file_membership: None,
366                comment: stored_comment,
367            };
368            let path = Cow::from("");
369            let autosar_root_element = self.parse_element(new_element, path, &mut lexer)?;
370            self.verify_end_of_input(&mut lexer)?;
371
372            return Ok(autosar_root_element);
373        }
374        Err(self.error(ArxmlParserError::InvalidArxmlFileHeader))
375    }
376
377    /// parse the arxml file header
378    fn parse_file_header(&mut self, attributes: &SmallVec<[Attribute; 1]>) -> Result<(), AutosarDataError> {
379        let attr_xmlns = attributes.iter().find(|attr| attr.attrname == AttributeName::xmlns);
380        let attr_xsi = attributes.iter().find(|attr| attr.attrname == AttributeName::xmlnsXsi);
381        let attr_schema = attributes
382            .iter()
383            .find(|attr| attr.attrname == AttributeName::xsiSchemalocation);
384        if let (
385            Some(Attribute {
386                content: CharacterData::String(xmlns),
387                ..
388            }),
389            Some(Attribute {
390                content: CharacterData::String(xsi),
391                ..
392            }),
393            Some(Attribute {
394                content: CharacterData::String(schema),
395                ..
396            }),
397        ) = (attr_xmlns, attr_xsi, attr_schema)
398        {
399            if xmlns != "http://autosar.org/schema/r4.0" || xsi != "http://www.w3.org/2001/XMLSchema-instance" {
400                return Err(self.error(ArxmlParserError::InvalidArxmlFileHeader));
401            }
402            self.fileversion = self.parse_file_version(schema)?;
403
404            Ok(())
405        } else {
406            Err(self.error(ArxmlParserError::InvalidArxmlFileHeader))
407        }
408    }
409
410    /// get the file version from the value of the xsi:schemaLocation attribute
411    fn parse_file_version(&mut self, schema: &str) -> Result<AutosarVersion, AutosarDataError> {
412        let mut schema_parts = schema.split(' ');
413        let schema_base = schema_parts.next().unwrap_or("");
414        if schema_base != "http://autosar.org/schema/r4.0" {
415            return Err(self.error(ArxmlParserError::InvalidArxmlFileHeader));
416        }
417        let xsd_file_raw = schema_parts.next().unwrap_or("");
418        let xsd_file: String = if xsd_file_raw.starts_with("autosar") {
419            format!("AUTOSAR{}", xsd_file_raw.strip_prefix("autosar").unwrap())
420        } else {
421            xsd_file_raw.to_owned()
422        };
423        let version = if let Ok(autosar_version) = AutosarVersion::from_str(&xsd_file) {
424            autosar_version
425        } else if xsd_file == "AUTOSAR_4-3-1.xsd" {
426            // compat helper - a manually edited file might have a plausible but invalid version which can be corrected
427            // AUTOSAR_4-3-1.xsd -> AUTOSAR_00044.xsd
428            self.optional_error(ArxmlParserError::InvalidAutosarVersion {
429                input_verstring: xsd_file.to_string(),
430                replacement: AutosarVersion::Autosar_00044,
431            })?;
432            AutosarVersion::Autosar_00044
433        } else if xsd_file == "AUTOSAR_4-4-0.xsd" {
434            // compat helper - a manually edited file might have a plausible but invalid version which can be corrected
435            // AUTOSAR_4-4-0.xsd -> AUTOSAR_00046.xsd
436            self.optional_error(ArxmlParserError::InvalidAutosarVersion {
437                input_verstring: xsd_file.to_string(),
438                replacement: AutosarVersion::Autosar_00046,
439            })?;
440            AutosarVersion::Autosar_00046
441        } else if xsd_file == "AUTOSAR_4-5-0.xsd" {
442            // compat helper - a manually edited file might have a plausible but invalid version which can be corrected
443            // AUTOSAR_4-5-0.xsd -> AUTOSAR_00048.xsd
444            self.optional_error(ArxmlParserError::InvalidAutosarVersion {
445                input_verstring: xsd_file.to_string(),
446                replacement: AutosarVersion::Autosar_00048,
447            })?;
448            AutosarVersion::Autosar_00048
449        } else {
450            self.optional_error(ArxmlParserError::UnknownAutosarVersion {
451                input_verstring: xsd_file.to_string(),
452            })?;
453            AutosarVersion::LATEST
454        };
455        Ok(version)
456    }
457
458    /// return the standalone attribute from the xml header
459    pub(crate) fn get_standalone(&self) -> Option<bool> {
460        self.standalone
461    }
462
463    /// parse a single element of an arxml file
464    fn parse_element(
465        &mut self,
466        raw_element: ElementRaw,
467        mut path: Cow<str>,
468        lexer: &mut ArxmlLexer,
469    ) -> Result<Element, AutosarDataError> {
470        let wrapped_element = raw_element.wrap();
471        let mut element = wrapped_element.0.write();
472
473        let mut elem_idx: Vec<usize> = Vec::new();
474        let mut short_name_found = false;
475
476        let mut stored_comment = None;
477        loop {
478            // 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
479            self.current_element = element.elemname;
480            let arxmlevent = self.next(lexer)?;
481            match arxmlevent {
482                ArxmlEvent::BeginElement(elem_text, attr_text) => {
483                    if let Ok(name) = ElementName::from_bytes(elem_text) {
484                        let (sub_elemtype, idx) = self.find_element_in_spec_checked(name, element.elemtype)?;
485                        self.check_element_conflict(name, element.elemtype, &elem_idx, &idx)?;
486                        elem_idx = idx;
487
488                        // make sure there aren't too many of this kind of element
489                        if !element.content.is_empty() {
490                            self.check_multiplicity(name, element.elemtype, &elem_idx, &element)?;
491                        }
492
493                        // recursively parse the sub element and its sub sub elements
494                        let new_element = ElementRaw {
495                            parent: ElementOrModel::Element(wrapped_element.downgrade(), self.model.clone()),
496                            elemname: name,
497                            elemtype: sub_elemtype,
498                            content: SmallVec::new(),
499                            attributes: self.parse_attribute_text(sub_elemtype, attr_text)?,
500                            file_membership: None,
501                            comment: stored_comment,
502                        };
503                        let sub_element = self.parse_element(new_element, Cow::from(path.as_ref()), lexer)?;
504                        stored_comment = None;
505                        // if this sub element was a short name, then Autosar path handling is needed
506                        if name == ElementName::ShortName {
507                            short_name_found = true;
508                            let sub_element_inner = sub_element.0.read();
509                            if let Some(ElementContent::CharacterData(CharacterData::String(name_string))) =
510                                sub_element_inner.content.first()
511                            {
512                                let mut new_path = String::with_capacity(path.len() + name_string.len() + 1);
513                                new_path.push_str(&path);
514                                new_path.push('/');
515                                new_path.push_str(name_string);
516                                path = Cow::from(new_path.clone());
517                                self.identifiables.push((new_path, wrapped_element.downgrade()));
518                            } else {
519                                // An empty SHORT-NAME is not recoverable, so this is an error even when
520                                // strict == false: the element would report is_identifiable() == true while
521                                // having no name, so it could not be added to the path index of the model
522                                // and could never be found or referred to. Omitting the SHORT-NAME entirely
523                                // is a different case, which non-strict parsing still accepts: the element
524                                // is then consistently treated as not identifiable.
525                                return Err(self.error(ArxmlParserError::EmptyShortName {
526                                    element: element.elemname,
527                                }));
528                            }
529                        }
530                        element.content.push(ElementContent::Element(sub_element));
531                    } else {
532                        return Err(self.error(ArxmlParserError::InvalidBeginElement {
533                            element: element.elemname,
534                            invalid_element: String::from_utf8_lossy(elem_text).to_string(),
535                        }));
536                    }
537                }
538                ArxmlEvent::EndElement(elem_text) => {
539                    if let Ok(name) = ElementName::from_bytes(elem_text) {
540                        if name == element.elemname {
541                            break;
542                        }
543                        return Err(self.error(ArxmlParserError::IncorrectEndElement {
544                            element: element.elemname,
545                            other_element: name,
546                        }));
547                    }
548                    return Err(self.error(ArxmlParserError::InvalidEndElement {
549                        parent_element: element.elemname,
550                        invalid_element: String::from_utf8_lossy(elem_text).to_string(),
551                    }));
552                }
553                ArxmlEvent::Characters(text_content) => {
554                    // Known limitation: a comment (or a processing instruction) in the middle of
555                    // character data splits it into several Characters events, and each of them is
556                    // stored as a separate content item. According to the xml specification the
557                    // parts form a single value, so <SHORT-NAME>ab<!--x-->cd</SHORT-NAME> should be
558                    // read as "abcd". Instead the element ends up with two content items, which
559                    // breaks the assumption that an element with ContentMode::Characters holds
560                    // exactly one item: character_data() returns None, the Autosar path is built
561                    // from the first part only, and serializing writes just the first part.
562                    // This is not handled, because arxml is written by tools, which - in every
563                    // observed case - either write no comments at all or place them immediately
564                    // before an element, never inside character data.
565                    // Handling it requires collecting the parts and parsing them only once the run
566                    // of character data ends: parsing each part on its own does not work, since an
567                    // incomplete part may be rejected by the character data spec (e.g. the first
568                    // part "/Pkg/" of a reference does not match the reference regex).
569                    if let Some(character_data_spec) = element.elemtype.chardata_spec() {
570                        let value = self.parse_character_data(text_content, character_data_spec)?;
571                        if element.elemtype.is_ref()
572                            && let CharacterData::String(refpath) = &value
573                        {
574                            let base = element
575                                .attribute_value(AttributeName::Base)
576                                .and_then(|cdata| cdata.string_value());
577                            self.references
578                                .push((refpath.to_owned(), wrapped_element.downgrade(), base));
579                        }
580                        element.content.push(ElementContent::CharacterData(value));
581                    } else {
582                        self.optional_error(ArxmlParserError::CharacterContentForbidden {
583                            element: element.elemname,
584                        })?;
585                    }
586                }
587                ArxmlEvent::ArxmlHeader(_) => self.optional_error(ArxmlParserError::UnexpectedXmlFileHeader {
588                    element: element.elemname,
589                })?,
590                ArxmlEvent::EndOfFile => {
591                    return Err(self.error(ArxmlParserError::UnexpectedEndOfFile {
592                        element: element.elemname,
593                    }));
594                }
595                ArxmlEvent::Comment(comment_bytes) => {
596                    stored_comment = Some(String::from_utf8_lossy(comment_bytes).into());
597                }
598            }
599        }
600
601        if short_name_found {
602        } else if element.elemtype.is_named_in_version(self.fileversion) {
603            self.optional_error(ArxmlParserError::RequiredSubelementMissing {
604                element: element.elemname,
605                sub_element: ElementName::ShortName,
606            })?;
607        }
608
609        Ok(wrapped_element.clone())
610    }
611
612    fn find_element_in_spec_checked(
613        &mut self,
614        name: ElementName,
615        elemtype: ElementType,
616    ) -> Result<(ElementType, Vec<usize>), AutosarDataError> {
617        // Some elements have multiple entries, and the correct one must be chosen based on the autosar version
618        // First try to find the sub element using the current file version. If that fails then search again
619        // allowing elements from all autosar versions. This is useful in order to give better diagnostics.
620        let (sub_elem_type, new_elem_indices) =
621            if let Some(result) = elemtype.find_sub_element(name, self.fileversion as u32) {
622                // normal case: the element was found in the spec, while restricted to only the current version
623                result
624            } else {
625                // fallback: the search is retried, while allowing matching sub-elements from any AutosarVersion
626                let (sub_elemtype, elem_idx) = elemtype.find_sub_element(name, u32::MAX).ok_or_else(|| {
627                    self.error(ArxmlParserError::IncorrectBeginElement {
628                        element: self.current_element,
629                        sub_element: name,
630                    })
631                })?;
632                // now we need to get the version mask that tells us in what versions this element was actually allowed in
633                // unwrap() is ok here since this can't fail: elem_idx just came from find_sub_element
634                let version_mask = elemtype.get_sub_element_version_mask(&elem_idx).unwrap();
635                // check_version will return an ElementVersionError is strict parsing is on, otherwise it's a warning
636                self.check_version(
637                    version_mask,
638                    ArxmlParserError::ElementVersionError {
639                        element: self.current_element,
640                        sub_element: name,
641                        version: self.fileversion,
642                    },
643                )?;
644                (sub_elemtype, elem_idx)
645            };
646
647        Ok((sub_elem_type, new_elem_indices))
648    }
649
650    fn check_element_conflict(
651        &mut self,
652        name: ElementName,
653        elemtype: ElementType,
654        elem_indices: &[usize],
655        new_elem_indices: &Vec<usize>,
656    ) -> Result<(), AutosarDataError> {
657        if elem_indices.is_empty() || (elem_indices == new_elem_indices) {
658            // when elem_indices is empty, that means that this is the first sub-element or found the exact same element as last time
659            // no ordering checks are possible
660        } else {
661            // find_common_group always succeeds here, since both index lists were returned by find_sub_element
662            let Some(group_type) = elemtype.find_common_group(elem_indices, new_elem_indices) else {
663                return Ok(());
664            };
665            let mode = group_type.content_mode();
666
667            match mode {
668                ContentMode::Sequence => {
669                    // We could check if the elements are in the specified order.
670                    // Unfortunately the tool used by the Autosar organisation to derive the xsd files from the meta model seems to be buggy.
671                    // For example, VARIATION-POINT should always be last according to the meta model, but some of the xsd files do not place it there.
672                    // Since other tools seem to skip this check, lets also ignore ordering.
673                }
674                ContentMode::Choice => {
675                    self.optional_error(ArxmlParserError::ElementChoiceConflict {
676                        element: self.current_element,
677                        sub_element: name,
678                    })?;
679                }
680                ContentMode::Characters => {
681                    // an element with ContentMode::Characters has no sub elements, so the outer "if let Some(new_elem_indices)" is never true
682                    panic!("accepted a sub-element inside a character-only element");
683                }
684                _ => {}
685            }
686        }
687        Ok(())
688    }
689
690    fn check_multiplicity(
691        &mut self,
692        name: ElementName,
693        elemtype: ElementType,
694        elem_idx: &[usize],
695        element: &ElementRaw,
696    ) -> Result<(), AutosarDataError> {
697        // get the parent type id, i.e. the type of the containing element or group
698        // multiplicity only matters if the mode is Choice or Sequence - modes Mixed and Bag allow arbitrary amounts of all elements
699        if let Some(ContentMode::Sequence | ContentMode::Choice) = elemtype.get_sub_element_container_mode(elem_idx)
700            && let Some(multiplicity) = elemtype.get_sub_element_multiplicity(elem_idx)
701        {
702            // multiplicity only needs to be checked if it is not Any - i.e. One / ZeroOrOne
703            if multiplicity != ElementMultiplicity::Any {
704                // there is a conflict if there is already a subelement with the same ElementName
705                if element.content.iter().any(|ec| {
706                    ec.unwrap_element()
707                        .is_some_and(|subelem| subelem.element_name() == name)
708                }) {
709                    self.optional_error(ArxmlParserError::TooManySubElements {
710                        element: self.current_element,
711                        sub_element: name,
712                    })?;
713                }
714            }
715        }
716        Ok(())
717    }
718
719    fn parse_attribute_text(
720        &mut self,
721        elemtype: ElementType,
722        attributes_text: &[u8],
723    ) -> Result<SmallVec<[Attribute; 1]>, AutosarDataError> {
724        let mut attributes: SmallVec<[Attribute; 1]> = SmallVec::new();
725        // attributes_text is a byte string containing all the attributes of an element
726        // 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"
727        let startpos = attributes_text
728            .iter()
729            .position(|c| !c.is_ascii_whitespace())
730            .unwrap_or(0);
731        let mut rem = &attributes_text[startpos..];
732        while let Some(mut equals_pos) = rem.iter().position(|c| *c == b'=') {
733            let attr_name_part = rem[..equals_pos].trim_ascii_end();
734            // skip whitespace after the equals sign
735            while let Some(c) = rem.get(equals_pos + 1)
736                && c.is_ascii_whitespace()
737            {
738                equals_pos += 1;
739            }
740            if rem.len() - equals_pos < 3 {
741                // minimally the attribute name should be followed by an equals sign and two quotes (empty string)
742                break;
743            }
744            let quote_char = rem[equals_pos + 1];
745            if quote_char != b'"' && quote_char != b'\'' {
746                // the attribute value should be enclosed in quotes
747                break;
748            }
749            rem = &rem[equals_pos + 2..];
750            let Some(endquote_pos) = rem.iter().position(|c| c == &quote_char) else {
751                // failed to find the end of the attribute value
752                break;
753            };
754            let attr_value_part = &rem[..endquote_pos];
755
756            if let Ok(attr_name) = AttributeName::from_bytes(attr_name_part) {
757                if let Some(AttributeSpec {
758                    spec: ctype,
759                    version: version_mask,
760                    ..
761                }) = elemtype.find_attribute_spec(attr_name)
762                {
763                    self.check_version(
764                        version_mask,
765                        ArxmlParserError::AttributeVersionError {
766                            element: self.current_element,
767                            attribute: attr_name,
768                            version: self.fileversion,
769                        },
770                    )?;
771                    let attr_value = self.parse_character_data(attr_value_part, ctype)?;
772                    if let Some(pos) = attributes
773                        .iter()
774                        .position(|attr: &Attribute| attr.attrname == attr_name)
775                    {
776                        self.optional_error(ArxmlParserError::DuplicateAttributeError {
777                            element: self.current_element,
778                            attribute: attr_name,
779                        })?;
780                        attributes[pos].content = attr_value;
781                    } else {
782                        attributes.push(Attribute {
783                            attrname: attr_name,
784                            content: attr_value,
785                        });
786                    }
787                } else {
788                    self.optional_error(ArxmlParserError::UnknownAttributeError {
789                        element: self.current_element,
790                        attribute: attr_name.to_string(),
791                    })?;
792                }
793            } else {
794                self.optional_error(ArxmlParserError::UnknownAttributeError {
795                    element: self.current_element,
796                    attribute: String::from_utf8_lossy(attr_name_part).to_string(),
797                })?;
798            }
799
800            // skip whitespace and move to the next attribute
801            let mut nextattr_start = endquote_pos + 1;
802            while nextattr_start < rem.len() && rem[nextattr_start].is_ascii_whitespace() {
803                nextattr_start += 1;
804            }
805
806            // verify that there was at least one whitespace character after the end quote
807            if nextattr_start < rem.len() && nextattr_start == endquote_pos + 1 {
808                // the attributes should be separated by whitespace; if not then there is a problem in the file
809                break;
810            }
811            rem = &rem[nextattr_start..];
812        }
813
814        if !rem.is_empty() && !rem.iter().all(|c| c.is_ascii_whitespace()) {
815            self.optional_error(ArxmlParserError::AttributeValueError {
816                element: self.current_element,
817                attribute_text: String::from_utf8_lossy(attributes_text).into_owned(),
818            })?;
819        }
820
821        for (name, _ctype, required) in elemtype.attribute_spec_iter() {
822            if required && !attributes.iter().any(|attr: &Attribute| attr.attrname == name) {
823                self.optional_error(ArxmlParserError::RequiredAttributeMissing {
824                    element: self.current_element,
825                    attribute: name,
826                })?;
827            }
828        }
829
830        Ok(attributes)
831    }
832
833    fn parse_character_data(
834        &mut self,
835        input: &[u8],
836        character_data_spec: &CharacterDataSpec,
837    ) -> Result<CharacterData, AutosarDataError> {
838        let trimmed_input = input.trim_ascii();
839        match character_data_spec {
840            CharacterDataSpec::Enum { items } => {
841                let value = EnumItem::from_bytes(trimmed_input).map_err(|_| {
842                    self.error(ArxmlParserError::UnknownEnumItem {
843                        value: String::from_utf8_lossy(trimmed_input).to_string(),
844                    })
845                })?;
846                let (_, version) = items.iter().find(|(item, _)| *item == value).ok_or_else(|| {
847                    self.error(ArxmlParserError::InvalidEnumItem {
848                        element: self.current_element,
849                        item: value,
850                    })
851                })?;
852                self.check_version(
853                    *version,
854                    ArxmlParserError::EnumItemVersionError {
855                        element: self.current_element,
856                        enum_item: value,
857                        version: self.fileversion,
858                    },
859                )?;
860                Ok(CharacterData::Enum(value))
861            }
862            CharacterDataSpec::Pattern {
863                check_fn,
864                regex,
865                max_length,
866            } => {
867                let text = match std::str::from_utf8(trimmed_input) {
868                    Ok(utf8string) => Cow::Borrowed(utf8string),
869                    Err(err) => {
870                        self.optional_error(ArxmlParserError::Utf8Error { source: err })?;
871                        String::from_utf8_lossy(trimmed_input)
872                    }
873                };
874                // unescape the string before checking length and regex match, since unescape is part of parsing, while the checks are part of validation.
875                let unescaped_text = self.unescape_string(&text)?.into_owned();
876                if max_length.is_some() && unescaped_text.len() > max_length.unwrap() {
877                    self.optional_error(ArxmlParserError::StringValueTooLong {
878                        value: String::from_utf8_lossy(trimmed_input).to_string(), // use the raw value for the error message
879                        length: max_length.unwrap(),
880                    })?;
881                }
882                if !check_fn(unescaped_text.as_bytes()) {
883                    self.optional_error(ArxmlParserError::RegexMatchError {
884                        value: String::from_utf8_lossy(trimmed_input).to_string(), // use the raw value for the error message
885                        regex: (*regex).to_string(),
886                    })?;
887                }
888                Ok(CharacterData::String(unescaped_text))
889            }
890            CharacterDataSpec::String {
891                preserve_whitespace,
892                max_length,
893            } => {
894                let raw_text = if *preserve_whitespace { input } else { trimmed_input };
895                let text = match std::str::from_utf8(raw_text) {
896                    Ok(utf8string) => Cow::from(utf8string),
897                    Err(err) => {
898                        self.optional_error(ArxmlParserError::Utf8Error { source: err })?;
899                        String::from_utf8_lossy(raw_text)
900                    }
901                };
902                let unescaped_text = self.unescape_string(&text)?.into_owned();
903                if max_length.is_some() && unescaped_text.len() > max_length.unwrap() {
904                    self.optional_error(ArxmlParserError::StringValueTooLong {
905                        value: String::from_utf8_lossy(trimmed_input).to_string(),
906                        length: max_length.unwrap(),
907                    })?;
908                }
909                Ok(CharacterData::String(unescaped_text))
910            }
911            CharacterDataSpec::UnsignedInteger => {
912                let strval = std::str::from_utf8(trimmed_input)
913                    .map_err(|err| self.error(ArxmlParserError::Utf8Error { source: err }))?;
914                let value = match strval.parse::<u64>() {
915                    Ok(parsed) => parsed,
916                    Err(_) => {
917                        self.optional_error(ArxmlParserError::InvalidNumber {
918                            input: strval.to_owned(),
919                        })?;
920                        0
921                    }
922                };
923                Ok(CharacterData::UnsignedInteger(value))
924            }
925            CharacterDataSpec::Float => {
926                let strval = std::str::from_utf8(trimmed_input)
927                    .map_err(|err| self.error(ArxmlParserError::Utf8Error { source: err }))?;
928                let value = match strval.parse::<f64>() {
929                    Ok(parsed) if !(parsed.is_infinite() || parsed.is_nan()) => parsed,
930                    _ => {
931                        self.optional_error(ArxmlParserError::InvalidNumber {
932                            input: strval.to_owned(),
933                        })?;
934                        0.0
935                    }
936                };
937                Ok(CharacterData::Float(value))
938            }
939        }
940    }
941
942    fn unescape_string<'b>(&mut self, input: &'b str) -> Result<Cow<'b, str>, AutosarDataError> {
943        if input.contains('&') {
944            let mut unescaped = String::with_capacity(input.len());
945            let mut rem = input;
946            while let Some(pos) = rem.find('&') {
947                unescaped.push_str(&rem[..pos]);
948                rem = &rem[pos..];
949                if rem.starts_with("&lt;") {
950                    unescaped.push('<');
951                    rem = &rem[4..];
952                } else if rem.starts_with("&gt;") {
953                    unescaped.push('>');
954                    rem = &rem[4..];
955                } else if rem.starts_with("&amp;") {
956                    unescaped.push('&');
957                    rem = &rem[5..];
958                } else if rem.starts_with("&apos;") {
959                    unescaped.push('\'');
960                    rem = &rem[6..];
961                } else if rem.starts_with("&quot;") {
962                    unescaped.push('"');
963                    rem = &rem[6..];
964                } else if rem.starts_with("&#x") {
965                    // hexadecimal character reference
966                    let mut valid = false;
967                    if let Some(endpos) = rem.find(';') {
968                        let hextxt = &rem[3..endpos];
969                        if let Ok(hexval) = u32::from_str_radix(hextxt, 16)
970                            && let Some(ch) = char::from_u32(hexval)
971                        {
972                            unescaped.push(ch);
973                            rem = &rem[endpos + 1..];
974                            valid = true;
975                        }
976                    }
977                    if !valid {
978                        self.optional_error(ArxmlParserError::InvalidXmlEntity {
979                            input: input.to_owned(),
980                        })?;
981                        unescaped.push('&');
982                        rem = &rem[1..];
983                    }
984                } else if rem.starts_with("&#") {
985                    // decimal character reference
986                    let mut valid = false;
987                    if let Some(endpos) = rem.find(';') {
988                        let numtxt = &rem[2..endpos];
989                        if let Ok(val) = u32::from_str(numtxt)
990                            && let Some(ch) = char::from_u32(val)
991                        {
992                            unescaped.push(ch);
993                            rem = &rem[endpos + 1..];
994                            valid = true;
995                        }
996                    }
997                    if !valid {
998                        self.optional_error(ArxmlParserError::InvalidXmlEntity {
999                            input: input.to_owned(),
1000                        })?;
1001                        unescaped.push('&');
1002                        rem = &rem[1..];
1003                    }
1004                } else {
1005                    self.optional_error(ArxmlParserError::InvalidXmlEntity {
1006                        input: input.to_owned(),
1007                    })?;
1008                    unescaped.push('&');
1009                    rem = &rem[1..];
1010                }
1011            }
1012            unescaped.push_str(rem);
1013
1014            Ok(Cow::Owned(unescaped))
1015        } else {
1016            Ok(Cow::Borrowed(input))
1017        }
1018    }
1019
1020    pub(crate) fn get_fileversion(&self) -> AutosarVersion {
1021        self.fileversion
1022    }
1023
1024    fn verify_end_of_input(&mut self, lexer: &mut ArxmlLexer) -> Result<(), AutosarDataError> {
1025        let (_, next_event) = lexer.next()?;
1026        if let ArxmlEvent::EndOfFile = next_event {
1027            Ok(())
1028        } else {
1029            self.optional_error(ArxmlParserError::AdditionalDataError)?;
1030            Ok(())
1031        }
1032    }
1033
1034    /// parse an arxml file and return true if there is a valid arxml header, false otherwise.
1035    pub(crate) fn check_arxml_header(&mut self) -> bool {
1036        let mut lexer = ArxmlLexer::new(self.buffer, self.filename.clone());
1037
1038        if let Ok(ArxmlEvent::ArxmlHeader(_)) = self.next(&mut lexer) {
1039            // skip any comments
1040            let mut arxmlevent = self.next(&mut lexer);
1041            while let Ok(ArxmlEvent::Comment(..)) = arxmlevent {
1042                arxmlevent = self.next(&mut lexer);
1043            }
1044            if let Ok(ArxmlEvent::BeginElement(elemname, attributes_text)) = arxmlevent
1045                && let Ok(ElementName::Autosar) = ElementName::from_bytes(elemname)
1046                && let Ok(attributes) = self.parse_attribute_text(ElementType::ROOT, attributes_text)
1047                && self.parse_file_header(&attributes).is_ok()
1048            {
1049                // no errors after parsing the header - this looks like an arxml file
1050                return true;
1051            }
1052        }
1053
1054        false
1055    }
1056}
1057
1058#[cfg(test)]
1059mod test {
1060    use crate::parser::*;
1061    use crate::*;
1062
1063    fn test_helper(buffer: &[u8], target_error: std::mem::Discriminant<ArxmlParserError>, optional: bool) {
1064        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), buffer, true);
1065        let result = parser.parse_arxml();
1066        if let Err(AutosarDataError::ParserError { source, .. }) = result {
1067            println!("Error result: {source:?}");
1068            assert_eq!(
1069                std::mem::discriminant(&source),
1070                target_error,
1071                "Did not get the expected parser error"
1072            );
1073        } else {
1074            panic!("Did not get any parser error when one was expected");
1075        }
1076
1077        if optional {
1078            let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), buffer, false);
1079            let _result = parser.parse_arxml();
1080            if let Some(AutosarDataError::ParserError { source, .. }) = parser.warnings.first() {
1081                println!("Warnings result: {source:?}");
1082                assert_eq!(
1083                    std::mem::discriminant(source),
1084                    target_error,
1085                    "Did not get the expected parser error"
1086                );
1087            } else {
1088                panic!("Did not get a parser warning");
1089            }
1090        }
1091    }
1092
1093    const INVALID_HEADER_1: &str = "BLA BLA bla";
1094    const INVALID_HEADER_2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1095    <something>"#;
1096    const INVALID_HEADER_3: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1097    <AUTOSAR xsi:schemaLocation="nonsense" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#;
1098    const INVALID_HEADER_4: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1099    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00049.xsd" xmlns="http://other" xmlns:xsi="http://other">"#;
1100
1101    #[test]
1102    fn test_invalid_header() {
1103        test_helper(
1104            INVALID_HEADER_1.as_bytes(),
1105            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1106            false,
1107        );
1108        test_helper(
1109            INVALID_HEADER_2.as_bytes(),
1110            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1111            false,
1112        );
1113        test_helper(
1114            INVALID_HEADER_3.as_bytes(),
1115            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1116            false,
1117        );
1118        test_helper(
1119            INVALID_HEADER_4.as_bytes(),
1120            std::mem::discriminant(&ArxmlParserError::InvalidArxmlFileHeader),
1121            false,
1122        );
1123    }
1124
1125    const HDR_SINGLE_QUOTE: &str = r#"<?xml version='1.0' encoding='utf-8'?>
1126    <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'>
1127    </AUTOSAR>"#;
1128
1129    #[test]
1130    fn single_quotes_in_header() {
1131        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), HDR_SINGLE_QUOTE.as_bytes(), true);
1132        let result = parser.parse_arxml();
1133        assert!(result.is_ok());
1134    }
1135
1136    const SCHEMA_VERSION_LC: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1137    <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">
1138    </AUTOSAR>"#;
1139    const INVALID_VERSION_4_3_1: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1140    <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">
1141    </AUTOSAR>"#;
1142    const INVALID_VERSION_4_4_0: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1143    <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">
1144    </AUTOSAR>"#;
1145    const INVALID_VERSION_4_5_0: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1146    <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">
1147    </AUTOSAR>"#;
1148
1149    #[test]
1150    fn test_invalid_version() {
1151        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), SCHEMA_VERSION_LC.as_bytes(), true);
1152        let result = parser.parse_arxml();
1153        assert!(result.is_ok());
1154
1155        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidAutosarVersion {
1156            input_verstring: "".to_string(),
1157            replacement: AutosarVersion::Autosar_00044,
1158        });
1159        test_helper(INVALID_VERSION_4_3_1.as_bytes(), discriminant, true);
1160        test_helper(INVALID_VERSION_4_4_0.as_bytes(), discriminant, true);
1161        test_helper(INVALID_VERSION_4_5_0.as_bytes(), discriminant, true);
1162    }
1163
1164    const UNKNOWN_VERSION: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1165    <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">
1166    </AUTOSAR>"#;
1167
1168    #[test]
1169    fn test_unknown_version() {
1170        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownAutosarVersion {
1171            input_verstring: "".to_string(),
1172        });
1173        test_helper(UNKNOWN_VERSION.as_bytes(), discriminant, true);
1174    }
1175
1176    const UNEXPECTED_XML_FILE_HEADER: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1177    <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">
1178    <?xml version="1.0" encoding="utf-8"?>
1179    </AUTOSAR>"#;
1180
1181    #[test]
1182    fn test_unexpected_xml_file_header() {
1183        let discriminant = std::mem::discriminant(&ArxmlParserError::UnexpectedXmlFileHeader {
1184            element: ElementName::Autosar,
1185        });
1186        test_helper(UNEXPECTED_XML_FILE_HEADER.as_bytes(), discriminant, true);
1187    }
1188
1189    const INCORRECT_BEGIN_ELEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1190    <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">
1191    <ELEMENT>"#;
1192
1193    #[test]
1194    fn test_incorrect_begin_element() {
1195        let discriminant = std::mem::discriminant(&ArxmlParserError::IncorrectBeginElement {
1196            element: ElementName::Autosar,
1197            sub_element: ElementName::Autosar,
1198        });
1199        test_helper(INCORRECT_BEGIN_ELEMENT.as_bytes(), discriminant, false);
1200    }
1201
1202    const INVALID_BEGIN_ELEMENT: &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    <NOT_AN_AUTOSAR_ELEMENT>"#;
1205
1206    #[test]
1207    fn test_invalid_begin_element() {
1208        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidBeginElement {
1209            element: ElementName::Autosar,
1210            invalid_element: "".to_string(),
1211        });
1212        test_helper(INVALID_BEGIN_ELEMENT.as_bytes(), discriminant, false);
1213    }
1214
1215    const INCORRECT_END_ELEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1216    <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">
1217    <AR-PACKAGES></AUTOSAR>"#;
1218
1219    #[test]
1220    fn test_incorrect_end_element() {
1221        let discriminant = std::mem::discriminant(&ArxmlParserError::IncorrectEndElement {
1222            element: ElementName::Autosar,
1223            other_element: ElementName::Autosar,
1224        });
1225        test_helper(INCORRECT_END_ELEMENT.as_bytes(), discriminant, false);
1226    }
1227
1228    const INVALID_END_ELEMENT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1229    <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">
1230    <AR-PACKAGES></NOT_AN_AUTOSAR_ELEMENT>"#;
1231
1232    #[test]
1233    fn test_invalid_end_element() {
1234        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidEndElement {
1235            parent_element: ElementName::Autosar,
1236            invalid_element: "".to_string(),
1237        });
1238        test_helper(INVALID_END_ELEMENT.as_bytes(), discriminant, false);
1239    }
1240
1241    const ELEMENT_VERSION_ERROR: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1242    <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">
1243    <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>TestPackage</SHORT-NAME><ELEMENTS><DIAGNOSTIC-ACCESS-PERMISSION>"#;
1244
1245    #[test]
1246    fn test_element_version_error() {
1247        let discriminant = std::mem::discriminant(&ArxmlParserError::ElementVersionError {
1248            element: ElementName::Autosar,
1249            sub_element: ElementName::Autosar,
1250            version: AutosarVersion::Autosar_00050,
1251        });
1252        test_helper(ELEMENT_VERSION_ERROR.as_bytes(), discriminant, false);
1253    }
1254
1255    const CHOICE_CONFLICT: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1256    <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">
1257        <AR-PACKAGES>
1258            <AR-PACKAGE>
1259                <SHORT-NAME>base</SHORT-NAME>
1260                <ELEMENTS>
1261                    <DIAGNOSTIC-CONTRIBUTION-SET>
1262                        <SHORT-NAME>dcs</SHORT-NAME>
1263                        <COMMON-PROPERTIES>
1264                            <DIAGNOSTIC-COMMON-PROPS-VARIANTS>
1265                                <DIAGNOSTIC-COMMON-PROPS-CONDITIONAL>
1266                                    <DEBOUNCE-ALGORITHM-PROPSS>
1267                                        <DIAGNOSTIC-DEBOUNCE-ALGORITHM-PROPS>
1268                                            <SHORT-NAME>props</SHORT-NAME>
1269                                            <DEBOUNCE-ALGORITHM>
1270                                                <DIAG-EVENT-DEBOUNCE-COUNTER-BASED>
1271                                                    <SHORT-NAME>abc</SHORT-NAME>
1272                                                </DIAG-EVENT-DEBOUNCE-COUNTER-BASED>
1273                                                <DIAG-EVENT-DEBOUNCE-TIME-BASED>
1274                                                    <SHORT-NAME>def</SHORT-NAME>
1275                                                </DIAG-EVENT-DEBOUNCE-TIME-BASED>"#;
1276
1277    #[test]
1278    fn test_choice_conflict() {
1279        let discriminant = std::mem::discriminant(&ArxmlParserError::ElementChoiceConflict {
1280            element: ElementName::Autosar,
1281            sub_element: ElementName::Autosar,
1282        });
1283        test_helper(CHOICE_CONFLICT.as_bytes(), discriminant, true);
1284    }
1285
1286    const TOO_MANY_SUBELEMENTS: &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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1288        <AR-PACKAGES>
1289            <AR-PACKAGE>
1290                <SHORT-NAME>base</SHORT-NAME>
1291                <SHORT-NAME>base</SHORT-NAME>"#;
1292
1293    #[test]
1294    fn test_too_many_sub_elements() {
1295        let discriminant = std::mem::discriminant(&ArxmlParserError::TooManySubElements {
1296            element: ElementName::Autosar,
1297            sub_element: ElementName::Autosar,
1298        });
1299        test_helper(TOO_MANY_SUBELEMENTS.as_bytes(), discriminant, true);
1300    }
1301
1302    const REQUIRED_SUB_ELEMENT_MISSING: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1303    <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">
1304        <AR-PACKAGES>
1305            <AR-PACKAGE></AR-PACKAGE>"#;
1306
1307    #[test]
1308    fn test_required_sub_element_missing() {
1309        let discriminant = std::mem::discriminant(&ArxmlParserError::RequiredSubelementMissing {
1310            element: ElementName::Autosar,
1311            sub_element: ElementName::Autosar,
1312        });
1313        test_helper(REQUIRED_SUB_ELEMENT_MISSING.as_bytes(), discriminant, false);
1314    }
1315
1316    const UNKNOWN_ATTRIBUTE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1317    <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">
1318    <AR-PACKAGES UnknownAttribute="value">
1319    </AR-PACKAGES></AUTOSAR>"#;
1320    const UNKNOWN_ATTRIBUTE_2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1321    <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">
1322    <AR-PACKAGES DEST="value">
1323    </AR-PACKAGES></AUTOSAR>"#;
1324
1325    #[test]
1326    fn test_unknown_attribute() {
1327        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownAttributeError {
1328            element: ElementName::Autosar,
1329            attribute: "".to_string(),
1330        });
1331        test_helper(UNKNOWN_ATTRIBUTE.as_bytes(), discriminant, true);
1332        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownAttributeError {
1333            element: ElementName::Autosar,
1334            attribute: "DEST".to_string(),
1335        });
1336        test_helper(UNKNOWN_ATTRIBUTE_2.as_bytes(), discriminant, true);
1337    }
1338
1339    const REQUIRED_ATTRIBUTE_MISSING: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1340    <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0">
1341    </AUTOSAR>"#;
1342
1343    #[test]
1344    fn test_required_attribute_missing() {
1345        let discriminant = std::mem::discriminant(&ArxmlParserError::RequiredAttributeMissing {
1346            element: ElementName::Autosar,
1347            attribute: AttributeName::Accesskey,
1348        });
1349        test_helper(REQUIRED_ATTRIBUTE_MISSING.as_bytes(), discriminant, true);
1350    }
1351
1352    const CHARACTER_CONTENT_FORBIDDEN: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1353    <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">
1354    abcdef"#;
1355
1356    #[test]
1357    fn test_character_content_forbidden() {
1358        let discriminant = std::mem::discriminant(&ArxmlParserError::CharacterContentForbidden {
1359            element: ElementName::Autosar,
1360        });
1361        test_helper(CHARACTER_CONTENT_FORBIDDEN.as_bytes(), discriminant, false);
1362    }
1363
1364    const WRONG_ENUM_ITEM_VERSION: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1365    <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">
1366        <AR-PACKAGES>
1367            <AR-PACKAGE>
1368                <SHORT-NAME>base</SHORT-NAME>
1369                <ELEMENTS>
1370                    <SYSTEM>
1371                        <SHORT-NAME>System</SHORT-NAME>
1372                        <FIBEX-ELEMENTS>
1373                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1374                                <FIBEX-ELEMENT-REF DEST="SERVICE-INSTANCE-COLLECTION-SET">"#;
1375
1376    #[test]
1377    fn test_enum_item_version() {
1378        let discriminant = std::mem::discriminant(&ArxmlParserError::EnumItemVersionError {
1379            element: ElementName::Autosar,
1380            enum_item: EnumItem::Aa,
1381            version: AutosarVersion::Autosar_00050,
1382        });
1383        test_helper(WRONG_ENUM_ITEM_VERSION.as_bytes(), discriminant, false);
1384    }
1385
1386    const UNKNOWN_ENUM_ITEM: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1387    <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">
1388        <AR-PACKAGES>
1389            <AR-PACKAGE>
1390                <SHORT-NAME>base</SHORT-NAME>
1391                <ELEMENTS>
1392                    <SYSTEM>
1393                        <SHORT-NAME>System</SHORT-NAME>
1394                        <FIBEX-ELEMENTS>
1395                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1396                                <FIBEX-ELEMENT-REF DEST="invalid_value_for_the_test">"#;
1397
1398    #[test]
1399    fn test_unknown_enum_item() {
1400        let discriminant = std::mem::discriminant(&ArxmlParserError::UnknownEnumItem { value: "".to_string() });
1401        test_helper(UNKNOWN_ENUM_ITEM.as_bytes(), discriminant, false);
1402    }
1403
1404    const INVALID_ENUM_ITEM: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1405    <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">
1406        <AR-PACKAGES>
1407            <AR-PACKAGE>
1408                <SHORT-NAME>base</SHORT-NAME>
1409                <ELEMENTS>
1410                    <SYSTEM>
1411                        <SHORT-NAME>System</SHORT-NAME>
1412                        <FIBEX-ELEMENTS>
1413                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1414                                <FIBEX-ELEMENT-REF DEST="default">"#;
1415
1416    #[test]
1417    fn test_invalid_enum_item() {
1418        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidEnumItem {
1419            element: ElementName::Abs,
1420            item: EnumItem::Aa,
1421        });
1422        test_helper(INVALID_ENUM_ITEM.as_bytes(), discriminant, false);
1423    }
1424
1425    const STRING_VALUE_TOO_LONG: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1426    <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">
1427        <AR-PACKAGES><AR-PACKAGE>
1428            <SHORT-NAME>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</SHORT-NAME>
1429        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1430
1431    #[test]
1432    fn test_string_value_too_long() {
1433        let discriminant = std::mem::discriminant(&ArxmlParserError::StringValueTooLong {
1434            value: "".to_string(),
1435            length: 1,
1436        });
1437        test_helper(STRING_VALUE_TOO_LONG.as_bytes(), discriminant, true);
1438    }
1439
1440    const REGEX_MATCH_ERROR: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1441    <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">
1442        <AR-PACKAGES><AR-PACKAGE>
1443            <SHORT-NAME>0a</SHORT-NAME>
1444        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1445
1446    #[test]
1447    fn test_regex_match_error() {
1448        let discriminant = std::mem::discriminant(&ArxmlParserError::RegexMatchError {
1449            value: "".to_string(),
1450            regex: "".to_string(),
1451        });
1452        test_helper(REGEX_MATCH_ERROR.as_bytes(), discriminant, true);
1453    }
1454
1455    const UTF8_ERROR: &[u8] = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>
1456    <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\">
1457        <AR-PACKAGES><AR-PACKAGE S=\"\xff\xff\">";
1458
1459    #[test]
1460    fn test_utf8_error() {
1461        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), UTF8_ERROR, true);
1462        let result = parser.parse_arxml();
1463        assert!(
1464            matches!(
1465                result,
1466                Err(AutosarDataError::ParserError {
1467                    source: ArxmlParserError::Utf8Error { .. },
1468                    ..
1469                })
1470            ),
1471            "Did not get the expected parser error"
1472        );
1473
1474        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), UTF8_ERROR, false);
1475        let _ = parser.parse_arxml();
1476        let warning = parser.warnings.first();
1477        assert!(
1478            matches!(
1479                warning,
1480                Some(AutosarDataError::ParserError {
1481                    source: ArxmlParserError::Utf8Error { .. },
1482                    ..
1483                })
1484            ),
1485            "Did not get the expected parser warning"
1486        );
1487    }
1488
1489    const UNEXPECTED_END_OF_FILE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1490    <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">"#;
1491
1492    #[test]
1493    fn test_unexpected_end_of_file() {
1494        let discriminant = std::mem::discriminant(&ArxmlParserError::UnexpectedEndOfFile {
1495            element: ElementName::Autosar,
1496        });
1497        test_helper(UNEXPECTED_END_OF_FILE.as_bytes(), discriminant, false);
1498    }
1499
1500    const INVALID_NUMBER: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1501    <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">
1502    <AR-PACKAGES><AR-PACKAGE>
1503        <SHORT-NAME>base</SHORT-NAME>
1504        <ELEMENTS><I-SIGNAL-I-PDU>
1505            <SHORT-NAME>Pdu</SHORT-NAME>
1506            <I-PDU-TIMING-SPECIFICATIONS><I-PDU-TIMING><TRANSMISSION-MODE-DECLARATION><TRANSMISSION-MODE-TRUE-TIMING><CYCLIC-TIMING>
1507            <TIME-PERIOD><TOLERANCE><ABSOLUTE-TOLERANCE><ABSOLUTE>not a number</ABSOLUTE>"#;
1508
1509    #[test]
1510    fn test_invalid_number() {
1511        let discriminant = std::mem::discriminant(&ArxmlParserError::InvalidNumber { input: "".to_string() });
1512        test_helper(INVALID_NUMBER.as_bytes(), discriminant, true);
1513    }
1514
1515    const EMPTY_SHORT_NAME: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1516    <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">
1517    <AR-PACKAGES><AR-PACKAGE><SHORT-NAME></SHORT-NAME>
1518      <ELEMENTS><ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE></ELEMENTS>
1519    </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1520
1521    // a SHORT-NAME which only contains whitespace: the lexer discards whitespace-only character
1522    // data, so this is the same case as a SHORT-NAME with no content at all
1523    const WHITESPACE_SHORT_NAME: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1524    <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">
1525    <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>   </SHORT-NAME>
1526    </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1527
1528    // a SHORT-NAME which contains only a comment is also empty
1529    const COMMENT_SHORT_NAME: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1530    <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">
1531    <AR-PACKAGES><AR-PACKAGE><SHORT-NAME><!--the name goes here--></SHORT-NAME>
1532    </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1533
1534    // an AR-PACKAGE with no SHORT-NAME at all is a different case: the element is then consistently
1535    // not identifiable, so non-strict parsing still accepts it
1536    const MISSING_SHORT_NAME: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1537    <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">
1538    <AR-PACKAGES><AR-PACKAGE>
1539      <ELEMENTS><ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE></ELEMENTS>
1540    </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
1541
1542    #[test]
1543    fn test_empty_short_name() {
1544        // an empty SHORT-NAME is rejected in strict mode as well as in non-strict mode: an element
1545        // with an empty SHORT-NAME would claim to be identifiable while having no name, so it could
1546        // never be found by path or referred to
1547        for buffer in [EMPTY_SHORT_NAME, WHITESPACE_SHORT_NAME, COMMENT_SHORT_NAME] {
1548            for strict in [true, false] {
1549                let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), buffer.as_bytes(), strict);
1550                let result = parser.parse_arxml();
1551                println!("strict={strict}, result={result:?}");
1552                assert!(matches!(
1553                    result,
1554                    Err(AutosarDataError::ParserError {
1555                        source: ArxmlParserError::EmptyShortName {
1556                            element: ElementName::ArPackage
1557                        },
1558                        ..
1559                    })
1560                ));
1561                // the error is not downgraded to a warning when strict == false
1562                assert!(parser.warnings.is_empty());
1563            }
1564        }
1565    }
1566
1567    #[test]
1568    fn test_missing_short_name_is_not_an_empty_short_name() {
1569        // omitting the SHORT-NAME is still only a warning in non-strict mode
1570        let model = AutosarModel::new();
1571        let (_file, warnings) = model
1572            .load_buffer(MISSING_SHORT_NAME.as_bytes(), "test.arxml", false)
1573            .unwrap();
1574        assert!(matches!(
1575            warnings.first(),
1576            Some(AutosarDataError::ParserError {
1577                source: ArxmlParserError::RequiredSubelementMissing {
1578                    element: ElementName::ArPackage,
1579                    sub_element: ElementName::ShortName,
1580                },
1581                ..
1582            })
1583        ));
1584        let el_ar_package = model
1585            .root_element()
1586            .get_sub_element(ElementName::ArPackages)
1587            .and_then(|e| e.get_sub_element(ElementName::ArPackage))
1588            .unwrap();
1589        assert!(!el_ar_package.is_identifiable());
1590
1591        // ... and an error in strict mode
1592        let model = AutosarModel::new();
1593        assert!(matches!(
1594            model.load_buffer(MISSING_SHORT_NAME.as_bytes(), "test.arxml", true),
1595            Err(AutosarDataError::ParserError {
1596                source: ArxmlParserError::RequiredSubelementMissing { .. },
1597                ..
1598            })
1599        ));
1600    }
1601
1602    const ADDITIONAL_DATA: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1603    <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">
1604    </AUTOSAR>
1605    <extra>"#;
1606
1607    #[test]
1608    fn test_additional_data_error() {
1609        let discriminant = std::mem::discriminant(&ArxmlParserError::AdditionalDataError);
1610        test_helper(ADDITIONAL_DATA.as_bytes(), discriminant, true);
1611    }
1612
1613    #[test]
1614    fn unescape_entities() {
1615        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), &[], true);
1616        let result = parser
1617            .unescape_string("&amp;&amp;&lt;FOO&gt;&quot;&quot;&apos;&#32;&#x20;end")
1618            .unwrap();
1619        assert_eq!(&result, r#"&&<FOO>""'  end"#);
1620        let result = parser.unescape_string("&amp;&amp;&gt;FOO&lt;&quot&quot;&apos;end");
1621        assert!(result.is_err());
1622        // numeric character entity does not accept hex values
1623        let result = parser.unescape_string("&#abcde;");
1624        assert!(result.is_err());
1625        // values from 0x110000 to 0x1FFFFF are not valid unicode code points -> 0x110000 = 1114112
1626        let result = parser.unescape_string("&#1114112;");
1627        assert!(result.is_err());
1628    }
1629
1630    const PARSER_TEST_DATA: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1631    <!--comment-->
1632    <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">
1633        <!--comment-->
1634        <AR-PACKAGES>
1635            <AR-PACKAGE>
1636                <SHORT-NAME>base</SHORT-NAME>
1637                <ELEMENTS>
1638                    <SYSTEM UUID="12345678" S="some string" T="2022-01-31T13:00:59Z">
1639                        <SHORT-NAME>System</SHORT-NAME>
1640                        <FIBEX-ELEMENTS>
1641                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1642                                <FIBEX-ELEMENT-REF DEST="I-SIGNAL-I-PDU">/base/Pdu</FIBEX-ELEMENT-REF>
1643                            </FIBEX-ELEMENT-REF-CONDITIONAL>
1644                        </FIBEX-ELEMENTS>
1645                    </SYSTEM>
1646                    <I-SIGNAL-I-PDU>
1647                        <SHORT-NAME>Pdu</SHORT-NAME>
1648                        <I-PDU-TIMING-SPECIFICATIONS>
1649                            <I-PDU-TIMING>
1650                                <TRANSMISSION-MODE-DECLARATION>
1651                                    <TRANSMISSION-MODE-TRUE-TIMING>
1652                                        <CYCLIC-TIMING>
1653                                            <TIME-PERIOD>
1654                                                <TOLERANCE>
1655                                                    <ABSOLUTE-TOLERANCE>
1656                                                        <ABSOLUTE>1.0</ABSOLUTE>
1657                                                    </ABSOLUTE-TOLERANCE>
1658                                                </TOLERANCE>
1659                                            </TIME-PERIOD>
1660                                        </CYCLIC-TIMING>
1661                                    </TRANSMISSION-MODE-TRUE-TIMING>
1662                                </TRANSMISSION-MODE-DECLARATION>
1663                            </I-PDU-TIMING>
1664                        </I-PDU-TIMING-SPECIFICATIONS>
1665                    </I-SIGNAL-I-PDU>
1666                </ELEMENTS>
1667            </AR-PACKAGE>
1668        </AR-PACKAGES>
1669    </AUTOSAR>
1670    "#;
1671
1672    #[test]
1673    fn test_basic_functionality() {
1674        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), PARSER_TEST_DATA.as_bytes(), true);
1675        let result = parser.parse_arxml();
1676        assert!(result.is_ok());
1677    }
1678
1679    const PARSER_TEST_RELATIVE_REFERENCE_BASE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1680    <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">
1681        <AR-PACKAGES>
1682            <AR-PACKAGE>
1683                <SHORT-NAME>base</SHORT-NAME>
1684                <REFERENCE-BASES>
1685                    <REFERENCE-BASE>
1686                        <SHORT-LABEL>default</SHORT-LABEL>
1687                        <PACKAGE-REF DEST="AR-PACKAGE">/base</PACKAGE-REF>
1688                    </REFERENCE-BASE>
1689                </REFERENCE-BASES>
1690                <ELEMENTS>
1691                    <SYSTEM>
1692                        <SHORT-NAME>System</SHORT-NAME>
1693                        <FIBEX-ELEMENTS>
1694                            <FIBEX-ELEMENT-REF-CONDITIONAL>
1695                                <FIBEX-ELEMENT-REF DEST="I-SIGNAL-I-PDU" BASE="default">Pdu</FIBEX-ELEMENT-REF>
1696                            </FIBEX-ELEMENT-REF-CONDITIONAL>
1697                        </FIBEX-ELEMENTS>
1698                    </SYSTEM>
1699                    <I-SIGNAL-I-PDU>
1700                        <SHORT-NAME>Pdu</SHORT-NAME>
1701                    </I-SIGNAL-I-PDU>
1702                </ELEMENTS>
1703            </AR-PACKAGE>
1704        </AR-PACKAGES>
1705    </AUTOSAR>
1706    "#;
1707
1708    #[test]
1709    fn parse_reference_base_and_relative_reference() {
1710        let mut parser = ArxmlParser::new(
1711            PathBuf::from("test_buffer.arxml"),
1712            PARSER_TEST_RELATIVE_REFERENCE_BASE.as_bytes(),
1713            true,
1714        );
1715
1716        let result = parser.parse_arxml();
1717        assert!(result.is_ok());
1718
1719        // The parser only collects the references it encounters, as raw text: a relative reference can
1720        // only be resolved once the whole tree exists, which is the model's job.
1721        assert_eq!(parser.references.len(), 2);
1722        assert!(parser.references.iter().any(|(refpath, _, _)| refpath == "/base"));
1723        assert!(parser.references.iter().any(|(refpath, _, _)| refpath == "Pdu"));
1724    }
1725
1726    const GT_IN_ATTRIBUTE_VALUE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1727    <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">
1728        <AR-PACKAGES>
1729            <AR-PACKAGE S="a > b">
1730                <SHORT-NAME>x</SHORT-NAME>
1731            </AR-PACKAGE>
1732        </AR-PACKAGES>
1733    </AUTOSAR>
1734    "#;
1735
1736    #[test]
1737    fn gt_in_attribute_value() {
1738        // xml allows an unescaped '>' inside a quoted attribute value; the value must not be truncated
1739        let mut parser = ArxmlParser::new(
1740            PathBuf::from("test_buffer.arxml"),
1741            GT_IN_ATTRIBUTE_VALUE.as_bytes(),
1742            true,
1743        );
1744        let root = parser.parse_arxml().unwrap();
1745        let package = root
1746            .get_sub_element(ElementName::ArPackages)
1747            .and_then(|pkgs| pkgs.get_sub_element(ElementName::ArPackage))
1748            .unwrap();
1749        assert_eq!(
1750            package.attribute_value(AttributeName::S).unwrap(),
1751            CharacterData::String("a > b".to_string())
1752        );
1753    }
1754
1755    const EMPTY_CHARACTER_DATA: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1756    <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">
1757        <AR-PACKAGES>
1758            <AR-PACKAGE UUID="">
1759                <SHORT-NAME>x</SHORT-NAME>
1760            </AR-PACKAGE>
1761        </AR-PACKAGES>
1762    </AUTOSAR>
1763    "#;
1764
1765    #[test]
1766    fn test_empty_character_data() {
1767        let mut parser = ArxmlParser::new(
1768            PathBuf::from("test_buffer.arxml"),
1769            EMPTY_CHARACTER_DATA.as_bytes(),
1770            true,
1771        );
1772        let result = parser.parse_arxml();
1773        assert!(result.is_ok());
1774    }
1775
1776    const WHITESPACE_CHARACTER_DATA: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1777    <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">
1778        <AR-PACKAGES>
1779            <AR-PACKAGE UUID="   ">
1780                <SHORT-NAME>x</SHORT-NAME>
1781            </AR-PACKAGE>
1782        </AR-PACKAGES>
1783    </AUTOSAR>
1784    "#;
1785
1786    #[test]
1787    fn test_whitespace_character_data() {
1788        // an attribute value consisting only of whitespace must not cause a panic while trimming
1789        let mut parser = ArxmlParser::new(
1790            PathBuf::from("test_buffer.arxml"),
1791            WHITESPACE_CHARACTER_DATA.as_bytes(),
1792            true,
1793        );
1794        let result = parser.parse_arxml();
1795        assert!(result.is_ok());
1796    }
1797
1798    #[test]
1799    fn chardata_utf8_error() {
1800        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), b"", true);
1801        let mut parser_permissive = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), b"", false);
1802
1803        // correct input for CharacterDataSpec::Pattern
1804        let pattern_spec = CharacterDataSpec::Pattern {
1805            check_fn: |_| true,
1806            regex: "",
1807            max_length: Some(2),
1808        };
1809        let result = parser.parse_character_data(b"ab", &pattern_spec);
1810        assert!(result.is_ok());
1811
1812        // trigger the invalid utf-8 error for CharacterDataSpec::Pattern
1813        let result = parser.parse_character_data(&[0xff], &pattern_spec);
1814        assert!(result.is_err());
1815
1816        // permissive parsing allows invalid utf-8 in CharacterDataSpec::Pattern
1817        let result = parser_permissive.parse_character_data(&[0xff], &pattern_spec);
1818        assert!(result.is_ok());
1819
1820        // correct input for CharacterDataSpec::String
1821        let string_spec = CharacterDataSpec::String {
1822            max_length: Some(2),
1823            preserve_whitespace: false,
1824        };
1825        let result = parser.parse_character_data(b"ab", &string_spec);
1826        assert!(result.is_ok());
1827
1828        // input too long for CharacterDataSpec::String
1829        let result = parser.parse_character_data(b"abc", &string_spec);
1830        assert!(result.is_err());
1831
1832        // trigger the invalid utf-8 error for CharacterDataSpec::String
1833        let result = parser.parse_character_data(&[0xff], &string_spec);
1834        assert!(result.is_err());
1835
1836        // correct conversion for CharacterDataSpec::UnsignedInteger
1837        let int_spec = CharacterDataSpec::UnsignedInteger;
1838        let result = parser.parse_character_data(b"123", &int_spec);
1839        assert!(result.is_ok());
1840
1841        // conversion error for CharacterDataSpec::UnsignedInteger: valid utf-8, but not a number
1842        let result = parser.parse_character_data(b"abc", &int_spec);
1843        assert!(result.is_err());
1844
1845        // conversion error for CharacterDataSpec::UnsignedInteger: invalid utf-8
1846        let result = parser.parse_character_data(&[0xff], &int_spec);
1847        assert!(result.is_err());
1848
1849        // correct conversion for CharacterDataSpec::Float
1850        let float_spec = CharacterDataSpec::Float;
1851        let result = parser.parse_character_data(b"1.0", &float_spec);
1852        assert!(result.is_ok());
1853
1854        // conversion error for CharacterDataSpec::Float: valid utf-8, but not a number
1855        let result = parser.parse_character_data(b"abc", &float_spec);
1856        assert!(result.is_err());
1857
1858        // conversion error for CharacterDataSpec::Float: invalid utf-8
1859        let result = parser.parse_character_data(&[0xff], &float_spec);
1860        assert!(result.is_err());
1861    }
1862
1863    #[test]
1864    fn test_check_arxml_header() {
1865        let buffer = "abcde".as_bytes();
1866        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1867        assert!(!parser.check_arxml_header());
1868
1869        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>abcde"#.as_bytes();
1870        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1871        assert!(!parser.check_arxml_header());
1872
1873        let buffer = r#"<?xml version="1.0" encoding="utf-8"?><abcde>"#.as_bytes();
1874        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1875        assert!(!parser.check_arxml_header());
1876
1877        let buffer = r#"<?xml version="1.0" encoding="utf-8"?><AUTOSAR abcde="abcde">"#.as_bytes();
1878        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1879        assert!(!parser.check_arxml_header());
1880
1881        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>
1882<AUTOSAR>"#
1883            .as_bytes();
1884        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1885        assert!(!parser.check_arxml_header());
1886
1887        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>
1888<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();
1889        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1890        assert!(!parser.check_arxml_header());
1891
1892        let buffer = r#"<?xml version="1.0" encoding="utf-8"?>
1893<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();
1894        let mut parser = ArxmlParser::new(PathBuf::from("test"), buffer, true);
1895        assert!(parser.check_arxml_header());
1896    }
1897
1898    #[test]
1899    fn parse_attribute_text() {
1900        let mut parser = ArxmlParser::new(PathBuf::from("test"), &[], true);
1901        // find the element type of AR-PACKAGE
1902        let etype_arpackage = ElementType::ROOT
1903            .find_sub_element(ElementName::ArPackages, u32::MAX)
1904            .unwrap()
1905            .0
1906            .find_sub_element(ElementName::ArPackage, u32::MAX)
1907            .unwrap()
1908            .0;
1909
1910        // whitespace only, should not return an error
1911        let result = parser.parse_attribute_text(etype_arpackage, br#"   "#);
1912        assert!(result.is_ok());
1913        let value = result.unwrap();
1914        assert!(value.is_empty());
1915
1916        // invalid string
1917        let result = parser.parse_attribute_text(etype_arpackage, br#"   abc   "#);
1918        assert!(result.is_err());
1919
1920        // invalid attribute name, and no value after the '='
1921        let result = parser.parse_attribute_text(etype_arpackage, br#"abc="#);
1922        assert!(result.is_err());
1923
1924        // Attribute name without a value after the '='
1925        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID="#);
1926        assert!(result.is_err());
1927
1928        // the attribute value is not enclosed in quotes
1929        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID=1234"#);
1930        assert!(result.is_err());
1931
1932        // valid UUID attribute
1933        let value = parser
1934            .parse_attribute_text(etype_arpackage, br#"UUID="12345678""#)
1935            .unwrap();
1936        assert_eq!(value.len(), 1);
1937        assert_eq!(value[0].attrname, AttributeName::Uuid);
1938
1939        // whitespace after the attribute value
1940        let value = parser
1941            .parse_attribute_text(etype_arpackage, br#"UUID="12345678"   "#)
1942            .unwrap();
1943        assert_eq!(value.len(), 1);
1944
1945        // junk after the attribute value
1946        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID="12345678"   abc   "#);
1947        assert!(result.is_err());
1948
1949        // attribute enclosed in single quotes
1950        let value = parser
1951            .parse_attribute_text(etype_arpackage, br#"UUID='12345678'"#)
1952            .unwrap();
1953        assert_eq!(value.len(), 1);
1954
1955        // missing final quote
1956        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID='12345678"#);
1957        assert!(result.is_err());
1958
1959        // mixed quotes ' -> ", error
1960        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID='12345678""#);
1961        assert!(result.is_err());
1962
1963        // two attributes without whitespace between them
1964        let result = parser.parse_attribute_text(etype_arpackage, br#"UUID="12345678"T="2024-01-01""#);
1965        assert!(result.is_err());
1966
1967        // two attributes with whitespace between them
1968        let value = parser
1969            .parse_attribute_text(etype_arpackage, br#"UUID="12345678" T="2024-01-01""#)
1970            .unwrap();
1971        assert_eq!(value.len(), 2);
1972
1973        // two attributes with extra whitespace between them
1974        let value = parser
1975            .parse_attribute_text(etype_arpackage, br#"UUID="12345678"  T="2024-01-01""#)
1976            .unwrap();
1977        assert_eq!(value.len(), 2);
1978
1979        // two attributes with leading whitespace and extra whitespace between them
1980        let value = parser
1981            .parse_attribute_text(etype_arpackage, br#"  UUID="12345678"  T="2024-01-01""#)
1982            .unwrap();
1983        assert_eq!(value.len(), 2);
1984
1985        // two attributes with spaces around the '='
1986        let value = parser
1987            .parse_attribute_text(etype_arpackage, br#"  UUID = "12345678"  T = "2024-01-01""#)
1988            .unwrap();
1989        assert_eq!(value.len(), 2);
1990        assert_eq!(value[0].attrname, AttributeName::Uuid);
1991        assert_eq!(value[1].attrname, AttributeName::T);
1992
1993        // duplicate attribute error
1994        let result = parser.parse_attribute_text(etype_arpackage, br#" UUID="1"  UUID="2""#);
1995        assert!(matches!(
1996            result,
1997            Err(AutosarDataError::ParserError {
1998                source: ArxmlParserError::DuplicateAttributeError { .. },
1999                ..
2000            })
2001        ));
2002    }
2003
2004    #[test]
2005    fn parse_attribute_text_non_strict() {
2006        let mut parser = ArxmlParser::new(PathBuf::from("test"), &[], false);
2007        let etype_arpackage = ElementType::ROOT
2008            .find_sub_element(ElementName::ArPackages, u32::MAX)
2009            .unwrap()
2010            .0
2011            .find_sub_element(ElementName::ArPackage, u32::MAX)
2012            .unwrap()
2013            .0;
2014
2015        // in non-strict mode a duplicate attribute is only a warning, and the last value wins
2016        let value = parser
2017            .parse_attribute_text(etype_arpackage, br#" UUID="1"  UUID="2""#)
2018            .unwrap();
2019        assert_eq!(value.len(), 1);
2020        assert_eq!(value[0].attrname, AttributeName::Uuid);
2021        assert_eq!(value[0].content.to_string(), "2");
2022        assert!(matches!(
2023            parser.warnings.first(),
2024            Some(AutosarDataError::ParserError {
2025                source: ArxmlParserError::DuplicateAttributeError { .. },
2026                ..
2027            })
2028        ));
2029    }
2030
2031    #[test]
2032    fn parse_invalid_numbers_non_strict() {
2033        let mut parser = ArxmlParser::new(PathBuf::from("test"), &[], false);
2034
2035        // an unsigned integer which cannot be parsed becomes 0, and a warning is recorded
2036        let value = parser
2037            .parse_character_data(b"not a number", &CharacterDataSpec::UnsignedInteger)
2038            .unwrap();
2039        assert_eq!(value, CharacterData::UnsignedInteger(0));
2040
2041        // a float which is not finite is rejected in the same way
2042        let value = parser.parse_character_data(b"inf", &CharacterDataSpec::Float).unwrap();
2043        assert_eq!(value, CharacterData::Float(0.0));
2044
2045        assert_eq!(parser.warnings.len(), 2);
2046        for warning in &parser.warnings {
2047            assert!(matches!(
2048                warning,
2049                AutosarDataError::ParserError {
2050                    source: ArxmlParserError::InvalidNumber { .. },
2051                    ..
2052                }
2053            ));
2054        }
2055    }
2056
2057    #[test]
2058    fn unescape_entities_non_strict() {
2059        let mut parser = ArxmlParser::new(PathBuf::from("test_buffer.arxml"), &[], false);
2060
2061        // an unknown entity is passed through unchanged
2062        let result = parser.unescape_string("a&nbsp;b").unwrap();
2063        assert_eq!(&result, "a&nbsp;b");
2064
2065        // a hexadecimal character reference with a value that is not a valid code point
2066        let result = parser.unescape_string("a&#xZZ;b").unwrap();
2067        assert_eq!(&result, "a&#xZZ;b");
2068        let result = parser.unescape_string("a&#xD800;b").unwrap();
2069        assert_eq!(&result, "a&#xD800;b");
2070
2071        // a decimal character reference with a value that is not a valid code point
2072        let result = parser.unescape_string("a&#abcde;b").unwrap();
2073        assert_eq!(&result, "a&#abcde;b");
2074        let result = parser.unescape_string("a&#1114112;b").unwrap();
2075        assert_eq!(&result, "a&#1114112;b");
2076
2077        // character references whose terminating ';' is missing
2078        let result = parser.unescape_string("a&#x41b").unwrap();
2079        assert_eq!(&result, "a&#x41b");
2080        let result = parser.unescape_string("a&#65b").unwrap();
2081        assert_eq!(&result, "a&#65b");
2082
2083        assert_eq!(parser.warnings.len(), 7);
2084        for warning in &parser.warnings {
2085            assert!(matches!(
2086                warning,
2087                AutosarDataError::ParserError {
2088                    source: ArxmlParserError::InvalidXmlEntity { .. },
2089                    ..
2090                }
2091            ));
2092        }
2093    }
2094}