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