autosar-data 0.21.2

read, write and modify Autosar arxml data
Documentation
use std::collections::HashSet;

use autosar_data::*;
use autosar_data_specification::CharacterDataSpec;

static VERSIONS: [AutosarVersion; 22] = [
    AutosarVersion::Autosar_4_0_1,
    AutosarVersion::Autosar_4_0_2,
    AutosarVersion::Autosar_4_0_3,
    AutosarVersion::Autosar_4_1_1,
    AutosarVersion::Autosar_4_1_2,
    AutosarVersion::Autosar_4_1_3,
    AutosarVersion::Autosar_4_2_1,
    AutosarVersion::Autosar_4_2_2,
    AutosarVersion::Autosar_4_3_0,
    AutosarVersion::Autosar_00042,
    AutosarVersion::Autosar_00043,
    AutosarVersion::Autosar_00044,
    AutosarVersion::Autosar_00045,
    AutosarVersion::Autosar_00046,
    AutosarVersion::Autosar_00047,
    AutosarVersion::Autosar_00048,
    AutosarVersion::Autosar_00049,
    AutosarVersion::Autosar_00050,
    AutosarVersion::Autosar_00051,
    AutosarVersion::Autosar_00052,
    AutosarVersion::Autosar_00053,
    AutosarVersion::Autosar_00054,
];

fn main() {
    for version in VERSIONS {
        let filename = format!("{}.arxml", version.filename());
        let mut completed: HashSet<(ElementName, ElementName)> = HashSet::new();

        println!("Generating {filename} for \'{}\'", version.describe());

        let model = AutosarModel::new();
        let arxml_file = model.create_file(&filename, version).unwrap();
        let autosar_element = model.root_element();

        let mut counter = 1;
        create_sub_elements(&autosar_element, &mut counter, &mut completed, version);

        let _ = autosar_element.set_attribute_string(AttributeName::xmlns, "http://autosar.org/schema/r4.0");
        let _ =
            autosar_element.set_attribute_string(AttributeName::xmlnsXsi, "http://www.w3.org/2001/XMLSchema-instance");
        let _ = autosar_element.set_attribute_string(
            AttributeName::xsiSchemalocation,
            &format!("http://autosar.org/schema/r4.0 {}", version.filename()),
        );

        let text = arxml_file.serialize().unwrap();
        std::fs::write(&filename, text).unwrap();
    }
}

fn create_sub_elements(
    elem: &Element,
    counter: &mut usize,
    completed: &mut HashSet<(ElementName, ElementName)>,
    version: AutosarVersion,
) -> (bool, bool) {
    create_value(elem, version);
    create_attributes(elem, version);
    let elem_name = elem.element_name();
    let mut any_created = false;
    let mut element_complete = true;
    for ValidSubElementInfo {
        element_name: se_name,
        is_named,
        ..
    } in elem.list_valid_sub_elements()
    {
        if completed.get(&(elem_name, se_name)).is_none() {
            match create_sub_element_helper(elem, se_name, is_named, counter) {
                Ok(sub_elem) => {
                    any_created = true;
                    if is_named {
                        completed.insert((se_name, ElementName::ShortName));
                    }
                    completed.insert((elem_name, se_name));

                    let (se_complete, _) = create_sub_elements(&sub_elem, counter, completed, version);
                    if !se_complete {
                        completed.remove(&(elem_name, se_name));
                        while let Ok(sub_elem) = create_sub_element_helper(elem, se_name, is_named, counter) {
                            let (se_complete, se_any_created) =
                                create_sub_elements(&sub_elem, counter, completed, version);
                            if se_complete {
                                break;
                            }
                            if !se_any_created {
                                element_complete = false;
                                let _ = elem.remove_sub_element(sub_elem);
                                break;
                            }
                        }
                    }
                }
                Err(_) => {
                    element_complete = false;
                }
            }
        }
    }

    (element_complete, any_created)
}

fn create_sub_element_helper(
    elem: &Element,
    se_name: ElementName,
    named: bool,
    counter: &mut usize,
) -> Result<Element, AutosarDataError> {
    if named {
        let item_name_raw = format!("{}_{counter}", se_name.to_str().to_ascii_lowercase());
        let item_name: String = item_name_raw.chars().map(|c| if c == '-' { '_' } else { c }).collect();
        *counter += 1;
        elem.create_named_sub_element(se_name, &item_name)
    } else {
        elem.create_sub_element(se_name)
    }
}

fn create_value(elem: &Element, version: AutosarVersion) {
    if elem.content_type() == ContentType::CharacterData {
        let spec = elem.element_type().chardata_spec().unwrap();
        let cdata = make_cdata(spec, version);
        elem.set_character_data(cdata.clone())
            .unwrap_or_else(|err| panic!("error {err} while setting {cdata} with spec {spec:?}",));
    } else if elem.content_type() == ContentType::Mixed {
        let _ = elem.insert_character_content_item("xXxXx", 0);
    }
}

fn create_attributes(elem: &Element, version: AutosarVersion) {
    for (attr_name, spec, required) in elem.element_type().attribute_spec_iter() {
        if required {
            let _ = elem.set_attribute(attr_name, make_cdata(spec, version));
        }
    }
}

fn make_cdata(spec: &CharacterDataSpec, version: AutosarVersion) -> CharacterData {
    match spec {
        autosar_data_specification::CharacterDataSpec::Enum { items } => {
            let valid_item = items
                .iter()
                .find(|(_, ver_mask)| version.compatible(*ver_mask))
                .unwrap_or_else(|| panic!("found no valid enum value from {items:?} in version {version}"));
            CharacterData::Enum(valid_item.0)
        }
        autosar_data_specification::CharacterDataSpec::Pattern { regex, .. } => match *regex {
            r"0x[0-9a-z]*" => CharacterData::String("0xdeadbeef".to_string()),
            r"0[xX][0-9a-fA-F]+" => CharacterData::String("0xbaadf00d".to_string()),
            r"[1-9][0-9]*|0[xX][0-9a-fA-F]*|0[bB][0-1]+|0[0-7]*|UNSPECIFIED|UNKNOWN|BOOLEAN|PTR" => {
                CharacterData::String("UNSPECIFIED".to_string())
            }
            r"[1-9][0-9]*|0[xX][0-9a-fA-F]+|0[0-7]*|0[bB][0-1]+|ANY|ALL" => CharacterData::String("ALL".to_string()),
            r"[0-9]+|ANY" => CharacterData::String("000".to_string()),
            r"[0-9]+|STRING|ARRAY" => CharacterData::String("ARRAY".to_string()),
            r"0|1|true|false" => CharacterData::String("false".to_string()),
            r"[a-zA-Z_][a-zA-Z0-9_]*" => CharacterData::String("_identifier".to_string()),
            r"[a-zA-Z][a-zA-Z0-9_]*" => CharacterData::String("identifier".to_string()),
            r"([0-9]{4}-[0-9]{2}-[0-9]{2})(T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|([+\-][0-9]{2}:[0-9]{2})))?" => {
                CharacterData::String("2022-01-01T12:00:00Z".to_string())
            }
            r"[a-zA-Z][a-zA-Z0-9-]*" => CharacterData::String("identifier-".to_string()),
            r"[0-9a-zA-Z_\-]+" => CharacterData::String("09AZ_-".to_string()),
            r"%[ \-+#]?[0-9]*(\.[0-9]+)?[bBdiouxXfeEgGcs]" => CharacterData::String("%23.456d".to_string()),
            r"0|[\+\-]?[1-9][0-9]*|0[xX][0-9a-fA-F]+|0[bB][0-1]+|0[0-7]+" => {
                CharacterData::String("0b1010101".to_string())
            }
            r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|ANY" => {
                CharacterData::String("192.168.0.1".to_string())
            }
            r"[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){7,7}|ANY" => {
                CharacterData::String("fe80:0:abcd:1234:0:0:0:1".to_string())
            }
            r"(0[xX][0-9a-fA-F]+)|(0[0-7]+)|(0[bB][0-1]+)|(([+\-]?[1-9][0-9]+(\.[0-9]+)?|[+\-]?[0-9](\.[0-9]+)?)([eE]([+\-]?)[0-9]+)?)|\.0|INF|-INF|NaN" => {
                CharacterData::String("-INF".to_string())
            }
            r"([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}" => CharacterData::String("00:00:00:00:00:00".to_string()),
            r"[a-zA-Z_][a-zA-Z0-9_]*(\[([a-zA-Z_][a-zA-Z0-9_]*|[0-9]+)\])*(\.[a-zA-Z_][a-zA-Z0-9_]*(\[([a-zA-Z_][a-zA-Z0-9_]*|[0-9]+)\])*)*" => {
                CharacterData::String("aabb9_cd[x][y].cde".to_string())
            }
            r"[A-Z][a-zA-Z0-9_]*" => CharacterData::String("Q_9".to_string()),
            r"[1-9][0-9]*" => CharacterData::String("1234567890".to_string()),
            r"0|[\+]?[1-9][0-9]*|0[xX][0-9a-fA-F]+|0[bB][0-1]+|0[0-7]+" => CharacterData::String("123".to_string()),
            r"-?([0-9]+|MAX-TEXT-SIZE|ARRAY-SIZE)" => CharacterData::String("MAX-TEXT-SIZE".to_string()),
            r"/?[a-zA-Z][a-zA-Z0-9_]{0,127}(/[a-zA-Z][a-zA-Z0-9_]{0,127})*" => {
                CharacterData::String("/invalid".to_string())
            }
            r"[0-9]+\.[0-9]+\.[0-9]+([\._;].*)?" => CharacterData::String("0.1.2_something".to_string()),
            r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?" => {
                CharacterData::String("0.0.0-ab-c.0.0+zz-Z".to_string())
            }
            _ => {
                panic!("unknown regex: {regex}");
            }
        },
        autosar_data_specification::CharacterDataSpec::String { .. } => {
            CharacterData::String("lorem ipsum".to_string())
        }
        autosar_data_specification::CharacterDataSpec::UnsignedInteger => CharacterData::UnsignedInteger(42),
        autosar_data_specification::CharacterDataSpec::Float => CharacterData::Float(std::f64::consts::PI),
    }
}