autosar_data/element.rs
1use std::cmp::Ordering;
2use std::hash::Hash;
3use std::str::FromStr;
4
5use super::*;
6
7impl Element {
8 /// Get the parent element of the current element
9 ///
10 /// Returns None if the current element is the root, or if it has been deleted from the element hierarchy
11 ///
12 /// # Example
13 ///
14 /// ```
15 /// # use autosar_data::*;
16 /// # fn main() -> Result<(), AutosarDataError> {
17 /// # let model = AutosarModel::new();
18 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
19 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
20 /// if let Some(parent) = element.parent()? {
21 /// // ...
22 /// }
23 /// # Ok(())
24 /// # }
25 /// ```
26 ///
27 /// # Errors
28 ///
29 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
30 pub fn parent(&self) -> Result<Option<Element>, AutosarDataError> {
31 self.0.read().parent()
32 }
33
34 /// Get the next named parent (or grandparent, etc) of the current element
35 ///
36 /// This function steps through the hierarchy until an identifiable element is found.
37 /// It never returns the current element, even if the current element is identifiable.
38 ///
39 /// The function returns a suitable element if one is found, or None if the root is reached.
40 ///
41 /// # Example
42 ///
43 /// ```
44 /// # use autosar_data::*;
45 /// # fn main() -> Result<(), AutosarDataError> {
46 /// # let model = AutosarModel::new();
47 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050)?;
48 /// # let el_arpackage = model.root_element().create_sub_element(ElementName::ArPackages)?.create_named_sub_element(ElementName::ArPackage, "Pkg")?;
49 /// let el_elements = el_arpackage.create_sub_element(ElementName::Elements)?;
50 /// let el_system = el_elements.create_named_sub_element(ElementName::System, "Sys")?;
51 /// let named_parent = el_elements.named_parent()?.unwrap();
52 /// let named_parent2 = el_system.named_parent()?.unwrap();
53 /// assert_eq!(named_parent, el_arpackage);
54 /// assert_eq!(named_parent2, el_arpackage);
55 /// # Ok(())
56 /// # }
57 /// ```
58 ///
59 /// # Errors
60 ///
61 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
62 pub fn named_parent(&self) -> Result<Option<Element>, AutosarDataError> {
63 let mut cur_elem_opt = self.parent()?;
64 while let Some(parent) = cur_elem_opt {
65 if parent.is_identifiable() {
66 return Ok(Some(parent));
67 }
68 cur_elem_opt = parent.parent()?;
69 }
70
71 Ok(None)
72 }
73
74 pub(crate) fn set_parent(&self, new_parent: ElementOrModel) {
75 self.0.write().set_parent(new_parent);
76 }
77
78 /// Get the [`ElementName`] of the element
79 ///
80 /// # Example
81 ///
82 /// ```
83 /// # use autosar_data::*;
84 /// # let model = AutosarModel::new();
85 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
86 /// let element = model.root_element();
87 /// let element_name = element.element_name();
88 /// assert_eq!(element_name, ElementName::Autosar);
89 /// ```
90 #[must_use]
91 pub fn element_name(&self) -> ElementName {
92 self.0.read().elemname
93 }
94
95 /// Get the [`ElementType`] of the element
96 ///
97 /// The `ElementType` is needed in order to call methods from the autosar-data-specification crate
98 ///
99 /// # Example
100 ///
101 /// ```
102 /// # use autosar_data::*;
103 /// # let model = AutosarModel::new();
104 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
105 /// # let element = model.root_element();
106 /// let element_type = element.element_type();
107 /// ```
108 #[must_use]
109 pub fn element_type(&self) -> ElementType {
110 self.0.read().elemtype
111 }
112
113 /// Get the name of an identifiable element
114 ///
115 /// An identifiable element has a `<SHORT-NAME>` sub element and can be referenced using an autosar path.
116 ///
117 /// If the element is not identifiable, this function returns None
118 ///
119 /// # Example
120 ///
121 /// ```
122 /// # use autosar_data::*;
123 /// # let model = AutosarModel::new();
124 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
125 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
126 /// if let Some(item_name) = element.item_name() {
127 /// // ...
128 /// }
129 /// ```
130 #[must_use]
131 pub fn item_name(&self) -> Option<String> {
132 self.0.read().item_name()
133 }
134
135 /// Set the item name of this element
136 ///
137 /// This operation will update all references pointing to the element or its sub-elements so that they remain valid.
138 ///
139 /// # Example
140 ///
141 /// ```
142 /// # use autosar_data::*;
143 /// # let model = AutosarModel::new();
144 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
145 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
146 /// element.set_item_name("NewName");
147 /// ```
148 ///
149 /// # Note
150 ///
151 /// In order to rename an element *without* updating any references, do this instead:
152 /// ```
153 /// # use autosar_data::*;
154 /// # let model = AutosarModel::new();
155 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
156 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
157 /// if let Some(short_name) = element.get_sub_element(ElementName::ShortName) {
158 /// short_name.set_character_data("the_new_name");
159 /// }
160 /// ```
161 ///
162 /// # Errors
163 ///
164 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
165 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
166 /// The operation was aborted to avoid a deadlock, but can be retried.
167 /// - [`AutosarDataError::ItemNameRequired`] this function was called for an element which is not identifiable
168 ///
169 pub fn set_item_name(&self, new_name: &str) -> Result<(), AutosarDataError> {
170 // a new name is required
171 if new_name.is_empty() {
172 return Err(AutosarDataError::ItemNameRequired {
173 element: self.element_name(),
174 });
175 }
176 let model = self.model()?;
177 let version = self.min_version()?;
178 self.0.write().set_item_name(new_name, &model, version)
179 }
180
181 /// Returns true if the element is identifiable
182 ///
183 /// In order to be identifiable, the specification must require a SHORT-NAME
184 /// sub-element and the SHORT-NAME must actually be present.
185 ///
186 /// # Example
187 /// ```
188 /// # use autosar_data::*;
189 /// # let model = AutosarModel::new();
190 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
191 /// # let element = model.root_element();
192 /// if element.is_identifiable() {
193 /// // ...
194 /// }
195 /// ```
196 #[must_use]
197 pub fn is_identifiable(&self) -> bool {
198 self.0.read().is_identifiable()
199 }
200
201 /// Returns true if the element should contain a reference to another element
202 ///
203 /// The function does not check if the reference is valid
204 ///
205 /// # Example
206 ///
207 /// ```
208 /// # use autosar_data::*;
209 /// # let model = AutosarModel::new();
210 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
211 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
212 /// if element.is_reference() {
213 /// // ex: element.set_reference_target(...)
214 /// }
215 /// ```
216 #[must_use]
217 pub fn is_reference(&self) -> bool {
218 self.elemtype().is_ref()
219 }
220
221 /// Get the Autosar path of an identifiable element
222 ///
223 /// # Example
224 ///
225 /// ```
226 /// # use autosar_data::*;
227 /// # fn main() -> Result<(), AutosarDataError> {
228 /// # let model = AutosarModel::new();
229 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
230 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
231 /// let path = element.path()?;
232 /// # Ok(())
233 /// # }
234 /// ```
235 ///
236 /// # Errors
237 ///
238 /// - [`AutosarDataError::ItemDeleted`]: Th ecurrent element is in the deleted state and will be freed once the last reference is dropped
239 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
240 /// The operation was aborted to avoid a deadlock, but can be retried.
241 /// - [`AutosarDataError::ElementNotIdentifiable`]: The current element is not identifiable, so it has no Autosar path
242 ///
243 pub fn path(&self) -> Result<String, AutosarDataError> {
244 self.0.read().path()
245 }
246
247 /// Get the package element containing the current element
248 ///
249 /// It never returns the current element, even if the current element is a package, but always
250 /// goes up the hierarchy to find the nearest parent package.
251 ///
252 /// Returns
253 /// Ok(Some(Element)) if a parent package is found
254 /// Ok(None) at the top level.
255 /// Err if the action cannot be completed
256 ///
257 /// # Example
258 ///
259 /// ```
260 /// # use autosar_data::*;
261 /// # fn main() -> Result<(), AutosarDataError> {
262 /// # let model = AutosarModel::new();
263 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
264 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)?.create_named_sub_element(ElementName::ArPackage, "Pkg")?;
265 /// let package = element.package()?;
266 /// # Ok(())
267 /// # }
268 /// ```
269 ///
270 /// # Errors
271 ///
272 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
273 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
274 /// The operation was aborted to avoid a deadlock, but can be retried.
275 pub fn package(&self) -> Result<Option<Element>, AutosarDataError> {
276 let mut cur_elem = self.clone();
277 loop {
278 let parent = {
279 let element = cur_elem
280 .0
281 .try_read_for(std::time::Duration::from_millis(10))
282 .ok_or(AutosarDataError::ParentElementLocked)?;
283 match &element.parent {
284 ElementOrModel::Element(weak_parent) => {
285 let parent = weak_parent.upgrade().ok_or(AutosarDataError::ItemDeleted)?;
286 if parent.element_name() == ElementName::ArPackage {
287 return Ok(Some(parent));
288 }
289 parent
290 }
291 ElementOrModel::Model(_) => {
292 return Ok(None);
293 }
294 ElementOrModel::None => return Err(AutosarDataError::ItemDeleted),
295 }
296 };
297 cur_elem = parent;
298 }
299 }
300
301 /// Get a reference to the [`AutosarModel`] containing the current element
302 ///
303 /// # Example
304 ///
305 /// ```
306 /// # use autosar_data::*;
307 /// # fn main() -> Result<(), AutosarDataError> {
308 /// # let model = AutosarModel::new();
309 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
310 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
311 /// let file = element.model()?;
312 /// # Ok(())
313 /// # }
314 /// ```
315 ///
316 /// # Errors
317 ///
318 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
319 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
320 /// The operation was aborted to avoid a deadlock, but can be retried.
321 ///
322 pub fn model(&self) -> Result<AutosarModel, AutosarDataError> {
323 let mut cur_elem = self.clone();
324 loop {
325 let parent = {
326 let element = cur_elem
327 .0
328 .try_read_for(std::time::Duration::from_millis(10))
329 .ok_or(AutosarDataError::ParentElementLocked)?;
330 match &element.parent {
331 ElementOrModel::Element(weak_parent) => {
332 weak_parent.upgrade().ok_or(AutosarDataError::ItemDeleted)?
333 }
334 ElementOrModel::Model(weak_arxmlfile) => {
335 return weak_arxmlfile.upgrade().ok_or(AutosarDataError::ItemDeleted);
336 }
337 ElementOrModel::None => return Err(AutosarDataError::ItemDeleted),
338 }
339 };
340 cur_elem = parent;
341 }
342 }
343
344 /// Get the [`ContentType`] of the current element
345 ///
346 /// # Example
347 ///
348 /// ```
349 /// # use autosar_data::*;
350 /// # let model = AutosarModel::new();
351 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
352 /// # let element = model.root_element();
353 /// if element.content_type() == ContentType::CharacterData {
354 /// // ...
355 /// }
356 /// ```
357 #[must_use]
358 pub fn content_type(&self) -> ContentType {
359 match self.elemtype().content_mode() {
360 ContentMode::Sequence => ContentType::Elements,
361 ContentMode::Choice => ContentType::Elements,
362 ContentMode::Bag => ContentType::Elements,
363 ContentMode::Characters => ContentType::CharacterData,
364 ContentMode::Mixed => ContentType::Mixed,
365 }
366 }
367
368 /// Create a sub element at a suitable insertion position
369 ///
370 /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
371 /// It is not possible to create named sub elements with this function; use `create_named_sub_element`() for that instead.
372 ///
373 /// # Example
374 ///
375 /// ```
376 /// # use autosar_data::*;
377 /// # fn main() -> Result<(), AutosarDataError> {
378 /// # let model = AutosarModel::new();
379 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
380 /// let element = model.root_element().create_sub_element(ElementName::ArPackages)?;
381 /// # Ok(())
382 /// # }
383 /// ```
384 ///
385 /// # Errors
386 ///
387 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
388 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
389 /// The operation was aborted to avoid a deadlock, but can be retried.
390 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
391 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
392 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
393 /// - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element`().
394 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
395 pub fn create_sub_element(&self, element_name: ElementName) -> Result<Element, AutosarDataError> {
396 let version = self.min_version()?;
397 self.0
398 .try_write()
399 .ok_or(AutosarDataError::ParentElementLocked)?
400 .create_sub_element(self.downgrade(), element_name, version)
401 }
402
403 /// Create a sub element at the specified insertion position
404 ///
405 /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
406 /// It is not possible to create named sub elements with this function; use `create_named_sub_element_at`() for that instead.
407 ///
408 /// The specified insertion position will be compared to the range of valid insertion positions; if it falls outside that range then the function fails.
409 ///
410 /// # Example
411 ///
412 /// ```
413 /// # use autosar_data::*;
414 /// # fn main() -> Result<(), AutosarDataError> {
415 /// # let model = AutosarModel::new();
416 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
417 /// let element = model.root_element().create_sub_element_at(ElementName::ArPackages, 0)?;
418 /// # Ok(())
419 /// # }
420 /// ```
421 ///
422 /// # Errors
423 ///
424 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
425 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
426 /// The operation was aborted to avoid a deadlock, but can be retried.
427 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
428 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
429 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
430 /// - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element_at`().
431 /// - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position
432 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
433 pub fn create_sub_element_at(
434 &self,
435 element_name: ElementName,
436 position: usize,
437 ) -> Result<Element, AutosarDataError> {
438 let version = self.min_version()?;
439 self.0
440 .write()
441 .create_sub_element_at(self.downgrade(), element_name, position, version)
442 }
443
444 /// Create a named/identifiable sub element at a suitable insertion position
445 ///
446 /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
447 ///
448 /// This method can only be used to create identifiable sub elements.
449 ///
450 /// # Example
451 ///
452 /// ```
453 /// # use autosar_data::*;
454 /// # fn main() -> Result<(), AutosarDataError> {
455 /// # let model = AutosarModel::new();
456 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
457 /// let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
458 /// let element = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")?;
459 /// # Ok(())
460 /// # }
461 /// ```
462 ///
463 /// # Errors
464 ///
465 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
466 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
467 /// The operation was aborted to avoid a deadlock, but can be retried.
468 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
469 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
470 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
471 /// - [`AutosarDataError::ElementNotIdentifiable`]: The sub element does not have an item name, so you must use `create_sub_element`() instead.
472 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
473 pub fn create_named_sub_element(
474 &self,
475 element_name: ElementName,
476 item_name: &str,
477 ) -> Result<Element, AutosarDataError> {
478 let model = self.model()?;
479 let version = self.min_version()?;
480 self.0
481 .write()
482 .create_named_sub_element(self.downgrade(), element_name, item_name, &model, version)
483 }
484
485 /// Create a named/identifiable sub element at the specified insertion position
486 ///
487 /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
488 /// The specified insertion position will be compared to the range of valid insertion positions; if it falls outside that range then the function fails.
489 ///
490 /// This method can only be used to create identifiable sub elements.
491 ///
492 /// # Example
493 ///
494 /// ```
495 /// # use autosar_data::*;
496 /// # fn main() -> Result<(), AutosarDataError> {
497 /// # let model = AutosarModel::new();
498 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
499 /// let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
500 /// let element = pkgs_element.create_named_sub_element_at(ElementName::ArPackage, "Package", 0)?;
501 /// # Ok(())
502 /// # }
503 /// ```
504 ///
505 /// # Errors
506 ///
507 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
508 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
509 /// The operation was aborted to avoid a deadlock, but can be retried.
510 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
511 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
512 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
513 /// - [`AutosarDataError::ElementNotIdentifiable`]: The sub element does not have an item name, so you must use `create_sub_element`() instead.
514 /// - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position.
515 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
516 pub fn create_named_sub_element_at(
517 &self,
518 element_name: ElementName,
519 item_name: &str,
520 position: usize,
521 ) -> Result<Element, AutosarDataError> {
522 let model = self.model()?;
523 let version = self.min_version()?;
524 self.0
525 .write()
526 .create_named_sub_element_at(self.downgrade(), element_name, item_name, position, &model, version)
527 }
528
529 /// Create a deep copy of the given element and insert it as a sub-element
530 ///
531 /// The other element must be a permissible sub-element in this element and not conflict with any existing sub element.
532 /// The other element can originate from any loaded [`AutosarModel`], it does not have to originate from the same model or file as the current element.
533 ///
534 /// The [`AutosarVersion`] of the other element might differ from the version of the current file;
535 /// in this case a partial copy will be performed that omits all incompatible elements.
536 ///
537 /// If the copied element is identifiable, then the item name might be extended with a numerical suffix, if one is required in order to make the name unique.
538 /// For example: An identifiable element "Foo" already exists at the same path; the copied identifiable element will be renamed to "`Foo_1`".
539 ///
540 /// If the copied element or the hierarchy of elements under it contain any references, then these will need to be adjusted manually after copying.
541 ///
542 /// # Example
543 ///
544 /// ```
545 /// # use autosar_data::*;
546 /// # fn main() -> Result<(), AutosarDataError> {
547 /// # let model = AutosarModel::new();
548 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
549 /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
550 /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
551 /// # .and_then(|p| p.create_sub_element(ElementName::Elements))?;
552 /// # base.create_named_sub_element(ElementName::System, "Path")?;
553 /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
554 /// let element = base.create_copied_sub_element(&other_element)?;
555 /// # Ok(())
556 /// # }
557 /// ```
558 ///
559 /// # Errors
560 ///
561 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
562 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
563 /// The operation was aborted to avoid a deadlock, but can be retried.
564 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
565 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
566 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
567 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
568 pub fn create_copied_sub_element(&self, other: &Element) -> Result<Element, AutosarDataError> {
569 if self == other {
570 // trying to copy self into self never makes sense, and would deadlock
571 return Err(AutosarDataError::InvalidSubElement {
572 parent: self.element_name(),
573 element: self.element_name(),
574 });
575 }
576 let model = self.model()?;
577 let version = self.min_version()?;
578 self.0
579 .write()
580 .create_copied_sub_element(self.downgrade(), other, &model, version)
581 }
582
583 /// Create a deep copy of the given element and insert it as a sub-element at the given position
584 ///
585 /// The other element must be a permissible sub-element in this element and not conflict with any existing sub element.
586 /// The other element can originate from any loaded [`AutosarModel`], it does not have to originate from the same model or file as the current element.
587 ///
588 /// The [`AutosarVersion`] of the other element might differ from the version of the current file;
589 /// in this case a partial copy will be performed that omits all incompatible elements.
590 ///
591 /// If the copied element is identifiable, then the item name might be extended with a numerical suffix, if one is required in order to make the name unique.
592 /// For example: An identifiable element "Foo" already exists at the same path; the copied identifiable element will be renamed to "`Foo_1`".
593 ///
594 /// If the copied element or the hierarchy of elements under it contain any references, then these will need to be adjusted manually after copying.
595 ///
596 /// # Example
597 ///
598 /// ```
599 /// # use autosar_data::*;
600 /// # fn main() -> Result<(), AutosarDataError> {
601 /// # let model = AutosarModel::new();
602 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
603 /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
604 /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
605 /// # .and_then(|pkg| pkg.create_sub_element(ElementName::Elements))?;
606 /// # base.create_named_sub_element(ElementName::System, "Path")?;
607 /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
608 /// let element = base.create_copied_sub_element_at(&other_element, 0)?;
609 /// # Ok(())
610 /// # }
611 /// ```
612 ///
613 /// # Errors
614 ///
615 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
616 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
617 /// The operation was aborted to avoid a deadlock, but can be retried.
618 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
619 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
620 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
621 /// - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position.
622 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
623 pub fn create_copied_sub_element_at(&self, other: &Element, position: usize) -> Result<Element, AutosarDataError> {
624 if self == other {
625 // trying to copy self into self never makes sense, and would deadlock
626 return Err(AutosarDataError::InvalidSubElement {
627 parent: self.element_name(),
628 element: self.element_name(),
629 });
630 }
631 let model = self.model()?;
632 let version = self.min_version()?;
633 self.0
634 .write()
635 .create_copied_sub_element_at(self.downgrade(), other, position, &model, version)
636 }
637
638 /// Take an `element` from it's current location and place it in this element as a sub element
639 ///
640 /// The moved element can be taken from anywhere - even from a different arxml document that is not part of the same `AutosarModel`
641 ///
642 /// Restrictions:
643 /// 1) The element must have a compatible element type. If it could not have been created here, then it can't be moved either.
644 /// 2) The origin document of the element must have exactly the same `AutosarVersion` as the destination.
645 ///
646 /// # Example
647 ///
648 /// ```
649 /// # use autosar_data::*;
650 /// # fn main() -> Result<(), AutosarDataError> {
651 /// # let model = AutosarModel::new();
652 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
653 /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
654 /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
655 /// # .and_then(|pkg| pkg.create_sub_element(ElementName::Elements))?;
656 /// # base.create_named_sub_element(ElementName::System, "Path")?;
657 /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
658 /// let element = base.move_element_here(&other_element)?;
659 /// # Ok(())
660 /// # }
661 /// ```
662 ///
663 /// # Errors
664 ///
665 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
666 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
667 /// The operation was aborted to avoid a deadlock, but can be retried.
668 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
669 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
670 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
671 /// - [`AutosarDataError::VersionMismatch`]: The Autosar versions of the source and destination are different
672 /// - [`AutosarDataError::ForbiddenMoveToSubElement`]: The destination is a sub element of the source. Moving here is not possible
673 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
674 pub fn move_element_here(&self, move_element: &Element) -> Result<Element, AutosarDataError> {
675 let model_src = move_element.model()?;
676 let model = self.model()?;
677 let version_src = move_element.min_version()?;
678 let version = self.min_version()?;
679 if version != version_src {
680 return Err(AutosarDataError::VersionMismatch {
681 version_cur: version,
682 version_new: version_src,
683 });
684 }
685 self.0
686 .write()
687 .move_element_here(self.downgrade(), move_element, &model, &model_src, version)
688 }
689
690 /// Take an `element` from it's current location and place it at the given position in this element as a sub element
691 ///
692 /// The moved element can be taken from anywhere - even from a different arxml document that is not part of the same `AutosarModel`
693 ///
694 /// Restrictions:
695 /// 1) The element must have a compatible element type. If it could not have been created here, then it can't be moved either.
696 /// 2) The origin document of the element must have exactly the same `AutosarVersion` as the destination.
697 ///
698 /// # Example
699 ///
700 /// ```
701 /// # use autosar_data::*;
702 /// # fn main() -> Result<(), AutosarDataError> {
703 /// # let model = AutosarModel::new();
704 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
705 /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
706 /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
707 /// # .and_then(|p| p.create_sub_element(ElementName::Elements))?;
708 /// # base.create_named_sub_element(ElementName::System, "Path")?;
709 /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
710 /// let element = base.move_element_here_at(&other_element, 0)?;
711 /// # Ok(())
712 /// # }
713 /// ```
714 ///
715 /// # Errors
716 ///
717 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
718 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
719 /// The operation was aborted to avoid a deadlock, but can be retried.
720 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
721 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
722 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
723 /// - [`AutosarDataError::VersionMismatch`]: The Autosar versions of the source and destination are different
724 /// - [`AutosarDataError::ForbiddenMoveToSubElement`]: The destination is a sub element of the source. Moving here is not possible
725 /// - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position.
726 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
727 pub fn move_element_here_at(&self, move_element: &Element, position: usize) -> Result<Element, AutosarDataError> {
728 let model_src = move_element.model()?;
729 let model = self.model()?;
730 let version_src = move_element.min_version()?;
731 let version = self.min_version()?;
732 if version != version_src {
733 return Err(AutosarDataError::VersionMismatch {
734 version_cur: version,
735 version_new: version_src,
736 });
737 }
738 self.0
739 .write()
740 .move_element_here_at(self.downgrade(), move_element, position, &model, &model_src, version)
741 }
742
743 /// Remove the sub element `sub_element`
744 ///
745 /// The `sub_element` will be unlinked from the hierarchy of elements.
746 /// All of the sub-sub-elements nested under the removed element will also be recusively removed.
747 ///
748 /// Since all elements are reference counted, they might not be deallocated immediately, however they do become invalid and unusable immediately.
749 ///
750 /// # Example
751 ///
752 /// ```
753 /// # use autosar_data::*;
754 /// # fn main() -> Result<(), AutosarDataError> {
755 /// # let model = AutosarModel::new();
756 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
757 /// let packages = model.root_element().create_sub_element(ElementName::ArPackages)?;
758 /// model.root_element().remove_sub_element(packages)?;
759 /// # Ok(())
760 /// # }
761 /// ```
762 ///
763 /// # Errors
764 ///
765 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
766 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
767 /// The operation was aborted to avoid a deadlock, but can be retried.
768 /// - [`AutosarDataError::ElementNotFound`]: The sub element was not found in this element
769 /// - [`AutosarDataError::ShortNameRemovalForbidden`]: It is not permitted to remove the SHORT-NAME of identifiable elements since this would result in invalid data
770 pub fn remove_sub_element(&self, sub_element: Element) -> Result<(), AutosarDataError> {
771 let model = self.model()?;
772 self.0.write().remove_sub_element(sub_element, &model)
773 }
774
775 /// Remove a sub element identified by an ElementName
776 ///
777 /// If multiple sub elements with the same ElementName exist, only the first one will be removed.
778 ///
779 /// A sub element with the given ElementName will be unlinked from the hierarchy of elements.
780 /// All of the sub-sub-elements nested under the removed element will also be recusively removed.
781 ///
782 /// This is a convenience function that is equivalent to calling `get_sub_element()` followed by `remove_sub_element()`.
783 ///
784 /// # Example
785 ///
786 /// ```
787 /// # use autosar_data::*;
788 /// # fn main() -> Result<(), AutosarDataError> {
789 /// # let model = AutosarModel::new();
790 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
791 /// let packages = model.root_element().create_sub_element(ElementName::ArPackages)?;
792 /// model.root_element().remove_sub_element_kind(ElementName::ArPackages)?;
793 /// # Ok(())
794 /// # }
795 /// ```
796 ///
797 /// # Errors
798 ///
799 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
800 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
801 /// The operation was aborted to avoid a deadlock, but can be retried.
802 /// - [`AutosarDataError::ElementNotFound`]: The sub element was not found in this element
803 /// - [`AutosarDataError::ShortNameRemovalForbidden`]: It is not permitted to remove the SHORT-NAME of identifiable elements since this would result in invalid data
804 pub fn remove_sub_element_kind(&self, element_name: ElementName) -> Result<(), AutosarDataError> {
805 let Some(sub_element) = self.get_sub_element(element_name) else {
806 return Err(AutosarDataError::ElementNotFound {
807 target: element_name,
808 parent: self.element_name(),
809 });
810 };
811 self.remove_sub_element(sub_element)
812 }
813
814 /// Set the reference target for the element to target
815 ///
816 /// When the reference is updated, the DEST attribute is also updated to match the referenced element.
817 /// The current element must be a reference element, otherwise the function fails.
818 ///
819 /// # Example
820 ///
821 /// ```
822 /// # use autosar_data::*;
823 /// # fn main() -> Result<(), AutosarDataError> {
824 /// # let model = AutosarModel::new();
825 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
826 /// # let elements = model.root_element().create_sub_element(ElementName::ArPackages)
827 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
828 /// # .and_then(|e| e.create_sub_element(ElementName::Elements))?;
829 /// # let ref_element = elements.create_named_sub_element(ElementName::System, "System")
830 /// # .and_then(|e| e.create_sub_element(ElementName::FibexElements))
831 /// # .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
832 /// # .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))?;
833 /// let cluster_element = elements.create_named_sub_element(ElementName::CanCluster, "Cluster")?;
834 /// ref_element.set_reference_target(&cluster_element)?;
835 /// # Ok(())
836 /// # }
837 /// ```
838 ///
839 /// # Errors
840 ///
841 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
842 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
843 /// The operation was aborted to avoid a deadlock, but can be retried.
844 /// - [`AutosarDataError::NotReferenceElement`]: The current element is not a reference, so it is not possible to set a reference target
845 /// - [`AutosarDataError::InvalidReference`]: The target element is not a valid reference target for this reference
846 /// - [`AutosarDataError::ElementNotIdentifiable`]: The target element is not identifiable, so it cannot be referenced by an Autosar path
847 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
848 pub fn set_reference_target(&self, target: &Element) -> Result<(), AutosarDataError> {
849 self.set_reference_target_internal(target, None)
850 }
851
852 /// Set the reference target using a relative path and explicit BASE label
853 ///
854 /// This method behaves like [`Self::set_reference_target`], but it writes
855 /// a relative reference path and sets the BASE attribute to `base_label`.
856 ///
857 /// # Errors
858 ///
859 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
860 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
861 /// The operation was aborted to avoid a deadlock, but can be retried.
862 /// - [`AutosarDataError::NotReferenceElement`]: The current element is not a reference, so it is not possible to set a reference target
863 /// - [`AutosarDataError::InvalidReference`]: The target element is not a valid reference target for this reference
864 /// - [`AutosarDataError::InvalidReferenceBase`]: The target element uses an invalid base label
865 /// - [`AutosarDataError::ElementNotIdentifiable`]: The target element is not identifiable, so it cannot be referenced by an Autosar path
866 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
867 pub fn set_relative_reference_target(&self, target: &Element, base_label: &str) -> Result<(), AutosarDataError> {
868 self.set_reference_target_internal(target, Some(base_label))
869 }
870
871 fn set_reference_target_internal(
872 &self,
873 target: &Element,
874 base_label: Option<&str>,
875 ) -> Result<(), AutosarDataError> {
876 // the current element must be a reference
877 if !self.is_reference() {
878 return Err(AutosarDataError::NotReferenceElement);
879 }
880
881 // the target element must be identifiable, i.e. it has an autosar path
882 let new_ref = target.path()?;
883 // it must be possible to use the name of the referenced element name as an enum item in the dest attribute of the reference
884 let Some(enum_item) = EnumItem::from_str(target.element_name().to_str())
885 .ok()
886 .or(self.element_type().reference_dest_value(&target.element_type()))
887 else {
888 return Err(AutosarDataError::InvalidReference);
889 };
890
891 let model = self.model()?;
892 // build a target string, which is either the absolute path or the relative path depending on whether a base label was provided
893 let target_string = if let Some(base_label) = base_label {
894 // note - a reference can never occur outside a package, so we can unwrap Some(package) here without risking a panic
895 let ref_package_path = self.package()?.unwrap().path()?;
896 let base_path = model
897 .resolve_reference_base(base_label, &ref_package_path)
898 .ok_or(AutosarDataError::InvalidReferenceBase)?;
899 let mut trimmed_target_path = new_ref
900 .strip_prefix(&base_path)
901 .ok_or(AutosarDataError::InvalidReferenceBase)?;
902 if let Some(no_leading_slash) = trimmed_target_path.strip_prefix('/') {
903 trimmed_target_path = no_leading_slash;
904 }
905 trimmed_target_path.to_string()
906 } else {
907 new_ref
908 };
909
910 let version = self.min_version()?;
911 let mut element = self.0.write();
912 // set the DEST attribute first - this could fail if the target element has the wrong type
913 if element
914 .set_attribute_internal(AttributeName::Dest, CharacterData::Enum(enum_item), version)
915 .is_err()
916 {
917 return Err(AutosarDataError::InvalidReference);
918 }
919
920 let opt_old_ref = element.character_data().and_then(|cdata| cdata.string_value());
921
922 // if this reference previously referenced some other element, update
923 if let Some(old_ref) = opt_old_ref {
924 let old_base = element
925 .attribute_value(AttributeName::Base)
926 .and_then(|cdata| cdata.string_value());
927 model.fix_reference_origins(
928 &old_ref,
929 &target_string,
930 old_base.as_deref(),
931 base_label,
932 self.downgrade(),
933 );
934 } else {
935 // else initialise the new reference
936 model.add_reference_origin(&target_string, base_label, self.downgrade());
937 }
938
939 // if this is the PackageRef of a ReferenceBase, then we need to update the ReferenceBase index in the model
940 if element.element_name() == ElementName::PackageRef
941 && let Ok(Some(parent)) = element.parent()
942 && parent.element_name() == ElementName::ReferenceBase
943 && let Some(label) = parent
944 .get_sub_element(ElementName::ShortLabel)
945 .and_then(|e| e.character_data())
946 .and_then(|c| c.string_value())
947 && let Ok(Some(package)) = parent.package()
948 && let Ok(owner_package_path) = package.path()
949 {
950 model.fix_reference_base(
951 Some(label.clone()),
952 label,
953 target_string.clone(),
954 base_label.map(|s| s.to_string()),
955 owner_package_path,
956 );
957 }
958
959 // do the update
960 element.set_character_data(CharacterData::String(target_string), version)?;
961 if let Some(base_label) = base_label {
962 element.set_attribute_internal(
963 AttributeName::Base,
964 CharacterData::String(base_label.to_string()),
965 version,
966 )?;
967 } else {
968 // ensure BASE does not remain when retargeting from a relative reference to an absolute reference
969 element.remove_attribute(AttributeName::Base);
970 }
971
972 Ok(())
973 }
974
975 /// Get the referenced element
976 ///
977 /// This function will get the reference string from the character data of the element
978 /// as well as the destination type from the DEST attribute. Then a lookup of the Autosar
979 /// path is performed, and if an element is found at that path, then the type of the
980 /// element is compared to the expected type.
981 ///
982 /// The element is returned if it exists and its type is correct.
983 ///
984 /// # Example
985 ///
986 /// ```
987 /// # use autosar_data::*;
988 /// # fn main() -> Result<(), AutosarDataError> {
989 /// # let model = AutosarModel::new();
990 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
991 /// # let elements = model.root_element().create_sub_element(ElementName::ArPackages)
992 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
993 /// # .and_then(|e| e.create_sub_element(ElementName::Elements))?;
994 /// # let ref_element = elements.create_named_sub_element(ElementName::System, "System")
995 /// # .and_then(|e| e.create_sub_element(ElementName::FibexElements))
996 /// # .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
997 /// # .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))?;
998 /// let cluster_element = elements.create_named_sub_element(ElementName::CanCluster, "Cluster")?;
999 /// ref_element.set_reference_target(&cluster_element)?;
1000 /// let ref_target = ref_element.get_reference_target()?;
1001 /// assert_eq!(cluster_element, ref_target);
1002 /// # Ok(())
1003 /// # }
1004 /// ```
1005 ///
1006 /// # Errors
1007 ///
1008 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1009 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1010 /// The operation was aborted to avoid a deadlock, but can be retried.
1011 /// - [`AutosarDataError::NotReferenceElement`]: The current element is not a reference, so it is not possible to get the reference target
1012 /// - [`AutosarDataError::InvalidReference`]: The reference is invalid; there is no element with the referenced Autosar path
1013 pub fn get_reference_target(&self) -> Result<Element, AutosarDataError> {
1014 if self.is_reference() {
1015 if let Some(CharacterData::String(reference)) = self.character_data() {
1016 let model = self.model()?;
1017
1018 let full_path = if let Some(base_label) = self
1019 .attribute_value(AttributeName::Base)
1020 .and_then(|cdata| cdata.string_value())
1021 {
1022 // note - a reference can never occur outside a package, so we can unwrap Some(package) here without risking a panic
1023 let ref_package_path = self.package()?.unwrap().path()?;
1024 let reference_base = model
1025 .resolve_reference_base(&base_label, &ref_package_path)
1026 .ok_or(AutosarDataError::InvalidReference)?;
1027 format!("{reference_base}/{reference}")
1028 } else {
1029 reference
1030 };
1031 let target_elem = model
1032 .get_element_by_path(&full_path)
1033 .ok_or(AutosarDataError::InvalidReference)?;
1034
1035 let dest_value = self
1036 .attribute_value(AttributeName::Dest)
1037 .and_then(|cdata| cdata.enum_value())
1038 .ok_or(AutosarDataError::InvalidReference)?;
1039 if target_elem.element_type().verify_reference_dest(dest_value) {
1040 Ok(target_elem)
1041 } else {
1042 Err(AutosarDataError::InvalidReference)
1043 }
1044 } else {
1045 Err(AutosarDataError::InvalidReference)
1046 }
1047 } else {
1048 Err(AutosarDataError::NotReferenceElement)
1049 }
1050 }
1051
1052 /// Set the character data of this element
1053 ///
1054 /// This method only applies to elements which contain character data, i.e. `element.content_type` == `CharacterData` or Mixed.
1055 /// On elements with mixed content this function will replace all current content with the single new `CharacterData` item.
1056 ///
1057 /// # Example
1058 ///
1059 /// ```
1060 /// # use autosar_data::*;
1061 /// # fn main() -> Result<(), AutosarDataError> {
1062 /// # let model = AutosarModel::new();
1063 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1064 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1065 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?
1066 /// # .get_sub_element(ElementName::ShortName).unwrap();
1067 /// element.set_character_data("value")?;
1068 /// # Ok(())
1069 /// # }
1070 /// ```
1071 ///
1072 /// # Errors
1073 ///
1074 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1075 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1076 /// The operation was aborted to avoid a deadlock, but can be retried.
1077 /// - [`AutosarDataError::IncorrectContentType`]: Cannot set character data on an element which does not contain character data
1078 pub fn set_character_data<T: Into<CharacterData>>(&self, value: T) -> Result<(), AutosarDataError> {
1079 let chardata: CharacterData = value.into();
1080 self.set_character_data_internal(chardata)
1081 }
1082
1083 // internal function to set the character data - separated out since it doesn't need to be generic
1084 fn set_character_data_internal(&self, mut chardata: CharacterData) -> Result<(), AutosarDataError> {
1085 let elemtype = self.elemtype();
1086 if (elemtype.content_mode() == ContentMode::Characters || elemtype.content_mode() == ContentMode::Mixed)
1087 && let Some(cdata_spec) = elemtype.chardata_spec()
1088 {
1089 let model = self.model()?;
1090 let version = self.min_version()?;
1091 let mut compatible_value = CharacterData::check_value(&chardata, cdata_spec, version);
1092 if !compatible_value
1093 && matches!(
1094 cdata_spec,
1095 CharacterDataSpec::Pattern { .. } | CharacterDataSpec::String { .. }
1096 )
1097 {
1098 chardata = CharacterData::String(chardata.to_string());
1099 compatible_value = CharacterData::check_value(&chardata, cdata_spec, version);
1100 }
1101 if compatible_value {
1102 // if this is a SHORT-NAME element a whole lot of handling is needed in order to unbreak all the cross references
1103 let mut prev_path = None;
1104 if self.element_name() == ElementName::ShortName {
1105 // this SHORT-NAME element might be newly created, in which case there is no previous path
1106 if self.character_data().is_some()
1107 && let Some(parent) = self.parent()?
1108 {
1109 prev_path = Some(parent.path()?);
1110 }
1111 };
1112
1113 // if this is a reference, then some extra effort is needed there too
1114 let old_refval = if elemtype.is_ref() {
1115 self.character_data().and_then(|cdata| cdata.string_value())
1116 } else {
1117 None
1118 };
1119
1120 let old_label = if self.element_name() == ElementName::ShortLabel {
1121 self.character_data().and_then(|cdata| cdata.string_value())
1122 } else {
1123 None
1124 };
1125
1126 // update the character data
1127 {
1128 let mut element = self.0.write();
1129 element.content.clear();
1130 element.content.push(ElementContent::CharacterData(chardata));
1131 }
1132
1133 // short-name: make sure the hashmap in the top-level AutosarModel is updated so that this element can still be found
1134 if let Some(prev_path) = prev_path
1135 && let Some(parent) = self.parent()?
1136 {
1137 let new_path = parent.path()?;
1138 model.fix_identifiables(&prev_path, &new_path);
1139 }
1140
1141 // reference: update the references hashmap in the top-level AutosarModel
1142 if elemtype.is_ref()
1143 && let Some(CharacterData::String(refval)) = self.character_data()
1144 {
1145 let base = self
1146 .attribute_value(AttributeName::Base)
1147 .and_then(|cdata| cdata.string_value());
1148 if let Some(old_refval) = old_refval {
1149 model.fix_reference_origins(
1150 &old_refval,
1151 &refval,
1152 base.as_deref(),
1153 base.as_deref(),
1154 self.downgrade(),
1155 );
1156 } else {
1157 model.add_reference_origin(&refval, base.as_deref(), self.downgrade());
1158 }
1159 } else if self.element_name() == ElementName::ShortLabel
1160 && let Ok(Some(parent)) = self.parent()
1161 && parent.element_name() == ElementName::ReferenceBase
1162 && let Ok(Some(package)) = parent.package()
1163 && let Ok(owner_package_path) = package.path()
1164 {
1165 // setting or updating the label of a ReferenceBase
1166 if let Some(package_ref) = parent.get_sub_element(ElementName::PackageRef)
1167 && let Some(package_ref_val) =
1168 package_ref.character_data().and_then(|cdata| cdata.string_value())
1169 {
1170 let base_attr = package_ref
1171 .attribute_value(AttributeName::Base)
1172 .and_then(|cdata| cdata.string_value());
1173 // obviously the new label exists since we just set it
1174 let new_label = self.character_data().and_then(|cdata| cdata.string_value()).unwrap();
1175 model.fix_reference_base(old_label, new_label, package_ref_val, base_attr, owner_package_path);
1176 }
1177 }
1178
1179 return Ok(());
1180 }
1181 }
1182 Err(AutosarDataError::IncorrectContentType {
1183 element: self.element_name(),
1184 })
1185 }
1186
1187 /// Remove the character data of this element
1188 ///
1189 /// This method only applies to elements which contain character data, i.e. `element.content_type` == `CharacterData`
1190 ///
1191 /// # Example
1192 ///
1193 /// ```
1194 /// # use autosar_data::*;
1195 /// # fn main() -> Result<(), AutosarDataError> {
1196 /// # let model = AutosarModel::new();
1197 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1198 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1199 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
1200 /// # .and_then(|e| e.create_sub_element(ElementName::Elements))
1201 /// # .and_then(|e| e.create_named_sub_element(ElementName::System, "System"))
1202 /// # .and_then(|e| e. create_sub_element(ElementName::PncVectorLength))
1203 /// # .unwrap();
1204 /// element.remove_character_data()?;
1205 /// # Ok(())
1206 /// # }
1207 /// ```
1208 ///
1209 /// # Errors
1210 ///
1211 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1212 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1213 /// The operation was aborted to avoid a deadlock, but can be retried. Only relevant when removing references.
1214 /// - [`AutosarDataError::ShortNameRemovalForbidden`]: Removing the character content of a SHORT-NAME is forbidden
1215 /// - [`AutosarDataError::IncorrectContentType`]: Cannot set character data on an element whoch does not contain character data
1216 pub fn remove_character_data(&self) -> Result<(), AutosarDataError> {
1217 let elemtype = self.elemtype();
1218 if elemtype.content_mode() == ContentMode::Characters {
1219 if self.element_name() == ElementName::ShortName {
1220 Err(AutosarDataError::ShortNameRemovalForbidden)
1221 } else {
1222 if self.character_data().is_some() {
1223 if self.is_reference() {
1224 let model = self.model()?;
1225 if let Some(CharacterData::String(reference)) = self.character_data() {
1226 let base = self
1227 .attribute_value(AttributeName::Base)
1228 .and_then(|cdata| cdata.string_value());
1229 model.remove_reference_origin(&reference, base.as_deref(), self.downgrade());
1230 }
1231 }
1232 self.0.write().content.clear();
1233 }
1234 Ok(())
1235 }
1236 } else {
1237 Err(AutosarDataError::IncorrectContentType {
1238 element: self.element_name(),
1239 })
1240 }
1241 }
1242
1243 /// Insert a character data item into the content of this element
1244 ///
1245 /// This method only applies to elements which contain mixed data, i.e. `element.content_type`() == Mixed.
1246 /// Use `create_sub_element_at` to add an element instead of a character data item
1247 ///
1248 /// # Example
1249 ///
1250 /// ```
1251 /// # use autosar_data::*;
1252 /// # fn main() -> Result<(), AutosarDataError> {
1253 /// # let model = AutosarModel::new();
1254 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1255 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1256 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1257 /// // mixed content elements are primarily used for documentation and description
1258 /// let desc = element.create_sub_element(ElementName::Desc)?;
1259 /// let l2 = desc.create_sub_element(ElementName::L2)?;
1260 /// l2.insert_character_content_item("descriptive text", 0)?;
1261 /// # Ok(())
1262 /// # }
1263 /// ```
1264 ///
1265 /// # Errors
1266 ///
1267 /// - [`AutosarDataError::IncorrectContentType`] the element `content_type` is not Mixed
1268 /// - [`AutosarDataError::InvalidPosition`] the position is not valid
1269 pub fn insert_character_content_item(&self, chardata: &str, position: usize) -> Result<(), AutosarDataError> {
1270 let mut element = self.0.write();
1271 if let ContentMode::Mixed = element.elemtype.content_mode() {
1272 if position <= element.content.len() {
1273 element.content.insert(
1274 position,
1275 ElementContent::CharacterData(CharacterData::String(chardata.to_owned())),
1276 );
1277 Ok(())
1278 } else {
1279 Err(AutosarDataError::InvalidPosition)
1280 }
1281 } else {
1282 Err(AutosarDataError::IncorrectContentType {
1283 element: element.element_name(),
1284 })
1285 }
1286 }
1287
1288 /// Remove a character data item from the content of this element
1289 ///
1290 /// This method only applies to elements which contain mixed data, i.e. `element.content_type` == Mixed
1291 ///
1292 /// # Example
1293 ///
1294 /// ```
1295 /// # use autosar_data::*;
1296 /// # fn main() -> Result<(), AutosarDataError> {
1297 /// # let model = AutosarModel::new();
1298 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1299 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1300 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
1301 /// # .and_then(|e| e.create_sub_element(ElementName::Desc))
1302 /// # .and_then(|e| e.create_sub_element(ElementName::L2))?;
1303 /// element.insert_character_content_item("descriptive text", 0)?;
1304 /// element.remove_character_content_item(0)?;
1305 /// # Ok(())
1306 /// # }
1307 /// ```
1308 ///
1309 /// # Errors
1310 ///
1311 /// - [`AutosarDataError::IncorrectContentType`] the element `content_type` is not Mixed
1312 /// - [`AutosarDataError::InvalidPosition`] the position is not valid
1313 pub fn remove_character_content_item(&self, position: usize) -> Result<(), AutosarDataError> {
1314 let mut element = self.0.write();
1315 if let ContentMode::Mixed = element.elemtype.content_mode() {
1316 if position < element.content.len()
1317 && let ElementContent::CharacterData(_) = element.content[position]
1318 {
1319 element.content.remove(position);
1320 return Ok(());
1321 }
1322 Err(AutosarDataError::InvalidPosition)
1323 } else {
1324 Err(AutosarDataError::IncorrectContentType {
1325 element: element.element_name(),
1326 })
1327 }
1328 }
1329
1330 /// returns the number of content items in this element
1331 /// ```
1332 /// # use autosar_data::*;
1333 /// # fn main() -> Result<(), AutosarDataError> {
1334 /// # let model = AutosarModel::new();
1335 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1336 /// # let pkg = model.root_element().create_sub_element(ElementName::ArPackages)
1337 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1338 /// assert_eq!(pkg.content_item_count(), 1);
1339 /// # Ok(())
1340 /// # }
1341 /// ```
1342 #[must_use]
1343 pub fn content_item_count(&self) -> usize {
1344 self.0.read().content.len()
1345 }
1346
1347 /// Get the character content of the element
1348 ///
1349 /// This method only applies to elements which contain character data, i.e. `element.content_type`() == `CharacterData`
1350 ///
1351 /// # Example
1352 ///
1353 /// ```
1354 /// # use autosar_data::*;
1355 /// # fn main() -> Result<(), AutosarDataError> {
1356 /// # let model = AutosarModel::new();
1357 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1358 /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1359 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?
1360 /// # .get_sub_element(ElementName::ShortName).unwrap();
1361 /// match element.character_data() {
1362 /// Some(CharacterData::String(stringval)) => {},
1363 /// Some(CharacterData::Enum(enumval)) => {},
1364 /// Some(CharacterData::UnsignedInteger(intval)) => {},
1365 /// Some(CharacterData::Float(floatval)) => {},
1366 /// None => {},
1367 /// }
1368 /// # Ok(())
1369 /// # }
1370 /// ```
1371 #[must_use]
1372 pub fn character_data(&self) -> Option<CharacterData> {
1373 self.0.read().character_data()
1374 }
1375
1376 /// Create an iterator over all of the content of this element
1377 ///
1378 /// The iterator can return both sub elements and character data, wrapped as `ElementContent::Element` and `ElementContent::CharacterData`
1379 ///
1380 /// This method is intended to be used with elements that contain mixed content.
1381 ///
1382 /// # Example
1383 ///
1384 /// ```
1385 /// # use autosar_data::*;
1386 /// # let model = AutosarModel::new();
1387 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1388 /// # let element = model.root_element();
1389 /// for content_item in element.content() {
1390 /// match content_item {
1391 /// ElementContent::CharacterData(data) => {},
1392 /// ElementContent::Element(element) => {},
1393 /// }
1394 /// }
1395 /// ```
1396 #[must_use]
1397 pub fn content(&self) -> ElementContentIterator {
1398 ElementContentIterator::new(self)
1399 }
1400
1401 /// Create a weak reference to this element
1402 ///
1403 /// A weak reference can be stored without preventing the element from being deallocated.
1404 /// The weak reference has to be upgraded in order to be used, which can fail if the element no longer exists.
1405 ///
1406 /// See the documentation for [Arc]
1407 ///
1408 /// # Example
1409 ///
1410 /// ```
1411 /// # use autosar_data::*;
1412 /// # let model = AutosarModel::new();
1413 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1414 /// # let element = model.root_element();
1415 /// let weak_element = element.downgrade();
1416 /// ```
1417 #[must_use]
1418 pub fn downgrade(&self) -> WeakElement {
1419 WeakElement(Arc::downgrade(&self.0))
1420 }
1421
1422 /// return the position of this element within the parent element
1423 ///
1424 /// None may be returned if the element has been deleted, or for the root element (AUTOSAR) which has no parent.
1425 /// The returned position can be used with `get_sub_element_at()`.
1426 ///
1427 /// # Example
1428 ///
1429 /// ```
1430 /// # use autosar_data::*;
1431 /// # let model = AutosarModel::new();
1432 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1433 /// # let el_ar_packages = model.root_element().create_sub_element(ElementName::ArPackages).unwrap();
1434 /// let el_pkg1 = el_ar_packages.create_named_sub_element(ElementName::ArPackage, "Pkg1").unwrap();
1435 /// let el_pkg2 = el_ar_packages.create_named_sub_element(ElementName::ArPackage, "Pkg2").unwrap();
1436 /// let el_pkg3 = el_ar_packages.create_named_sub_element(ElementName::ArPackage, "Pkg3").unwrap();
1437 /// let position = el_pkg2.position().unwrap();
1438 /// assert_eq!(position, 1);
1439 /// assert_eq!(el_pkg2, el_ar_packages.get_sub_element_at(position).unwrap());
1440 /// ```
1441 #[must_use]
1442 pub fn position(&self) -> Option<usize> {
1443 if let Ok(Some(parent)) = self.parent() {
1444 parent
1445 .0
1446 .read()
1447 .content
1448 .iter()
1449 .position(|ec| matches!(ec, ElementContent::Element(elem) if elem == self))
1450 } else {
1451 None
1452 }
1453 }
1454
1455 /// Create an iterator over all sub elements of this element
1456 ///
1457 /// # Example
1458 ///
1459 /// ```
1460 /// # use autosar_data::*;
1461 /// # let model = AutosarModel::new();
1462 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1463 /// # let element = model.root_element();
1464 /// for sub_element in element.sub_elements() {
1465 /// // ...
1466 /// }
1467 /// ```
1468 #[must_use]
1469 pub fn sub_elements(&self) -> ElementsIterator {
1470 ElementsIterator::new(self.clone())
1471 }
1472
1473 /// Get the sub element with the given element name
1474 ///
1475 /// Returns None if no such element exists. if there are multiple sub elements with the requested name, then only the first is returned
1476 ///
1477 /// # Example
1478 ///
1479 /// ```
1480 /// # use autosar_data::*;
1481 /// # fn main() -> Result<(), AutosarDataError> {
1482 /// # let model = AutosarModel::new();
1483 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1484 /// # let pkg = model.root_element().create_sub_element(ElementName::ArPackages)
1485 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1486 /// let element = pkg.get_sub_element(ElementName::ShortName).unwrap();
1487 /// assert_eq!(element.element_name(), ElementName::ShortName);
1488 /// # Ok(())
1489 /// # }
1490 /// ```
1491 #[must_use]
1492 pub fn get_sub_element(&self, name: ElementName) -> Option<Element> {
1493 let locked_elem = self.0.read();
1494 for item in &locked_elem.content {
1495 if let ElementContent::Element(subelem) = item
1496 && subelem.element_name() == name
1497 {
1498 return Some(subelem.clone());
1499 }
1500 }
1501 None
1502 }
1503
1504 /// Get the sub element at the given position.
1505 ///
1506 /// Returns None if no such element exists.
1507 ///
1508 /// # Example
1509 ///
1510 /// ```
1511 /// # use autosar_data::*;
1512 /// # fn main() -> Result<(), AutosarDataError> {
1513 /// # let model = AutosarModel::new();
1514 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1515 /// # let pkg = model.root_element().create_sub_element(ElementName::ArPackages)
1516 /// # .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1517 /// let element = pkg.get_sub_element_at(0).unwrap();
1518 /// assert_eq!(element.element_name(), ElementName::ShortName);
1519 /// # Ok(())
1520 /// # }
1521 /// ```
1522 #[must_use]
1523 pub fn get_sub_element_at(&self, position: usize) -> Option<Element> {
1524 let locked_elem = self.0.read();
1525 if let Some(ElementContent::Element(subelem)) = locked_elem.content.get(position) {
1526 return Some(subelem.clone());
1527 }
1528 None
1529 }
1530
1531 /// Get or create a sub element
1532 ///
1533 /// This is a shorthand for `get_sub_element` followed by `create_cub_element` if getting an existing element fails.
1534 ///
1535 /// # Example
1536 ///
1537 /// ```
1538 /// # use autosar_data::*;
1539 /// # fn main() -> Result<(), AutosarDataError> {
1540 /// # let model = AutosarModel::new();
1541 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1542 /// let element = model.root_element().get_or_create_sub_element(ElementName::ArPackages)?;
1543 /// let element2 = model.root_element().get_or_create_sub_element(ElementName::ArPackages)?;
1544 /// assert_eq!(element, element2);
1545 /// # Ok(())
1546 /// # }
1547 /// ```
1548 ///
1549 /// # Errors
1550 ///
1551 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1552 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1553 /// The operation was aborted to avoid a deadlock, but can be retried.
1554 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
1555 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
1556 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
1557 /// - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element`().
1558 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1559 pub fn get_or_create_sub_element(&self, name: ElementName) -> Result<Element, AutosarDataError> {
1560 let version = self.min_version()?;
1561 let mut locked_elem = self.0.try_write().ok_or(AutosarDataError::ParentElementLocked)?;
1562 for item in &locked_elem.content {
1563 if let ElementContent::Element(subelem) = item
1564 && subelem.element_name() == name
1565 {
1566 return Ok(subelem.clone());
1567 }
1568 }
1569 locked_elem.create_sub_element(self.downgrade(), name, version)
1570 }
1571 /// Get or create a named sub element
1572 ///
1573 /// Checks if a matching subelement exists, and returns it if it does.
1574 /// If no matching subelement exists, tries to create one.
1575 ///
1576 /// # Example
1577 ///
1578 /// ```
1579 /// # use autosar_data::*;
1580 /// # fn main() -> Result<(), AutosarDataError> {
1581 /// # let model = AutosarModel::new();
1582 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1583 /// let ar_packages = model.root_element().get_or_create_sub_element(ElementName::ArPackages)?;
1584 /// let pkg = ar_packages.get_or_create_named_sub_element(ElementName::ArPackage, "Pkg")?;
1585 /// let pkg_2 = ar_packages.get_or_create_named_sub_element(ElementName::ArPackage, "Pkg")?;
1586 /// assert_eq!(pkg, pkg_2);
1587 /// # Ok(())
1588 /// # }
1589 /// ```
1590 ///
1591 /// # Errors
1592 ///
1593 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1594 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1595 /// The operation was aborted to avoid a deadlock, but can be retried.
1596 /// - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
1597 /// - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
1598 /// - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
1599 /// - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element`().
1600 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1601 pub fn get_or_create_named_sub_element(
1602 &self,
1603 element_name: ElementName,
1604 item_name: &str,
1605 ) -> Result<Element, AutosarDataError> {
1606 let model = self.model()?;
1607 let version = self.min_version()?;
1608 let mut locked_elem = self.0.try_write().ok_or(AutosarDataError::ParentElementLocked)?;
1609 for item in &locked_elem.content {
1610 if let ElementContent::Element(subelem) = item
1611 && subelem.element_name() == element_name
1612 && subelem.item_name().as_deref().unwrap_or("") == item_name
1613 {
1614 return Ok(subelem.clone());
1615 }
1616 }
1617 locked_elem.create_named_sub_element(self.downgrade(), element_name, item_name, &model, version)
1618 }
1619
1620 /// Create a depth first iterator over this element and all of its sub elements
1621 ///
1622 /// Each step in the iteration returns the depth and an element. Due to the nature of a depth first search,
1623 /// the returned depth can remain the same, increase by one, or decrease by an arbitrary number in each step.
1624 ///
1625 /// The dfs iterator will always return this element as the first item.
1626 ///
1627 /// # Example
1628 ///
1629 /// ```
1630 /// # use autosar_data::*;
1631 /// # let model = AutosarModel::new();
1632 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1633 /// # let element = model.root_element();
1634 /// for (depth, elem) in element.elements_dfs() {
1635 /// // ...
1636 /// }
1637 /// ```
1638 #[must_use]
1639 pub fn elements_dfs(&self) -> ElementsDfsIterator {
1640 ElementsDfsIterator::new(self, 0)
1641 }
1642
1643 /// Create a depth first iterator over this element and all of its sub elements up to a maximum depth
1644 ///
1645 /// Each step in the iteration returns the depth and an element. Due to the nature of a depth first search,
1646 /// the returned depth can remain the same, increase by one, or decrease by an arbitrary number in each step.
1647 ///
1648 /// The dfs iterator will always return this element as the first item. A `max_depth` of `0` returns all
1649 /// child elements, regardless of depth (like `elements_dfs` does).
1650 ///
1651 /// # Example
1652 ///
1653 /// ```
1654 /// # use autosar_data::*;
1655 /// # let model = AutosarModel::new();
1656 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1657 /// # let element = model.root_element();
1658 /// # element.create_sub_element(ElementName::ArPackages).unwrap();
1659 /// # let sub_elem = element.get_sub_element(ElementName::ArPackages).unwrap();
1660 /// # sub_elem.create_named_sub_element(ElementName::ArPackage, "test2").unwrap();
1661 /// for (depth, elem) in element.elements_dfs_with_max_depth(1) {
1662 /// assert!(depth <= 1);
1663 /// // ...
1664 /// }
1665 /// ```
1666 #[must_use]
1667 pub fn elements_dfs_with_max_depth(&self, max_depth: usize) -> ElementsDfsIterator {
1668 ElementsDfsIterator::new(self, max_depth)
1669 }
1670
1671 /// Create an iterator over all the attributes in this element
1672 ///
1673 /// # Example
1674 ///
1675 /// ```
1676 /// # use autosar_data::*;
1677 /// # let model = AutosarModel::new();
1678 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1679 /// # let element = model.root_element();
1680 /// for attribute in element.attributes() {
1681 /// println!("{} = {}", attribute.attrname, attribute.content);
1682 /// }
1683 /// ```
1684 #[must_use]
1685 pub fn attributes(&self) -> AttributeIterator {
1686 AttributeIterator {
1687 element: self.clone(),
1688 index: 0,
1689 }
1690 }
1691
1692 /// Get the value of an attribute by name
1693 ///
1694 /// # Example
1695 ///
1696 /// ```
1697 /// # use autosar_data::*;
1698 /// # let model = AutosarModel::new();
1699 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1700 /// let value = model.root_element().attribute_value(AttributeName::xsiSchemalocation);
1701 /// ```
1702 #[must_use]
1703 pub fn attribute_value(&self, attrname: AttributeName) -> Option<CharacterData> {
1704 self.0.read().attribute_value(attrname)
1705 }
1706
1707 /// Set the value of a named attribute
1708 ///
1709 /// If no attribute by that name exists, and the attribute is a valid attribute of the element, then the attribute will be created.
1710 ///
1711 /// Returns Ok(()) if the attribute was set, otherwise the Err indicates why setting the attribute failed.
1712 ///
1713 /// ```
1714 /// # use autosar_data::*;
1715 /// # let model = AutosarModel::new();
1716 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1717 /// # let element = model.root_element();
1718 /// let result = element.set_attribute(AttributeName::S, CharacterData::String("1234-5678".to_string()));
1719 /// # assert!(result.is_ok());
1720 /// ```
1721 ///
1722 /// # Errors
1723 ///
1724 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1725 /// - [`AutosarDataError::InvalidAttribute`]: The `AttributeName` is not valid for this element
1726 /// - [`AutosarDataError::InvalidAttributeValue`]: The value is not valid for this attribute in this element
1727 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1728 pub fn set_attribute<T: Into<CharacterData>>(
1729 &self,
1730 attrname: AttributeName,
1731 value: T,
1732 ) -> Result<(), AutosarDataError> {
1733 let version = self.min_version()?;
1734 let (old_base, reference_value) = self.base_attribute_info(attrname);
1735
1736 self.0.write().set_attribute_internal(attrname, value.into(), version)?;
1737
1738 self.base_attribute_fixup(attrname, old_base, reference_value);
1739
1740 Ok(())
1741 }
1742
1743 /// Set the value of a named attribute from a string
1744 ///
1745 /// The function tries to convert the string to the correct data type for the attribute
1746 ///
1747 /// Returns Ok(()) if the attribute was set, otherwise the Err indicates why setting the attribute failed.
1748 ///
1749 /// ```
1750 /// # use autosar_data::*;
1751 /// # let model = AutosarModel::new();
1752 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1753 /// # let element = model.root_element();
1754 /// let result = element.set_attribute_string(AttributeName::T, "2022-01-31T13:59:59Z");
1755 /// # assert!(result.is_ok());
1756 /// ```
1757 ///
1758 /// # Errors
1759 ///
1760 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1761 /// - [`AutosarDataError::InvalidAttribute`]: The `AttributeName` is not valid for this element
1762 /// - [`AutosarDataError::InvalidAttributeValue`]: The value is not valid for this attribute in this element
1763 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1764 pub fn set_attribute_string(&self, attrname: AttributeName, stringvalue: &str) -> Result<(), AutosarDataError> {
1765 let version = self.min_version()?;
1766 let (old_base, reference_value) = self.base_attribute_info(attrname);
1767
1768 self.0.write().set_attribute_string(attrname, stringvalue, version)?;
1769
1770 // post-change fixup for BASE attribute changes only
1771 self.base_attribute_fixup(attrname, old_base, reference_value);
1772
1773 Ok(())
1774 }
1775
1776 /// Remove an attribute from the element
1777 ///
1778 /// Returns true if the attribute existed and could be removed.
1779 ///
1780 /// # Example
1781 ///
1782 /// ```
1783 /// # use autosar_data::*;
1784 /// # let model = AutosarModel::new();
1785 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1786 /// let result = model.root_element().remove_attribute(AttributeName::xsiSchemalocation);
1787 /// // xsiSchemalocation exists in the AUTOSAR element, but it is mandatory and cannot be removed
1788 /// assert_eq!(result, false);
1789 /// ```
1790 #[must_use]
1791 pub fn remove_attribute(&self, attrname: AttributeName) -> bool {
1792 let (old_base, reference_value) = self.base_attribute_info(attrname);
1793
1794 let removed = self.0.write().remove_attribute(attrname);
1795
1796 if removed {
1797 // fix the cache of reference origins if a BASE attribute was removed
1798 self.base_attribute_fixup(attrname, old_base, reference_value);
1799 }
1800 removed
1801 }
1802
1803 /// helper function to get the old BASE attribute value and the reference value before changing or removing the attribute
1804 fn base_attribute_info(&self, attrname: AttributeName) -> (Option<String>, Option<String>) {
1805 if !self.is_reference() || attrname != AttributeName::Base {
1806 return (None, None);
1807 }
1808 let old_base = self
1809 .attribute_value(AttributeName::Base)
1810 .and_then(|cdata| cdata.string_value());
1811 let reference_value = self.character_data().and_then(|cdata| cdata.string_value());
1812 (old_base, reference_value)
1813 }
1814
1815 /// fix the reference origins of a reference element if the BASE attribute was changed or removed
1816 fn base_attribute_fixup(&self, attrname: AttributeName, old_base: Option<String>, reference_value: Option<String>) {
1817 if attrname == AttributeName::Base
1818 && let Some(reference_value) = reference_value
1819 && let Ok(model) = self.model()
1820 {
1821 let new_base = self
1822 .attribute_value(AttributeName::Base)
1823 .and_then(|cdata| cdata.string_value());
1824 model.fix_reference_origins(
1825 &reference_value,
1826 &reference_value,
1827 old_base.as_deref(),
1828 new_base.as_deref(),
1829 self.downgrade(),
1830 );
1831 }
1832 }
1833
1834 /// Recursively sort all sub-elements of this element
1835 ///
1836 /// All sub elements of the current element are sorted alphabetically.
1837 /// If the sub-elements are named, then the sorting is performed according to the item names,
1838 /// otherwise the serialized form of the sub-elements is used for sorting.
1839 /// Element attributes are not taken into account while sorting.
1840 /// The elements are sorted in place, and sorting cannot fail, so there is no return value.
1841 ///
1842 /// # Example
1843 /// ```
1844 /// # use autosar_data::*;
1845 /// # let model = AutosarModel::new();
1846 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1847 /// # let element = model.root_element();
1848 /// element.sort();
1849 /// ```
1850 pub fn sort(&self) {
1851 self.0.write().sort();
1852 }
1853
1854 /// Serialize the element and all of its content to a string
1855 ///
1856 /// The serialized text generated for elements below the root element cannot be loaded, but it may be useful for display.
1857 ///
1858 /// # Example
1859 ///
1860 /// ```
1861 /// # use autosar_data::*;
1862 /// # let model = AutosarModel::new();
1863 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1864 /// # let element = model.root_element();
1865 /// let text = element.serialize();
1866 /// ```
1867 #[must_use]
1868 pub fn serialize(&self) -> String {
1869 let mut outstring = String::new();
1870
1871 self.serialize_internal(&mut outstring, 0, false, &None);
1872
1873 outstring
1874 }
1875
1876 pub(crate) fn serialize_internal(
1877 &self,
1878 outstring: &mut String,
1879 indent: usize,
1880 inline: bool,
1881 for_file: &Option<WeakArxmlFile>,
1882 ) {
1883 let element = self.0.read();
1884 let element_name = element.elemname.to_str();
1885
1886 if let Some(comment) = &self.0.read().comment {
1887 // put the comment on a separate line
1888 if !inline {
1889 Self::serialize_newline_indent(outstring, indent);
1890 }
1891 outstring.push_str("<!--");
1892 outstring.push_str(comment);
1893 outstring.push_str("-->");
1894 }
1895
1896 // write the opening tag on a new line and indent it
1897 if !inline {
1898 Self::serialize_newline_indent(outstring, indent);
1899 }
1900
1901 if !element.content.is_empty() {
1902 outstring.push('<');
1903 outstring.push_str(element_name);
1904 self.serialize_attributes(outstring);
1905 outstring.push('>');
1906
1907 match self.content_type() {
1908 ContentType::Elements => {
1909 // serialize each sub-element
1910 for subelem in self.sub_elements() {
1911 if for_file.is_none()
1912 || subelem.0.read().file_membership.is_empty()
1913 || subelem.0.read().file_membership.contains(for_file.as_ref().unwrap())
1914 {
1915 subelem.serialize_internal(outstring, indent + 1, false, for_file);
1916 }
1917 }
1918 // put the closing tag on a new line and indent it
1919 Self::serialize_newline_indent(outstring, indent);
1920 outstring.push_str("</");
1921 outstring.push_str(element_name);
1922 outstring.push('>');
1923 }
1924 ContentType::CharacterData => {
1925 // write the character data on the same line as the opening tag
1926 if let Some(ElementContent::CharacterData(chardata)) = element.content.first() {
1927 chardata.serialize_internal(outstring);
1928 }
1929
1930 // write the closing tag on the same line
1931 outstring.push_str("</");
1932 outstring.push_str(element_name);
1933 outstring.push('>');
1934 }
1935 ContentType::Mixed => {
1936 for item in self.content() {
1937 match item {
1938 ElementContent::Element(subelem) => {
1939 if for_file.is_none()
1940 || subelem.0.read().file_membership.is_empty()
1941 || subelem.0.read().file_membership.contains(for_file.as_ref().unwrap())
1942 {
1943 subelem.serialize_internal(outstring, indent + 1, true, for_file);
1944 }
1945 }
1946 ElementContent::CharacterData(chardata) => {
1947 chardata.serialize_internal(outstring);
1948 }
1949 }
1950 }
1951 // write the closing tag on the same line
1952 outstring.push_str("</");
1953 outstring.push_str(element_name);
1954 outstring.push('>');
1955 }
1956 }
1957 } else {
1958 outstring.push('<');
1959 outstring.push_str(element_name);
1960 self.serialize_attributes(outstring);
1961 outstring.push('/');
1962 outstring.push('>');
1963 }
1964 }
1965
1966 fn serialize_newline_indent(outstring: &mut String, indent: usize) {
1967 outstring.push('\n');
1968 for _ in 0..indent {
1969 outstring.push_str(" ");
1970 }
1971 }
1972
1973 fn serialize_attributes(&self, outstring: &mut String) {
1974 let element = self.0.read();
1975 if !element.attributes.is_empty() {
1976 for attribute in &element.attributes {
1977 outstring.push(' ');
1978 outstring.push_str(attribute.attrname.to_str());
1979 outstring.push_str("=\"");
1980 attribute.content.serialize_internal(outstring);
1981 outstring.push('"');
1982 }
1983 }
1984 }
1985
1986 pub(crate) fn elemtype(&self) -> ElementType {
1987 self.0.read().elemtype
1988 }
1989
1990 // an element might have a diffeent element type depending on the version - as a result of a
1991 // changed datatype of the CharacterData, or because the element ordering was changed
1992 fn recalc_element_type(&self, target_version: AutosarVersion) -> ElementType {
1993 if let Ok(Some(parent)) = self.parent()
1994 && let Some((etype, ..)) = parent
1995 .element_type()
1996 .find_sub_element(self.element_name(), target_version as u32)
1997 {
1998 return etype;
1999 }
2000
2001 self.element_type()
2002 }
2003
2004 /// check if the sub elements and attributes of this element are compatible with some `target_version`
2005 pub(crate) fn check_version_compatibility(
2006 &self,
2007 file: &WeakArxmlFile,
2008 target_version: AutosarVersion,
2009 ) -> (Vec<CompatibilityError>, u32) {
2010 let mut compat_errors = Vec::new();
2011 let mut overall_version_mask = u32::MAX;
2012
2013 // make sure compatibility checks are performed with the element type used in the target version
2014 let elemtype_new = self.recalc_element_type(target_version);
2015
2016 // check the compatibility of all the attributes in this element
2017 {
2018 let element = self.0.read();
2019 for attribute in &element.attributes {
2020 // find the specification for the current attribute
2021 if let Some(AttributeSpec {
2022 spec: value_spec,
2023 version: version_mask,
2024 ..
2025 }) = elemtype_new.find_attribute_spec(attribute.attrname)
2026 {
2027 overall_version_mask &= version_mask;
2028 // check if the attribute is allowed at all
2029 if !target_version.compatible(version_mask) {
2030 compat_errors.push(CompatibilityError::IncompatibleAttribute {
2031 element: self.clone(),
2032 attribute: attribute.attrname,
2033 version_mask,
2034 });
2035 } else {
2036 let (is_compatible, value_version_mask) = attribute
2037 .content
2038 .check_version_compatibility(value_spec, target_version);
2039 if !is_compatible {
2040 compat_errors.push(CompatibilityError::IncompatibleAttributeValue {
2041 element: self.clone(),
2042 attribute: attribute.attrname,
2043 attribute_value: attribute.content.to_string(),
2044 version_mask: value_version_mask,
2045 });
2046 }
2047 overall_version_mask &= value_version_mask;
2048 }
2049 }
2050 }
2051 }
2052
2053 // check the compatibility of all sub-elements
2054 for sub_element in self.sub_elements() {
2055 if (sub_element.0.read().file_membership.is_empty() || sub_element.0.read().file_membership.contains(file))
2056 && let Some((_, indices)) = elemtype_new
2057 .find_sub_element(sub_element.element_name(), target_version as u32)
2058 .or(elemtype_new.find_sub_element(sub_element.element_name(), u32::MAX))
2059 {
2060 let version_mask = self.element_type().get_sub_element_version_mask(&indices).unwrap();
2061 overall_version_mask &= version_mask;
2062 if !target_version.compatible(version_mask) {
2063 compat_errors.push(CompatibilityError::IncompatibleElement {
2064 element: sub_element.clone(),
2065 version_mask,
2066 });
2067 } else {
2068 let (mut sub_element_errors, sub_element_mask) =
2069 sub_element.check_version_compatibility(file, target_version);
2070 compat_errors.append(&mut sub_element_errors);
2071 overall_version_mask &= sub_element_mask;
2072 }
2073 }
2074 }
2075
2076 (compat_errors, overall_version_mask)
2077 }
2078
2079 /// List all `sub_elements` that are valid in the current element
2080 ///
2081 /// The target use case is direct interaction with a user, e.g. through a selection dialog
2082 ///
2083 /// # Return Value
2084 ///
2085 /// A list of tuples consisting of
2086 /// `ElementName` of the potential sub element
2087 /// bool: is the sub element named
2088 /// bool: can this sub element be inserted considering the current content of the element
2089 ///
2090 /// # Example
2091 ///
2092 /// ```
2093 /// # use autosar_data::*;
2094 /// # let model = AutosarModel::new();
2095 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2096 /// # let element = model.root_element();
2097 /// for ValidSubElementInfo{element_name, is_named, is_allowed} in element.list_valid_sub_elements() {
2098 /// // ...
2099 /// }
2100 /// ```
2101 #[must_use]
2102 pub fn list_valid_sub_elements(&self) -> Vec<ValidSubElementInfo> {
2103 let etype = self.0.read().elemtype;
2104 let mut valid_sub_elements = Vec::new();
2105
2106 if let Ok(version) = self.min_version() {
2107 for (element_name, _, version_mask, named_mask) in etype.sub_element_spec_iter() {
2108 if version.compatible(version_mask) {
2109 let is_named = version.compatible(named_mask);
2110 let is_allowed = self.0.read().calc_element_insert_range(element_name, version).is_ok();
2111 valid_sub_elements.push(ValidSubElementInfo {
2112 element_name,
2113 is_named,
2114 is_allowed,
2115 });
2116 }
2117 }
2118 }
2119
2120 valid_sub_elements
2121 }
2122
2123 /// Return the set of files in which the current element is present
2124 ///
2125 /// # Return Value
2126 ///
2127 /// A tuple (bool, `HashSet`); if the bool value is true, then the file set is stored in this element, otherwise it is inherited from a parent element.
2128 ///
2129 /// # Example
2130 ///
2131 /// ```
2132 /// # use autosar_data::*;
2133 /// # fn main() -> Result<(), AutosarDataError> {
2134 /// # let model = AutosarModel::new();
2135 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2136 /// # let element = model.root_element();
2137 /// let (inherited, file_membership) = element.file_membership()?;
2138 /// # Ok(())
2139 /// # }
2140 /// ```
2141 /// # Errors
2142 ///
2143 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2144 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2145 /// The operation was aborted to avoid a deadlock, but can be retried.
2146 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
2147 pub fn file_membership(&self) -> Result<(bool, HashSet<WeakArxmlFile>), AutosarDataError> {
2148 let mut cur_elem_opt = Some(self.clone());
2149 while let Some(cur_elem) = &cur_elem_opt {
2150 let locked_cur_elem = cur_elem
2151 .0
2152 .try_read_for(std::time::Duration::from_millis(10))
2153 .ok_or(AutosarDataError::ParentElementLocked)?;
2154 if !locked_cur_elem.file_membership.is_empty() {
2155 return Ok((cur_elem == self, locked_cur_elem.file_membership.clone()));
2156 }
2157 drop(locked_cur_elem);
2158
2159 cur_elem_opt = cur_elem.parent()?;
2160 }
2161
2162 // no file membership info found at any level - this only happens if the model does not contain any files
2163 Err(AutosarDataError::NoFilesInModel)
2164 }
2165
2166 /// return the file membership of this element without trying to get an inherited value
2167 pub(crate) fn file_membership_local(&self) -> HashSet<WeakArxmlFile> {
2168 self.0.read().file_membership.clone()
2169 }
2170
2171 /// set the file membership of an element
2172 ///
2173 /// The passed set acts as a restriction of the file membership of the parent element.
2174 /// This means that the set of a child cannot be greater than that of the parent.
2175 ///
2176 /// Setting an empty set has a special meaning: it reverts the membership to default,
2177 /// i.e. inherited from the parent with no additional restriction
2178 pub(crate) fn set_file_membership(&self, file_membership: HashSet<WeakArxmlFile>) {
2179 // find out if the parent is splittable. If the parent is unavaliable, assume
2180 // that the caller knows what they're doing and assume it is splittable
2181 let parent_splittable = self
2182 .parent()
2183 .ok()
2184 .flatten()
2185 .map_or(u32::MAX, |p| p.element_type().splittable());
2186 // can always reset the membership to empty = inherited; otherwise the parent must be splittable
2187 if file_membership.is_empty() || parent_splittable != 0 {
2188 self.0.write().file_membership = file_membership;
2189 }
2190 }
2191
2192 /// add the current element to the given file
2193 ///
2194 /// In order to successfully cause the element to appear in the serialized file data, all parent elements
2195 /// of the current element will also be added if required.
2196 ///
2197 /// If the model only has a single file then this function does nothing.
2198 ///
2199 /// # Example
2200 /// ```
2201 /// # use autosar_data::*;
2202 /// # use std::collections::HashSet;
2203 /// # let model = AutosarModel::new();
2204 /// let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2205 /// # let element = model.root_element();
2206 /// element.add_to_file(&file);
2207 /// ```
2208 ///
2209 /// # Errors
2210 ///
2211 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2212 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2213 /// The operation was aborted to avoid a deadlock, but can be retried.
2214 ///
2215 pub fn add_to_file(&self, file: &ArxmlFile) -> Result<(), AutosarDataError> {
2216 let parent_splittable = self.parent()?.is_none_or(|p| p.element_type().splittable() != 0);
2217 if parent_splittable {
2218 if file.model()? == self.model()? {
2219 let weak_file = file.downgrade();
2220 // current_fileset is the set of files which contain the current element
2221 let (_, current_fileset) = self.file_membership()?;
2222 // if the model only has a single file or if the element is already in the set then there is nothing to do
2223 if !current_fileset.contains(&weak_file) {
2224 let mut updated_fileset = current_fileset;
2225 updated_fileset.insert(weak_file);
2226 self.0.write().file_membership = updated_fileset;
2227
2228 // recursively continue with the parent
2229 if let Some(parent) = self.parent()? {
2230 parent.add_to_file_restricted(file)?;
2231 }
2232 }
2233 Ok(())
2234 } else {
2235 // adding a file from a different model is not permitted
2236 Err(AutosarDataError::InvalidFile)
2237 }
2238 } else {
2239 Err(AutosarDataError::FilesetModificationForbidden)
2240 }
2241 }
2242
2243 /// add only this element and its direct parents to a file, but not its children
2244 pub(crate) fn add_to_file_restricted(&self, file: &ArxmlFile) -> Result<(), AutosarDataError> {
2245 let weak_file = file.downgrade();
2246 let (local, current_fileset) = self.file_membership().unwrap_or((true, HashSet::new()));
2247
2248 if !current_fileset.contains(&weak_file) {
2249 // if the current element is splittable, then all of its subelements are allowed to have their own filesets
2250 // unless something else is already set, they should get the current unmodified file membership of this element
2251 // which does not include the new file
2252 if self.element_type().splittable() != 0 {
2253 for se in self.sub_elements() {
2254 if let Some(mut subelem) = se.0.try_write()
2255 && subelem.file_membership.is_empty()
2256 {
2257 subelem.file_membership.clone_from(¤t_fileset);
2258 }
2259 }
2260 }
2261
2262 let mut extended_fileset = current_fileset;
2263 extended_fileset.insert(weak_file);
2264 // if the parent is splittable, or if the current element already has a fileset, then that fileset should be updated
2265 let parent_splittable = self.parent()?.is_none_or(|p| p.element_type().splittable() != 0);
2266 if parent_splittable || local {
2267 self.0.write().file_membership = extended_fileset;
2268 }
2269
2270 // recursively continue with the parent
2271 if let Some(parent) = self.parent()? {
2272 parent.add_to_file_restricted(file)?;
2273 }
2274 }
2275
2276 Ok(())
2277 }
2278
2279 /// remove this element from a file
2280 ///
2281 /// If the model consists of multiple files, then the set of files in
2282 /// which this element appears will be restricted.
2283 /// It may be required to also omit its parent(s), up to the next splittable point.
2284 ///
2285 /// If the element is only present in single file then an attempt to delete it will be made instead.
2286 /// Deleting the element fails if the element is the root AUTOSAR element, or if it is a SHORT-NAME.
2287 ///
2288 /// # Example
2289 /// ```
2290 /// # use autosar_data::*;
2291 /// # use std::collections::HashSet;
2292 /// # let model = AutosarModel::new();
2293 /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2294 /// # let file2 = model.create_file("test2", AutosarVersion::Autosar_00050).unwrap();
2295 /// # let element = model.root_element();
2296 /// assert!(model.files().count() > 1);
2297 /// element.remove_from_file(&file);
2298 /// ```
2299 ///
2300 /// # Errors
2301 ///
2302 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2303 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2304 /// The operation was aborted to avoid a deadlock, but can be retried.
2305 ///
2306 pub fn remove_from_file(&self, file: &ArxmlFile) -> Result<(), AutosarDataError> {
2307 let parent_splittable = self.parent()?.is_none_or(|p| p.element_type().splittable() != 0);
2308 if parent_splittable {
2309 if file.model()? == self.model()? {
2310 let weak_file = file.downgrade();
2311
2312 // current_fileset is the set of files which contain the current element
2313 let (_, current_fileset) = self.file_membership()?;
2314 let mut restricted_fileset = current_fileset;
2315 restricted_fileset.remove(&weak_file);
2316 if restricted_fileset.is_empty() {
2317 // the element will no longer be part of any file, so try to delete it
2318 if let Some(parent) = self.parent()? {
2319 let _ = parent.remove_sub_element(self.to_owned());
2320 }
2321 }
2322 // this works even if the element was just removed
2323 self.0.write().file_membership = restricted_fileset;
2324
2325 // update all sub elements with non-default file_membership
2326 let mut to_delete = Vec::new();
2327 for (_, subelem) in self.elements_dfs() {
2328 // only need to care about those where file_membership is not empty. All other inherit from their parent
2329 if !subelem.0.read().file_membership.is_empty() {
2330 subelem.0.write().file_membership.remove(&weak_file);
2331 // if the file_membership just went to empty, then subelem should be deleted
2332 if subelem.0.read().file_membership.is_empty() {
2333 to_delete.push(subelem);
2334 }
2335 }
2336 }
2337 // delete elements that are no longer in any file
2338 for delete_elem in to_delete {
2339 if let Ok(Some(parent)) = delete_elem.parent() {
2340 let _ = parent.remove_sub_element(delete_elem);
2341 }
2342 }
2343
2344 Ok(())
2345 } else {
2346 // adding a file from a different model is not permitted
2347 Err(AutosarDataError::InvalidFile)
2348 }
2349 } else {
2350 Err(AutosarDataError::FilesetModificationForbidden)
2351 }
2352 }
2353
2354 /// Return a path that includes non-identifiable elements by their xml names
2355 ///
2356 /// This function cannot fail completely, it will always collect as much information as possible.
2357 /// It is intended for display in error messages.
2358 #[must_use]
2359 pub fn xml_path(&self) -> String {
2360 self.0.read().xml_path()
2361 }
2362
2363 /// Find the upper and lower bound on the insert position for a new sub element
2364 ///
2365 /// If the sub element is allowed for this element given its current content, this function
2366 /// returns the lower and upper bound on the position the new sub element could have.
2367 /// If the sub element is not allowed, then an Err is returned instead.
2368 ///
2369 /// The lower and upper bounds are inclusive: lower <= (element insert pos) <= upper.
2370 /// In many situations lower == upper, this means there is only a single valid position.
2371 ///
2372 /// # Example
2373 /// ```
2374 /// # use autosar_data::*;
2375 /// # fn main() -> Result<(), AutosarDataError> {
2376 /// # use std::collections::HashSet;
2377 /// # let model = AutosarModel::new();
2378 /// # model.create_file("test", AutosarVersion::LATEST).unwrap();
2379 /// let (lbound, ubound) = model.root_element()
2380 /// .calc_element_insert_range(ElementName::ArPackages, AutosarVersion::LATEST)?;
2381 /// model.root_element().create_sub_element_at(ElementName::ArPackages, lbound)?;
2382 /// # Ok(())
2383 /// # }
2384 /// ```
2385 ///
2386 /// # Errors
2387 ///
2388 /// - [`AutosarDataError::ElementInsertionConflict`]: The sub element conflicts with an existing sub element
2389 /// - [`AutosarDataError::InvalidSubElement`]: The sub element is not valid inside this element
2390 pub fn calc_element_insert_range(
2391 &self,
2392 element_name: ElementName,
2393 version: AutosarVersion,
2394 ) -> Result<(usize, usize), AutosarDataError> {
2395 self.0.read().calc_element_insert_range(element_name, version)
2396 }
2397
2398 /// Return the comment attachd to the element (if any)
2399 ///
2400 /// A comment directly preceding the opening tag is considered to be atached and is returned here.
2401 ///
2402 /// In the arxml text:
2403 /// ```xml
2404 /// <!--element comment-->
2405 /// <ELEMENT> ...
2406 /// ```
2407 ///
2408 /// # Example
2409 ///
2410 /// ```
2411 /// # use autosar_data::*;
2412 /// # use std::collections::HashSet;
2413 /// # let model = AutosarModel::new();
2414 /// # let file = model.create_file("test", AutosarVersion::LATEST).unwrap();
2415 /// # let element = model.root_element();
2416 /// let opt_comment = element.comment();
2417 /// ```
2418 #[must_use]
2419 pub fn comment(&self) -> Option<String> {
2420 self.0.read().comment.clone()
2421 }
2422
2423 /// Set or delete the comment attached to the element
2424 ///
2425 /// Set None to remove the comment.
2426 ///
2427 /// If the new comment value contains "--", then this is replaced with "__", because "--" is forbidden inside XML comments.
2428 ///
2429 /// # Example
2430 ///
2431 /// ```
2432 /// # use autosar_data::*;
2433 /// # use std::collections::HashSet;
2434 /// # let model = AutosarModel::new();
2435 /// # let file = model.create_file("test", AutosarVersion::LATEST).unwrap();
2436 /// # let element = model.root_element();
2437 /// # let string = "".to_string();
2438 /// element.set_comment(Some(string));
2439 /// ```
2440 pub fn set_comment(&self, mut opt_comment: Option<String>) {
2441 if let Some(comment) = &mut opt_comment {
2442 // make sure the comment we store never contains "--" as this is forbidden by the w3 xml specification
2443 if comment.contains("--") {
2444 *comment = comment.replace("--", "__");
2445 }
2446 }
2447 self.0.write().comment = opt_comment;
2448 }
2449
2450 /// find the minumum version of all arxml files which contain this element
2451 ///
2452 /// Typically this reduces to finding out which single file contains the element and returning this version.
2453 ///
2454 /// # Example
2455 ///
2456 /// ```
2457 /// # use autosar_data::*;
2458 /// # use std::collections::HashSet;
2459 /// # let model = AutosarModel::new();
2460 /// let file1 = model.create_file("file1", AutosarVersion::LATEST).unwrap();
2461 /// let file2 = model.create_file("file2", AutosarVersion::Autosar_4_3_0).unwrap();
2462 /// let version = model.root_element().min_version().unwrap();
2463 /// assert_eq!(version, AutosarVersion::Autosar_4_3_0);
2464 /// ```
2465 ///
2466 /// # Errors
2467 ///
2468 /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2469 /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2470 /// The operation was aborted to avoid a deadlock, but can be retried.
2471 /// - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
2472 pub fn min_version(&self) -> Result<AutosarVersion, AutosarDataError> {
2473 let (_, files) = self.file_membership()?;
2474 let mut ver = AutosarVersion::LATEST;
2475 for f in files.iter().filter_map(WeakArxmlFile::upgrade) {
2476 if f.version() < ver {
2477 ver = f.version();
2478 }
2479 }
2480 Ok(ver)
2481 }
2482}
2483
2484impl Ord for Element {
2485 /// compare the content of two elements
2486 ///
2487 /// This function compares the content of two elements, returning a cmp::Ordering value.
2488 /// The purpose of this function is to allow sorting of elements based on their content.
2489 ///
2490 /// The comparison is performed in the following order:
2491 /// 1. Element name
2492 /// 2. Index (if present)
2493 /// 3. Item name (if present)
2494 /// 4. Definition reference (if present)
2495 /// 5. DEST attribute (if present)
2496 /// 6. Content of the element
2497 /// 7. Attributes of the element
2498 ///
2499 /// If the comparison returns `Ordering::Equal`, then the two elements are identical, but this does not imply that they are the same object.
2500 ///
2501 /// # Example
2502 /// ```
2503 /// # use autosar_data::*;
2504 /// # let model = AutosarModel::new();
2505 /// # let file = model.create_file("test", AutosarVersion::LATEST).unwrap();
2506 /// # let element1 = model.root_element();
2507 /// # let element2 = model.root_element();
2508 /// let ordering = element1.cmp(&element2);
2509 /// ```
2510 fn cmp(&self, other: &Element) -> std::cmp::Ordering {
2511 // Sort by the element name first. This test prevents the other comparisons from being performed when they don't make sense.
2512 match self.element_name().to_str().cmp(other.element_name().to_str()) {
2513 Ordering::Equal => {}
2514 other => return other,
2515 }
2516
2517 // if both elements have an index, then compare the index; if only one has an index, then it comes first
2518 // if neither has an index, then continue and compare other criteria
2519 let index1 = self
2520 .get_sub_element(ElementName::Index)
2521 .and_then(|indexelem| indexelem.character_data())
2522 .and_then(|cdata| cdata.parse_integer::<u64>());
2523 let index2 = other
2524 .get_sub_element(ElementName::Index)
2525 .and_then(|indexelem| indexelem.character_data())
2526 .and_then(|cdata| cdata.parse_integer::<u64>());
2527 match (index1, index2) {
2528 (Some(idx1), Some(idx2)) => {
2529 let result = idx1.cmp(&idx2);
2530 if result != Ordering::Equal {
2531 return result;
2532 }
2533 }
2534 (Some(_), None) => return std::cmp::Ordering::Less,
2535 (None, Some(_)) => return std::cmp::Ordering::Greater,
2536 (None, None) => {}
2537 }
2538
2539 // sort by item name if present
2540 if let (Some(name1), Some(name2)) = (self.item_name(), other.item_name()) {
2541 // both items have a name - try to decompose the name into a base and an index
2542 // this allows for a more natural sorting of indexed items (e.g. "item2" < "item10")
2543 if let (Some((base1, idx1)), Some((base2, idx2))) =
2544 (decompose_item_name(&name1), decompose_item_name(&name2))
2545 && base1 == base2
2546 {
2547 let result = idx1.cmp(&idx2);
2548 if result != Ordering::Equal {
2549 return result;
2550 }
2551 }
2552 // if the decomposition fails, then just compare the full item names
2553 let result = name1.cmp(&name2);
2554 if result != Ordering::Equal {
2555 return result;
2556 }
2557 }
2558
2559 // for BSW values: compare the definition references
2560 let definition1 = self
2561 .get_sub_element(ElementName::DefinitionRef)
2562 .and_then(|defref| defref.character_data())
2563 .and_then(|cdata| cdata.string_value());
2564 let definition2 = other
2565 .get_sub_element(ElementName::DefinitionRef)
2566 .and_then(|defref| defref.character_data())
2567 .and_then(|cdata| cdata.string_value());
2568 if let (Some(def1), Some(def2)) = (definition1, definition2) {
2569 let result = def1.cmp(&def2);
2570 if result != Ordering::Equal {
2571 return result;
2572 }
2573 }
2574
2575 // for references: compare the DEST attribute
2576 let dest1 = self
2577 .attribute_value(AttributeName::Dest)
2578 .and_then(|cdata| cdata.enum_value());
2579 let dest2 = other
2580 .attribute_value(AttributeName::Dest)
2581 .and_then(|cdata| cdata.enum_value());
2582 match (dest1, dest2) {
2583 (Some(dest1), Some(dest2)) => {
2584 let result = dest1.to_str().cmp(dest2.to_str());
2585 if result != Ordering::Equal {
2586 return result;
2587 }
2588 }
2589 (Some(_), None) => return std::cmp::Ordering::Less,
2590 (None, Some(_)) => return std::cmp::Ordering::Greater,
2591 (None, None) => {}
2592 }
2593
2594 // if all else fails, compare the content of the elements
2595 let locked_self = self.0.read();
2596 let locked_other = other.0.read();
2597 locked_self
2598 .content
2599 .cmp(&locked_other.content)
2600 .then(locked_self.attributes.cmp(&locked_other.attributes))
2601 }
2602}
2603
2604impl PartialOrd for Element {
2605 fn partial_cmp(&self, other: &Element) -> Option<std::cmp::Ordering> {
2606 Some(self.cmp(other))
2607 }
2608}
2609
2610/// decompose an item name into a base name and an index
2611/// The index is expected to be a decimal number at the end of the string
2612///
2613/// E.g. "item123" -> ("item", 123)
2614fn decompose_item_name(name: &str) -> Option<(String, u64)> {
2615 let bytestr = name.as_bytes();
2616 let mut pos = bytestr.len();
2617 while pos > 0 && bytestr[pos - 1].is_ascii_digit() {
2618 pos -= 1;
2619 }
2620 if let Ok(index) = name[pos..].parse() {
2621 Some((name[0..pos].to_owned(), index))
2622 } else {
2623 None
2624 }
2625}
2626
2627// a helper that provides compact debug output for the content of an element
2628struct ElementContentFormatter<'a>(&'a SmallVec<[ElementContent; 4]>);
2629impl std::fmt::Debug for ElementContentFormatter<'_> {
2630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2631 let mut list_fmt = f.debug_list();
2632 for item in self.0.iter() {
2633 match item {
2634 ElementContent::Element(elem) => list_fmt.entry(&elem.element_name()),
2635 ElementContent::CharacterData(cdata) => list_fmt.entry(&cdata),
2636 };
2637 }
2638 list_fmt.finish()
2639 }
2640}
2641
2642// A custom type is needed in order to print a custom value in the Debug implementation without double quoting
2643struct DebugDisplay<'a>(&'a str);
2644impl std::fmt::Debug for DebugDisplay<'_> {
2645 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2646 f.write_str(self.0)
2647 }
2648}
2649
2650// custom debug implementation: print the content instead of only showing the pointer of the Arc
2651impl std::fmt::Debug for Element {
2652 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2653 let elem = self.0.read();
2654 let mut dbgstruct = f.debug_struct("Element");
2655 if let Some(name) = elem.item_name() {
2656 dbgstruct.field("name", &name);
2657 }
2658 dbgstruct.field("elemname", &elem.elemname);
2659 dbgstruct.field("elemtype", &elem.elemtype);
2660 dbgstruct.field("parent", &elem.parent);
2661 dbgstruct.field("content", &ElementContentFormatter(&elem.content));
2662 dbgstruct.field("attributes", &elem.attributes);
2663 // only print the file membership if the element is splittable
2664 // elements that are not splittable may not modify their file membership
2665 if elem.elemtype.splittable() != 0 {
2666 if elem.file_membership.is_empty() {
2667 dbgstruct.field("file_membership", &DebugDisplay("(inherited)"));
2668 } else {
2669 dbgstruct.field("file_membership", &elem.file_membership);
2670 }
2671 }
2672 dbgstruct.finish()
2673 }
2674}
2675
2676impl PartialEq for Element {
2677 fn eq(&self, other: &Self) -> bool {
2678 Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
2679 }
2680}
2681
2682impl Eq for Element {}
2683
2684impl Hash for Element {
2685 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2686 state.write_usize(Arc::as_ptr(&self.0) as usize);
2687 }
2688}
2689
2690impl std::fmt::Debug for WeakElement {
2691 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2692 if let Some(elem) = self.upgrade() {
2693 // write!(f, "Element:WeakRef {:p}", Arc::as_ptr(&elem.0));
2694 f.write_fmt(format_args!("Element:WeakRef ({})", elem.element_name()))
2695 } else {
2696 f.write_fmt(format_args!("Element:WeakRef {:p} (invalid)", Weak::as_ptr(&self.0)))
2697 }
2698 }
2699}
2700
2701impl WeakElement {
2702 /// try to get a strong reference to the [Element]
2703 pub fn upgrade(&self) -> Option<Element> {
2704 Weak::upgrade(&self.0).map(Element)
2705 }
2706}
2707
2708impl PartialEq for WeakElement {
2709 fn eq(&self, other: &Self) -> bool {
2710 Weak::as_ptr(&self.0) == Weak::as_ptr(&other.0)
2711 }
2712}
2713
2714impl Eq for WeakElement {}
2715
2716impl Hash for WeakElement {
2717 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2718 state.write_usize(Weak::as_ptr(&self.0) as usize);
2719 }
2720}
2721
2722impl ElementContent {
2723 /// returns the element contained inside this `ElementContent`, or None if the content is `CharacterData`
2724 #[must_use]
2725 pub fn unwrap_element(&self) -> Option<Element> {
2726 if let ElementContent::Element(element) = self {
2727 Some(element.clone())
2728 } else {
2729 None
2730 }
2731 }
2732
2733 /// returns the `CharacterData` inside this `ElementContent`, or None if the content is an Element
2734 #[must_use]
2735 pub fn unwrap_cdata(&self) -> Option<CharacterData> {
2736 if let ElementContent::CharacterData(cdata) = self {
2737 Some(cdata.clone())
2738 } else {
2739 None
2740 }
2741 }
2742}
2743
2744// custom debug implementation: skip printing any content, since the content is only "WeakRef(0x...)"
2745impl std::fmt::Debug for ElementOrModel {
2746 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2747 match self {
2748 ElementOrModel::Element(_) => f.write_str("Element"),
2749 ElementOrModel::Model(_) => f.write_str("Model"),
2750 ElementOrModel::None => f.write_str("None/Invalid"),
2751 }
2752 }
2753}
2754
2755// custom debug implementation: skip printing the name of the wrapper-enum and directly show the content
2756impl std::fmt::Debug for ElementContent {
2757 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2758 match self {
2759 ElementContent::Element(elem) => elem.fmt(f),
2760 ElementContent::CharacterData(cdata) => cdata.fmt(f),
2761 }
2762 }
2763}
2764
2765#[cfg(test)]
2766mod test {
2767 use crate::*;
2768 use std::ffi::OsString;
2769
2770 const BASIC_AUTOSAR_FILE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
2771 <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">
2772 <AR-PACKAGES>
2773 <AR-PACKAGE>
2774 <SHORT-NAME>TestPackage</SHORT-NAME>
2775 </AR-PACKAGE>
2776 </AR-PACKAGES>
2777 </AUTOSAR>"#;
2778
2779 #[test]
2780 fn element_creation() {
2781 let model = AutosarModel::new();
2782 model
2783 .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
2784 .unwrap();
2785 let el_autosar = model.root_element();
2786 let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
2787
2788 let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
2789 let el_compu_method = el_elements
2790 .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod")
2791 .unwrap();
2792 el_elements
2793 .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod2")
2794 .unwrap();
2795 el_elements
2796 .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod3")
2797 .unwrap();
2798 // elements with duplicate names are not allowed
2799 assert!(
2800 el_elements
2801 .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod3")
2802 .is_err()
2803 );
2804
2805 let count = el_elements.sub_elements().count();
2806 assert_eq!(count, 3);
2807 assert_eq!(count, el_elements.content_item_count());
2808
2809 // inserting another COMPU-METHOD into ELEMENTS hould be allowed at any position
2810 let (start_pos, end_pos) = el_elements
2811 .0
2812 .read()
2813 .calc_element_insert_range(ElementName::CompuMethod, AutosarVersion::Autosar_00050)
2814 .unwrap();
2815 assert_eq!(start_pos, 0);
2816 assert_eq!(end_pos, 3); // upper limit is 3 since there are currently 3 elements
2817
2818 // check if create_named_sub_element correctly registered the element in the hashmap so that it can be found
2819 let el_compu_method_test = model.get_element_by_path("/TestPackage/TestCompuMethod").unwrap();
2820 assert_eq!(el_compu_method, el_compu_method_test);
2821
2822 // create more hierarchy
2823 let el_compu_internal_to_phys = el_compu_method
2824 .create_sub_element(ElementName::CompuInternalToPhys)
2825 .unwrap();
2826 let el_compu_scales = el_compu_internal_to_phys
2827 .create_sub_element(ElementName::CompuScales)
2828 .unwrap();
2829 let el_compu_scale = el_compu_scales.create_sub_element(ElementName::CompuScale).unwrap();
2830 el_compu_scale.create_sub_element(ElementName::Desc).unwrap();
2831
2832 // SHORT-LABEL should only be allowed before DESC inside COMPU-SCALE
2833 let (start_pos, end_pos) = el_compu_scale
2834 .calc_element_insert_range(ElementName::ShortLabel, AutosarVersion::Autosar_00050)
2835 .unwrap();
2836 assert_eq!(start_pos, 0);
2837 assert_eq!(end_pos, 0);
2838
2839 // COMPU-CONST should only be allowed after DESC inside COMPU-SCALE
2840 let (start_pos, end_pos) = el_compu_scale
2841 .calc_element_insert_range(ElementName::CompuConst, AutosarVersion::Autosar_00050)
2842 .unwrap();
2843 assert_eq!(start_pos, 1);
2844 assert_eq!(end_pos, 1);
2845
2846 // create COMPU-RATIONAL-COEFFS in COMPU-SCALE. It's presence excludes COMPU-CONST from being inserted
2847 el_compu_scale
2848 .create_sub_element(ElementName::CompuRationalCoeffs)
2849 .unwrap();
2850 // try to insert COMPU-CONST anyway
2851 let result = el_compu_scale.calc_element_insert_range(ElementName::CompuConst, AutosarVersion::Autosar_00050);
2852 assert!(result.is_err());
2853 // it is also not possible to create a second COMPU-RATIONAL-COEFFS
2854 let result =
2855 el_compu_scale.calc_element_insert_range(ElementName::CompuRationalCoeffs, AutosarVersion::Autosar_00050);
2856 assert!(result.is_err());
2857
2858 // creating a sub element at an invalid position fails
2859 assert!(
2860 el_elements
2861 .create_named_sub_element_at(ElementName::System, "System", 99)
2862 .is_err()
2863 );
2864 assert!(el_autosar.create_sub_element_at(ElementName::AdminData, 99).is_err());
2865
2866 // an identifiable element cannot be created without a name
2867 assert!(el_elements.create_sub_element(ElementName::System).is_err());
2868 // the name for an identifiable element must be valid according to the rules
2869 assert!(el_elements.create_named_sub_element(ElementName::System, "").is_err());
2870 assert!(
2871 el_elements
2872 .create_named_sub_element(ElementName::System, "abc def")
2873 .is_err()
2874 );
2875
2876 // a non-identifiable element cannot be created with a name
2877 assert!(
2878 el_autosar
2879 .create_named_sub_element(ElementName::AdminData, "AdminData")
2880 .is_err()
2881 );
2882
2883 // only valid sub-elements can be created
2884 assert!(
2885 el_autosar
2886 .create_named_sub_element(ElementName::Autosar, "Autosar")
2887 .is_err()
2888 );
2889 assert!(
2890 el_autosar
2891 .create_named_sub_element_at(ElementName::Autosar, "Autosar", 0)
2892 .is_err()
2893 );
2894 assert!(el_autosar.create_sub_element(ElementName::Autosar).is_err());
2895 assert!(el_autosar.create_sub_element_at(ElementName::Autosar, 0).is_err());
2896
2897 // creating a sub element fails when any parent element in the hierarchy is locked for writing
2898 let el_autosar_locked = el_autosar.0.write();
2899 assert!(
2900 el_elements
2901 .create_named_sub_element(ElementName::System, "System")
2902 .is_err()
2903 );
2904 assert!(
2905 el_elements
2906 .create_named_sub_element_at(ElementName::System, "System", 0)
2907 .is_err()
2908 );
2909 assert!(el_autosar.create_sub_element(ElementName::AdminData).is_err());
2910 assert!(el_autosar.create_sub_element_at(ElementName::AdminData, 0).is_err());
2911 drop(el_autosar_locked);
2912 }
2913
2914 #[test]
2915 fn parent() {
2916 let model = AutosarModel::new();
2917 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
2918 let el_autosar = model.root_element();
2919 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
2920 let el_ar_package = el_ar_packages
2921 .create_named_sub_element(ElementName::ArPackage, "Package")
2922 .unwrap();
2923 let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
2924 let el_system = el_elements
2925 .create_named_sub_element(ElementName::System, "Sys")
2926 .unwrap();
2927 let el_fibex = el_system.create_sub_element(ElementName::FibexElements).unwrap();
2928 let el_fibex_cond = el_fibex
2929 .create_sub_element(ElementName::FibexElementRefConditional)
2930 .unwrap();
2931
2932 let parent = el_fibex_cond.parent().unwrap().unwrap();
2933 assert_eq!(parent, el_fibex);
2934 let named_parent = el_fibex_cond.named_parent().unwrap().unwrap();
2935 assert_eq!(named_parent, el_system);
2936
2937 let named_parent = el_system.named_parent().unwrap().unwrap();
2938 assert_eq!(named_parent, el_ar_package);
2939
2940 let named_parent = el_autosar.named_parent().unwrap();
2941 assert!(named_parent.is_none());
2942
2943 // trying to get the named parent of a removed element should fail
2944 el_autosar.remove_sub_element(el_ar_packages).unwrap();
2945 assert!(el_ar_package.named_parent().is_err());
2946 }
2947
2948 #[test]
2949 fn package() {
2950 let model = AutosarModel::new();
2951 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
2952 let el_autosar = model.root_element();
2953 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
2954 let el_ar_package = el_ar_packages
2955 .create_named_sub_element(ElementName::ArPackage, "Package")
2956 .unwrap();
2957 let el_ar_packages_2 = el_ar_package.create_sub_element(ElementName::ArPackages).unwrap();
2958 let el_ar_package_2 = el_ar_packages_2
2959 .create_named_sub_element(ElementName::ArPackage, "SubPackage")
2960 .unwrap();
2961
2962 assert_eq!(el_ar_package.package().unwrap(), None); // top level package has no parent package
2963 assert_eq!(el_ar_packages_2.package().unwrap().unwrap(), el_ar_package);
2964 assert_eq!(el_ar_package_2.package().unwrap().unwrap(), el_ar_package); // SubPackage -> Package
2965
2966 drop(el_autosar);
2967 drop(model);
2968 assert!(el_ar_package.package().is_err()); // the model is dropped, so the package cannot be accessed anymore
2969 }
2970
2971 #[test]
2972 fn element_rename() {
2973 let model = AutosarModel::new();
2974 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
2975 let el_autosar = model.root_element();
2976 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
2977 let el_ar_package = el_ar_packages
2978 .create_named_sub_element(ElementName::ArPackage, "Package")
2979 .unwrap();
2980 let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
2981 let el_can_cluster = el_elements
2982 .create_named_sub_element_at(ElementName::CanCluster, "CanCluster", 0)
2983 .unwrap();
2984 let el_can_physical_channel = el_can_cluster
2985 .create_sub_element(ElementName::CanClusterVariants)
2986 .and_then(|ccv| ccv.create_sub_element(ElementName::CanClusterConditional))
2987 .and_then(|ccc| ccc.create_sub_element(ElementName::PhysicalChannels))
2988 .and_then(|pc| pc.create_named_sub_element(ElementName::CanPhysicalChannel, "CanPhysicalChannel"))
2989 .unwrap();
2990
2991 let el_can_frame_triggering = el_can_physical_channel
2992 .create_sub_element_at(ElementName::FrameTriggerings, 1)
2993 .and_then(|ft| ft.create_named_sub_element(ElementName::CanFrameTriggering, "CanFrameTriggering"))
2994 .unwrap();
2995
2996 let el_ar_package2 = el_ar_packages
2997 .create_named_sub_element(ElementName::ArPackage, "Package2")
2998 .unwrap();
2999 let el_can_frame = el_ar_package2
3000 .create_sub_element(ElementName::Elements)
3001 .and_then(|e| e.create_named_sub_element(ElementName::CanFrame, "CanFrame"))
3002 .unwrap();
3003 let el_frame_ref = el_can_frame_triggering
3004 .create_sub_element(ElementName::FrameRef)
3005 .unwrap();
3006 let _ = el_frame_ref.set_reference_target(&el_can_frame);
3007
3008 // initial value of the reference
3009 let refstr = el_frame_ref.character_data().unwrap().string_value().unwrap();
3010 assert_eq!(refstr, "/Package2/CanFrame");
3011
3012 // empty name, renaming should fail
3013 let result = el_ar_package.set_item_name("");
3014 assert!(result.is_err());
3015
3016 // rename 1. package
3017 el_ar_package.set_item_name("NewPackage").unwrap();
3018 // setting the current name again - should be a no-op
3019 el_ar_package.set_item_name("NewPackage").unwrap();
3020
3021 // duplicate name for Package2, renaming should fail
3022 let result = el_ar_package2.set_item_name("NewPackage");
3023 assert!(result.is_err());
3024
3025 // rename package 2 with a valid name
3026 el_ar_package2.set_item_name("OtherPackage").unwrap();
3027 let refstr = el_frame_ref.character_data().unwrap().string_value().unwrap();
3028 assert_eq!(refstr, "/OtherPackage/CanFrame");
3029
3030 // make sure get_reference_target still works after renaming
3031 let el_can_frame2 = el_frame_ref.get_reference_target().unwrap();
3032 assert_eq!(el_can_frame, el_can_frame2);
3033
3034 // rename the CanFrame as well
3035 el_can_frame.set_item_name("CanFrame_renamed").unwrap();
3036 let refstr = el_frame_ref.character_data().unwrap().string_value().unwrap();
3037 assert_eq!(refstr, "/OtherPackage/CanFrame_renamed");
3038
3039 // invalid element
3040 assert!(el_autosar.set_item_name("Autosar").is_err());
3041
3042 // invalid preconditions
3043 let el_autosar_locked = el_autosar.0.write();
3044 // fails because a parent element is locked
3045 assert!(el_ar_package.set_item_name("TestPackage_renamed").is_err());
3046 drop(el_autosar_locked);
3047 drop(model);
3048 // the reference count of model is now zero, so set_item_name can't get a new reference to it
3049 assert!(el_ar_package.set_item_name("TestPackage_renamed").is_err());
3050 }
3051
3052 #[test]
3053 fn element_copy() {
3054 let model = AutosarModel::new();
3055 model
3056 .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3057 .unwrap();
3058 model.create_file("test", AutosarVersion::LATEST).unwrap();
3059 let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
3060 el_ar_package
3061 .set_attribute(AttributeName::Uuid, CharacterData::String("0123456".to_string()))
3062 .unwrap();
3063 let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3064 let el_compu_method = el_elements
3065 .create_named_sub_element(ElementName::CompuMethod, "CompuMethod")
3066 .unwrap();
3067 el_elements
3068 .create_named_sub_element(ElementName::DdsServiceInstanceToMachineMapping, "ApItem")
3069 .unwrap();
3070 el_elements
3071 .create_named_sub_element(ElementName::AclObjectSet, "AclObjectSet")
3072 .and_then(|el| el.create_sub_element(ElementName::DerivedFromBlueprintRefs))
3073 .and_then(|el| el.create_sub_element(ElementName::DerivedFromBlueprintRef))
3074 .and_then(|el| {
3075 el.set_attribute(
3076 AttributeName::Dest,
3077 CharacterData::Enum(EnumItem::AbstractImplementationDataType),
3078 )
3079 })
3080 .unwrap();
3081 el_elements
3082 .create_named_sub_element(ElementName::System, "System")
3083 .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3084 .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3085 .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3086 .and_then(|el| el.set_character_data("/invalid"))
3087 .unwrap();
3088
3089 let project2 = AutosarModel::new();
3090 project2
3091 .create_file("test.arxml", AutosarVersion::Autosar_00044)
3092 .unwrap();
3093
3094 // it should not be possible to create an AR-PACKAGE element directly in the AUTOSAR element by copying data
3095 let result = project2.root_element().create_copied_sub_element(&el_ar_package);
3096 assert!(result.is_err());
3097
3098 // create an AR-PACKAGES element and copy the data there. This should succeed.
3099 // the copied data shoud contain the COMPU-METHOD, but not the DDS-SERVICE-INSTANCE-TO-MACHINE-MAPPING
3100 // because the latter was specified in Adaptive 18-03 (Autosar_00045) and is not valid in Autosar_00044
3101 let el_ar_packages2 = project2
3102 .root_element()
3103 .create_sub_element(ElementName::ArPackages)
3104 .unwrap();
3105 el_ar_packages2.create_copied_sub_element(&el_ar_package).unwrap();
3106
3107 // it should be possible to look up the copied compu-method by its path
3108 let el_compu_method_2 = project2.get_element_by_path("/TestPackage/CompuMethod").unwrap();
3109
3110 // the copy should not refer to the same memory as the original
3111 assert_ne!(el_compu_method, el_compu_method_2);
3112 // the copy should serialize to exactly the same string as the original
3113 assert_eq!(el_compu_method.serialize(), el_compu_method_2.serialize());
3114
3115 // verify that the DDS-SERVICE-INSTANCE-TO-MACHINE-MAPPING element was not copied
3116 let result = project2.get_element_by_path("/TestPackage/ApItem");
3117 assert!(result.is_none());
3118
3119 // make sure the element ordering constraints are considered when copying with the _at() variant
3120 let el_ar_package2 = el_ar_packages2
3121 .create_named_sub_element(ElementName::ArPackage, "Package2")
3122 .unwrap();
3123 let result = el_ar_package2.create_copied_sub_element_at(&el_elements, 0);
3124 assert!(result.is_err()); // position 0 is already used by the SHORT-NAME
3125 let el_elements2 = el_ar_package2.create_sub_element(ElementName::Elements).unwrap();
3126 let result = el_elements2.create_copied_sub_element_at(&el_compu_method, 99);
3127 assert!(result.is_err()); // position 99 is not valid
3128 let result = el_elements2.create_copied_sub_element_at(&el_compu_method, 0);
3129 assert!(result.is_ok()); // position 0 is valid
3130
3131 // can't copy an element that is not a valid sub element here
3132 let result = el_ar_package2.create_copied_sub_element_at(&el_compu_method, 0);
3133 assert!(result.is_err()); // COMPU-METHOS id not a valid sub-element of AR-PACKAGE
3134 }
3135
3136 #[test]
3137 fn element_copy_loop() {
3138 let model = AutosarModel::new();
3139 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3140 let el_autosar = model.root_element();
3141 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3142 let el_ar_package = el_ar_packages
3143 .create_named_sub_element(ElementName::ArPackage, "Pkg")
3144 .unwrap();
3145
3146 let result = el_ar_package.create_copied_sub_element(&el_ar_packages);
3147 assert!(result.is_err());
3148
3149 // copying an element into itself should return an error and should not deadlock
3150 let result = el_ar_package.create_copied_sub_element(&el_ar_package);
3151 assert!(result.is_err());
3152 let result = el_ar_package.create_copied_sub_element_at(&el_ar_package, 0);
3153 assert!(result.is_err());
3154 }
3155
3156 #[test]
3157 fn element_deletion() {
3158 let model = AutosarModel::new();
3159 model
3160 .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3161 .unwrap();
3162 let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
3163 let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
3164 el_ar_package
3165 .create_sub_element(ElementName::Elements)
3166 .and_then(|el| el.create_named_sub_element(ElementName::System, "System"))
3167 .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3168 .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3169 .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3170 .and_then(|el| el.set_character_data("/invalid"))
3171 .unwrap();
3172
3173 // removing the SHORT-NAME of an identifiable element is forbidden
3174 let result = el_ar_package.remove_sub_element(el_short_name);
3175 if let Err(AutosarDataError::ShortNameRemovalForbidden) = result {
3176 // correct
3177 } else {
3178 panic!("Removing the SHORT-NAME was not prohibited");
3179 }
3180 let el_ar_package_clone = el_ar_package.clone();
3181 let el_ar_packages = el_ar_package.parent().unwrap().unwrap();
3182 let result = el_ar_packages.remove_sub_element(el_ar_package);
3183 // deleting identifiable elements should also cause the cached references to them to be removed
3184 assert_eq!(model.0.read().identifiables.len(), 0);
3185 assert!(result.is_ok());
3186
3187 // alternative: remove_sub_element_kind
3188 el_ar_packages
3189 .create_named_sub_element(ElementName::ArPackage, "SecondPackage")
3190 .unwrap();
3191 assert_eq!(el_ar_packages.content_item_count(), 1);
3192 let result = el_ar_packages.remove_sub_element_kind(ElementName::ArPackage);
3193 assert!(result.is_ok());
3194 assert_eq!(el_ar_packages.content_item_count(), 0);
3195 let result = el_ar_packages.remove_sub_element_kind(ElementName::ArPackage);
3196 assert!(result.is_err());
3197
3198 // the removed element may still exist if there were other references to it, but it is no longer usable
3199 let result = el_ar_package_clone.parent();
3200 assert!(matches!(result, Err(AutosarDataError::ItemDeleted)));
3201 let result = el_ar_package_clone.model();
3202 assert!(matches!(result, Err(AutosarDataError::ItemDeleted)));
3203 assert_eq!(el_ar_package_clone.position(), None);
3204 }
3205
3206 #[test]
3207 fn element_position() {
3208 let model = AutosarModel::new();
3209 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3210 let el_autosar = model.root_element();
3211 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3212 let el_ar_package1 = el_ar_packages
3213 .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3214 .unwrap();
3215 let el_ar_package2 = el_ar_packages
3216 .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3217 .unwrap();
3218 let el_ar_package3 = el_ar_packages
3219 .create_named_sub_element(ElementName::ArPackage, "Pkg3")
3220 .unwrap();
3221
3222 assert_eq!(el_ar_packages.content_item_count(), 3);
3223 assert_eq!(el_ar_package2.position().unwrap(), 1);
3224 assert_eq!(el_ar_packages.get_sub_element_at(1).unwrap(), el_ar_package2);
3225 assert_eq!(el_ar_package3.position().unwrap(), 2);
3226 assert_eq!(el_ar_packages.get_sub_element_at(2).unwrap(), el_ar_package3);
3227
3228 // there is no subelement at position 1
3229 let nonexistent = el_ar_package1.get_sub_element_at(1);
3230 assert_eq!(nonexistent, None);
3231 }
3232
3233 #[test]
3234 fn element_type() {
3235 let model = AutosarModel::new();
3236 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3237 let el_autosar = model.root_element();
3238
3239 assert_eq!(el_autosar.element_type(), ElementType::ROOT);
3240 }
3241
3242 #[test]
3243 fn content_type() {
3244 let model = AutosarModel::new();
3245 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3246 let el_autosar = model.root_element();
3247 let el_ar_package = el_autosar
3248 .create_sub_element(ElementName::ArPackages)
3249 .and_then(|ar_pkgs| ar_pkgs.create_named_sub_element(ElementName::ArPackage, "Package"))
3250 .unwrap();
3251 let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
3252
3253 let el_l4 = el_ar_package
3254 .create_sub_element(ElementName::LongName)
3255 .and_then(|ln| ln.create_sub_element(ElementName::L4))
3256 .unwrap();
3257
3258 let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3259 let el_debounce_algo = el_elements
3260 .create_named_sub_element(ElementName::DiagnosticContributionSet, "DCS")
3261 .and_then(|dcs| dcs.create_sub_element(ElementName::CommonProperties))
3262 .and_then(|cp| cp.create_sub_element(ElementName::DiagnosticCommonPropsVariants))
3263 .and_then(|dcpv| dcpv.create_sub_element(ElementName::DiagnosticCommonPropsConditional))
3264 .and_then(|dcpc| dcpc.create_sub_element(ElementName::DebounceAlgorithmPropss))
3265 .and_then(|dap| dap.create_named_sub_element(ElementName::DiagnosticDebounceAlgorithmProps, "ddap"))
3266 .and_then(|ddap| ddap.create_sub_element(ElementName::DebounceAlgorithm))
3267 .unwrap();
3268
3269 assert_eq!(el_autosar.element_type().content_mode(), ContentMode::Sequence);
3270 assert_eq!(el_autosar.content_type(), ContentType::Elements);
3271 assert_eq!(el_elements.element_type().content_mode(), ContentMode::Bag);
3272 assert_eq!(el_elements.content_type(), ContentType::Elements);
3273 assert_eq!(el_debounce_algo.element_type().content_mode(), ContentMode::Choice);
3274 assert_eq!(el_debounce_algo.content_type(), ContentType::Elements);
3275 assert_eq!(el_short_name.element_type().content_mode(), ContentMode::Characters);
3276 assert_eq!(el_short_name.content_type(), ContentType::CharacterData);
3277 assert_eq!(el_l4.element_type().content_mode(), ContentMode::Mixed);
3278 assert_eq!(el_l4.content_type(), ContentType::Mixed);
3279 }
3280
3281 #[test]
3282 fn attributes() {
3283 let model = AutosarModel::new();
3284 model
3285 .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3286 .unwrap();
3287 model.create_file("test", AutosarVersion::LATEST).unwrap();
3288 let el_autosar = model.root_element();
3289 let el_ar_packages = el_autosar.get_sub_element(ElementName::ArPackages).unwrap();
3290
3291 let count = el_autosar.attributes().count();
3292 assert_eq!(count, 3);
3293
3294 // set the attribute S on the element AUTOSAR
3295 el_autosar
3296 .set_attribute(AttributeName::S, CharacterData::String(String::from("something")))
3297 .unwrap();
3298
3299 // AUTOSAR has no DEST attribute, so this should fail
3300 assert!(
3301 el_autosar
3302 .set_attribute(AttributeName::Dest, CharacterData::String(String::from("something")))
3303 .is_err()
3304 );
3305
3306 // The attribute S exists and is optional, so it can be removed
3307 let result = el_autosar.remove_attribute(AttributeName::S);
3308 assert!(result);
3309
3310 // the attribute xmlns is required and cannot be removed
3311 let result = el_autosar.remove_attribute(AttributeName::xmlns);
3312 assert!(!result);
3313
3314 // the attribute ACCESSKEY does not exist in the element AUTOSAR and cannot be removed
3315 let result = el_autosar.remove_attribute(AttributeName::Accesskey);
3316 assert!(!result);
3317
3318 // the attribute T is permitted on AUTOSAR and the string is a valid value
3319 el_autosar
3320 .set_attribute_string(AttributeName::T, "2022-01-31T13:00:59Z")
3321 .unwrap();
3322
3323 // update an existing attribute
3324 el_autosar
3325 .set_attribute_string(AttributeName::T, "2022-01-31T14:00:59Z")
3326 .unwrap();
3327
3328 // fail set an attribute due to data validation
3329 assert!(el_autosar.set_attribute_string(AttributeName::T, "abc").is_err());
3330
3331 // can't set unknown attributes with set_attribute_string
3332 assert!(
3333 el_ar_packages
3334 .set_attribute_string(AttributeName::xmlns, "abc")
3335 .is_err()
3336 );
3337
3338 // directly return an attribute as a string
3339 let xmlns = el_autosar
3340 .attribute_value(AttributeName::xmlns)
3341 .map(|cdata| cdata.to_string())
3342 .unwrap();
3343 assert_eq!(xmlns, "http://autosar.org/schema/r4.0".to_string());
3344
3345 // attribute operation fails when a parent element is locked for writing
3346 let lock = el_autosar.0.write();
3347 assert!(
3348 el_ar_packages
3349 .set_attribute(AttributeName::Uuid, CharacterData::String(String::from("1234")))
3350 .is_err()
3351 );
3352 assert!(
3353 el_ar_packages
3354 .set_attribute_string(AttributeName::Uuid, "1234")
3355 .is_err()
3356 );
3357 drop(lock);
3358 }
3359
3360 #[test]
3361 fn mixed_content() {
3362 let model = AutosarModel::new();
3363 model
3364 .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3365 .unwrap();
3366 let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
3367 let el_long_name = el_ar_package.create_sub_element(ElementName::LongName).unwrap();
3368 assert_eq!(el_long_name.content_type(), ContentType::Elements);
3369 let el_l_4 = el_long_name.create_sub_element(ElementName::L4).unwrap();
3370 assert_eq!(el_l_4.content_type(), ContentType::Mixed);
3371
3372 el_l_4.create_sub_element(ElementName::E).unwrap();
3373 el_l_4.insert_character_content_item("foo", 1).unwrap();
3374 el_l_4.create_sub_element(ElementName::Sup).unwrap();
3375 el_l_4.insert_character_content_item("bar", 0).unwrap();
3376 assert_eq!(el_l_4.content().count(), 4);
3377
3378 // character data item "foo" is now in position 2 and gets removed
3379 assert!(el_l_4.remove_character_content_item(2).is_ok());
3380 assert_eq!(el_l_4.content().count(), 3);
3381 // character data item "bar" should be in postion 0
3382 let item = el_l_4.content().next().unwrap();
3383 if let ElementContent::CharacterData(CharacterData::String(content)) = item {
3384 assert_eq!(content, "bar");
3385 } else {
3386 panic!("unexpected content in <L-4>: {item:?}");
3387 }
3388 }
3389
3390 #[test]
3391 fn move_element_position() {
3392 // move an element to a different position within its parent
3393 let model = AutosarModel::new();
3394 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3395 let el_autosar = model.root_element();
3396 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3397 let pkg1 = el_ar_packages
3398 .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3399 .unwrap();
3400 let pkg2 = el_ar_packages
3401 .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3402 .unwrap();
3403 let pkg3 = el_ar_packages
3404 .create_named_sub_element(ElementName::ArPackage, "Pkg3")
3405 .unwrap();
3406
3407 // "moving" an element inside its parent without actually giving a position is a no-op
3408 el_ar_packages.move_element_here(&pkg3).unwrap();
3409
3410 // moving to an invalid position fails
3411 assert!(el_ar_packages.move_element_here_at(&pkg1, 99).is_err());
3412 assert!(el_ar_packages.move_element_here_at(&pkg1, 3).is_err()); // special boundary case
3413
3414 // move an element forward
3415 el_ar_packages.move_element_here_at(&pkg2, 0).unwrap();
3416 // move an element backward
3417 el_ar_packages.move_element_here_at(&pkg1, 2).unwrap();
3418 // check the new ordering
3419 let mut packages_iter = el_ar_packages.sub_elements();
3420 assert_eq!(packages_iter.next().unwrap(), pkg2);
3421 assert_eq!(packages_iter.next().unwrap(), pkg3);
3422 assert_eq!(packages_iter.next().unwrap(), pkg1);
3423
3424 // moving elements should also work with mixed content
3425 let el_l_4 = pkg1
3426 .create_sub_element(ElementName::LongName)
3427 .and_then(|el| el.create_sub_element(ElementName::L4))
3428 .unwrap();
3429 el_l_4.create_sub_element(ElementName::E).unwrap();
3430 el_l_4.insert_character_content_item("foo", 1).unwrap();
3431 let el_sup = el_l_4.create_sub_element(ElementName::Sup).unwrap();
3432 el_l_4.insert_character_content_item("bar", 0).unwrap();
3433 el_l_4.move_element_here_at(&el_sup, 0).unwrap();
3434 let mut iter = el_l_4.sub_elements();
3435 assert_eq!(iter.next().unwrap(), el_sup);
3436 }
3437
3438 #[test]
3439 fn move_element_local() {
3440 // move an element within the same model
3441 let model = AutosarModel::new();
3442 model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3443 let el_autosar = model.root_element();
3444 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3445 let el_pkg1 = el_ar_packages
3446 .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3447 .unwrap();
3448 let el_elements1 = el_pkg1.create_sub_element(ElementName::Elements).unwrap();
3449 let el_ecu_instance = el_elements1
3450 .create_named_sub_element(ElementName::EcuInstance, "EcuInstance")
3451 .unwrap();
3452 let el_pkg2 = el_ar_packages
3453 .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3454 .unwrap();
3455 let el_pkg3 = el_ar_packages
3456 .create_named_sub_element(ElementName::ArPackage, "Pkg3")
3457 .unwrap();
3458 let el_fibex_element_ref = el_pkg3
3459 .create_sub_element(ElementName::Elements)
3460 .and_then(|el| el.create_named_sub_element(ElementName::System, "System"))
3461 .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3462 .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3463 .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3464 .unwrap();
3465 el_fibex_element_ref.set_reference_target(&el_ecu_instance).unwrap();
3466
3467 // can't move an element of the wrong type
3468 assert!(el_ar_packages.move_element_here(&el_autosar).is_err());
3469 assert!(el_ar_packages.move_element_here_at(&el_autosar, 0).is_err());
3470
3471 // moving an element into its own sub element (creating a loop) is forbidden
3472 assert!(el_pkg1.move_element_here(&el_ar_packages).is_err());
3473 assert!(el_pkg1.move_element_here_at(&el_ar_packages, 1).is_err());
3474
3475 // move an unnamed element
3476 assert!(model.get_element_by_path("/Pkg1/EcuInstance").is_some());
3477 el_pkg2.move_element_here(&el_elements1).unwrap();
3478 assert_eq!(el_elements1.parent().unwrap().unwrap(), el_pkg2);
3479 assert!(model.get_element_by_path("/Pkg2/EcuInstance").is_some());
3480 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3481
3482 // move the unnamed element back using the _at variant
3483 el_pkg1.move_element_here_at(&el_elements1, 1).unwrap();
3484 assert_eq!(el_elements1.parent().unwrap().unwrap(), el_pkg1);
3485 assert!(model.get_element_by_path("/Pkg1/EcuInstance").is_some());
3486 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3487
3488 // move a named element
3489 let el_elements2 = el_pkg2.create_sub_element(ElementName::Elements).unwrap();
3490 el_elements2.move_element_here(&el_ecu_instance).unwrap();
3491 assert_eq!(el_ecu_instance.parent().unwrap().unwrap(), el_elements2);
3492 assert!(model.get_element_by_path("/Pkg2/EcuInstance").is_some());
3493 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3494
3495 // moving an element should automatically resolve name conflicts
3496 el_elements1
3497 .create_named_sub_element(ElementName::EcuInstance, "EcuInstance")
3498 .unwrap();
3499 el_elements1.move_element_here_at(&el_ecu_instance, 0).unwrap();
3500 assert_eq!(el_ecu_instance.parent().unwrap().unwrap(), el_elements1);
3501 assert!(model.get_element_by_path("/Pkg1/EcuInstance_1").is_some());
3502 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3503 }
3504
3505 #[test]
3506 fn move_element_full() {
3507 // move an element between two projects
3508 let model1 = AutosarModel::new();
3509 model1
3510 .create_file("test1.arxml", AutosarVersion::Autosar_00050)
3511 .unwrap();
3512 let el_autosar = model1.root_element();
3513 let el_ar_packages1 = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3514 let el_pkg1 = el_ar_packages1
3515 .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3516 .unwrap();
3517 let el_elements1 = el_pkg1.create_sub_element(ElementName::Elements).unwrap();
3518 let el_ecu_instance = el_elements1
3519 .create_named_sub_element(ElementName::EcuInstance, "EcuInstance")
3520 .unwrap();
3521 let el_pkg2 = el_ar_packages1
3522 .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3523 .unwrap();
3524 let el_fibex_element_ref = el_pkg2
3525 .create_sub_element(ElementName::Elements)
3526 .and_then(|el| el.create_named_sub_element(ElementName::System, "System"))
3527 .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3528 .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3529 .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3530 .unwrap();
3531 el_fibex_element_ref.set_reference_target(&el_ecu_instance).unwrap();
3532
3533 let model2 = AutosarModel::new();
3534 model2
3535 .create_file("test2.arxml", AutosarVersion::Autosar_00050)
3536 .unwrap();
3537 let el_autosar2 = model2.root_element();
3538 let el_ar_packages2 = el_autosar2.create_sub_element(ElementName::ArPackages).unwrap();
3539
3540 // move a named element
3541 el_ar_packages2.move_element_here(&el_pkg1).unwrap();
3542 assert!(model1.get_element_by_path("/Pkg1").is_none());
3543 assert!(model2.get_element_by_path("/Pkg1").is_some());
3544 el_ar_packages2.move_element_here_at(&el_pkg2, 1).unwrap();
3545 assert!(model1.get_element_by_path("/Pkg2").is_none());
3546 assert!(model2.get_element_by_path("/Pkg2").is_some());
3547
3548 // move an unnamed element
3549 el_autosar.remove_sub_element(el_ar_packages1).unwrap();
3550 el_autosar.move_element_here(&el_ar_packages2).unwrap();
3551 assert!(model1.get_element_by_path("/Pkg1/EcuInstance").is_some());
3552 assert!(model1.get_element_by_path("/Pkg2/System").is_some());
3553 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3554
3555 // can't move an element when one of the projects is deleted
3556 drop(model2);
3557 assert!(el_autosar2.move_element_here(&el_ar_packages2).is_err());
3558 assert!(el_autosar2.move_element_here_at(&el_ar_packages2, 0).is_err());
3559
3560 // can't move between files with different versions
3561 let project3 = AutosarModel::new();
3562 project3
3563 .create_file("test2.arxml", AutosarVersion::Autosar_4_3_0)
3564 .unwrap();
3565 let el_autosar3 = project3.root_element();
3566 assert!(el_autosar3.move_element_here(&el_ar_packages2).is_err());
3567 assert!(el_autosar3.move_element_here_at(&el_ar_packages2, 0).is_err());
3568 }
3569
3570 #[test]
3571 fn get_set_reference_target() {
3572 let model = AutosarModel::new();
3573 model.create_file("text.arxml", AutosarVersion::Autosar_00050).unwrap();
3574 let el_autosar = model.root_element();
3575 let el_ar_package = el_autosar
3576 .create_sub_element(ElementName::ArPackages)
3577 .and_then(|arpkgs| arpkgs.create_named_sub_element(ElementName::ArPackage, "Package"))
3578 .unwrap();
3579 let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3580 let el_ecu_instance1 = el_elements
3581 .create_named_sub_element(ElementName::EcuInstance, "EcuInstance1")
3582 .unwrap();
3583 let el_ecu_instance2 = el_elements
3584 .create_named_sub_element(ElementName::EcuInstance, "EcuInstance2")
3585 .unwrap();
3586 let el_req_result = el_elements
3587 .create_named_sub_element(ElementName::DiagnosticRoutine, "DiagRoutine")
3588 .and_then(|dr| dr.create_named_sub_element(ElementName::RequestResult, "RequestResult"))
3589 .unwrap();
3590 let el_fibex_element_ref = el_elements
3591 .create_named_sub_element(ElementName::System, "System")
3592 .and_then(|sys| sys.create_sub_element(ElementName::FibexElements))
3593 .and_then(|fe| fe.create_sub_element(ElementName::FibexElementRefConditional))
3594 .and_then(|ferc| ferc.create_sub_element(ElementName::FibexElementRef))
3595 .unwrap();
3596 let el_physical_request_ref = el_elements
3597 .create_named_sub_element(ElementName::DiagnosticConnection, "DiagnosticConnection")
3598 .and_then(|dc| dc.create_sub_element(ElementName::PhysicalRequestRef))
3599 .unwrap();
3600 let el_connection_ident = el_elements
3601 .create_named_sub_element(ElementName::CanTpConfig, "CanTpConfig")
3602 .and_then(|ctc| ctc.create_sub_element(ElementName::TpConnections))
3603 .and_then(|tc: Element| tc.create_sub_element(ElementName::CanTpConnection))
3604 .and_then(|ctc: Element| ctc.create_named_sub_element(ElementName::Ident, "ConnectionIdent"))
3605 .unwrap();
3606
3607 // set_reference_target does not work for elements which are not references
3608 assert!(el_elements.set_reference_target(&el_ar_package).is_err());
3609 // element AUTOSAR is not identifiable, and a reference to it cannot be set
3610 assert!(el_fibex_element_ref.set_reference_target(&el_autosar).is_err());
3611 // element AR-PACKAGE is identifiable, but not a valid reference target for a FIBEX-ELEMENT-REF
3612 assert!(el_fibex_element_ref.set_reference_target(&el_ar_package).is_err());
3613 // element REQUEST-RESULT is identifiable, but cannot be referenced by any other element as here is no valid DEST enum entry for it
3614 assert!(el_fibex_element_ref.set_reference_target(&el_req_result).is_err());
3615
3616 // set a valid reference and verify that the reference can be used
3617 el_fibex_element_ref.set_reference_target(&el_ecu_instance1).unwrap();
3618 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance1);
3619 // update with a different valid reference and verify that the reference can be used
3620 el_fibex_element_ref.set_reference_target(&el_ecu_instance2).unwrap();
3621 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance2);
3622
3623 // define a REFERENCE-BASE and then set a relative reference via BASE
3624 let el_reference_bases = el_ar_package.create_sub_element(ElementName::ReferenceBases).unwrap();
3625 let el_reference_base = el_reference_bases
3626 .create_sub_element(ElementName::ReferenceBase)
3627 .unwrap();
3628 el_reference_base
3629 .create_sub_element(ElementName::ShortLabel)
3630 .and_then(|short_label| short_label.set_character_data("default"))
3631 .unwrap();
3632 el_reference_base
3633 .create_sub_element(ElementName::PackageRef)
3634 .and_then(|package_ref| package_ref.set_reference_target(&el_ar_package))
3635 .unwrap();
3636 assert!(model.0.read().reference_bases.contains_key("default"));
3637
3638 el_fibex_element_ref
3639 .set_relative_reference_target(&el_ecu_instance2, "default")
3640 .unwrap();
3641 assert_eq!(
3642 el_fibex_element_ref
3643 .attribute_value(AttributeName::Base)
3644 .and_then(|cdata| cdata.string_value())
3645 .unwrap(),
3646 "default"
3647 );
3648 assert_eq!(
3649 el_fibex_element_ref
3650 .character_data()
3651 .and_then(|cdata| cdata.string_value())
3652 .unwrap(),
3653 "EcuInstance2"
3654 );
3655 assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance2);
3656 assert!(
3657 model
3658 .get_references_to("/Package/EcuInstance2")
3659 .contains(&el_fibex_element_ref.downgrade())
3660 );
3661
3662 // set a valid reference to <CAN-TP-CONNECTION><IDENT>.
3663 // This is a complex case, as the correct DEST attribute must be looked up in the specification
3664 el_physical_request_ref
3665 .set_reference_target(&el_connection_ident)
3666 .unwrap();
3667 assert_eq!(
3668 el_physical_request_ref.get_reference_target().unwrap(),
3669 el_connection_ident
3670 );
3671
3672 // invalid reference: bad DEST attribute
3673 el_fibex_element_ref
3674 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
3675 .unwrap();
3676 assert!(el_fibex_element_ref.get_reference_target().is_err());
3677 // invalid reference: no DEST attribute
3678 el_fibex_element_ref.0.write().attributes.clear(); // remove the DEST attribute
3679 assert!(el_fibex_element_ref.get_reference_target().is_err());
3680 el_fibex_element_ref.set_reference_target(&el_ecu_instance2).unwrap();
3681 // invalid reference: bad reference string
3682 el_fibex_element_ref
3683 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcuInstance))
3684 .unwrap();
3685 el_fibex_element_ref.set_character_data("/does/not/exist").unwrap();
3686 assert!(el_fibex_element_ref.get_reference_target().is_err());
3687 // invalid reference: refers to the wrong type of element
3688 el_fibex_element_ref.set_character_data("/Package").unwrap();
3689 assert!(el_fibex_element_ref.get_reference_target().is_err());
3690 // invalid reference: no reference string
3691 el_fibex_element_ref.remove_character_data().unwrap();
3692 assert!(el_fibex_element_ref.get_reference_target().is_err());
3693 el_fibex_element_ref.set_reference_target(&el_ecu_instance2).unwrap();
3694 // not a reference
3695 assert!(el_elements.get_reference_target().is_err());
3696 // model is deleted
3697 drop(model);
3698 assert!(el_fibex_element_ref.get_reference_target().is_err());
3699 }
3700
3701 #[test]
3702 fn modify_character_data() {
3703 let model = AutosarModel::new();
3704 model.create_file("text.arxml", AutosarVersion::Autosar_00050).unwrap();
3705 let el_autosar = model.root_element();
3706 let el_ar_package = el_autosar
3707 .create_sub_element(ElementName::ArPackages)
3708 .and_then(|arpkgs| arpkgs.create_named_sub_element(ElementName::ArPackage, "Package"))
3709 .unwrap();
3710 let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
3711 let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3712 let el_system = el_elements
3713 .create_named_sub_element(ElementName::System, "System")
3714 .unwrap();
3715 let el_fibex_element_ref = el_system
3716 .create_sub_element(ElementName::FibexElements)
3717 .and_then(|fe| fe.create_sub_element(ElementName::FibexElementRefConditional))
3718 .and_then(|ferc| ferc.create_sub_element(ElementName::FibexElementRef))
3719 .unwrap();
3720 let el_pnc_vector_length = el_system.create_sub_element(ElementName::PncVectorLength).unwrap();
3721
3722 // set character data on an "ordinary" element that has no special handling
3723 assert!(
3724 el_pnc_vector_length
3725 .set_character_data(CharacterData::String("2".to_string()))
3726 .is_ok()
3727 ); // "native" type is String, without automatic wrapping
3728 assert!(el_pnc_vector_length.set_character_data("2".to_string()).is_ok()); // "native" type is String
3729 assert!(el_pnc_vector_length.set_character_data("2").is_ok()); // automatic conversion: &str -> String
3730 assert!(el_pnc_vector_length.set_character_data(2).is_ok()); // automatic conversion: u64 -> String
3731
3732 // set a new SHORT-NAME, this also updates path cache
3733 assert!(
3734 el_short_name
3735 .set_character_data(CharacterData::String("PackageRenamed".to_string()))
3736 .is_ok()
3737 );
3738 assert_eq!(
3739 el_short_name.character_data().unwrap().string_value().unwrap(),
3740 "PackageRenamed"
3741 );
3742 model.get_element_by_path("/PackageRenamed").unwrap();
3743
3744 // set a new reference target, which creates an entry in the reference origin cache
3745 assert!(
3746 el_fibex_element_ref
3747 .set_character_data("/PackageRenamed/EcuInstance1")
3748 .is_ok()
3749 );
3750 model
3751 .0
3752 .read()
3753 .reference_origins
3754 .get("/PackageRenamed/EcuInstance1")
3755 .unwrap();
3756
3757 // modify the reference target, which updates the entry in the reference origin cache
3758 assert!(
3759 el_fibex_element_ref
3760 .set_character_data("/PackageRenamed/EcuInstance2")
3761 .is_ok()
3762 );
3763 model
3764 .0
3765 .read()
3766 .reference_origins
3767 .get("/PackageRenamed/EcuInstance2")
3768 .unwrap();
3769 assert!(
3770 !model
3771 .0
3772 .read()
3773 .reference_origins
3774 .contains_key("/PackageRenamed/EcuInstance1")
3775 );
3776
3777 // can only set character data that are specified with ContentMode::Characters
3778 assert!(el_autosar.set_character_data("text").is_err());
3779
3780 // can't set a value that doesn't match the target spec
3781 assert!(el_short_name.set_character_data(0).is_err());
3782 assert!(el_short_name.set_character_data("").is_err());
3783
3784 // remove character data
3785 assert!(el_pnc_vector_length.remove_character_data().is_ok());
3786
3787 // remove the character data of a reference
3788 assert!(el_fibex_element_ref.remove_character_data().is_ok());
3789 assert!(
3790 !model
3791 .0
3792 .read()
3793 .reference_origins
3794 .contains_key("/PackageRenamed/EcuInstance2")
3795 );
3796
3797 // remove on an element whose character data has already been removed is not an error
3798 assert!(el_fibex_element_ref.remove_character_data().is_ok());
3799
3800 // can't remove SHORT-NAME
3801 assert!(el_short_name.remove_character_data().is_err());
3802
3803 // can't remove from elements which do not contain character data
3804 assert!(el_autosar.remove_character_data().is_err());
3805
3806 // slightly different behavior for the internal version that is used for locked elements
3807 assert!(
3808 el_autosar
3809 .0
3810 .write()
3811 .set_character_data(0, AutosarVersion::Autosar_00050)
3812 .is_err()
3813 );
3814 assert!(
3815 el_fibex_element_ref
3816 .0
3817 .write()
3818 .set_character_data(0, AutosarVersion::Autosar_00050)
3819 .is_err()
3820 );
3821
3822 // operation fails if the model is needed (e.g. reference or short name update), but the model has been deleted
3823 el_fibex_element_ref
3824 .set_character_data("/PackageRenamed/EcuInstance2")
3825 .unwrap();
3826 drop(model);
3827 assert!(
3828 el_fibex_element_ref
3829 .set_character_data("/PackageRenamed/EcuInstance1")
3830 .is_err()
3831 );
3832 assert!(el_fibex_element_ref.remove_character_data().is_err());
3833 }
3834
3835 #[test]
3836 fn mixed_character_content() {
3837 let model = AutosarModel::new();
3838 model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
3839 let el_ar_package = model
3840 .root_element()
3841 .create_sub_element(ElementName::ArPackages)
3842 .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
3843 .unwrap();
3844 let el_desc = el_ar_package.create_sub_element(ElementName::Desc).unwrap();
3845 let el_l2 = el_desc.create_sub_element(ElementName::L2).unwrap();
3846
3847 // ok: add a character content item to a vaild element at a valid position
3848 el_l2.insert_character_content_item("descriptive text", 0).unwrap();
3849
3850 // ok: add an element to the mixed item as well
3851 el_l2.create_sub_element(ElementName::Br).unwrap();
3852
3853 // not ok: add a character content item to a valid element at an invalid position
3854 assert!(el_l2.insert_character_content_item("more text", 99).is_err());
3855
3856 // not ok: add a character content item to an invalid element
3857 assert!(el_desc.insert_character_content_item("text", 0).is_err());
3858
3859 // not ok: remove character content from an invalid position
3860 assert!(el_l2.remove_character_content_item(99).is_err());
3861
3862 // not ok: remove character content from an invalid element
3863 assert!(el_desc.remove_character_content_item(0).is_err());
3864
3865 // not ok: remove a sub-element
3866 assert!(el_l2.remove_character_content_item(1).is_err());
3867
3868 // ok: remove character content from a valid element at a valid position
3869 el_l2.remove_character_content_item(0).unwrap();
3870 }
3871
3872 #[test]
3873 fn get_sub_element() {
3874 let model = AutosarModel::new();
3875 model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
3876 let el_autosar = model.root_element();
3877 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3878 let el_ar_package = el_ar_packages
3879 .create_named_sub_element(ElementName::ArPackage, "Package")
3880 .unwrap();
3881 let el_desc = el_ar_package.create_sub_element(ElementName::Desc).unwrap();
3882 let el_l2 = el_desc.create_sub_element(ElementName::L2).unwrap();
3883
3884 el_l2.insert_character_content_item("descriptive text", 0).unwrap();
3885 el_l2.create_sub_element(ElementName::Br).unwrap();
3886
3887 assert_eq!(
3888 el_autosar.get_sub_element(ElementName::ArPackages).unwrap(),
3889 el_ar_packages
3890 );
3891 assert!(el_autosar.get_sub_element(ElementName::Abs).is_none());
3892 assert!(el_l2.get_sub_element(ElementName::Br).is_some());
3893 }
3894
3895 #[test]
3896 fn get_or_create() {
3897 let model = AutosarModel::new();
3898 model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
3899 let el_autosar = model.root_element();
3900
3901 assert_eq!(el_autosar.sub_elements().count(), 0);
3902 let el_admin_data = el_autosar.get_or_create_sub_element(ElementName::AdminData).unwrap();
3903 let el_ar_packages = el_autosar.get_or_create_sub_element(ElementName::ArPackages).unwrap();
3904 let el_ar_packages2 = el_autosar.get_or_create_sub_element(ElementName::ArPackages).unwrap();
3905 assert_ne!(el_admin_data, el_ar_packages);
3906 assert_eq!(el_ar_packages, el_ar_packages2);
3907
3908 let el_ar_package = el_ar_packages
3909 .get_or_create_named_sub_element(ElementName::ArPackage, "Pkg")
3910 .unwrap();
3911 let el_ar_package2 = el_ar_packages
3912 .get_or_create_named_sub_element(ElementName::ArPackage, "Pkg2")
3913 .unwrap();
3914 let el_ar_package3 = el_ar_packages
3915 .get_or_create_named_sub_element(ElementName::ArPackage, "Pkg2")
3916 .unwrap();
3917 assert_ne!(el_ar_package, el_ar_package2);
3918 assert_eq!(el_ar_package2, el_ar_package3);
3919 }
3920
3921 #[test]
3922 fn serialize() {
3923 const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
3924<!--comment-->
3925<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">
3926 <AR-PACKAGES>
3927 <AR-PACKAGE>
3928 <SHORT-NAME>Pkg</SHORT-NAME>
3929 <DESC>
3930 <L-2 L="EN">Description<BR/>Description</L-2>
3931 </DESC>
3932 </AR-PACKAGE>
3933 </AR-PACKAGES>
3934</AUTOSAR>"#;
3935 let model = AutosarModel::new();
3936 model
3937 .load_buffer(FILEBUF.as_bytes(), OsString::from("test"), true)
3938 .unwrap();
3939 model.files().next().unwrap();
3940 let el_autosar = model.root_element();
3941 el_autosar.set_comment(Some("comment".to_string()));
3942
3943 let mut outstring = String::from(r#"<?xml version="1.0" encoding="utf-8"?>"#);
3944 el_autosar.serialize_internal(&mut outstring, 0, false, &None);
3945
3946 assert_eq!(FILEBUF, outstring);
3947 }
3948
3949 #[test]
3950 fn list_valid_sub_elements() {
3951 let model = AutosarModel::new();
3952 model.create_file("test.arxml", AutosarVersion::Autosar_4_3_0).unwrap();
3953 let el_autosar = model.root_element();
3954 let el_elements = el_autosar
3955 .create_sub_element(ElementName::ArPackages)
3956 .and_then(|el| el.create_named_sub_element(ElementName::ArPackage, "Package"))
3957 .and_then(|el| el.create_sub_element(ElementName::Elements))
3958 .unwrap();
3959 let result = el_elements.list_valid_sub_elements();
3960 assert!(!result.is_empty());
3961 }
3962
3963 #[test]
3964 fn check_version_compatibility() {
3965 const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
3966<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">
3967 <AR-PACKAGES>
3968 <AR-PACKAGE>
3969 <SHORT-NAME>Pkg</SHORT-NAME>
3970 <ELEMENTS>
3971 <ACL-OBJECT-SET UUID="012345">
3972 <SHORT-NAME BLUEPRINT-VALUE="xyz">AclObjectSet</SHORT-NAME>
3973 <DERIVED-FROM-BLUEPRINT-REFS>
3974 <DERIVED-FROM-BLUEPRINT-REF DEST="ABSTRACT-IMPLEMENTATION-DATA-TYPE">/invalid</DERIVED-FROM-BLUEPRINT-REF>
3975 </DERIVED-FROM-BLUEPRINT-REFS>
3976 </ACL-OBJECT-SET>
3977 <ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE>
3978 <SHORT-NAME>AdaptiveApplicationSwComponentType</SHORT-NAME>
3979 </ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE>
3980 </ELEMENTS>
3981 </AR-PACKAGE>
3982 </AR-PACKAGES>
3983</AUTOSAR>"#;
3984 let model = AutosarModel::new();
3985 let (file, _) = model
3986 .load_buffer(FILEBUF.as_bytes(), OsString::from("test"), true)
3987 .unwrap();
3988 model.files().next().unwrap();
3989 let el_autosar = model.root_element();
3990
3991 let (compat_errors, _) =
3992 el_autosar.check_version_compatibility(&file.downgrade(), AutosarVersion::Autosar_4_3_0);
3993 assert_eq!(compat_errors.len(), 3);
3994
3995 for ce in compat_errors {
3996 match ce {
3997 CompatibilityError::IncompatibleElement { element, .. } => {
3998 assert_eq!(element.element_name(), ElementName::AdaptiveApplicationSwComponentType);
3999 }
4000 CompatibilityError::IncompatibleAttribute { element, attribute, .. } => {
4001 assert_eq!(element.element_name(), ElementName::ShortName);
4002 assert_eq!(attribute, AttributeName::BlueprintValue);
4003 }
4004 CompatibilityError::IncompatibleAttributeValue { element, attribute, .. } => {
4005 assert_eq!(element.element_name(), ElementName::DerivedFromBlueprintRef);
4006 assert_eq!(attribute, AttributeName::Dest);
4007 }
4008 }
4009 }
4010
4011 // regression test - CompuScales in CompuInternalToPhys was falsely detected as incompatible
4012 let model = AutosarModel::new();
4013 let file = model.create_file("filename", AutosarVersion::Autosar_00046).unwrap();
4014 model
4015 .root_element()
4016 .create_sub_element(ElementName::ArPackages)
4017 .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
4018 .and_then(|e| e.create_sub_element(ElementName::Elements))
4019 .and_then(|e| e.create_named_sub_element(ElementName::CompuMethod, "CompuMethod"))
4020 .and_then(|e| e.create_sub_element(ElementName::CompuInternalToPhys))
4021 .and_then(|e| e.create_sub_element(ElementName::CompuScales))
4022 .and_then(|e| e.create_sub_element(ElementName::CompuScale))
4023 .unwrap();
4024 let (compat_errors, _) = file.check_version_compatibility(AutosarVersion::Autosar_4_3_0);
4025 assert!(compat_errors.is_empty());
4026 }
4027
4028 #[test]
4029 fn find_element_insert_pos() {
4030 let model = AutosarModel::new();
4031 model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
4032 let el_autosar = model.root_element();
4033 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4034 let el_ar_package = el_ar_packages
4035 .create_named_sub_element(ElementName::ArPackage, "Pkg")
4036 .unwrap();
4037 let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
4038
4039 // find_element_insert_pos does not operat on CharacterData elements, e.g. SHORT-NAME
4040 assert!(
4041 el_short_name
4042 .0
4043 .read()
4044 .calc_element_insert_range(ElementName::Desc, AutosarVersion::Autosar_00050)
4045 .is_err()
4046 );
4047
4048 // find_element_insert_pos fails to find a place for a sequence element with multiplicity 0-1
4049 assert!(
4050 el_autosar
4051 .0
4052 .read()
4053 .calc_element_insert_range(ElementName::ArPackages, AutosarVersion::Autosar_00050)
4054 .is_err()
4055 );
4056 }
4057
4058 #[test]
4059 fn sort() {
4060 let model = AutosarModel::new();
4061 model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
4062 let el_autosar = model.root_element();
4063 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4064 let el_ar_package1 = el_ar_packages
4065 .create_named_sub_element(ElementName::ArPackage, "Z")
4066 .unwrap();
4067 let el_ar_package2 = el_ar_packages
4068 .create_named_sub_element(ElementName::ArPackage, "A")
4069 .unwrap();
4070 let el_elements = el_ar_package1.create_sub_element(ElementName::Elements).unwrap();
4071 el_ar_package1.create_sub_element(ElementName::AdminData).unwrap();
4072 // create some bsw values to sort inside el_ar_package1 "Z"
4073 let el_emcv = el_elements
4074 .create_named_sub_element(ElementName::EcucModuleConfigurationValues, "Config")
4075 .unwrap();
4076 let el_containers = el_emcv.create_sub_element(ElementName::Containers).unwrap();
4077 let el_ecv = el_containers
4078 .create_named_sub_element(ElementName::EcucContainerValue, "ConfigValues")
4079 .unwrap();
4080 let el_paramvalues = el_ecv.create_sub_element(ElementName::ParameterValues).unwrap();
4081 // first bsw value
4082 let el_value1 = el_paramvalues
4083 .create_sub_element(ElementName::EcucNumericalParamValue)
4084 .unwrap();
4085 let el_defref1 = el_value1.create_sub_element(ElementName::DefinitionRef).unwrap();
4086 el_defref1
4087 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcucBooleanParamDef))
4088 .unwrap();
4089 el_defref1.set_character_data("/DefRef_999").unwrap();
4090 // second bsw value
4091 let el_value2 = el_paramvalues
4092 .create_sub_element(ElementName::EcucNumericalParamValue)
4093 .unwrap();
4094 let el_defref2 = el_value2.create_sub_element(ElementName::DefinitionRef).unwrap();
4095 el_defref2
4096 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcucBooleanParamDef))
4097 .unwrap();
4098 el_defref2.set_character_data("/DefRef_111").unwrap();
4099 // Create some misc value sto sort inside el_ar_package2 "A"
4100 let el_elements2 = el_ar_package2.create_sub_element(ElementName::Elements).unwrap();
4101 let el_system = el_elements2
4102 .create_named_sub_element(ElementName::System, "System")
4103 .unwrap();
4104 let el_fibex_elements = el_system.create_sub_element(ElementName::FibexElements).unwrap();
4105 let el_fibex_element1 = el_fibex_elements
4106 .create_sub_element(ElementName::FibexElementRefConditional)
4107 .unwrap();
4108 let el_fibex_element_ref1 = el_fibex_element1
4109 .create_sub_element(ElementName::FibexElementRef)
4110 .unwrap();
4111 el_fibex_element_ref1
4112 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4113 .unwrap();
4114 el_fibex_element_ref1.set_character_data("/ZZZZZ").unwrap();
4115 let el_fibex_element2 = el_fibex_elements
4116 .create_sub_element(ElementName::FibexElementRefConditional)
4117 .unwrap();
4118 let el_fibex_element_ref2 = el_fibex_element2
4119 .create_sub_element(ElementName::FibexElementRef)
4120 .unwrap();
4121 el_fibex_element_ref2
4122 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4123 .unwrap();
4124 el_fibex_element_ref2.set_character_data("/AAAAA").unwrap();
4125
4126 model.sort();
4127 // validate that identifiable elements have been sorted
4128 let mut iter = el_ar_packages.sub_elements();
4129 let item1 = iter.next().unwrap();
4130 let item2 = iter.next().unwrap();
4131 assert_eq!(item1.item_name().unwrap(), "A");
4132 assert_eq!(item2.item_name().unwrap(), "Z");
4133
4134 // validate that BSW parameter values have been sorted
4135 let mut iter = el_paramvalues.sub_elements();
4136 let item1 = iter.next().unwrap();
4137 let item2 = iter.next().unwrap();
4138 assert_eq!(item1, el_value2);
4139 assert_eq!(item2, el_value1);
4140
4141 // validate that the misc elements (FIBEX-ELEMENT-REF-CONDITIONAL) have been sorted
4142 let mut iter = el_fibex_elements.sub_elements();
4143 let item1 = iter.next().unwrap();
4144 let item2 = iter.next().unwrap();
4145 assert_eq!(item1, el_fibex_element2);
4146 assert_eq!(item2, el_fibex_element1);
4147 }
4148
4149 fn helper_create_bsw_subelem(
4150 el_subcontainers: &Element,
4151 short_name: &str,
4152 defref: &str,
4153 ) -> Result<Element, AutosarDataError> {
4154 let e = el_subcontainers.create_named_sub_element(ElementName::EcucContainerValue, short_name)?;
4155 let defrefelem = e.create_sub_element(ElementName::DefinitionRef)?;
4156 defrefelem.set_character_data(defref)?;
4157 Ok(e)
4158 }
4159
4160 fn helper_create_indexed_bsw_subelem(
4161 el_subcontainers: &Element,
4162 short_name: &str,
4163 indexstr: &str,
4164 defref: &str,
4165 ) -> Result<Element, AutosarDataError> {
4166 let e = helper_create_bsw_subelem(el_subcontainers, short_name, defref)?;
4167 let indexelem = e.create_sub_element(ElementName::Index)?;
4168 indexelem.set_character_data(indexstr)?;
4169 Ok(e)
4170 }
4171
4172 #[test]
4173 fn sort_bsw_elements() {
4174 let model = AutosarModel::new();
4175 model.create_file("test", AutosarVersion::LATEST).unwrap();
4176 let el_subcontainers = model
4177 .root_element()
4178 .create_sub_element(ElementName::ArPackages)
4179 .and_then(|ap| ap.create_named_sub_element(ElementName::ArPackage, "Pkg"))
4180 .and_then(|ap| ap.create_sub_element(ElementName::Elements))
4181 .and_then(|elems| elems.create_named_sub_element(ElementName::EcucModuleConfigurationValues, "Config"))
4182 .and_then(|emcv| emcv.create_sub_element(ElementName::Containers))
4183 .and_then(|c| c.create_named_sub_element(ElementName::EcucContainerValue, "ConfigValues"))
4184 .and_then(|ecv| ecv.create_sub_element(ElementName::SubContainers))
4185 .unwrap();
4186 let elem1 =
4187 helper_create_indexed_bsw_subelem(&el_subcontainers, "Aaa", "06", "/Defref/Container/Value").unwrap(); // idx 6
4188 let elem2 =
4189 helper_create_indexed_bsw_subelem(&el_subcontainers, "Bbb", "5", "/Defref/Container/Value").unwrap(); // idx 5
4190 let elem3 =
4191 helper_create_indexed_bsw_subelem(&el_subcontainers, "Bbb2", "5", "/Defref/Container/Value").unwrap(); // idx 5 duplicate
4192 let elem4 =
4193 helper_create_indexed_bsw_subelem(&el_subcontainers, "Ccc", "0X4", "/Defref/Container/Value").unwrap(); // idx 4
4194 let elem5 = helper_create_bsw_subelem(&el_subcontainers, "Zzz", "/Defref/Container/Value").unwrap();
4195 let elem6 =
4196 helper_create_indexed_bsw_subelem(&el_subcontainers, "Ddd", "0b1", "/Defref/Container/Value").unwrap(); // idx 1
4197 let elem7 =
4198 helper_create_indexed_bsw_subelem(&el_subcontainers, "Eee", "0x3", "/Defref/Container/Value").unwrap(); // idx 3
4199 let elem8 =
4200 helper_create_indexed_bsw_subelem(&el_subcontainers, "Fff", "0B10", "/Defref/Container/Value").unwrap(); // idx 2
4201 let elem9 =
4202 helper_create_indexed_bsw_subelem(&el_subcontainers, "Ggg", "0", "/Defref/Container/Value").unwrap(); // idx 0
4203
4204 let elem10 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_0", "/Defref/Container/Value").unwrap();
4205 let elem11 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_5", "/Defref/Container/Value").unwrap();
4206 let elem12 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_10", "/Defref/Container/Value").unwrap();
4207 let elem13 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_9", "/Defref/Container/Value").unwrap();
4208
4209 el_subcontainers.sort();
4210 assert_eq!(elem1.position().unwrap(), 7);
4211 assert_eq!(elem2.position().unwrap(), 5);
4212 assert_eq!(elem3.position().unwrap(), 6);
4213 assert_eq!(elem4.position().unwrap(), 4);
4214 assert_eq!(elem6.position().unwrap(), 1);
4215 assert_eq!(elem7.position().unwrap(), 3);
4216 assert_eq!(elem8.position().unwrap(), 2);
4217 assert_eq!(elem9.position().unwrap(), 0);
4218 // elements without indices are sorted behind the indexed elements
4219 assert_eq!(elem10.position().unwrap(), 8);
4220 assert_eq!(elem11.position().unwrap(), 9);
4221 assert_eq!(elem13.position().unwrap(), 10);
4222 assert_eq!(elem12.position().unwrap(), 11);
4223 assert_eq!(elem5.position().unwrap(), 12);
4224 }
4225
4226 #[test]
4227 fn file_membership() {
4228 let model = AutosarModel::new();
4229 let file1 = model.create_file("test_1", AutosarVersion::Autosar_00050).unwrap();
4230 let file2 = model.create_file("test_2", AutosarVersion::Autosar_00050).unwrap();
4231 let el_autosar = model.root_element();
4232 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4233 let el_ar_package = el_ar_packages
4234 .create_named_sub_element(ElementName::ArPackage, "Pkg")
4235 .unwrap();
4236 el_ar_package.create_sub_element(ElementName::Elements).unwrap();
4237
4238 let fm: HashSet<WeakArxmlFile> = [file1.downgrade()].iter().cloned().collect();
4239 // setting the file membership of el_ar_packages should fail
4240 // its parent is not splittable, so this is not allowed
4241 el_ar_packages.set_file_membership(fm.clone());
4242 let (local, _) = el_ar_package.file_membership().unwrap();
4243 assert!(!local);
4244
4245 // setting the file membership of el_ar_package should succeed
4246 // this element is only part of file1, and is only serialized with file1
4247 el_ar_package.set_file_membership(fm.clone());
4248 let (local, fm2) = el_ar_package.file_membership().unwrap();
4249 assert!(local);
4250 assert_eq!(fm, fm2);
4251 let filetxt1 = file1.serialize().unwrap();
4252 let filetxt2 = file2.serialize().unwrap();
4253 assert_ne!(filetxt1, filetxt2);
4254
4255 // can't use a file from a different model in add_to_file / remove_from_file
4256 let model2 = AutosarModel::new();
4257 let model2_file = model2.create_file("file", AutosarVersion::LATEST).unwrap();
4258 assert!(el_ar_package.add_to_file(&model2_file).is_err());
4259 assert!(el_ar_package.remove_from_file(&model2_file).is_err());
4260
4261 // adding el_ar_package to file1 does nothing, since it is already present in this file
4262 el_ar_package.add_to_file(&file1).unwrap();
4263 let (local, fm3) = el_ar_package.file_membership().unwrap();
4264 assert!(local);
4265 assert_eq!(fm3.len(), 1);
4266
4267 // removing el_ar_package from file2 does nothing, it is not present in this file
4268 el_ar_package.remove_from_file(&file2).unwrap();
4269 let (local, fm3) = el_ar_package.file_membership().unwrap();
4270 assert!(local);
4271 assert_eq!(fm3.len(), 1);
4272
4273 // adding el_ar_package to file2 succeeds
4274 el_ar_package.add_to_file(&file2).unwrap();
4275 let (local, fm3) = el_ar_package.file_membership().unwrap();
4276 assert!(local);
4277 assert_eq!(fm3.len(), 2);
4278
4279 // removing el_ar_package from file1 and file2 causes it to be deleted
4280 assert!(el_ar_package.get_sub_element(ElementName::Elements).is_some());
4281 el_ar_package.remove_from_file(&file1).unwrap();
4282 el_ar_package.remove_from_file(&file2).unwrap();
4283 assert!(el_ar_package.get_sub_element(ElementName::Elements).is_none());
4284 assert!(el_ar_package.remove_from_file(&file2).is_err());
4285 }
4286
4287 #[test]
4288 fn comment() {
4289 let model = AutosarModel::new();
4290 model.create_file("test", AutosarVersion::LATEST).unwrap();
4291 let el_autosar = model.root_element();
4292
4293 // initially there is no comment
4294 assert!(el_autosar.comment().is_none());
4295
4296 // set and get a comment
4297 el_autosar.set_comment(Some("comment".to_string()));
4298 assert_eq!(el_autosar.comment().unwrap(), "comment");
4299
4300 // set a new comment containing "--" which is a forbidden sequence in XML comments
4301 el_autosar.set_comment(Some("comment--".to_string()));
4302 assert_eq!(el_autosar.comment().unwrap(), "comment__");
4303
4304 // remove the comment
4305 el_autosar.set_comment(None);
4306 assert!(el_autosar.comment().is_none());
4307 }
4308
4309 #[test]
4310 fn min_version() {
4311 let model = AutosarModel::new();
4312 let result = model.root_element().min_version();
4313 assert!(result.is_err());
4314
4315 model.create_file("test", AutosarVersion::LATEST).unwrap();
4316 let min_ver = model.root_element().min_version().unwrap();
4317 assert_eq!(min_ver, AutosarVersion::LATEST);
4318
4319 model.create_file("test2", AutosarVersion::Autosar_00042).unwrap();
4320 let min_ver = model.root_element().min_version().unwrap();
4321 assert_eq!(min_ver, AutosarVersion::Autosar_00042);
4322 }
4323
4324 #[test]
4325 fn traits() {
4326 let model = AutosarModel::new();
4327 model.create_file("test", AutosarVersion::LATEST).unwrap();
4328
4329 // traits of elements
4330 let el_autosar = model.root_element();
4331 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4332
4333 let el_autosar_second_ref = el_autosar.clone();
4334 assert_eq!(el_autosar, el_autosar_second_ref);
4335 assert_eq!(format!("{el_autosar:?}"), format!("{el_autosar_second_ref:?}"));
4336 assert_ne!(el_autosar, el_ar_packages);
4337
4338 let weak1 = el_autosar.downgrade();
4339 let weak2 = el_autosar_second_ref.downgrade();
4340 assert_eq!(weak1, weak2);
4341 assert_eq!(format!("{weak1:?}"), format!("{weak2:?}"));
4342
4343 let mut hs = HashSet::new();
4344 hs.insert(el_autosar);
4345 hs.insert(el_ar_packages);
4346 // can't insert el_autosar_second_ref, it is already in the set
4347 assert!(!hs.insert(el_autosar_second_ref));
4348 assert_eq!(hs.len(), 2);
4349
4350 let mut hs2 = HashSet::new();
4351 hs2.insert(weak1);
4352 assert!(!hs2.insert(weak2));
4353 assert_eq!(hs2.len(), 1);
4354
4355 // traits of elementcontent
4356 let ec_elem = ElementContent::Element(model.root_element());
4357 assert_eq!(format!("{:?}", model.root_element()), format!("{ec_elem:?}"));
4358 assert_eq!(ec_elem.unwrap_element(), Some(model.root_element()));
4359 assert!(ec_elem.unwrap_cdata().is_none());
4360 let cdata = CharacterData::String("test".to_string());
4361 let ec_chars = ElementContent::CharacterData(cdata.clone());
4362 assert_eq!(format!("{cdata:?}"), format!("{ec_chars:?}"));
4363 assert_eq!(ec_chars.unwrap_cdata(), Some(cdata));
4364 assert!(ec_chars.unwrap_element().is_none());
4365 }
4366
4367 #[test]
4368 fn element_order() {
4369 let model = AutosarModel::new();
4370 let _file = model.create_file("test", AutosarVersion::LATEST).unwrap();
4371 let el_autosar = model.root_element();
4372 let el_elements = el_autosar
4373 .create_sub_element(ElementName::ArPackages)
4374 .unwrap()
4375 .create_named_sub_element(ElementName::ArPackage, "pkg")
4376 .unwrap()
4377 .create_sub_element(ElementName::Elements)
4378 .unwrap();
4379 let el_system = el_elements
4380 .create_named_sub_element(ElementName::System, "sys")
4381 .unwrap();
4382 let fibex_elements = el_system.create_sub_element(ElementName::FibexElements).unwrap();
4383
4384 let item1 = el_elements
4385 .create_named_sub_element(ElementName::ApplicationPrimitiveDataType, "adt_2")
4386 .unwrap();
4387 let item2 = el_elements
4388 .create_named_sub_element(ElementName::ApplicationPrimitiveDataType, "adt_10")
4389 .unwrap();
4390 let item3 = el_elements
4391 .create_named_sub_element(ElementName::ApplicationArrayDataType, "adt_12")
4392 .unwrap();
4393 // items 1 and 2 are sorted after separating the index from the name, so 10 comes after 2
4394 assert!(item1 < item2);
4395 // items 2 and 3 are sorted by the element type, so in this case the index does not matter
4396 assert!(item3 < item1);
4397
4398 let item4 = fibex_elements
4399 .create_sub_element(ElementName::FibexElementRefConditional)
4400 .unwrap();
4401 let item4_ref = item4.create_sub_element(ElementName::FibexElementRef).unwrap();
4402 item4_ref
4403 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4404 .unwrap();
4405 item4_ref.set_character_data("/aaa").unwrap();
4406
4407 let item5 = fibex_elements
4408 .create_sub_element(ElementName::FibexElementRefConditional)
4409 .unwrap();
4410 let item5_ref = item5.create_sub_element(ElementName::FibexElementRef).unwrap();
4411 item5_ref
4412 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4413 .unwrap();
4414 item5_ref.set_character_data("/bbb").unwrap();
4415
4416 // items 4 and 5 are sorted by the character data of the reference
4417 assert!(item4 < item5);
4418
4419 let item6 = fibex_elements
4420 .create_sub_element(ElementName::FibexElementRefConditional)
4421 .unwrap();
4422 let item6_ref = item6.create_sub_element(ElementName::FibexElementRef).unwrap();
4423 item6_ref
4424 .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcuInstance))
4425 .unwrap();
4426
4427 // items 4 and 6 are sorted by the DEST attribute of the reference
4428 assert!(item6 < item4);
4429
4430 let item7 = fibex_elements
4431 .create_sub_element(ElementName::FibexElementRefConditional)
4432 .unwrap();
4433 item7.create_sub_element(ElementName::FibexElementRef).unwrap();
4434
4435 // item7 is incomplete, lacking the DEST attribute so it is sorted last
4436 assert!(item7 > item6);
4437 assert!(item6 < item7);
4438 }
4439
4440 #[test]
4441 fn elements_dfs_with_max_depth() {
4442 const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
4443 <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">
4444 <AR-PACKAGES>
4445 <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
4446 <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
4447 <SHORT-NAME>BswModuleValues</SHORT-NAME>
4448 <PARAMETER-VALUES>
4449 <ECUC-NUMERICAL-PARAM-VALUE>
4450 <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
4451 </ECUC-NUMERICAL-PARAM-VALUE>
4452 <ECUC-NUMERICAL-PARAM-VALUE>
4453 <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
4454 </ECUC-NUMERICAL-PARAM-VALUE>
4455 <ECUC-NUMERICAL-PARAM-VALUE>
4456 <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_C</DEFINITION-REF>
4457 </ECUC-NUMERICAL-PARAM-VALUE>
4458 </PARAMETER-VALUES>
4459 </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
4460 </ELEMENTS></AR-PACKAGE>
4461 <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
4462 <AR-PACKAGE><SHORT-NAME>Pkg_C</SHORT-NAME></AR-PACKAGE>
4463 </AR-PACKAGES></AUTOSAR>"#.as_bytes();
4464 let model = AutosarModel::new();
4465 let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
4466 let root_elem = model.root_element();
4467 let ar_packages_elem = root_elem.get_sub_element(ElementName::ArPackages).unwrap();
4468 let root_all_count = root_elem.elements_dfs().count();
4469 let ar_packages_all_count = ar_packages_elem.elements_dfs().count();
4470 assert_eq!(root_all_count, ar_packages_all_count + 1);
4471
4472 let root_lvl3_count = root_elem.elements_dfs_with_max_depth(3).count();
4473 let ar_packages_lvl2_count = ar_packages_elem.elements_dfs_with_max_depth(2).count();
4474 assert_eq!(root_lvl3_count, ar_packages_lvl2_count + 1);
4475
4476 root_elem
4477 .elements_dfs_with_max_depth(3)
4478 .skip(1)
4479 .zip(ar_packages_elem.elements_dfs_with_max_depth(2))
4480 .for_each(|((_, x), (_, y))| assert_eq!(x, y));
4481
4482 for elem in ar_packages_elem.elements_dfs_with_max_depth(2) {
4483 assert!(elem.0 <= 2);
4484 }
4485 }
4486
4487 #[test]
4488 fn parse_reference_bases() {
4489 // from issue #36
4490 const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
4491<AUTOSAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autosar.org/schema/r4.0" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00048.xsd">
4492 <AR-PACKAGES>
4493 <AR-PACKAGE>
4494 <SHORT-NAME>My</SHORT-NAME>
4495 <AR-PACKAGES>
4496 <AR-PACKAGE>
4497 <SHORT-NAME>Lib</SHORT-NAME>
4498 <AR-PACKAGES>
4499 <AR-PACKAGE>
4500 <SHORT-NAME>Hierarchy</SHORT-NAME>
4501 <AR-PACKAGES>
4502 <AR-PACKAGE>
4503 <SHORT-NAME>Units</SHORT-NAME>
4504 <ELEMENTS>
4505 <UNIT>
4506 <SHORT-NAME>MyUnit</SHORT-NAME>
4507 <FACTOR-SI-TO-UNIT>1</FACTOR-SI-TO-UNIT>
4508 <OFFSET-SI-TO-UNIT>0</OFFSET-SI-TO-UNIT>
4509 </UNIT>
4510 </ELEMENTS>
4511 </AR-PACKAGE>
4512 </AR-PACKAGES>
4513 </AR-PACKAGE>
4514 </AR-PACKAGES>
4515 </AR-PACKAGE>
4516 <AR-PACKAGE>
4517 <SHORT-NAME>SWC</SHORT-NAME>
4518 <REFERENCE-BASES>
4519 <REFERENCE-BASE>
4520 <SHORT-LABEL>Units</SHORT-LABEL>
4521 <PACKAGE-REF DEST="AR-PACKAGE">/My/Lib/Hierarchy/Units</PACKAGE-REF>
4522 </REFERENCE-BASE>
4523 </REFERENCE-BASES>
4524 <AR-PACKAGES>
4525 <AR-PACKAGE>
4526 <SHORT-NAME>ApplicationDataTypes</SHORT-NAME>
4527 <ELEMENTS>
4528 <APPLICATION-PRIMITIVE-DATA-TYPE>
4529 <SHORT-NAME>MyDataType</SHORT-NAME>
4530 <CATEGORY>VALUE</CATEGORY>
4531 <SW-DATA-DEF-PROPS>
4532 <SW-DATA-DEF-PROPS-VARIANTS>
4533 <SW-DATA-DEF-PROPS-CONDITIONAL>
4534 <SW-CALIBRATION-ACCESS>NOT-ACCESSIBLE</SW-CALIBRATION-ACCESS>
4535 <UNIT-REF DEST="UNIT" BASE="Units">MyUnit</UNIT-REF>
4536 </SW-DATA-DEF-PROPS-CONDITIONAL>
4537 </SW-DATA-DEF-PROPS-VARIANTS>
4538 </SW-DATA-DEF-PROPS>
4539 </APPLICATION-PRIMITIVE-DATA-TYPE>
4540 </ELEMENTS>
4541 </AR-PACKAGE>
4542 </AR-PACKAGES>
4543 </AR-PACKAGE>
4544 </AR-PACKAGES>
4545 </AR-PACKAGE>
4546 </AR-PACKAGES>
4547</AUTOSAR>"#.as_bytes();
4548 let model = AutosarModel::new();
4549 let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
4550
4551 let unit_elem = model.get_element_by_path("/My/Lib/Hierarchy/Units/MyUnit").unwrap();
4552 assert_eq!(unit_elem.element_name(), ElementName::Unit);
4553
4554 // verify that the reference base has been correctly parsed and cached
4555 assert_eq!(model.0.read().reference_bases.len(), 1);
4556 let locked_model = model.0.read();
4557 let (rb, rb_infos) = &locked_model.reference_bases.iter().next().unwrap();
4558 assert_eq!(*rb, "Units");
4559 assert_eq!(rb_infos.len(), 1);
4560 assert_eq!(rb_infos[0].package_ref, "/My/Lib/Hierarchy/Units");
4561 drop(locked_model);
4562
4563 // verify that we're correctly tracking incoming references to the unit element from the reference base
4564 let refs_to_unit = model.get_references_to(&unit_elem.path().unwrap());
4565 assert_eq!(refs_to_unit.len(), 1);
4566 let reference_elem = refs_to_unit[0].upgrade().unwrap();
4567 assert_eq!(reference_elem.element_name(), ElementName::UnitRef);
4568 assert!(reference_elem.attribute_value(AttributeName::Base).is_some());
4569
4570 // verify that we can navigate from the reference element to the target unit element
4571 let target = reference_elem.get_reference_target().unwrap();
4572 assert_eq!(target, unit_elem);
4573 }
4574
4575 #[test]
4576 fn modify_relative_ref() {
4577 const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
4578<AUTOSAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autosar.org/schema/r4.0" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00048.xsd">
4579 <AR-PACKAGES>
4580 <AR-PACKAGE>
4581 <SHORT-NAME>First</SHORT-NAME>
4582 <AR-PACKAGES>
4583 <AR-PACKAGE>
4584 <SHORT-NAME>Second</SHORT-NAME>
4585 <ELEMENTS>
4586 <ECU-INSTANCE>
4587 <SHORT-NAME>Ecu</SHORT-NAME>
4588 </ECU-INSTANCE>
4589 </ELEMENTS>
4590 </AR-PACKAGE>
4591 </AR-PACKAGES>
4592 </AR-PACKAGE>
4593 <AR-PACKAGE>
4594 <SHORT-NAME>Ref</SHORT-NAME>
4595 <REFERENCE-BASES>
4596 <REFERENCE-BASE>
4597 <SHORT-LABEL>First</SHORT-LABEL>
4598 <PACKAGE-REF DEST="AR-PACKAGE">/First</PACKAGE-REF>
4599 </REFERENCE-BASE>
4600 <REFERENCE-BASE>
4601 <SHORT-LABEL>Second</SHORT-LABEL>
4602 <PACKAGE-REF DEST="AR-PACKAGE">/First/Second</PACKAGE-REF>
4603 </REFERENCE-BASE>
4604 </REFERENCE-BASES>
4605 <ELEMENTS>
4606 <SYSTEM>
4607 <SHORT-NAME>System</SHORT-NAME>
4608 <FIBEX-ELEMENTS>
4609 <FIBEX-ELEMENT-REF-CONDITIONAL>
4610 <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="First">Second/Ecu</FIBEX-ELEMENT-REF>
4611 </FIBEX-ELEMENT-REF-CONDITIONAL>
4612 </FIBEX-ELEMENTS>
4613 </SYSTEM>
4614 </ELEMENTS>
4615 </AR-PACKAGE>
4616 </AR-PACKAGES>
4617</AUTOSAR>"#.as_bytes();
4618 let model = AutosarModel::new();
4619 let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
4620
4621 let ref_elem = model
4622 .get_element_by_path("/Ref/System")
4623 .unwrap()
4624 .get_sub_element(ElementName::FibexElements)
4625 .unwrap()
4626 .get_sub_element(ElementName::FibexElementRefConditional)
4627 .unwrap()
4628 .get_sub_element(ElementName::FibexElementRef)
4629 .unwrap();
4630 let ecu_elem = ref_elem.get_reference_target().unwrap();
4631 assert_eq!(ecu_elem.element_name(), ElementName::EcuInstance);
4632 assert_eq!(model.get_references_to(&ecu_elem.path().unwrap()).len(), 1);
4633
4634 ref_elem.set_relative_reference_target(&ecu_elem, "Second").unwrap();
4635 assert_eq!(ref_elem.character_data().unwrap().string_value().unwrap(), "Ecu");
4636 assert_eq!(model.get_references_to(&ecu_elem.path().unwrap()).len(), 1);
4637 }
4638
4639 #[test]
4640 fn create_modify_delete_ref_bases() {
4641 let model = AutosarModel::new();
4642 model.create_file("test", AutosarVersion::LATEST).unwrap();
4643 let el_autosar = model.root_element();
4644 let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4645 let el_ar_package = el_ar_packages
4646 .create_named_sub_element(ElementName::ArPackage, "Pkg")
4647 .unwrap();
4648 let el_ar_package2 = el_ar_packages
4649 .create_named_sub_element(ElementName::ArPackage, "Pkg2")
4650 .unwrap();
4651 let el_reference_bases = el_ar_package.create_sub_element(ElementName::ReferenceBases).unwrap();
4652 let el_reference_base = el_reference_bases
4653 .create_sub_element(ElementName::ReferenceBase)
4654 .unwrap();
4655 // no cached reference base, because label and packageref are missing
4656 assert_eq!(model.0.read().reference_bases.len(), 0);
4657 let el_short_label = el_reference_base.create_sub_element(ElementName::ShortLabel).unwrap();
4658 el_short_label.set_character_data("Units").unwrap();
4659 // no cached reference base, because packageref is missing
4660 assert_eq!(model.0.read().reference_bases.len(), 0);
4661 let el_package_ref = el_reference_base.create_sub_element(ElementName::PackageRef).unwrap();
4662 el_package_ref.set_reference_target(&el_ar_package2).unwrap();
4663 // cached reference base should be created now
4664 assert_eq!(model.0.read().reference_bases.len(), 1);
4665
4666 el_short_label.set_character_data("Modified").unwrap();
4667 // cached reference base should be updated with the new label
4668 let locked_model = model.0.read();
4669 let (rb, rb_infos) = &locked_model.reference_bases.iter().next().unwrap();
4670 assert_eq!(*rb, "Modified");
4671 assert_eq!(rb_infos[0].package_ref, "/Pkg2");
4672 assert_eq!(rb_infos.len(), 1);
4673 drop(locked_model);
4674
4675 el_package_ref.set_reference_target(&el_ar_package).unwrap();
4676 // cached reference base should be updated with the new package ref
4677 let locked_model = model.0.read();
4678 let (rb, rb_infos) = &locked_model.reference_bases.iter().next().unwrap();
4679 assert_eq!(*rb, "Modified");
4680 assert_eq!(rb_infos[0].package_ref, "/Pkg");
4681 drop(locked_model);
4682
4683 el_reference_base.remove_sub_element(el_package_ref).unwrap();
4684 // cached reference base should be removed again
4685 assert_eq!(model.0.read().reference_bases.len(), 0);
4686 // create it again
4687 let el_package_ref = el_reference_base.create_sub_element(ElementName::PackageRef).unwrap();
4688 el_package_ref.set_reference_target(&el_ar_package2).unwrap();
4689 assert_eq!(model.0.read().reference_bases.len(), 1);
4690 // remove the SHORT-LABEL
4691 el_reference_base.remove_sub_element(el_short_label).unwrap();
4692 // cached reference base should be removed again, because the label is missing
4693 assert_eq!(model.0.read().reference_bases.len(), 0);
4694 let el_short_label = el_reference_base.create_sub_element(ElementName::ShortLabel).unwrap();
4695 el_short_label.set_character_data("Units").unwrap();
4696 // cached reference base should be created again
4697 assert_eq!(model.0.read().reference_bases.len(), 1);
4698
4699 el_reference_bases.remove_sub_element(el_reference_base).unwrap();
4700 // cached reference base should be removed again, because the reference base itself is removed
4701 assert_eq!(model.0.read().reference_bases.len(), 0);
4702 }
4703
4704 #[test]
4705 fn rename_reference_base() {
4706 let model = AutosarModel::new();
4707 model.create_file("test", AutosarVersion::LATEST).unwrap();
4708
4709 let el_ar_packages = model
4710 .root_element()
4711 .create_sub_element(ElementName::ArPackages)
4712 .unwrap();
4713 let owner1 = el_ar_packages
4714 .create_named_sub_element(ElementName::ArPackage, "Owner1")
4715 .unwrap();
4716 let owner2 = el_ar_packages
4717 .create_named_sub_element(ElementName::ArPackage, "Owner2")
4718 .unwrap();
4719 let target1 = el_ar_packages
4720 .create_named_sub_element(ElementName::ArPackage, "Target1")
4721 .unwrap();
4722 let target2 = el_ar_packages
4723 .create_named_sub_element(ElementName::ArPackage, "Target2")
4724 .unwrap();
4725
4726 let short_label_1 = owner1
4727 .create_sub_element(ElementName::ReferenceBases)
4728 .and_then(|e| e.create_sub_element(ElementName::ReferenceBase))
4729 .and_then(|e| {
4730 e.create_sub_element(ElementName::ShortLabel)
4731 .and_then(|l| l.set_character_data("Shared").map(|_| l))
4732 })
4733 .unwrap();
4734 let owner1_reference_base = owner1
4735 .get_sub_element(ElementName::ReferenceBases)
4736 .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
4737 .unwrap();
4738 owner1_reference_base
4739 .create_sub_element(ElementName::PackageRef)
4740 .and_then(|p| p.set_reference_target(&target1))
4741 .unwrap();
4742
4743 owner2
4744 .create_sub_element(ElementName::ReferenceBases)
4745 .and_then(|e| e.create_sub_element(ElementName::ReferenceBase))
4746 .and_then(|e| {
4747 e.create_sub_element(ElementName::ShortLabel)
4748 .and_then(|l| l.set_character_data("Shared").map(|_| l))
4749 })
4750 .unwrap();
4751 let owner2_reference_base = owner2
4752 .get_sub_element(ElementName::ReferenceBases)
4753 .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
4754 .unwrap();
4755 owner2_reference_base
4756 .create_sub_element(ElementName::PackageRef)
4757 .and_then(|p| p.set_reference_target(&target2))
4758 .unwrap();
4759
4760 assert_eq!(model.0.read().reference_bases.get("Shared").unwrap().len(), 2);
4761
4762 short_label_1.set_character_data("Renamed").unwrap();
4763
4764 let data = model.0.read();
4765 let shared_entries = data.reference_bases.get("Shared").unwrap();
4766 assert_eq!(shared_entries.len(), 1);
4767 assert_eq!(shared_entries[0].owner_package_path, "/Owner2");
4768 assert_eq!(shared_entries[0].package_ref, "/Target2");
4769
4770 let renamed_entries = data.reference_bases.get("Renamed").unwrap();
4771 assert_eq!(renamed_entries.len(), 1);
4772 assert_eq!(renamed_entries[0].owner_package_path, "/Owner1");
4773 assert_eq!(renamed_entries[0].package_ref, "/Target1");
4774 }
4775
4776 #[test]
4777 fn changing_base_attribute() {
4778 let model = AutosarModel::new();
4779 model.create_file("test", AutosarVersion::LATEST).unwrap();
4780
4781 let el_ar_packages = model
4782 .root_element()
4783 .create_sub_element(ElementName::ArPackages)
4784 .unwrap();
4785 let base_a = el_ar_packages
4786 .create_named_sub_element(ElementName::ArPackage, "BaseA")
4787 .unwrap();
4788 let base_b = el_ar_packages
4789 .create_named_sub_element(ElementName::ArPackage, "BaseB")
4790 .unwrap();
4791 let ref_pkg = el_ar_packages
4792 .create_named_sub_element(ElementName::ArPackage, "RefPkg")
4793 .unwrap();
4794
4795 let ecu_a = base_a
4796 .create_sub_element(ElementName::Elements)
4797 .and_then(|e| e.create_named_sub_element(ElementName::EcuInstance, "Ecu"))
4798 .unwrap();
4799 let _ecu_b = base_b
4800 .create_sub_element(ElementName::Elements)
4801 .and_then(|e| e.create_named_sub_element(ElementName::EcuInstance, "Ecu"))
4802 .unwrap();
4803
4804 let ref_bases = ref_pkg.create_sub_element(ElementName::ReferenceBases).unwrap();
4805 let rb_a = ref_bases.create_sub_element(ElementName::ReferenceBase).unwrap();
4806 rb_a.create_sub_element(ElementName::ShortLabel)
4807 .and_then(|e| e.set_character_data("A"))
4808 .unwrap();
4809 rb_a.create_sub_element(ElementName::PackageRef)
4810 .and_then(|e| e.set_reference_target(&base_a))
4811 .unwrap();
4812
4813 let rb_b = ref_bases.create_sub_element(ElementName::ReferenceBase).unwrap();
4814 rb_b.create_sub_element(ElementName::ShortLabel)
4815 .and_then(|e| e.set_character_data("B"))
4816 .unwrap();
4817 rb_b.create_sub_element(ElementName::PackageRef)
4818 .and_then(|e| e.set_reference_target(&base_b))
4819 .unwrap();
4820
4821 let ref_elem = ref_pkg
4822 .create_sub_element(ElementName::Elements)
4823 .and_then(|e| e.create_named_sub_element(ElementName::System, "Sys"))
4824 .and_then(|e| e.create_sub_element(ElementName::FibexElements))
4825 .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
4826 .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))
4827 .unwrap();
4828 ref_elem.set_relative_reference_target(&ecu_a, "A").unwrap();
4829
4830 ref_elem.set_attribute_string(AttributeName::Base, "B").unwrap();
4831
4832 let origins = model.0.read().relative_reference_origins.get("Ecu").unwrap().clone();
4833 let ref_origin = ref_elem.downgrade();
4834 assert!(origins.iter().any(|(elem, base)| *elem == ref_origin && base == "B"));
4835 }
4836}