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