diff --git a/cimgen/cimgen.py b/cimgen/cimgen.py index 67813dd..dfea746 100644 --- a/cimgen/cimgen.py +++ b/cimgen/cimgen.py @@ -2,12 +2,12 @@ import os import textwrap import warnings -import re from time import time import xmltodict from bs4 import BeautifulSoup +logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__) @@ -210,11 +210,12 @@ def __init__(self, rdfsEntry): self.origin_list = [] self.super = rdfsEntry.subClassOf() self.subclasses = [] + self.stereotype = rdfsEntry.stereotype() def attributes(self): return self.attribute_list - def addAttribute(self, attribute): + def add_attribute(self, attribute): self.attribute_list.append(attribute) def is_an_enum_class(self): @@ -227,10 +228,6 @@ def add_enum_instance(self, instance): instance["index"] = len(self.enum_instance_list) self.enum_instance_list.append(instance) - def addAttributes(self, attributes): - for attribute in attributes: - self.attribute_list.append(attribute) - def origins(self): return self.origin_list @@ -249,52 +246,11 @@ def subClasses(self): def setSubClasses(self, classes): self.subclasses = classes - def _simple_float_attribute(attr): - if "dataType" in attr: - return attr["label"] == "value" and attr["dataType"] == "#Float" - return False - - def is_a_float_class(self): - if self.about == "Float": - return True - simple_float = False - for attr in self.attribute_list: - if CIMComponentDefinition._simple_float_attribute(attr): - simple_float = True - for attr in self.attribute_list: - if not CIMComponentDefinition._simple_float_attribute(attr): - simple_float = False - if simple_float: - return True - - candidate_array = {"value": False, "unit": False, "multiplier": False} - optional_attributes = ["denominatorUnit", "denominatorMultiplier"] - for attr in self.attribute_list: - key = attr["label"] - if key in candidate_array: - candidate_array[key] = True - elif key not in optional_attributes: - return False - for key in candidate_array: - if not candidate_array[key]: - return False - return True - - -def get_profile_name(descriptions): - for list_elem in descriptions: - # only for CGMES-Standard - rdfsEntry = RDFSEntry(list_elem) - if rdfsEntry.stereotype() == "Entsoe": - return rdfsEntry.about() - + def is_a_primitive_class(self): + return self.stereotype == "Primitive" -def get_short_profile_name(descriptions): - for list_elem in descriptions: - # only for CGMES-Standard - rdfsEntry = RDFSEntry(list_elem) - if rdfsEntry.label() == "shortName": - return rdfsEntry.fixed() + def is_a_datatype_class(self): + return self.stereotype == "CIMDatatype" def wrap_and_clean(txt: str, width: int = 120, initial_indent="", subsequent_indent=" ") -> str: @@ -318,10 +274,9 @@ def wrap_and_clean(txt: str, width: int = 120, initial_indent="", subsequent_ind ) -short_package_name = {} +long_profile_names = {} package_listed_by_short_name = {} cim_namespace = "" -profiles = {} def _rdfs_entry_types(rdfs_entry: RDFSEntry, version) -> list: @@ -380,27 +335,24 @@ def _add_class(classes_map, rdfs_entry): """ if rdfs_entry.label() in classes_map: logger.error("Class {} already exists".format(rdfs_entry.label())) - if rdfs_entry.label() != "String": - classes_map[rdfs_entry.label()] = CIMComponentDefinition(rdfs_entry) + classes_map[rdfs_entry.label()] = CIMComponentDefinition(rdfs_entry) -def _add_profile_to_packages(profile_name, short_profile_name, profile_uri_list): +def _add_profile_to_packages(profile_name: str, short_profile_name: str, profile_uri_list: list[str]) -> None: """ - Add profile_uris + Add profile_uris and set long profile_name. """ - if profile_name not in profiles and profile_uri_list: - profiles[profile_name] = profile_uri_list - else: - profiles[profile_name].extend(profile_uri_list) - if short_profile_name not in package_listed_by_short_name and profile_uri_list: - package_listed_by_short_name[short_profile_name] = profile_uri_list - else: - package_listed_by_short_name[short_profile_name].extend(profile_uri_list) + uri_list = package_listed_by_short_name.setdefault(short_profile_name, []) + for uri in profile_uri_list: + if uri not in uri_list: + uri_list.append(uri) + long_profile_names[short_profile_name] = profile_name.removesuffix("Version").removesuffix("Profile") def _parse_rdf(input_dic, version): # NOSONAR classes_map = {} profile_name = "" + short_profile_name = "" profile_uri_list = [] attributes = [] enum_instances = [] @@ -424,27 +376,28 @@ def _parse_rdf(input_dic, version): # NOSONAR attributes.append(object_dic) if "rest_non_class_category" in rdfs_entry_types: enum_instances.append(object_dic) - if "profile_name_v2_4" in rdfs_entry_types: - profile_name = rdfsEntry.about() - if "profile_name_v3" in rdfs_entry_types: - profile_name = rdfsEntry.label() - if "short_profile_name_v2_4" in rdfs_entry_types and rdfsEntry.fixed(): - short_profile_name = rdfsEntry.fixed() - if "short_profile_name_v3" in rdfs_entry_types: - short_profile_name = rdfsEntry.keyword() + if not profile_name: + if "profile_name_v2_4" in rdfs_entry_types: + profile_name = rdfsEntry.about() + if "profile_name_v3" in rdfs_entry_types: + profile_name = rdfsEntry.label() + if not short_profile_name: + if "short_profile_name_v2_4" in rdfs_entry_types and rdfsEntry.fixed(): + short_profile_name = rdfsEntry.fixed() + if "short_profile_name_v3" in rdfs_entry_types: + short_profile_name = rdfsEntry.keyword() if "profile_iri_v2_4" in rdfs_entry_types and rdfsEntry.fixed(): profile_uri_list.append(rdfsEntry.fixed()) if "profile_iri_v3" in rdfs_entry_types: profile_uri_list.append(rdfsEntry.version_iri()) - short_package_name[profile_name] = short_profile_name - package_listed_by_short_name[short_profile_name] = [] _add_profile_to_packages(profile_name, short_profile_name, profile_uri_list) + # Add attributes to corresponding class for attribute in attributes: clarse = attribute["domain"] if clarse and classes_map[clarse]: - classes_map[clarse].addAttribute(attribute) + classes_map[clarse].add_attribute(attribute) else: logger.info("Class {} for attribute {} not found.".format(clarse, attribute)) @@ -472,13 +425,14 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): for class_name in elem_dict.keys(): class_details = { - "attributes": _find_multiple_attributes(elem_dict[class_name].attributes()), + "attributes": elem_dict[class_name].attributes(), "class_location": lang_pack.get_class_location(class_name, elem_dict, version), "class_name": class_name, "class_origin": elem_dict[class_name].origins(), "enum_instances": elem_dict[class_name].enum_instances(), "is_an_enum_class": elem_dict[class_name].is_an_enum_class(), - "is_a_float_class": elem_dict[class_name].is_a_float_class(), + "is_a_primitive_class": elem_dict[class_name].is_a_primitive_class(), + "is_a_datatype_class": elem_dict[class_name].is_a_datatype_class(), "langPack": lang_pack, "sub_class_of": elem_dict[class_name].superClass(), "sub_classes": elem_dict[class_name].subClasses(), @@ -506,12 +460,12 @@ def _write_python_files(elem_dict, lang_pack, output_path, version): subsequent_indent=" " * 6, ) attribute_class = _get_attribute_class(attribute) - is_an_enum_class = attribute_class in elem_dict and elem_dict[attribute_class].is_an_enum_class() - attribute_type = _get_attribute_type(attribute, is_an_enum_class) + attribute_type = _get_attribute_type(attribute, elem_dict[attribute_class]) attribute["is_class_attribute"] = _get_bool_string(attribute_type == "class") attribute["is_enum_attribute"] = _get_bool_string(attribute_type == "enum") attribute["is_list_attribute"] = _get_bool_string(attribute_type == "list") attribute["is_primitive_attribute"] = _get_bool_string(attribute_type == "primitive") + attribute["is_datatype_attribute"] = _get_bool_string(attribute_type == "datatype") attribute["attribute_class"] = attribute_class class_details["attributes"].sort(key=lambda d: d["label"]) @@ -551,20 +505,6 @@ def _write_files(class_details, output_path, version): class_details["langPack"].run_template(output_path, class_details) -# Find multiple entries for the same attribute -def _find_multiple_attributes(attributes_array): - merged_attributes = [] - for elem in attributes_array: - found = False - for i in range(len(merged_attributes)): - if elem["label"] == merged_attributes[i]["label"]: - found = True - break - if found is False: - merged_attributes.append(elem) - return merged_attributes - - # If multiple CGMES schema files for one profile are read, e.g. Equipment Core and Equipment Core Short Circuit # this function merges these into one profile, e.g. Equipment, after this function only one dictionary entry for each # profile exists. The profiles_array contains one entry for each CGMES schema file which was read. @@ -573,21 +513,21 @@ def _merge_profiles(profiles_array): # Iterate through array elements for elem_dict in profiles_array: # Iterate over profile names - for profile_key in elem_dict.keys(): - if profile_key in profiles_dict.keys(): - # Iterate over classes and check for multiple class definitions - for class_key in elem_dict[profile_key]: - if class_key in profiles_dict[profile_key].keys(): - # If class already exists in packageDict add attributes to attributes array - if len(elem_dict[profile_key][class_key].attributes()) > 0: - attributes_array = elem_dict[profile_key][class_key].attributes() - profiles_dict[profile_key][class_key].addAttributes(attributes_array) - # If class is not in packageDict, create entry - else: - profiles_dict[profile_key][class_key] = elem_dict[profile_key][class_key] - # If package name not in packageDict create entry - else: - profiles_dict[profile_key] = elem_dict[profile_key] + for profile_key, new_class_dict in elem_dict.items(): + class_dict = profiles_dict.setdefault(profile_key, {}) + # Iterate over classes and check for multiple class definitions + for class_key, new_class_infos in new_class_dict.items(): + if class_key in class_dict: + class_infos = class_dict[class_key] + for new_attr in new_class_infos.attributes(): + # Iterate over attributes and check for multiple attribute definitions + for attr in class_infos.attributes(): + if new_attr["label"] == attr["label"]: + break + else: + class_infos.add_attribute(new_attr) + else: + class_dict[class_key] = new_class_infos return profiles_dict @@ -596,63 +536,37 @@ def _merge_profiles(profiles_array): # the possibleProfileList used for the serialization. def _merge_classes(profiles_dict): class_dict = {} - # Iterate over profiles - for package_key in profiles_dict.keys(): - # get short name of the profile - short_name = "" - if package_key in short_package_name: - short_name = short_package_name[package_key] - else: - short_name = package_key - + for profile_key, new_class_dict in profiles_dict.items(): + origin = {"origin": profile_key} # iterate over classes in the current profile - for class_key in profiles_dict[package_key]: - # class already defined? - if class_key not in class_dict: - # store class and class origin - class_dict[class_key] = profiles_dict[package_key][class_key] - class_dict[class_key].addOrigin({"origin": short_name}) - for attr in class_dict[class_key].attributes(): - # store origin of the attributes - attr["attr_origin"] = [{"origin": short_name}] - else: + for class_key, new_class_infos in new_class_dict.items(): + if class_key in class_dict: + class_infos = class_dict[class_key] # some inheritance information is stored only in one of the packages. Therefore it has to be checked # if the subClassOf attribute is set. See for example TopologicalNode definitions in SV and TP. - if not class_dict[class_key].superClass(): - if profiles_dict[package_key][class_key].superClass(): - class_dict[class_key].super = profiles_dict[package_key][class_key].superClass() - - # check if profile is already stored in class origin list - multiple_origin = False - for origin in class_dict[class_key].origins(): - if short_name == origin["origin"]: - # origin already stored - multiple_origin = True - break - if not multiple_origin: - class_dict[class_key].addOrigin({"origin": short_name}) - - for attr in profiles_dict[package_key][class_key].attributes(): - # check if attribute is already in attributes list - multiple_attr = False - for attr_set in class_dict[class_key].attributes(): - if attr["label"] == attr_set["label"]: + if not class_infos.superClass(): + class_infos.super = new_class_infos.superClass() + if origin not in class_infos.origins(): + class_infos.addOrigin(origin) + for new_attr in new_class_infos.attributes(): + for attr in class_infos.attributes(): + if attr["label"] == new_attr["label"]: # attribute already in attributes list, check if origin is new - multiple_attr = True - for origin in attr_set["attr_origin"]: - multiple_attr_origin = False - if origin["origin"] == short_name: - multiple_attr_origin = True - break - if not multiple_attr_origin: - # new origin - attr_set["attr_origin"].append({"origin": short_name}) + origin_list = attr["attr_origin"] + if origin not in origin_list: + origin_list.append(origin) break - if not multiple_attr: + else: # new attribute - attr["attr_origin"] = [{"origin": short_name}] - class_dict[class_key].addAttribute(attr) + new_attr["attr_origin"] = [origin] + class_infos.add_attribute(new_attr) + else: + # store new class and origin + new_class_infos.addOrigin(origin) + for attr in new_class_infos.attributes(): + attr["attr_origin"] = [origin] + class_dict[class_key] = new_class_infos return class_dict @@ -720,7 +634,7 @@ def cim_generate(directory, output_path, version, lang_pack): superClass = class_dict_with_origins[superClassName] superClass.addSubClass(className) else: - print("No match for superClass in dict: :", superClassName) + logger.error("No match for superClass in dict: %s", superClassName) # recursively add the subclasses of subclasses addSubClassesOfSubClasses(class_dict_with_origins) @@ -740,32 +654,13 @@ def _get_profile_details(cgmes_profile_uris): profile_info = { "index": index, "short_name": profile, - "long_name": _extract_profile_long_name(cgmes_profile_uris[profile]), + "long_name": long_profile_names[profile], "uris": [{"uri": uri} for uri in cgmes_profile_uris[profile]], } profile_details.append(profile_info) return profile_details -def _extract_profile_long_name(profile_uris): - # Extract name from uri, e.g. "Topology" from "http://iec.ch/TC57/2013/61970-456/Topology/4" - # Examples of other possible uris: "http://entsoe.eu/CIM/Topology/4/1", "http://iec.ch/TC57/ns/CIM/Topology-EU/3.0" - # If more than one uri given, extract common part (e.g. "Equipment" from "EquipmentCore" and "EquipmentOperation") - long_name = "" - for uri in profile_uris: - match = re.search(r"/([^/-]*)(-[^/]*)?(/\d+)?/[\d.]+?$", uri) - if match: - name = match.group(1) - if long_name: - for idx in range(1, len(long_name)): - if idx >= len(name) or long_name[idx] != name[idx]: - long_name = long_name[:idx] - break - else: - long_name = name - return long_name - - def _get_sorted_profile_keys(profile_key_list): """Sort profiles alphabetically, but "EQ" to the first place. @@ -806,7 +701,7 @@ def _get_recommended_class_profiles(elem_dict): profile_count_map = {} name = class_name while name: - for attribute in _find_multiple_attributes(elem_dict[name].attributes()): + for attribute in elem_dict[name].attributes(): profiles = [origin["origin"] for origin in attribute["attr_origin"]] ambiguous_profile = len(profiles) > 1 for profile in profiles: @@ -835,28 +730,21 @@ def _get_attribute_class(attribute: dict) -> str: return _get_rid_of_hash(name) -def _get_attribute_type(attribute: dict, is_an_enum_class: bool) -> str: - """Get the type of an attribute: "class", "enum", "list", or "primitive". +def _get_attribute_type(attribute: dict, class_infos: CIMComponentDefinition) -> str: + """Get the type of an attribute: "class", "datatype", "enum", "list", or "primitive". :param attribute: Dictionary with information about an attribute of a class. - :param is_an_enum_class: Is this attribute an enumation? + :param class_infos: Information about the attribute class. :return: Type of the attribute. """ - so_far_not_primitive = _get_attribute_class(attribute) in ( - "Date", - "DateTime", - "MonthDay", - "Status", - "StreetAddress", - "StreetDetail", - "TownDetail", - ) attribute_type = "class" - if "dataType" in attribute and not so_far_not_primitive: + if class_infos.is_a_datatype_class(): + attribute_type = "datatype" + elif class_infos.is_a_primitive_class(): attribute_type = "primitive" - elif is_an_enum_class: + elif class_infos.is_an_enum_class(): attribute_type = "enum" - elif attribute.get("multiplicity") in ("M:0..n", "M:1..n"): + elif attribute.get("multiplicity") in ("M:0..n", "M:0..2", "M:1..n", "M:2..n"): attribute_type = "list" return attribute_type diff --git a/cimgen/languages/cpp/lang_pack.py b/cimgen/languages/cpp/lang_pack.py index 09d73a9..f4bc29b 100644 --- a/cimgen/languages/cpp/lang_pack.py +++ b/cimgen/languages/cpp/lang_pack.py @@ -39,6 +39,10 @@ def setup(output_path: str, cgmes_profile_details: list, cim_namespace: str): # {"filename": "cpp_enum_header_template.mustache", "ext": ".hpp"}, {"filename": "cpp_enum_object_template.mustache", "ext": ".cpp"}, ] +string_template_files = [ + {"filename": "cpp_string_header_template.mustache", "ext": ".hpp"}, + {"filename": "cpp_string_object_template.mustache", "ext": ".cpp"}, +] def get_class_location(class_name, class_map, version): # NOSONAR @@ -56,18 +60,16 @@ def get_class_location(class_name, class_map, version): # NOSONAR # This is the function that runs the template. def run_template(output_path, class_details): - if class_details["is_a_float_class"]: + if class_details["is_a_datatype_class"] or class_details["class_name"] in ("Float", "Decimal"): templates = float_template_files elif class_details["is_an_enum_class"]: templates = enum_template_files + elif class_details["is_a_primitive_class"]: + templates = string_template_files else: templates = template_files - if ( - class_details["class_name"] == "Integer" - or class_details["class_name"] == "Boolean" - or class_details["class_name"] == "Date" - ): + if class_details["class_name"] in ("Integer", "Boolean"): # These classes are defined already # We have to implement operators for them return @@ -108,12 +110,12 @@ def label(text, render): def insert_assign_fn(text, render): attribute_txt = render(text) attribute_json = eval(attribute_txt) - if not (attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]): + if not _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label = attribute_json["label"] class_name = attribute_json["domain"] return ( - 'assign_map.insert(std::make_pair(std::string("cim:' + ' assign_map.insert(std::make_pair(std::string("cim:' + class_name + "." + label @@ -128,12 +130,12 @@ def insert_assign_fn(text, render): def insert_class_assign_fn(text, render): attribute_txt = render(text) attribute_json = eval(attribute_txt) - if attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label = attribute_json["label"] class_name = attribute_json["domain"] return ( - 'assign_map.insert(std::make_pair(std::string("cim:' + ' assign_map.insert(std::make_pair(std::string("cim:' + class_name + "." + label @@ -151,13 +153,13 @@ def create_nullptr_assigns(text, render): return "" else: attributes_json = eval(attributes_txt) - nullptr_init_string = ": " + nullptr_init_string = "" for attribute in attributes_json: if attribute["is_class_attribute"]: nullptr_init_string += "LABEL(nullptr), ".replace("LABEL", attribute["label"]) if len(nullptr_init_string) > 2: - return nullptr_init_string[:-2] + return " : " + nullptr_init_string[:-2] else: return "" @@ -169,37 +171,75 @@ def create_class_assign(text, render): attribute_json = eval(attribute_txt) assign = "" attribute_class = attribute_json["attribute_class"] - if attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" if attribute_json["is_list_attribute"]: - assign = ( - """ -bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) { - if(OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1)) { - if(dynamic_cast(BaseClass_ptr2) != nullptr) { - element->LABEL.push_back(dynamic_cast(BaseClass_ptr2)); + if "inverseRole" in attribute_json: + inverse = attribute_json["inverseRole"].split(".") + assign = ( + """ +bool assign_INVERSEC_INVERSEL(BaseClass*, BaseClass*); +bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) +{ + OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1); + ATTRIBUTE_CLASS* element2 = dynamic_cast(BaseClass_ptr2); + if (element != nullptr && element2 != nullptr) + { + if (std::find(element->LABEL.begin(), element->LABEL.end(), element2) == element->LABEL.end()) + { + element->LABEL.push_back(element2); + return assign_INVERSEC_INVERSEL(BaseClass_ptr2, BaseClass_ptr1); + } + return true; + } + return false; +}""".replace( # noqa: E101,W191 + "OBJECT_CLASS", attribute_json["domain"] + ) + .replace("ATTRIBUTE_CLASS", attribute_class) + .replace("LABEL", attribute_json["label"]) + .replace("INVERSEC", inverse[0]) + .replace("INVERSEL", inverse[1]) + ) + else: + assign = ( + """ +bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) +{ + if (OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1)) + { + if (dynamic_cast(BaseClass_ptr2) != nullptr) + { + element->LABEL.push_back(dynamic_cast(BaseClass_ptr2)); return true; } } return false; }""".replace( # noqa: E101,W191 - "OBJECT_CLASS", attribute_json["domain"] + "OBJECT_CLASS", attribute_json["domain"] + ) + .replace("ATTRIBUTE_CLASS", attribute_class) + .replace("LABEL", attribute_json["label"]) ) - .replace("ATTRIBUTE_CLASS", attribute_class) - .replace("LABEL", attribute_json["label"]) - ) - elif "inverseRole" in attribute_json and attribute_json["is_used"]: + elif "inverseRole" in attribute_json: inverse = attribute_json["inverseRole"].split(".") assign = ( """ bool assign_INVERSEC_INVERSEL(BaseClass*, BaseClass*); -bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) { - if(OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1)) { - element->LABEL = dynamic_cast(BaseClass_ptr2); - if(element->LABEL != nullptr) - return assign_INVERSEC_INVERSEL(BaseClass_ptr2, BaseClass_ptr1); - } - return false; +bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) +{ + OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1); + ATTRIBUTE_CLASS* element2 = dynamic_cast(BaseClass_ptr2); + if (element != nullptr && element2 != nullptr) + { + if (element->LABEL != element2) + { + element->LABEL = element2; + return assign_INVERSEC_INVERSEL(BaseClass_ptr2, BaseClass_ptr1); + } + return true; + } + return false; }""".replace( # noqa: E101,W191 "OBJECT_CLASS", attribute_json["domain"] ) @@ -211,13 +251,17 @@ def create_class_assign(text, render): else: assign = ( """ -bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) { - if(OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1)) { - element->LABEL = dynamic_cast(BaseClass_ptr2); - if(element->LABEL != nullptr) - return true; - } - return false; +bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) +{ + if(OBJECT_CLASS* element = dynamic_cast(BaseClass_ptr1)) + { + element->LABEL = dynamic_cast(BaseClass_ptr2); + if (element->LABEL != nullptr) + { + return true; + } + } + return false; }""".replace( # noqa: E101,W191 "OBJECT_CLASS", attribute_json["domain"] ) @@ -233,37 +277,44 @@ def create_assign(text, render): attribute_json = eval(attribute_txt) assign = "" _class = attribute_json["attribute_class"] - if not (attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]): + if not _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label_without_keyword = attribute_json["label"] if label_without_keyword == "switch": label_without_keyword = "_switch" - if _class != "String": + if ( + attribute_json["is_enum_attribute"] + or attribute_json["is_datatype_attribute"] + or _class in ("Float", "Decimal", "Integer", "Boolean") + ): assign = ( """ -bool assign_CLASS_LABEL(std::stringstream &buffer, BaseClass* BaseClass_ptr1) { - if(CLASS* element = dynamic_cast(BaseClass_ptr1)) { - buffer >> element->LBL_WO_KEYWORD; - if(buffer.fail()) - return false; - else - return true; - } - else - return false; +bool assign_CLASS_LABEL(std::stringstream &buffer, BaseClass* BaseClass_ptr1) +{ + if (CLASS* element = dynamic_cast(BaseClass_ptr1)) + { + buffer >> element->LBL_WO_KEYWORD; + if (buffer.fail()) + return false; + else + return true; + } + return false; }""".replace( # noqa: E101,W191 "CLASS", attribute_json["domain"] ) .replace("LABEL", attribute_json["label"]) .replace("LBL_WO_KEYWORD", label_without_keyword) ) - else: + else: # is_primitive_string_attribute assign = """ -bool assign_CLASS_LABEL(std::stringstream &buffer, BaseClass* BaseClass_ptr1) { - if(CLASS* element = dynamic_cast(BaseClass_ptr1)) { +bool assign_CLASS_LABEL(std::stringstream &buffer, BaseClass* BaseClass_ptr1) +{ + if (CLASS* element = dynamic_cast(BaseClass_ptr1)) + { element->LABEL = buffer.str(); - if(buffer.fail()) + if (buffer.fail()) return false; else return true; @@ -286,7 +337,7 @@ def attribute_decl(text, render): def _attribute_decl(attribute): _class = attribute["attribute_class"] - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): return "CIMPP::" + _class if attribute["is_list_attribute"]: return "std::list" @@ -303,10 +354,10 @@ def _create_attribute_includes(text, render): if jsonStringNoHtmlEsc is not None and jsonStringNoHtmlEsc != "": attributes = json.loads(jsonStringNoHtmlEsc) for attribute in attributes: - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): unique[attribute["attribute_class"]] = True - for clarse in unique: - include_string += '\n#include "' + clarse + '.hpp"' + for clarse in sorted(unique): + include_string += '#include "' + clarse + '.hpp"\n' return include_string @@ -321,8 +372,8 @@ def _create_attribute_class_declarations(text, render): for attribute in attributes: if attribute["is_class_attribute"] or attribute["is_list_attribute"]: unique[attribute["attribute_class"]] = True - for clarse in unique: - include_string += "\nclass " + clarse + ";" + for clarse in sorted(unique): + include_string += " class " + clarse + ";\n" return include_string @@ -353,6 +404,14 @@ def set_default(dataType): return "nullptr" +def _attribute_is_primitive_or_datatype_or_enum(attribute: dict) -> bool: + return _attribute_is_primitive_or_datatype(attribute) or attribute["is_enum_attribute"] + + +def _attribute_is_primitive_or_datatype(attribute: dict) -> bool: + return attribute["is_primitive_attribute"] or attribute["is_datatype_attribute"] + + # The code below this line is used after the main cim_generate phase to generate # two include files. They are called CIMClassList.hpp and IEC61970.hpp, and # contain the list of the class files and the list of define functions that add @@ -368,7 +427,6 @@ def set_default(dataType): "Factory", "Folders", "IEC61970", - "String", "Task", "UnknownType", ] @@ -376,15 +434,15 @@ def set_default(dataType): iec61970_blacklist = ["CIMClassList", "CIMNamespaces", "Folders", "Task", "IEC61970"] -def _is_enum_class(filepath): +def _is_primitive_or_enum_class(filepath): with open(filepath, encoding="utf-8") as f: try: for line in f: - if "enum class" in line: - return True + if "static const BaseClassDefiner declare();" in line: + return False except UnicodeDecodeError as error: print("Warning: UnicodeDecodeError parsing {0}: {1}".format(filepath, error)) - return False + return True def _create_header_include_file(directory, header_include_filename, header, footer, before, after, blacklist): @@ -395,7 +453,7 @@ def _create_header_include_file(directory, header_include_filename, header, foot filepath = os.path.join(directory, filename) basepath, ext = os.path.splitext(filepath) basename = os.path.basename(basepath) - if ext == ".hpp" and not _is_enum_class(filepath) and basename not in blacklist: + if ext == ".hpp" and not _is_primitive_or_enum_class(filepath) and basename not in blacklist: lines.append(before + basename + after) for line in lines: header.append(line) @@ -410,12 +468,18 @@ def resolve_headers(path: str, version: str): # NOSONAR class_list_header = [ "#ifndef CIMCLASSLIST_H\n", "#define CIMCLASSLIST_H\n", - "using namespace CIMPP;\n", + "/*\n", + "Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen\n", + "*/\n", "#include \n", - "static std::list CIMClassList = {\n", + '#include "IEC61970.hpp"\n', + "using namespace CIMPP;\n", + "static std::list CIMClassList =\n", + "{\n", ] class_list_footer = [ - " UnknownType::declare() };\n", + " UnknownType::declare(),\n", + "};\n", "#endif // CIMCLASSLIST_H\n", ] @@ -424,13 +488,23 @@ def resolve_headers(path: str, version: str): # NOSONAR "CIMClassList.hpp", class_list_header, class_list_footer, - " ", + " ", "::declare(),\n", class_blacklist, ) - iec61970_header = ["#ifndef IEC61970_H\n", "#define IEC61970_H\n"] - iec61970_footer = ['#include "UnknownType.hpp"\n', "#endif"] + iec61970_header = [ + "#ifndef IEC61970_H\n", + "#define IEC61970_H\n", + "/*\n", + "Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen\n", + "*/\n", + "\n", + ] + iec61970_footer = [ + '#include "UnknownType.hpp"\n', + "#endif", + ] _create_header_include_file( path, diff --git a/cimgen/languages/cpp/src/CIMContentHandler.cpp b/cimgen/languages/cpp/src/CIMContentHandler.cpp new file mode 100644 index 0000000..036b382 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMContentHandler.cpp @@ -0,0 +1,285 @@ +#include "CIMContentHandler.hpp" + +#include +#include + +#include "CIMFactory.hpp" +#include "assignments.hpp" +#include "CIMExceptions.hpp" + +CIMContentHandler::CIMContentHandler() : Objects(nullptr), RDFMap(nullptr) +{ +} + +CIMContentHandler::~CIMContentHandler() +{ + checkStacksEmpty(); +} + +void CIMContentHandler::checkStacksEmpty() +{ + if (!objectStack.empty()) + { + throw CriticalError("CIMContentHandler: Critical Error: objectStack is not empty!"); + } + if (!tagStack.empty()) + { + std::cerr << "First 5 items on stack: " << std::endl; + int i = 10; + while (!tagStack.empty() && i--) { + std::string name = tagStack.top(); + std::cerr << "{" << name << "}" << std::endl; + tagStack.pop(); + } + throw CriticalError("CIMContentHandler: Critical Error: tagStack is not empty!"); + } +} + +void CIMContentHandler::setObjectsContainer(std::vector *Objects) +{ + this->Objects = Objects; +} + +void CIMContentHandler::setRDFMap(std::unordered_map *RDFMap) +{ + this->RDFMap = RDFMap; +} + +void CIMContentHandler::setDocumentLocator(const LocatorT &locator) +{} + +void CIMContentHandler::startDocument() +{ + if(Objects == nullptr) + { + throw NoObjectsContainer(this); + } + if(RDFMap == nullptr) + { + throw NoRdfMap(this); + } + +} + +void CIMContentHandler::endDocument() +{ +} + +void CIMContentHandler::startPrefixMapping(const std::string &prefix, const std::string &uri) +{} + +void CIMContentHandler::endPrefixMapping(const std::string &prefix) +{} + +void CIMContentHandler::startElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName, const AttributesT &atts) +{ + // Only process tags in cim namespace + if(qName.find("cim:") == std::string::npos) + { + bool isModelDescription = qName.find("md:") != std::string::npos || qName.find("DependentOn:") != std::string::npos || qName.find("createdBy") != std::string::npos; + bool isModel = qName.find("rdf:") != std::string::npos; + + if(!isModelDescription && !isModel) + { + std::cerr << "WARNING: "<< qName << " not in namespace \"cim\"" << std::endl; + } + return; + } + + // Remember last opened tag + tagStack.push(qName); + + // If there is no RDF ID (an XML attribute!) then we don't have a new CIM + // object or RDF relation therefore the XML element will contain a value + // assignment. + if(atts.getLength() == 0) + return; + // If name is a CIM class check if to create a new object + if(CIMFactory::IsCIMClass(qName)) + { + // Get rdf_id + std::string rdf_id = get_rdf_id(atts); + if(rdf_id.empty()) + { + throw NoRdfID(); + } + // check if object already exists + std::unordered_map::iterator it = RDFMap->find(rdf_id); + if(it != RDFMap->end()) // object exists -> push it on the stack + { + objectStack.push(it->second); + } + else // object does not exist -> create object + { + BaseClass* BaseClass_ptr = CIMFactory::CreateNew(qName); + + //Check if created Object is IdentifiedObject and place rdf_id into mRID + if(CIMPP::IdentifiedObject* idOb = dynamic_cast(BaseClass_ptr)) + { + (*idOb).mRID = rdf_id; + } + RDFMap->emplace(rdf_id, BaseClass_ptr); + objectStack.push(BaseClass_ptr); + Objects->push_back(BaseClass_ptr); + } + return; + } + // Create a task if the XML element is no CIM class and contains a RDF ID + std::string rdf_id = get_rdf_resource(atts); + if (!rdf_id.empty()) + { + taskQueue.push_back(Task(objectStack.top(), qName, rdf_id)); + return; + } + // Assign an enum symbol if the rdf id contains a enum symbol + std::string enumSymbol = get_rdf_enum(atts); + if(!enumSymbol.empty()) + { + if(!assign(objectStack.top(), qName, enumSymbol)) + std::cerr << "CIMContentHandler: Error: " << enumSymbol << " can not be assigned" << std::endl; + return; + } + + // Nobody knows what to do + std::cerr << "CIMContentHandler: Error: Nobody knows, the " << qName << " I've seen... *sing*" << std::endl; +} + +void CIMContentHandler::endElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName) +{ + // Only process tags in cim namespace + if(qName.find("cim:") == std::string::npos) + { + return; + } + + // Pop Stacks + if (tagStack.size() == 0) { + std::cerr << "WARNING: Nearly tried to pop empty tag stack for tag: " << qName << std::endl; + } + else { + tagStack.pop(); + } + if(CIMFactory::IsCIMClass(qName)) + { + if (objectStack.size() == 0) { + std::cerr << "WARNING: Nearly tried to pop empty object stack for tag: " << qName << std::endl; + } + else { + objectStack.pop(); + } + //std::cout << "Popped " << name << std::endl; + } +} + +void CIMContentHandler::characters(const std::string &characters) +{ + // Only process tags in "cim" namespace + if(tagStack.empty()) + { + return; + } + if(objectStack.empty()) + { + throw CriticalError("CIMContentHandler: Critical Error: objectStack empty"); + } + +#ifndef DEBUG + assign(objectStack.top(), tagStack.top(), characters); +#else + // Check if the characters only contain whitespace + if(is_only_whitespace(characters)) + { + return; + } + if(!assign(objectStack.top(), tagStack.top(), characters)) + std::cout << "CIMContentHandler: Note: Cannot assign '" << characters << "' to " << tagStack.top() << std::endl; +#endif +} + +void CIMContentHandler::ignorableWhitespace(const std::string &ch) +{} + +void CIMContentHandler::processingInstruction(const std::string &target, const std::string &data) +{} + +void CIMContentHandler::skippedEntity(const std::string &name) +{} + +std::string CIMContentHandler::get_rdf_id(const AttributesT &attributes) +{ + for(int i = 0; i < attributes.getLength(); i++) + { + if(attributes.getQName(i) == "rdf:ID") + return attributes.getValue(i); + if(attributes.getQName(i) == "rdf:about") + return attributes.getValue(i).substr(1); + } + return std::string(); +} + +std::string CIMContentHandler::get_rdf_resource(const AttributesT &attributes) +{ + for(int i = 0; i < attributes.getLength(); i++) + { + if(attributes.getQName(i) == "rdf:resource") + { + if(attributes.getValue(i).at(0) == '#') + { + return attributes.getValue(i).substr(1); + } + } + } + return std::string(); +} + +bool CIMContentHandler::is_only_whitespace(const std::string& characters) +{ + return std::regex_match(characters, std::regex("^[[:space:]]*$")); +} + +std::string CIMContentHandler::get_rdf_enum(const AttributesT &attributes) +{ + for(int i = 0; i < attributes.getLength(); i++) + { + if(attributes.getQName(i) == "rdf:resource") + { + std::regex expr("^http[s]*://[a-zA-Z0-9./_]*CIM-schema-cim[0-9]+#([a-zA-z0-9]*).([a-zA-z0-9]*)"); + std::smatch m; + std::string str = attributes.getValue(i); + if(std::regex_match(str, m, expr)) + { + return std::string(m[1]).append(".").append(m[2]); + } + std::cerr << "CIMContentHandler: Note: rdf:resource does not relate to an object in this file "<< str << std::endl; + } + } + std::cerr << "CIMContentHandler: Note: Attribute contain no rdf:resource" << std::endl; + return std::string(); +} + +bool CIMContentHandler::resolveRDFRelations() +{ + unsigned int unresolved; + unresolved = 0; + size_t taskSize = taskQueue.size(); + std::list::iterator it = taskQueue.begin(); + while(it != taskQueue.end()) + { + if(!it->resolve(RDFMap)) + { + std::cout << "CIMContentHandler: Note: Cannot resolve following RDF relationship: "; + it->print(); + unresolved++; + ++it; + } + else + { + taskQueue.erase(it++); + } + } + std::cout << "CIMContentHandler: Note: " << unresolved << " out of " << taskSize << " tasks remain unresolved!" << std::endl; + if(unresolved > 0) + return false; + else + return true; +} diff --git a/cimgen/languages/cpp/src/CIMContentHandler.hpp b/cimgen/languages/cpp/src/CIMContentHandler.hpp new file mode 100644 index 0000000..df23d00 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMContentHandler.hpp @@ -0,0 +1,57 @@ +#ifndef CIMCONTENTHANDLER_HPP +#define CIMCONTENTHANDLER_HPP + +#include +#include + +#include +#include +#include +#include + +#include "BaseClass.h" +#include "Task.hpp" +#include "Folders.hpp" + +class CIMContentHandler : public Arabica::SAX::ContentHandler +{ +public: + CIMContentHandler(); + ~CIMContentHandler(); + + std::vector *Objects; + std::unordered_map *RDFMap; + void setObjectsContainer(std::vector *Objects); + void setRDFMap(std::unordered_map *RDFMap); + + void checkStacksEmpty(); //Check if tagStack or ObjectStack is empty, throw CriticalError if true + + bool resolveRDFRelations(); // AKA work through task queue + +protected: + void setDocumentLocator(const LocatorT &locator) override; + void startDocument() override; + void endDocument() override; + void startPrefixMapping(const std::string &prefix, const std::string &uri) override; + void endPrefixMapping(const std::string &prefix) override; + void startElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName, const AttributesT &atts) override; + void endElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName) override; + void characters(const std::string &ch) override; + void ignorableWhitespace(const std::string &ch) override; + void processingInstruction(const std::string &target, const std::string &data) override; + void skippedEntity(const std::string &name) override; + + static std::string get_rdf_id(const AttributesT &properties); + static std::string get_rdf_resource(const AttributesT &properties); + static std::string get_rdf_enum(const AttributesT &properties); + static bool is_only_whitespace(const std::string &characters); + +private: + std::stack objectStack; + std::stack tagStack; + std::list taskQueue; +}; + + + +#endif // CIMCONTENTHANDLER_HPP diff --git a/cimgen/languages/cpp/src/CIMExceptions.cpp b/cimgen/languages/cpp/src/CIMExceptions.cpp new file mode 100644 index 0000000..2903b3d --- /dev/null +++ b/cimgen/languages/cpp/src/CIMExceptions.cpp @@ -0,0 +1,56 @@ +#include "CIMExceptions.hpp" + + +CIMException::CIMException() +{ +} + +CriticalError::CriticalError(const std::string& what) : runtime_error(what) +{ +} + +MissingDependencyFile::MissingDependencyFile(const CIMModel* model, const std::string id) + : Model(model), + rdfID(id) +{ + message = "CIMModel: Dependency is missing"; +} + +const char* CIMException::what() const noexcept +{ + return message.c_str(); +} + + + +MissingModelDescription::MissingModelDescription(const ModelDescriptionHandler* desc) + : DescriptionHandler(desc) +{ + message = "ModelDescriptionHandler: modelDescription not set"; +} + + +NoObjectsContainer::NoObjectsContainer(const CIMContentHandler* handler) + : ContentHandler(handler) +{ + message = "CIMContentHandler: Object container not set"; +} + + +NoRdfID::NoRdfID() +{ + message = "CIMContentHandler: Attributes contain no rdf:ID"; +} + + +NoRdfMap::NoRdfMap(const CIMContentHandler* handler) + : ContentHandler(handler) +{ + message = "CIMContentHandler: RDFMap not set"; +} + + +ReadingUninitializedField::ReadingUninitializedField() +{ + message = "Error: Uninitialized Field"; +} diff --git a/cimgen/languages/cpp/src/CIMExceptions.hpp b/cimgen/languages/cpp/src/CIMExceptions.hpp new file mode 100644 index 0000000..bd49150 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMExceptions.hpp @@ -0,0 +1,81 @@ +#ifndef CIMEXCEPTIONS_HPP +#define CIMEXCEPTIONS_HPP + +#include +#include + +class CIMException; +class CIMModel; +class ModelDescriptionHandler; +class CIMContentHandler; + +class CIMException : public std::exception +{ +public: + CIMException(); +protected: + std::string message; + virtual const char* what() const noexcept override; +}; + + +class CriticalError : public std::runtime_error +{ +public: + CriticalError(const std::string& what); +}; + + +class MissingDependencyFile : CIMException +{ +public: + MissingDependencyFile(const CIMModel* model, const std::string id); + + const CIMModel* Model; + const std::string rdfID; +}; + + + +class MissingModelDescription : CIMException +{ +public: + MissingModelDescription(const ModelDescriptionHandler* desc); + const ModelDescriptionHandler* DescriptionHandler; +}; + + + +class NoObjectsContainer : CIMException +{ +public: + NoObjectsContainer(const CIMContentHandler* handler); + const CIMContentHandler* ContentHandler; +}; + + + +class NoRdfID : CIMException +{ +public: + NoRdfID(); +}; + + + +class NoRdfMap : CIMException +{ +public: + NoRdfMap(const CIMContentHandler* handler); + const CIMContentHandler* ContentHandler; +}; + + + +class ReadingUninitializedField : CIMException +{ +public: + ReadingUninitializedField(); +}; + +#endif // CIMEXCEPTIONS_HPP diff --git a/cimgen/languages/cpp/src/CIMFile.cpp b/cimgen/languages/cpp/src/CIMFile.cpp new file mode 100644 index 0000000..9f78627 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMFile.cpp @@ -0,0 +1,54 @@ +#include "CIMFile.hpp" +#include "ModelDescription.hpp" + +#include "ModelDescriptionHandler.hpp" +#include "ModelDescription.hpp" +#include "SAX/InputSource.hpp" +#include "SAX/XMLReader.hpp" + +#include +#include + +CIMFile::CIMFile(std::string path) : path(path), modelDescription(nullptr) +{ +} + +std::string CIMFile::getpath() +{ + return path; +} + +ModelDescription* CIMFile::getModelDescription() +{ + if(modelDescription == nullptr) + { + if(!this->good()) + { + //TODO: file not good, say something + return nullptr; + } + modelDescription = new ModelDescription(); + ModelDescriptionHandler DescriptionHandler; + + DescriptionHandler.setModelDescription(modelDescription); + + Arabica::SAX::XMLReader Reader; + Reader.setContentHandler(DescriptionHandler); + + Arabica::SAX::InputSource source(path); + Reader.parse(source); + } + + return modelDescription; +} + +bool CIMFile::good() +{ + if(path.find(".xml") == std::string::npos) + { + return false; + } + + std::ifstream infile(path); + return infile.good(); +} diff --git a/cimgen/languages/cpp/src/CIMFile.hpp b/cimgen/languages/cpp/src/CIMFile.hpp new file mode 100644 index 0000000..2a54d85 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMFile.hpp @@ -0,0 +1,26 @@ +#ifndef CIMFILE_HPP +#define CIMFILE_HPP + +#include +#include "ModelDescription.hpp" + +/** \brief Class for encapsulating CIM XML files + */ +class CIMFile +{ +public: + CIMFile(std::string path); + + std::string getpath(); + + ModelDescription* getModelDescription(); + + bool good(); + +private: + std::string path; + + ModelDescription* modelDescription;// Additional meta data +}; + +#endif // CIMFILE_HPP diff --git a/cimgen/languages/cpp/src/CIMModel.cpp b/cimgen/languages/cpp/src/CIMModel.cpp new file mode 100644 index 0000000..8f92bc0 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMModel.cpp @@ -0,0 +1,113 @@ +#include "CIMModel.hpp" +#include "CIMContentHandler.hpp" +#include "SAXErrorHandler.hpp" +#include "ModelDescriptionHandler.hpp" +#include "ModelDescription.hpp" +#include "SAX/InputSource.hpp" +#include "SAX/XMLReader.hpp" +#include "CIMExceptions.hpp" + +#include +#include +#include +#include + +CIMModel::CIMModel() : DependencyCheck(true) +{ +} + +CIMModel::~CIMModel() +{ + // Clean up the mess + for(BaseClass* Object : Objects) + delete Object; +} + +bool CIMModel::addCIMFile(CIMFile file) +{ + if(!file.good()) + { + return false; + } + + Files.push_back(file); + + return true; +} + +bool CIMModel::addCIMFile(std::string path) +{ + CIMFile file(path); + + if(!file.good()) + { + return false; + } + + Files.push_back(file); + + return true; +} + +void CIMModel::parseFiles() +{ + // TODO: What happens when run twice?! + CIMContentHandler ContentHandler; + ContentHandler.setObjectsContainer(&Objects); + ContentHandler.setRDFMap(&RDFMap); + + SAXErrorHandler ErrorHandler; + + for(CIMFile& file : Files) //TODO: Suche evtl. mit eigener dependency-liste beschleunigen + { + Arabica::SAX::XMLReader Reader; + Reader.setContentHandler(ContentHandler); + Reader.setErrorHandler(ErrorHandler); + + if(DependencyCheck == true) + { + if(!(file.getModelDescription()->dependencyID).empty()) //TODO: Ueberpruefung besser Implementieren + { + bool depFound; + for(std::string& fileDepID : (file.getModelDescription()->dependencyID)) + { + depFound = 0; + for(CIMFile& fileDep : Files) + { + if(fileDep.getModelDescription()->rdfID == fileDepID) + { + depFound = 1; + break; + } + } + if(!depFound) + { + //throw MissingDependencyFile(this, fileDepID); + std::cerr << "WARNING: Dependency " << fileDepID << " is missing" << std::endl; + } + } + } + } + + + Arabica::SAX::InputSource source(file.getpath()); + Reader.parse(source); + } + + ContentHandler.resolveRDFRelations(); +} + +void CIMModel::setDependencyCheckOn() +{ + DependencyCheck = true; +} + +void CIMModel::setDependencyCheckOff() +{ + DependencyCheck = false; +} + +bool CIMModel::getDependencyCheck() +{ + return DependencyCheck; +} diff --git a/cimgen/languages/cpp/src/CIMModel.hpp b/cimgen/languages/cpp/src/CIMModel.hpp new file mode 100644 index 0000000..3ec13e2 --- /dev/null +++ b/cimgen/languages/cpp/src/CIMModel.hpp @@ -0,0 +1,64 @@ +#ifndef CIMModel_HPP +#define CIMModel_HPP + +#include +#include +#include +#include "CIMFile.hpp" +#include "BaseClass.h" + +/** \brief CIM model handler + * + * This model handler organizes the files of a CIM model. It also gives access + * to model description. + * + * The model owns the CIM objects it parses therefore it will free them when the + * model object is destroyed. + */ +class CIMModel +{ +public: + CIMModel(); + ~CIMModel(); + + /** \brief Add a file to the current model + * + * If the file belongs to another model or the file handle is invalid it will + * return false otherwise true. + */ + bool addCIMFile(CIMFile file); + + /** \brief Add a file to the current model + * + * If the file belongs to another model or the path is invalid it will + * return false otherwise true. + */ + bool addCIMFile(std::string path); + + /** \brief Parse CIM Files + * + * This function parses all the CIM files specified in this model + */ + void parseFiles(); + + /** Container to access all the CIM objects */ + std::vector Objects; + + /** \brief Set dependency check on */ + void setDependencyCheckOn(); + + /** \brief Set dependency check off */ + void setDependencyCheckOff(); + + /** \brief get dependency check status */ + bool getDependencyCheck(); + + // Model meta data + +private: + std::list Files; + std::unordered_map RDFMap; + bool DependencyCheck; +}; + +#endif // CIMModel_HPP diff --git a/cimgen/languages/cpp/src/ModelDescription.cpp b/cimgen/languages/cpp/src/ModelDescription.cpp new file mode 100644 index 0000000..cab8f01 --- /dev/null +++ b/cimgen/languages/cpp/src/ModelDescription.cpp @@ -0,0 +1,12 @@ +#include "ModelDescription.hpp" + + +ModelDescription::ModelDescription() +{ + +} + +ModelDescription::~ModelDescription() +{ + +} diff --git a/cimgen/languages/cpp/src/ModelDescription.hpp b/cimgen/languages/cpp/src/ModelDescription.hpp new file mode 100644 index 0000000..11f658d --- /dev/null +++ b/cimgen/languages/cpp/src/ModelDescription.hpp @@ -0,0 +1,26 @@ +#ifndef MODELDESCRIPTION_HPP +#define MODELDESCRIPTION_HPP + +#include +#include + +class ModelDescription +{ +public: + ModelDescription(); + ~ModelDescription(); + + std::string rdfID; + std::string created; + std::string scenarioTime; + std::string version; + std::string description; + std::string modelingAuthoritySet; + std::string profile; + + std::vector dependencyID;//TODO:Besserer Speicher fuer Dependencies? (tree) + + //TODO: Evtl. Suche nach ID im Vector implementieren +}; + +#endif // MODELDESCRIPTION_HPP diff --git a/cimgen/languages/cpp/src/ModelDescriptionHandler.cpp b/cimgen/languages/cpp/src/ModelDescriptionHandler.cpp new file mode 100644 index 0000000..be0bdb1 --- /dev/null +++ b/cimgen/languages/cpp/src/ModelDescriptionHandler.cpp @@ -0,0 +1,153 @@ +#include "ModelDescriptionHandler.hpp" + +#include +#include + +#include "CIMExceptions.hpp" +#include "IEC61970/IEC61970CIMVersion.h" + +using CIMPP::IEC61970CIMVersion; + +ModelDescriptionHandler::ModelDescriptionHandler() : modelDescription(nullptr) +{ +} + +void ModelDescriptionHandler::setDocumentLocator(const LocatorT &locator) +{} + +void ModelDescriptionHandler::startDocument() +{ + if(modelDescription == nullptr) + throw MissingModelDescription(this); +} + +void ModelDescriptionHandler::endDocument() +{} + +void ModelDescriptionHandler::startPrefixMapping(const std::string &prefix, const std::string &uri) +{ + if(prefix == "cim") + { + std::string version = IEC61970CIMVersion::version; + std::size_t pos = version.find("CIM"); + std::string versionParser = version.substr(pos+3, 2); + + pos = uri.find("cim"); + std::string versionFile = uri.substr(pos+3,2); + + if(versionParser != versionFile) + { + std::cout << "CIM version(" << versionFile << ") in File does not match CIM version(" << versionParser << ") in Parser" << std::endl; + } + } +} + +void ModelDescriptionHandler::endPrefixMapping(const std::string &prefix) +{} + +void ModelDescriptionHandler::startElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName, const AttributesT &atts) +{ + //Only process tags in md namespace + if(qName.find("md:") == std::string::npos) + { + return; + } + + tagStack.push(qName); + + if(qName.find("DependentOn") != std::string::npos) + { + std::string rdfID = get_rdf_resource(atts); + + modelDescription->dependencyID.push_back(rdfID); + } + else if(qName.find("FullModel") != std::string::npos) + { + std::string rdfID = get_rdf_id(atts); + + modelDescription->rdfID = rdfID; + } +} + +void ModelDescriptionHandler::endElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName) +{ + //Only process tags in md namespace + if(qName.find("md:") == std::string::npos) + { + return; + } + + tagStack.pop(); +} + +void ModelDescriptionHandler::characters(const std::string &ch) //TODO:getter +{ + if(!tagStack.empty() && tagStack.top().find("md:") != std::string::npos) + { + std::string name = tagStack.top(); + if(name.find("created") != std::string::npos) + { + modelDescription->created = ch; + } + else if(name.find("scenarioTime") != std::string::npos) + { + modelDescription->scenarioTime = ch; + } + else if(name.find("version") != std::string::npos) + { + modelDescription->version = ch; + } + else if(name.find("description") != std::string::npos) + { + modelDescription->description = ch; + } + else if(name.find("modelingAuthoritySet") != std::string::npos) + { + modelDescription->modelingAuthoritySet = ch; + } + else if(name.find("profile") != std::string::npos) + { + modelDescription->profile = ch; + } + + } + +} + +void ModelDescriptionHandler::ignorableWhitespace(const std::string &ch) +{} + +void ModelDescriptionHandler::processingInstruction(const std::string &target, const std::string &data) +{} + +void ModelDescriptionHandler::skippedEntity(const std::string &name) +{} + +std::string ModelDescriptionHandler::get_rdf_id(const AttributesT &attributes) +{ + for(int i = 0; i < attributes.getLength(); i++) + { + if(attributes.getQName(i) == "rdf:ID") + return attributes.getValue(i); + if(attributes.getQName(i) == "rdf:about") + return attributes.getValue(i).substr(0); + } + return std::string(); +} + +std::string ModelDescriptionHandler::get_rdf_resource(const AttributesT &attributes) //TODO: Resource in get_rdf_id ? +{ + for(int i = 0; i < attributes.getLength(); i++) + { + if(attributes.getQName(i) == "rdf:resource") + { + return attributes.getValue(i).substr(0); + } + } + return std::string(); +} + +void ModelDescriptionHandler::setModelDescription(ModelDescription* mDesc) +{ + modelDescription = mDesc; +} diff --git a/cimgen/languages/cpp/src/ModelDescriptionHandler.hpp b/cimgen/languages/cpp/src/ModelDescriptionHandler.hpp new file mode 100644 index 0000000..3291fb4 --- /dev/null +++ b/cimgen/languages/cpp/src/ModelDescriptionHandler.hpp @@ -0,0 +1,41 @@ +#ifndef MODELDESCRIPTIONHANDLER_HPP +#define MODELDESCRIPTIONHANDLER_HPP + +#include +#include + +#include +#include + +#include "ModelDescription.hpp" + +class ModelDescriptionHandler : public Arabica::SAX::ContentHandler +{ +public: + ModelDescriptionHandler(); + + void setModelDescription(ModelDescription* mDesc); +protected: + void setDocumentLocator(const LocatorT &locator) override; + void startDocument() override; + void endDocument() override; + void startPrefixMapping(const std::string &prefix, const std::string &uri) override; + void endPrefixMapping(const std::string &prefix) override; + void startElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName, const AttributesT &atts) override; + void endElement(const std::string &namespaceURI, const std::string &localName, const std::string &qName) override; + void characters(const std::string &ch) override; + void ignorableWhitespace(const std::string &ch) override; + void processingInstruction(const std::string &target, const std::string &data) override; + void skippedEntity(const std::string &name) override; + + static std::string get_rdf_id(const AttributesT &properties); + static std::string get_rdf_resource(const AttributesT &properties); + +private: + ModelDescription* modelDescription; + + std::stack tagStack; + +}; + +#endif // MODELDESCRIPTIONHANDLER_HPP diff --git a/cimgen/languages/cpp/src/SAXErrorHandler.hpp b/cimgen/languages/cpp/src/SAXErrorHandler.hpp new file mode 100644 index 0000000..40766a5 --- /dev/null +++ b/cimgen/languages/cpp/src/SAXErrorHandler.hpp @@ -0,0 +1,44 @@ +#ifndef SAX_ERRORHANDLER_HPP +#define SAX_ERRORHANDLER_HPP + +#include +#include +#include +#include +#include + +class SAXErrorHandler : public Arabica::SAX::ErrorHandler +{ +private: + std::string saxParser; + +public: + SAXErrorHandler() + { +#ifdef ARABICA_USE_LIBXML2 + saxParser = "Libxml2"; +#elif defined(ARABICA_USE_MSXML) + saxParser = "MSXML"; +#else + saxParser = "SAX Parser"; +#endif + }; + + ~SAXErrorHandler(){}; + + void warning(const SAXParseExceptionT& exception){}; + + void error(const SAXParseExceptionT& exception) + { + std::cerr << saxParser << " encountered an Error:\n" << exception.what() << std::endl; + throw(exception); // SAXParseException is an std::runtime_error + }; + + void fatalError(const SAXParseExceptionT& exception) + { + std::cerr << saxParser << " encountered a Fatal Error:\n" << exception.what() << std::endl; + throw(exception); // SAXParseException is an std::runtime_error + }; +}; + +#endif //SAX_ERRORHANDLER_HPP diff --git a/cimgen/languages/cpp/static/BaseClass.cpp b/cimgen/languages/cpp/static/BaseClass.cpp new file mode 100644 index 0000000..ff3a94a --- /dev/null +++ b/cimgen/languages/cpp/static/BaseClass.cpp @@ -0,0 +1,20 @@ +#include "BaseClass.hpp" + +using namespace CIMPP; + +BaseClass::~BaseClass() {} + +const char BaseClass::debugName[] = "BaseClass"; +const char* BaseClass::debugString() const +{ + return BaseClass::debugName; +} + +void BaseClass::addConstructToMap(std::unordered_map& factory_map) {} +void BaseClass::addPrimitiveAssignFnsToMap(std::unordered_map& assign_map) {} +void BaseClass::addClassAssignFnsToMap(std::unordered_map& assign_map) {} + +const BaseClassDefiner BaseClass::declare() +{ + return BaseClassDefiner(BaseClass::addConstructToMap, BaseClass::addPrimitiveAssignFnsToMap, BaseClass::addClassAssignFnsToMap, BaseClass::debugName); +} diff --git a/cimgen/languages/cpp/static/BaseClass.h b/cimgen/languages/cpp/static/BaseClass.h new file mode 100644 index 0000000..d1ebb81 --- /dev/null +++ b/cimgen/languages/cpp/static/BaseClass.h @@ -0,0 +1 @@ +#include "BaseClass.hpp" diff --git a/cimgen/languages/cpp/static/BaseClass.hpp b/cimgen/languages/cpp/static/BaseClass.hpp new file mode 100644 index 0000000..7af998c --- /dev/null +++ b/cimgen/languages/cpp/static/BaseClass.hpp @@ -0,0 +1,27 @@ +#ifndef BASECLASS_HPP +#define BASECLASS_HPP + +#ifndef CGMES_BUILD +#define CGMES_BUILD +#endif + +#include +#include + +#include "BaseClassDefiner.hpp" + +class BaseClass +{ +public: + enum cgmesProfile {EQ = 0, SSH = 1, TP = 2, SV = 3, DY = 4, GL = 5, DI = 6}; + virtual ~BaseClass(); + + static const char debugName[]; + virtual const char* debugString() const; + + static void addConstructToMap(std::unordered_map& factory_map); + static void addPrimitiveAssignFnsToMap(std::unordered_map& assign_map); + static void addClassAssignFnsToMap(std::unordered_map& assign_map); + static const CIMPP::BaseClassDefiner declare(); +}; +#endif // BASECLASS_HPP diff --git a/cimgen/languages/cpp/static/BaseClassDefiner.cpp b/cimgen/languages/cpp/static/BaseClassDefiner.cpp new file mode 100644 index 0000000..95bcb95 --- /dev/null +++ b/cimgen/languages/cpp/static/BaseClassDefiner.cpp @@ -0,0 +1,14 @@ + +#include "BaseClassDefiner.hpp" + +using namespace CIMPP; + +BaseClassDefiner::BaseClassDefiner(void(*addConstruct)(std::unordered_map&), + void(*addPrimitiveAssignFns)(std::unordered_map&), + void(*addClassAssignFns)(std::unordered_map&), + const char *debugStr) { + addConstructToMap = addConstruct; + addPrimitiveAssignFnsToMap = addPrimitiveAssignFns; + addClassAssignFnsToMap = addClassAssignFns; + debugString = debugStr; +} diff --git a/cimgen/languages/cpp/static/BaseClassDefiner.hpp b/cimgen/languages/cpp/static/BaseClassDefiner.hpp new file mode 100644 index 0000000..a873538 --- /dev/null +++ b/cimgen/languages/cpp/static/BaseClassDefiner.hpp @@ -0,0 +1,24 @@ +#ifndef BASECLASSDEFINER_HPP +#define BASECLASSDEFINER_HPP + +#include +#include + +class BaseClass; +typedef bool (*class_assign_function)(BaseClass*, BaseClass*); +typedef bool (*assign_function)(std::stringstream&, BaseClass*); +namespace CIMPP { + + class BaseClassDefiner { + public: + BaseClassDefiner(void(*addConstruct)(std::unordered_map&), + void(*addPrimitiveAssignFns)(std::unordered_map&), + void(*addClassAssignFns)(std::unordered_map&), + const char *debugStr); + void (*addConstructToMap)(std::unordered_map&); + void (*addPrimitiveAssignFnsToMap)(std::unordered_map&); + void (*addClassAssignFnsToMap)(std::unordered_map&); + const char* debugString; + }; +} +#endif // BASECLASSDEFINER diff --git a/cimgen/languages/cpp/static/Boolean.cpp b/cimgen/languages/cpp/static/Boolean.cpp new file mode 100644 index 0000000..1818ab8 --- /dev/null +++ b/cimgen/languages/cpp/static/Boolean.cpp @@ -0,0 +1,73 @@ +#include "Boolean.hpp" + +#include +#include + +#include "../src/CIMExceptions.hpp" + +using namespace CIMPP; + +Boolean& Boolean::operator=(bool rop) +{ + value = rop; + initialized = true; + return *this; +} + +Boolean::operator bool() +{ + if (!initialized) + { + throw new ReadingUninitializedField(); + } + return value; +} + +const char Boolean::debugName[] = "Boolean"; +const char* Boolean::debugString() const +{ + return Boolean::debugName; +} + +namespace CIMPP +{ + std::istream& operator>>(std::istream& lop, Boolean& rop) + { + rop.initialized = false; + + std::string tmp; + lop >> tmp; + + if (tmp == "true" || tmp == "True" || tmp == "TRUE") + { + rop.value = true; + rop.initialized = true; + return lop; + } + if (tmp == "false" || tmp == "False" || tmp == "FALSE") + { + rop.value = false; + rop.initialized = true; + return lop; + } + + lop.setstate(std::ios::failbit); + return lop; + } + + std::ostream& operator<<(std::ostream& os, const Boolean& obj) + { + if (obj.initialized) + { + if (obj.value) + { + os << "true"; + } + else + { + os << "false"; + } + } + return os; + } +} diff --git a/cimgen/languages/cpp/static/Boolean.hpp b/cimgen/languages/cpp/static/Boolean.hpp new file mode 100644 index 0000000..9dc8d09 --- /dev/null +++ b/cimgen/languages/cpp/static/Boolean.hpp @@ -0,0 +1,31 @@ +#ifndef BOOLEAN_H +#define BOOLEAN_H + +#include +#include + +namespace CIMPP +{ + /** + * A type with the value space "true" and "false". + */ + class Boolean + { + public: + Boolean() : value(false), initialized(false) {} + Boolean(bool value) : value(value), initialized(true) {} + + Boolean& operator=(bool rop); + operator bool(); + + bool value; + bool initialized; + + static const char debugName[]; + const char* debugString() const; + + friend std::istream& operator>>(std::istream& lop, Boolean& rop); + friend std::ostream& operator<<(std::ostream& os, const Boolean& obj); + }; +} +#endif // BOOLEAN_H diff --git a/cimgen/languages/cpp/static/CIMFactory.cpp b/cimgen/languages/cpp/static/CIMFactory.cpp new file mode 100644 index 0000000..a2ed2e7 --- /dev/null +++ b/cimgen/languages/cpp/static/CIMFactory.cpp @@ -0,0 +1,44 @@ +#include "CIMFactory.hpp" +#include "Folders.hpp" +#include "CIMClassList.hpp" +#include +#include + +using namespace CIMPP; + +static std::unordered_map initialize(); +std::unordered_map CIMFactory::factory_map = initialize(); + +BaseClass* CIMFactory::CreateNew(const std::string& name) { + std::unordered_map::iterator it = factory_map.find(name); + if(it != factory_map.end()) { + return (*it->second)(); + } + else { + std::cerr << "!! ** Could not find factory for " << name << " ** !!" << std::endl; + return nullptr; + } +} + +bool CIMFactory::IsCIMClass(const std::string& name) { + std::unordered_map::iterator it = factory_map.find(name); + if(it == factory_map.end()) { + return false; + } + else { + return true; + } +} + +CIMFactory::CIMFactory() {} + +CIMFactory::~CIMFactory() {} + +static std::unordered_map initialize() { + std::unordered_map map; + for (const BaseClassDefiner& CIMClass : CIMClassList) + { + CIMClass.addConstructToMap(map); + } + return map; +} diff --git a/cimgen/languages/cpp/static/CIMFactory.hpp b/cimgen/languages/cpp/static/CIMFactory.hpp new file mode 100644 index 0000000..9e3cbe4 --- /dev/null +++ b/cimgen/languages/cpp/static/CIMFactory.hpp @@ -0,0 +1,22 @@ +#ifndef CIMFACTORY_HPP +#define CIMFACTORY_HPP + +#include +#include +#include "BaseClass.hpp" + +namespace CIMPP { + + class CIMFactory + { + public: + CIMFactory(); + virtual ~CIMFactory(); + static BaseClass* CreateNew(const std::string& name); + static bool IsCIMClass(const std::string& name); + + private: + static std::unordered_map factory_map; + }; +} +#endif // CIMFACTORY_HPP diff --git a/cimgen/languages/cpp/static/Folders.hpp b/cimgen/languages/cpp/static/Folders.hpp new file mode 100644 index 0000000..275606d --- /dev/null +++ b/cimgen/languages/cpp/static/Folders.hpp @@ -0,0 +1 @@ +#include "IEC61970.hpp" diff --git a/cimgen/languages/cpp/static/IEC61970/IEC61970CIMVersion.cpp b/cimgen/languages/cpp/static/IEC61970/IEC61970CIMVersion.cpp new file mode 100644 index 0000000..8cda241 --- /dev/null +++ b/cimgen/languages/cpp/static/IEC61970/IEC61970CIMVersion.cpp @@ -0,0 +1,11 @@ +/////////////////////////////////////////////////////////// +// IEC61970CIMVersion.cpp +// Implementation of the Class IEC61970CIMVersion +/////////////////////////////////////////////////////////// + +#include "IEC61970CIMVersion.h" + +using namespace CIMPP; + +const Date IEC61970CIMVersion::date = Date("2017-07-26"); +const String IEC61970CIMVersion::version = String("IEC61970CIM17v23"); diff --git a/cimgen/languages/cpp/static/IEC61970/IEC61970CIMVersion.h b/cimgen/languages/cpp/static/IEC61970/IEC61970CIMVersion.h new file mode 100644 index 0000000..5860ce0 --- /dev/null +++ b/cimgen/languages/cpp/static/IEC61970/IEC61970CIMVersion.h @@ -0,0 +1,27 @@ +#ifndef IEC61970CIMVERSION_H +#define IEC61970CIMVERSION_H +/////////////////////////////////////////////////////////// +// IEC61970CIMVersion.h +// Implementation of the Class IEC61970CIMVersion +/////////////////////////////////////////////////////////// + +#include "Date.hpp" +#include "String.hpp" + +namespace CIMPP { + + class IEC61970CIMVersion + { + public: + /** + * Form is YYYY-MM-DD for example for January 5, 2009 it is 2009-01-05. + */ + static const Date date; + /** + * Form is IEC61970CIMXXvYY where XX is the major CIM package version and the YY + * is the minor version. For example IEC61970CIM13v18. + */ + static const String version; + }; +} +#endif // IEC61970CIMVERSION_H diff --git a/cimgen/languages/cpp/static/Integer.cpp b/cimgen/languages/cpp/static/Integer.cpp new file mode 100644 index 0000000..2a1cda4 --- /dev/null +++ b/cimgen/languages/cpp/static/Integer.cpp @@ -0,0 +1,74 @@ +#include "Integer.hpp" + +#include + +#include "../src/CIMExceptions.hpp" + +using namespace CIMPP; + +Integer& Integer::operator=(long int rop) +{ + value = rop; + initialized = true; + return *this; +} + +Integer::operator long int() +{ + if (!initialized) + { + throw new ReadingUninitializedField(); + } + return value; +} + +const char Integer::debugName[] = "Integer"; +const char* Integer::debugString() const +{ + return Integer::debugName; +} + +Integer& Integer::operator+=(const Integer& rhs) +{ + value += rhs.value; + return *this; +} + +Integer& Integer::operator-=(const Integer& rhs) +{ + value -= rhs.value; + return *this; +} + +Integer& Integer::operator*=(const Integer& rhs) +{ + value *= rhs.value; + return *this; +} + +Integer& Integer::operator/=(const Integer& rhs) +{ + value /= rhs.value; + return *this; +} + +namespace CIMPP +{ + std::istream& operator>>(std::istream& lop, Integer& rop) + { + std::string tmp; + lop >> tmp; + rop.value = stol(tmp); + rop.initialized = true; + return lop; + } + + std::ostream& operator<<(std::ostream& os, const Integer& obj) + { + if (obj.initialized) + { + os << obj.value; + } + return os; + } +} diff --git a/cimgen/languages/cpp/static/Integer.hpp b/cimgen/languages/cpp/static/Integer.hpp new file mode 100644 index 0000000..fab9a71 --- /dev/null +++ b/cimgen/languages/cpp/static/Integer.hpp @@ -0,0 +1,36 @@ +#ifndef INTEGER_H +#define INTEGER_H + +#include +#include + +namespace CIMPP +{ + /** + * An Integer number. The range is unspecified and not limited. + */ + class Integer + { + public: + Integer() : value(0), initialized(false) {} + Integer(long int value) : value(value), initialized(true) {} + + Integer& operator=(long int rop); + operator long int(); + + long int value; + bool initialized; + + static const char debugName[]; + const char* debugString() const; + + Integer& operator+=(const Integer& rhs); + Integer& operator-=(const Integer& rhs); + Integer& operator*=(const Integer& rhs); + Integer& operator/=(const Integer& rhs); + + friend std::istream& operator>>(std::istream& lop, Integer& rop); + friend std::ostream& operator<<(std::ostream& os, const Integer& obj); + }; +} +#endif // INTEGER_H diff --git a/cimgen/languages/cpp/static/Task.cpp b/cimgen/languages/cpp/static/Task.cpp new file mode 100644 index 0000000..be10d25 --- /dev/null +++ b/cimgen/languages/cpp/static/Task.cpp @@ -0,0 +1,58 @@ +#include "Task.hpp" + +#include + +#include "IdentifiedObject.hpp" +#include "CIMClassList.hpp" + +using namespace CIMPP; + +typedef bool (*task_function)(BaseClass*, BaseClass*); +static std::unordered_map initialize(); +std::unordered_map Task::dynamic_switch = initialize(); + +Task::Task() {} + +Task::Task(BaseClass* CIMObj, const std::string CIMAttrName, const std::string Value) + : _CIMObj(CIMObj), _CIMAttrName(CIMAttrName), _Value(Value) {} + +Task::~Task() {} + +void Task::print() +{ + if(IdentifiedObject* IdObj = dynamic_cast(_CIMObj)) + std::cout << _CIMAttrName << " '" << IdObj->name << "' = '" << _Value << "'" << std::endl; + else + std::cout << _CIMAttrName << " = '" << _Value << "'" << std::endl; + +} + +bool Task::resolve(std::unordered_map *RDFMap) +{ + std::unordered_map::iterator it_id = RDFMap->find(_Value); + if(it_id == RDFMap->end()) { + std::cerr << "Couldn't find " << _CIMAttrName << " with value: " << _Value << " in RDFMap." << std::endl; + return false; + } + + std::unordered_map::iterator it_func = dynamic_switch.find(_CIMAttrName); + if (it_func == dynamic_switch.end()) { + std::cerr << "Couldn't find " << _CIMAttrName << " in dynamic_switch map." << std::endl; + return false; + } + + if((*it_func->second)(_CIMObj, it_id->second)) + return true; + else + return (*it_func->second)(it_id->second, _CIMObj); +} + +static std::unordered_map initialize() +{ + std::unordered_map object_map; + for (const BaseClassDefiner& CIMClass : CIMClassList) { + CIMClass.addClassAssignFnsToMap(object_map); + } + + return object_map; +} diff --git a/cimgen/languages/cpp/static/Task.hpp b/cimgen/languages/cpp/static/Task.hpp new file mode 100644 index 0000000..2dd484a --- /dev/null +++ b/cimgen/languages/cpp/static/Task.hpp @@ -0,0 +1,22 @@ +#ifndef TASK_HPP +#define TASK_HPP + +#include +#include +#include "BaseClass.hpp" + +class Task +{ +public: + Task(); + Task(BaseClass* CIMObj, const std::string CIMAttrName, const std::string Value); + ~Task(); + bool resolve(std::unordered_map *RDFMap); + void print(); +private: + BaseClass* _CIMObj; + std::string _CIMAttrName; + std::string _Value; + static std::unordered_map dynamic_switch; +}; +#endif // TASK_HPP diff --git a/cimgen/languages/cpp/static/UnknownType.cpp b/cimgen/languages/cpp/static/UnknownType.cpp new file mode 100644 index 0000000..fc55a92 --- /dev/null +++ b/cimgen/languages/cpp/static/UnknownType.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include + +#include "BaseClass.hpp" +#include "UnknownType.hpp" + +using namespace CIMPP; + +UnknownType::UnknownType() {}; + +UnknownType::~UnknownType() {}; + +bool seenAttribute(std::string name, std::string value) { + static std::list> seenAttributes; + bool found = false; + for(const std::pair & attribute : seenAttributes) { + if (name == attribute.first && value == attribute.second) { + found = true; + + } + } + if (!found) { + seenAttributes.push_back(std::pair(name, value)); + } + return found; +} + +bool assign_Unknown_Attribute(std::stringstream &buffer, std::string name) { + std::string attribute; + buffer >> attribute; + if(buffer.fail()) { + return false; + } + else if (!seenAttribute(name, attribute)) { + std::cout << "Warning: could not assign attribute with name: " << name << " and value: " << attribute << std::endl; + } + return true; +} + +bool assign_Name_name(std::stringstream &buffer, BaseClass* BaseClass_ptr1) { + return assign_Unknown_Attribute(buffer, "cim:Name.name"); +} + +bool assign_NameType_name(std::stringstream &buffer, BaseClass* BaseClass_ptr1) { + return assign_Unknown_Attribute(buffer, "cim:NameType.name"); +} + +bool assign_Name_IdentifiedObject(std::stringstream &buffer, BaseClass* BaseClass_ptr1) { + return assign_Unknown_Attribute(buffer, "cim:Name.IdentifiedObject"); +} + +bool assign_ACDCTerminal_connected(std::stringstream &buffer, BaseClass* BaseClass_ptr1); +bool assign_ACDCTerminal_sequenceNumber(std::stringstream &buffer, BaseClass* BaseClass_ptr1); + +bool assign_Unknown_Class(std::string type) { + std::cout << "Warning: could not assign class of unrecognised type " << type << "." << std::endl; + return true; +} + +bool assign_Class_NameType_name(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) { + static bool seen = false; + if (seen){ + return true; + } + else { + seen = true; + return assign_Unknown_Class("cim:NameType.name"); + } +} + +bool assign_Class_Name_NameType(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) { + static bool seen = false; + if (seen){ + return true; + } + else { + seen = true; + return assign_Unknown_Class("cim:Name.NameType"); + } +} + +bool assign_Class_Name_name(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) { + static bool seen = false; + if (seen){ + return true; + } + else { + seen = true; + return assign_Unknown_Class("cim:Name.name"); + } +} + +bool assign_Class_Name_IdentifiedObject(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) { + static bool seen = false; + if (seen){ + return true; + } + else { + seen = true; + return assign_Unknown_Class("cim:Name.IdentifiedObject"); + } +} + +void UnknownType::addPrimitiveAssignFnsToMap(std::unordered_map& assign_map) { + assign_map.insert(std::make_pair(std::string("cim:Terminal.sequenceNumber"), &assign_ACDCTerminal_sequenceNumber)); + assign_map.insert(std::make_pair(std::string("cim:Terminal.connected"), &assign_ACDCTerminal_connected)); + assign_map.insert(std::make_pair(std::string("cim:Name.name"), &assign_Name_name)); + assign_map.insert(std::make_pair(std::string("cim:NameType.name"), &assign_NameType_name)); +} + +void UnknownType::addClassAssignFnsToMap(std::unordered_map& assign_map) { + assign_map.insert(std::make_pair(std::string("cim:Name.name"), &assign_Class_Name_name)); + assign_map.insert(std::make_pair(std::string("cim:Name.IdentifiedObject"), &assign_Class_Name_IdentifiedObject)); + assign_map.insert(std::make_pair(std::string("cim:NameType.name"), &assign_Class_NameType_name)); + assign_map.insert(std::make_pair(std::string("cim:Name.NameType"), &assign_Class_Name_NameType)); +} + +namespace CIMPP { + BaseClass* UnknownType_factory() { + return new UnknownType; + } +} + +void UnknownType::addConstructToMap(std::unordered_map& factory_map) { + factory_map.insert(std::make_pair(std::string("cim:NameType"), &UnknownType_factory)); + factory_map.insert(std::make_pair(std::string("cim:Name"), &UnknownType_factory)); +} + +const char UnknownType::debugName[] = "UnknownType"; +const char* UnknownType::debugString() const { + return UnknownType::debugName; +} + +const BaseClassDefiner UnknownType::declare() +{ + return BaseClassDefiner(addConstructToMap, addPrimitiveAssignFnsToMap, addClassAssignFnsToMap, debugName); +} diff --git a/cimgen/languages/cpp/static/UnknownType.hpp b/cimgen/languages/cpp/static/UnknownType.hpp new file mode 100644 index 0000000..15f3609 --- /dev/null +++ b/cimgen/languages/cpp/static/UnknownType.hpp @@ -0,0 +1,26 @@ +#ifndef UnknownType_H +#define UnknownType_H +#include +#include +#include +#include "BaseClass.hpp" + + +namespace CIMPP { + + class UnknownType: public BaseClass + { +public: + static const char debugName[]; + const char* debugString() const override; + + UnknownType(); + virtual ~UnknownType(); + + static void addConstructToMap(std::unordered_map& factory_map); + static void addPrimitiveAssignFnsToMap(std::unordered_map&); + static void addClassAssignFnsToMap(std::unordered_map&); + static const BaseClassDefiner declare(); + }; +} +#endif diff --git a/cimgen/languages/cpp/static/assignments.cpp b/cimgen/languages/cpp/static/assignments.cpp new file mode 100644 index 0000000..b437106 --- /dev/null +++ b/cimgen/languages/cpp/static/assignments.cpp @@ -0,0 +1,40 @@ +#include +#include +#include + +#include "Folders.hpp" +#include "CIMClassList.hpp" +#include "assignments.hpp" +#include +#include "BaseClass.hpp" + +using namespace CIMPP; + +static std::unordered_map dynamic_switch_factory(); +static std::unordered_map dynamic_switch = dynamic_switch_factory(); + +bool assign(BaseClass* CIMObj, const std::string& CIMAttrName, const std::string& Value) +{ + std::unordered_map::iterator prim_it = dynamic_switch.find(CIMAttrName); + if(prim_it != dynamic_switch.end()) { + std::stringstream str; + str << Value; + return (*prim_it->second)(str, CIMObj); + } + +#ifdef DEBUG + std::cerr << "Couldn't assign attribute with value: " << Value << " to " << CIMAttrName << " in object of type " << CIMObj->debugString() << std::endl; +#endif + return false; +} + +std::unordered_map dynamic_switch_factory() +{ + std::unordered_map assign_map; + for (const BaseClassDefiner& CIMClass : CIMClassList) + { + CIMClass.addPrimitiveAssignFnsToMap(assign_map); + } + + return assign_map; +} diff --git a/cimgen/languages/cpp/static/assignments.hpp b/cimgen/languages/cpp/static/assignments.hpp new file mode 100644 index 0000000..1492ee2 --- /dev/null +++ b/cimgen/languages/cpp/static/assignments.hpp @@ -0,0 +1,11 @@ +#ifndef ASSIGN_HPP +#define ASSIGN_HPP + +#include +#include "BaseClass.hpp" + +using namespace CIMPP; + +bool assign(BaseClass* CIMObj, const std::string& CIMAttrName, const std::string& Value); + +#endif // ASSIGN_HPP diff --git a/cimgen/languages/cpp/templates/cpp_enum_header_template.mustache b/cimgen/languages/cpp/templates/cpp_enum_header_template.mustache index 888ac3c..3076101 100644 --- a/cimgen/languages/cpp/templates/cpp_enum_header_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_enum_header_template.mustache @@ -1,19 +1,48 @@ #ifndef {{class_name}}_H #define {{class_name}}_H +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ -namespace CIMPP { +#include +#include + +namespace CIMPP +{ +{{#class_comment}} /* {{{class_comment}}} */ - enum class {{class_name}} +{{/class_comment}} + class {{class_name}} { + public: + enum {{class_name}}_ENUM + { {{#enum_instances}} - /** - * {{comment}} - */ - {{label}}, +{{#comment}} + /** + * {{comment}} + */ +{{/comment}} + {{label}}, {{/enum_instances}} + }; + + {{class_name}}() : value(), initialized(false) {} + {{class_name}}({{class_name}}_ENUM value) : value(value), initialized(true) {} + + {{class_name}}& operator=({{class_name}}_ENUM rop); + operator {{class_name}}_ENUM() const; + + {{class_name}}_ENUM value; + bool initialized; + + static const char debugName[]; + const char* debugString() const; + + friend std::istream& operator>>(std::istream& lop, {{class_name}}& rop); + friend std::ostream& operator<<(std::ostream& os, const {{class_name}}& obj); }; - std::istream& operator>>(std::istream& lop, CIMPP::{{class_name}}& rop); } #endif diff --git a/cimgen/languages/cpp/templates/cpp_enum_object_template.mustache b/cimgen/languages/cpp/templates/cpp_enum_object_template.mustache index 63eecae..cb2998b 100644 --- a/cimgen/languages/cpp/templates/cpp_enum_object_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_enum_object_template.mustache @@ -1,12 +1,43 @@ -#include -#include +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ #include "{{class_name}}.hpp" +#include +#include + +#include "../src/CIMExceptions.hpp" + using namespace CIMPP; -namespace CIMPP { - std::istream& operator>>(std::istream& lop, CIMPP::{{class_name}}& rop) +{{class_name}}& {{class_name}}::operator=({{class_name}}_ENUM rop) +{ + value = rop; + initialized = true; + return *this; +} + +{{class_name}}::operator {{class_name}}_ENUM() const +{ + if (!initialized) + { + throw new ReadingUninitializedField(); + } + return value; +} + +const char {{class_name}}::debugName[] = "{{class_name}}"; +const char* {{class_name}}::debugString() const +{ + return {{class_name}}::debugName; +} + +namespace CIMPP +{ + std::istream& operator>>(std::istream& lop, {{class_name}}& rop) { + rop.initialized = false; + std::string EnumSymbol; lop >> EnumSymbol; @@ -30,4 +61,25 @@ namespace CIMPP { lop.setstate(std::ios::failbit); return lop; } + + std::ostream& operator<<(std::ostream& os, const {{class_name}}& obj) + { + if (obj.initialized) + { + std::string EnumSymbol; + +{{#enum_instances}} + if (obj.value == {{class_name}}::{{label}}) + { + EnumSymbol = "{{label}}"; + } +{{/enum_instances}} + + if (!EnumSymbol.empty()) + { + os << "{{class_name}}." << EnumSymbol; + } + } + return os; + } } diff --git a/cimgen/languages/cpp/templates/cpp_float_header_template.mustache b/cimgen/languages/cpp/templates/cpp_float_header_template.mustache index 7517148..e558d70 100644 --- a/cimgen/languages/cpp/templates/cpp_float_header_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_float_header_template.mustache @@ -1,38 +1,41 @@ #ifndef {{class_name}}_H #define {{class_name}}_H +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ -#include #include - -#include "BaseClass.hpp" +#include namespace CIMPP { - class {{class_name}} : public BaseClass +{{#class_comment}} + /* + {{{class_comment}}} + */ +{{/class_comment}} + class {{class_name}} { - public: - {{class_name}}(); - virtual ~{{class_name}}(); - {{class_name}}(long double value); - static const BaseClassDefiner declare(); - {{class_name}}& operator=(long double &rop); + {{class_name}}() : value(0.0), initialized(false) {} + {{class_name}}(long double value) : value(value), initialized(true) {} + + {{class_name}}& operator=(long double rop); + operator long double() const; + + long double value; + bool initialized; + + static const char debugName[]; + const char* debugString() const; + {{class_name}}& operator+=(const {{class_name}}& rhs); {{class_name}}& operator-=(const {{class_name}}& rhs); {{class_name}}& operator*=(const {{class_name}}& rhs); {{class_name}}& operator/=(const {{class_name}}& rhs); - friend std::istream& operator>>(std::istream& lop, {{class_name}}& rop); - operator long double(); - - long double value = 0.0; - bool initialized = false; - static const char debugName[]; - virtual const char* debugString(); - - static void addConstructToMap(std::unordered_map& factory_map); - static void addPrimitiveAssignFnsToMap(std::unordered_map&); - static void addClassAssignFnsToMap(std::unordered_map&); + friend std::istream& operator>>(std::istream& lop, {{class_name}}& rop); + friend std::ostream& operator<<(std::ostream& os, const {{class_name}}& obj); }; } #endif diff --git a/cimgen/languages/cpp/templates/cpp_float_object_template.mustache b/cimgen/languages/cpp/templates/cpp_float_object_template.mustache index 1d7116a..ca84b71 100644 --- a/cimgen/languages/cpp/templates/cpp_float_object_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_float_object_template.mustache @@ -1,70 +1,77 @@ +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ #include "{{class_name}}.hpp" -#include "../src/CIMExceptions.hpp" - -using namespace CIMPP; - -{{class_name}}::{{class_name}}() {} -{{class_name}}::~{{class_name}}(){} +#include -{{class_name}}::{{class_name}}(long double value) : value(value), initialized(true) {} +#include "../src/CIMExceptions.hpp" -void {{class_name}}::addConstructToMap(std::unordered_map& factory_map) {} +using namespace CIMPP; -void {{class_name}}::addPrimitiveAssignFnsToMap(std::unordered_map& assign_map) {} +{{class_name}}& {{class_name}}::operator=(long double rop) +{ + value = rop; + initialized = true; + return *this; +} -void {{class_name}}::addClassAssignFnsToMap(std::unordered_map& assign_map) {} +{{class_name}}::operator long double() const +{ + if (!initialized) + { + throw new ReadingUninitializedField(); + } + return value; +} const char {{class_name}}::debugName[] = "{{class_name}}"; -const char* {{class_name}}::debugString() { +const char* {{class_name}}::debugString() const +{ return {{class_name}}::debugName; } - -const BaseClassDefiner {{class_name}}::declare() { - return BaseClassDefiner({{class_name}}::addConstructToMap, {{class_name}}::addPrimitiveAssignFnsToMap, {{class_name}}::addClassAssignFnsToMap, {{class_name}}::debugName); +{{class_name}}& {{class_name}}::operator+=(const {{class_name}}& rhs) +{ + value += rhs.value; + return *this; } -namespace CIMPP { - {{class_name}}& {{class_name}}::operator=(long double &rop) { - value = rop; - initialized = true; - return *this; - } - - {{class_name}}& {{class_name}}::operator-=(const {{class_name}}& rhs) { - value -= rhs.value; - return *this; - } - - {{class_name}}& {{class_name}}::operator*=(const {{class_name}}& rhs) { - value *= rhs.value; - return *this; - } - - {{class_name}}& {{class_name}}::operator/=(const {{class_name}}& rhs) { - value /= rhs.value; - return *this; - } +{{class_name}}& {{class_name}}::operator-=(const {{class_name}}& rhs) +{ + value -= rhs.value; + return *this; +} - {{class_name}}& {{class_name}}::operator+=(const {{class_name}}& rhs) { - value += rhs.value; - return *this; - } +{{class_name}}& {{class_name}}::operator*=(const {{class_name}}& rhs) +{ + value *= rhs.value; + return *this; +} - {{class_name}}::operator long double() { - if(!initialized) - { - throw new ReadingUninitializedField(); - } - return value; - } +{{class_name}}& {{class_name}}::operator/=(const {{class_name}}& rhs) +{ + value /= rhs.value; + return *this; +} - std::istream& operator>>(std::istream& lop, {{class_name}}& rop) { +namespace CIMPP +{ + std::istream& operator>>(std::istream& lop, {{class_name}}& rop) + { std::string tmp; lop >> tmp; rop.value = stold(tmp); rop.initialized = true; return lop; } + + std::ostream& operator<<(std::ostream& os, const {{class_name}}& obj) + { + if (obj.initialized) + { + os << obj.value; + } + return os; + } } diff --git a/cimgen/languages/cpp/templates/cpp_header_template.mustache b/cimgen/languages/cpp/templates/cpp_header_template.mustache index 6205a4c..2d8322d 100644 --- a/cimgen/languages/cpp/templates/cpp_header_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_header_template.mustache @@ -1,39 +1,42 @@ #ifndef {{class_name}}_H #define {{class_name}}_H +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ -#include "{{sub_class_of}}.hpp" #include -#include "Boolean.hpp" -#include "Float.hpp" +#include +#include +#include "{{sub_class_of}}.hpp" +#include "BaseClassDefiner.hpp" {{#langPack._create_attribute_includes}}{{attributes}}{{/langPack._create_attribute_includes}} - -namespace CIMPP { - +namespace CIMPP +{ {{#langPack._create_attribute_class_declarations}}{{attributes}}{{/langPack._create_attribute_class_declarations}} +{{#class_comment}} /* {{{class_comment}}} */ - class {{class_name}}: public {{sub_class_of}} +{{/class_comment}} + class {{class_name}} : public {{sub_class_of}} { - public: - {{#attributes}} - {{> attribute}} {{> label}}; /* {{comment}} Default: {{#langPack._set_default}}{{dataType}}{{/langPack._set_default}} */ - {{/attributes}} - - static const char debugName[]; - virtual const char* debugString(); - /* constructor initialising all attributes to null */ {{class_name}}(); - virtual ~{{class_name}}(); + ~{{class_name}}() override; + +{{#attributes}} + {{> attribute}} {{> label}}; /* {{comment}} Default: {{#langPack._set_default}}{{dataType}}{{/langPack._set_default}} */ +{{/attributes}} + + static const char debugName[]; + const char* debugString() const override; static void addConstructToMap(std::unordered_map& factory_map); - static void addPrimitiveAssignFnsToMap(std::unordered_map&); - static void addClassAssignFnsToMap(std::unordered_map&); + static void addPrimitiveAssignFnsToMap(std::unordered_map& assign_map); + static void addClassAssignFnsToMap(std::unordered_map& assign_map); static const BaseClassDefiner declare(); - }; BaseClass* {{class_name}}_factory(); diff --git a/cimgen/languages/cpp/templates/cpp_object_template.mustache b/cimgen/languages/cpp/templates/cpp_object_template.mustache index ae60165..eec1a96 100644 --- a/cimgen/languages/cpp/templates/cpp_object_template.mustache +++ b/cimgen/languages/cpp/templates/cpp_object_template.mustache @@ -1,7 +1,11 @@ -#include -#include "{{sub_class_of}}.hpp" +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ #include "{{class_name}}.hpp" +#include +#include + {{#attributes}} #include "{{attribute_class}}.hpp" {{/attributes}} @@ -9,7 +13,6 @@ using namespace CIMPP; {{class_name}}::{{class_name}}(){{#langPack.create_nullptr_assigns}} {{attributes}} {{/langPack.create_nullptr_assigns}} {}; - {{class_name}}::~{{class_name}}() {}; {{#attributes}} @@ -20,35 +23,40 @@ using namespace CIMPP; {{#langPack.create_class_assign}}{{.}}{{/langPack.create_class_assign}} {{/attributes}} -namespace CIMPP { - BaseClass* {{class_name}}_factory() { - return new {{class_name}}; - } +const char {{class_name}}::debugName[] = "{{class_name}}"; +const char* {{class_name}}::debugString() const +{ + return {{class_name}}::debugName; } -void {{class_name}}::addConstructToMap(std::unordered_map& factory_map) { +void {{class_name}}::addConstructToMap(std::unordered_map& factory_map) +{ factory_map.insert(std::make_pair(std::string("cim:{{class_name}}"), &{{class_name}}_factory)); } -void {{class_name}}::addPrimitiveAssignFnsToMap(std::unordered_map& assign_map) { +void {{class_name}}::addPrimitiveAssignFnsToMap(std::unordered_map& assign_map) +{ {{#attributes}} - {{> insert_assign}} +{{> insert_assign}} {{/attributes}} } -void {{class_name}}::addClassAssignFnsToMap(std::unordered_map& assign_map) { +void {{class_name}}::addClassAssignFnsToMap(std::unordered_map& assign_map) +{ {{#attributes}} - {{> insert_class_assign}} +{{> insert_class_assign}} {{/attributes}} } -const char {{class_name}}::debugName[] = "{{class_name}}"; -const char* {{class_name}}::debugString() +const BaseClassDefiner {{class_name}}::declare() { - return {{class_name}}::debugName; + return BaseClassDefiner({{class_name}}::addConstructToMap, {{class_name}}::addPrimitiveAssignFnsToMap, {{class_name}}::addClassAssignFnsToMap, {{class_name}}::debugName); } -const BaseClassDefiner {{class_name}}::declare() +namespace CIMPP { - return BaseClassDefiner({{class_name}}::addConstructToMap, {{class_name}}::addPrimitiveAssignFnsToMap, {{class_name}}::addClassAssignFnsToMap, {{class_name}}::debugName); + BaseClass* {{class_name}}_factory() + { + return new {{class_name}}; + } } diff --git a/cimgen/languages/cpp/templates/cpp_string_header_template.mustache b/cimgen/languages/cpp/templates/cpp_string_header_template.mustache new file mode 100644 index 0000000..acec4ba --- /dev/null +++ b/cimgen/languages/cpp/templates/cpp_string_header_template.mustache @@ -0,0 +1,37 @@ +#ifndef {{class_name}}_H +#define {{class_name}}_H +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ + +#include +#include +#include + +namespace CIMPP +{ +{{#class_comment}} + /* + {{{class_comment}}} + */ +{{/class_comment}} + class {{class_name}} + { + public: + {{class_name}}() : initialized(false) {} + {{class_name}}(const std::string& value) : value(value), initialized(true) {} + + {{class_name}}& operator=(const std::string &rop); + operator std::string() const; + + std::string value; + bool initialized; + + static const char debugName[]; + const char* debugString() const; + + friend std::istream& operator>>(std::istream& lop, {{class_name}}& rop); + friend std::ostream& operator<<(std::ostream& os, const {{class_name}}& obj); + }; +} +#endif diff --git a/cimgen/languages/cpp/templates/cpp_string_object_template.mustache b/cimgen/languages/cpp/templates/cpp_string_object_template.mustache new file mode 100644 index 0000000..0743449 --- /dev/null +++ b/cimgen/languages/cpp/templates/cpp_string_object_template.mustache @@ -0,0 +1,49 @@ +/* +Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen +*/ +#include "{{class_name}}.hpp" + +#include "../src/CIMExceptions.hpp" + +using namespace CIMPP; + +{{class_name}}& {{class_name}}::operator=(const std::string& rop) +{ + value = rop; + initialized = true; + return *this; +} + +{{class_name}}::operator std::string() const +{ + if (!initialized) + { + throw new ReadingUninitializedField(); + } + return value; +} + +const char {{class_name}}::debugName[] = "{{class_name}}"; +const char* {{class_name}}::debugString() const +{ + return {{class_name}}::debugName; +} + +namespace CIMPP +{ + std::istream& operator>>(std::istream& lop, {{class_name}}& rop) + { + lop >> rop.value; + rop.initialized = true; + return lop; + } + + std::ostream& operator<<(std::ostream& os, const {{class_name}}& obj) + { + if (obj.initialized) + { + os << obj.value; + } + return os; + } +} diff --git a/cimgen/languages/java/lang_pack.py b/cimgen/languages/java/lang_pack.py index fd671f0..2e088a6 100644 --- a/cimgen/languages/java/lang_pack.py +++ b/cimgen/languages/java/lang_pack.py @@ -47,21 +47,17 @@ def run_template(output_path, class_details): class_details["primitives"] = [] for attr in class_details["attributes"]: - if attr["is_primitive_attribute"] or attr["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attr): class_details["primitives"].append(attr) - if class_details["is_a_float_class"]: + if class_details["is_a_datatype_class"] or class_details["class_name"] in ("Float", "Decimal"): templates = float_template_files elif class_details["is_an_enum_class"]: templates = enum_template_files else: templates = template_files - if ( - class_details["class_name"] == "Integer" - or class_details["class_name"] == "Boolean" - or class_details["class_name"] == "Date" - ): + if class_details["class_name"] in ("String", "Integer", "Boolean", "Date"): # These classes are defined already # We have to implement operators for them return @@ -107,7 +103,7 @@ def create_assign(text, render): attribute_json = eval(attribute_txt) assign = "" _class = attribute_json["attribute_class"] - if not (attribute_json["is_primitive_attribute"] or attribute_json["is_enum_attribute"]): + if not _attribute_is_primitive_or_datatype_or_enum(attribute_json): return "" label_without_keyword = attribute_json["label"] if label_without_keyword == "switch": @@ -144,7 +140,7 @@ def attribute_decl(text, render): def _attribute_decl(attribute): _class = attribute["attribute_class"] - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): return _class if attribute["is_list_attribute"]: return "List<" + _class + ">" @@ -161,7 +157,7 @@ def _create_attribute_includes(text, render): if jsonStringNoHtmlEsc is not None and jsonStringNoHtmlEsc != "": attributes = json.loads(jsonStringNoHtmlEsc) for attribute in attributes: - if attribute["is_primitive_attribute"] or attribute["is_enum_attribute"]: + if _attribute_is_primitive_or_datatype_or_enum(attribute): unique[attribute["attribute_class"]] = True for clarse in unique: if clarse != "String": @@ -210,6 +206,10 @@ def set_default(dataType): return "nullptr" +def _attribute_is_primitive_or_datatype_or_enum(attribute: dict) -> bool: + return attribute["is_primitive_attribute"] or attribute["is_datatype_attribute"] or attribute["is_enum_attribute"] + + # The code below this line is used after the main cim_generate phase to generate # two include files. They are called CIMClassList.hpp and IEC61970.hpp, and # contain the list of the class files and the list of define functions that add diff --git a/cimgen/languages/javascript/lang_pack.py b/cimgen/languages/javascript/lang_pack.py index f216816..6a00d23 100644 --- a/cimgen/languages/javascript/lang_pack.py +++ b/cimgen/languages/javascript/lang_pack.py @@ -76,7 +76,9 @@ def get_class_location(class_name, class_map, version): # NOSONAR def select_primitive_render_function(class_details): class_name = class_details["class_name"] render = "" - if class_details["is_a_float_class"]: + if class_details["is_a_datatype_class"]: + render = aggregateRenderer["renderFloat"] + elif class_name in ("Float", "Decimal"): render = aggregateRenderer["renderFloat"] elif class_name == "String": render = aggregateRenderer["renderString"] @@ -88,9 +90,6 @@ def select_primitive_render_function(class_details): elif class_name == "DateTime": # TODO: Implementation Required! render = aggregateRenderer["renderString"] - elif class_name == "Decimal": - # TODO: Implementation Required! - render = aggregateRenderer["renderString"] elif class_name == "Integer": # TODO: Implementation Required! render = aggregateRenderer["renderString"] @@ -102,6 +101,8 @@ def select_primitive_render_function(class_details): # This is the function that runs the template. def run_template(output_path, class_details): + if class_details["class_name"] == "String": + return for index, attribute in enumerate(class_details["attributes"]): if not attribute["is_used"]: @@ -156,8 +157,7 @@ def _create_cgmes_profile(output_path: str, profile_details: list, cim_namespace def _get_class_type(class_details): - class_name = class_details["class_name"] - if class_details["is_a_float_class"] or class_name in ("String", "Boolean", "Integer"): + if class_details["is_a_primitive_class"] or class_details["is_a_datatype_class"]: return "primitive" if class_details["is_an_enum_class"]: return "enum" diff --git a/cimgen/languages/modernpython/lang_pack.py b/cimgen/languages/modernpython/lang_pack.py index c4f9e83..99685bf 100644 --- a/cimgen/languages/modernpython/lang_pack.py +++ b/cimgen/languages/modernpython/lang_pack.py @@ -47,7 +47,7 @@ def get_class_location(class_name, class_map, version): # NOSONAR partials = {} -# called by chevron, text contains the label {{dataType}}, which is evaluated by the renderer (see class template) +# called by chevron, text contains the attribute infos, which are evaluated by the renderer (see class template) def _set_default(text, render): return _get_type_and_default(text, render)[1] @@ -56,32 +56,29 @@ def _set_type(text, render): return _get_type_and_default(text, render)[0] -def _get_type_and_default(text, renderer) -> tuple[str, str]: - result = renderer(text) - # the field {{dataType}} either contains the multiplicity of an attribute if it is a reference or otherwise the - # datatype of the attribute. If no datatype is set and there is also no multiplicity entry for an attribute, the - # default value is set to None. The multiplicity is set for all attributes, but the datatype is only set for basic - # data types. If the data type entry for an attribute is missing, the attribute contains a reference and therefore - # the default value is either None or [] depending on the multiplicity. See also write_python_files - # The default will be copied as-is, hence the possibility to have default or default_factory. - if result in ["M:1", "M:0..1", "M:1..1", ""]: +def _get_type_and_default(text, render) -> tuple[str, str]: + attribute_txt = render(text) + attribute_json = eval(attribute_txt) + if attribute_json["is_class_attribute"]: return ("Optional[str]", "default=None") - elif result in ["M:0..n", "M:1..n"] or "M:" in result: + elif attribute_json["is_list_attribute"]: return ("list", "default_factory=list") - - result = result.split("#")[1] - if result in ["integer", "Integer"]: + elif attribute_json["is_datatype_attribute"]: + return ("float", "default=0.0") + elif attribute_json["attribute_class"] in ("Float", "Decimal"): + return ("float", "default=0.0") + elif attribute_json["attribute_class"] == "Integer": return ("int", "default=0") - elif result in ["String", "DateTime", "Date"]: - return ("str", 'default=""') - elif result == "Boolean": + elif attribute_json["attribute_class"] == "Boolean": return ("bool", "default=False") else: - # everything else should be a float - return ("float", "default=0.0") + # everything else should be a string + return ("str", 'default=""') def run_template(output_path, class_details): + if class_details["is_a_primitive_class"] or class_details["is_a_datatype_class"]: + return for template_info in template_files: resource_file = Path( os.path.join( diff --git a/cimgen/languages/modernpython/templates/cimpy_class_template.mustache b/cimgen/languages/modernpython/templates/cimpy_class_template.mustache index 08e125d..58ba391 100644 --- a/cimgen/languages/modernpython/templates/cimpy_class_template.mustache +++ b/cimgen/languages/modernpython/templates/cimpy_class_template.mustache @@ -23,8 +23,8 @@ class {{class_name}}({{sub_class_of}}): """ {{#attributes}} - {{label}}: {{#setType}}{{dataType}}{{/setType}} = Field( - {{#setDefault}}{{dataType}}{{/setDefault}}, + {{label}}: {{#setType}}{{.}}{{/setType}} = Field( + {{#setDefault}}{{.}}{{/setDefault}}, json_schema_extra={ "in_profiles": [ {{#attr_origin}} @@ -33,6 +33,7 @@ class {{class_name}}({{sub_class_of}}): ], "is_used": {{#is_used}}True{{/is_used}}{{^is_used}}False{{/is_used}}, "is_class_attribute": {{#is_class_attribute}}True{{/is_class_attribute}}{{^is_class_attribute}}False{{/is_class_attribute}}, + "is_datatype_attribute": {{#is_datatype_attribute}}True{{/is_datatype_attribute}}{{^is_datatype_attribute}}False{{/is_datatype_attribute}}, "is_enum_attribute": {{#is_enum_attribute}}True{{/is_enum_attribute}}{{^is_enum_attribute}}False{{/is_enum_attribute}}, "is_list_attribute": {{#is_list_attribute}}True{{/is_list_attribute}}{{^is_list_attribute}}False{{/is_list_attribute}}, "is_primitive_attribute": {{#is_primitive_attribute}}True{{/is_primitive_attribute}}{{^is_primitive_attribute}}False{{/is_primitive_attribute}}, diff --git a/cimgen/languages/modernpython/utils/export_template.mustache b/cimgen/languages/modernpython/utils/export_template.mustache index 4055d9e..410a266 100644 --- a/cimgen/languages/modernpython/utils/export_template.mustache +++ b/cimgen/languages/modernpython/utils/export_template.mustache @@ -13,6 +13,9 @@ {{#is_class_attribute}} {{/is_class_attribute}} + {{#is_datatype_attribute}} + {{value}} + {{/is_datatype_attribute}} {{#is_enum_attribute}} {{/is_enum_attribute}} @@ -31,6 +34,9 @@ {{#is_class_attribute}} {{/is_class_attribute}} + {{#is_datatype_attribute}} + {{value}} + {{/is_datatype_attribute}} {{#is_enum_attribute}} {{/is_enum_attribute}} diff --git a/cimgen/languages/modernpython/utils/writer.py b/cimgen/languages/modernpython/utils/writer.py index 89abb2e..729a3d1 100644 --- a/cimgen/languages/modernpython/utils/writer.py +++ b/cimgen/languages/modernpython/utils/writer.py @@ -190,6 +190,7 @@ def get_attribute_infos(obj: Base) -> dict[str, dict[str, object]]: "namespace": extra.get("namespace", obj.namespace), "value": getattr(obj, attr), "is_class_attribute": extra.get("is_class_attribute"), + "is_datatype_attribute": extra.get("is_datatype_attribute"), "is_enum_attribute": extra.get("is_enum_attribute"), "is_list_attribute": extra.get("is_list_attribute"), "is_primitive_attribute": extra.get("is_primitive_attribute"), diff --git a/cimgen/languages/python/lang_pack.py b/cimgen/languages/python/lang_pack.py index 08167e0..a5af5ab 100644 --- a/cimgen/languages/python/lang_pack.py +++ b/cimgen/languages/python/lang_pack.py @@ -74,6 +74,8 @@ def _set_default(text, render): def run_template(output_path, class_details): + if class_details["class_name"] == "String": + return for template_info in template_files: class_file = os.path.join(output_path, class_details["class_name"] + template_info["ext"]) _write_templated_file(class_file, class_details, template_info["filename"]) diff --git a/pyproject.toml b/pyproject.toml index 3c4e017..1f2f8c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,8 @@ dependencies = [ "xmltodict >= 0.13.0, < 1", "chevron >= 0.14.0, < 1", "pydantic < 2", - "beautifulsoup4 >= 4.12.2, < 5" + "beautifulsoup4 >= 4.12.2, < 5", + "setuptools" ] requires-python = ">=3.11"