Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/vss_tools/exporters/samm/helpers/data_types_and_units.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"Nm": get_unit_uri("newtonMetre"),
"N.m": get_unit_uri("newtonMetre"),
"V": get_unit_uri("volt"),
"celsius": get_unit_uri("degreeCelsius"),
"Celsius": get_unit_uri("degreeCelsius"),
"cm/s": get_unit_uri("centimetrePerSecond"),
"degree": get_unit_uri("degreeUnitOfAngle"),
"degrees": get_unit_uri("degreeUnitOfAngle"),
Expand All @@ -64,6 +64,7 @@
"mbar": get_unit_uri("millibar"),
"min": get_unit_uri("minuteUnitOfTime"),
"ml": get_unit_uri("millilitre"),
"ms": get_unit_uri("secondUnitOfTime"),
Comment thread
Kostadin-Ivanov marked this conversation as resolved.
"pa": get_unit_uri("pascal"),
"Pa": get_unit_uri("pascal"),
"percent": get_unit_uri("percent"),
Expand Down
74 changes: 51 additions & 23 deletions src/vss_tools/exporters/samm/helpers/string_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,23 @@ def str_to_lc_first(string_to_update: str) -> str:

- SomeOTHERstringToUpdate will be converted to: someOTHERstringToUpdate

NOTE:
CONSTANTS will be converted to ALL LOWER CASE so to preserve the constant casing.

Args:
string_to_update (str): The string to be updated.

Returns:
str: Returns a first character lower case string, with untouched remainder of the string_to_update.
Exception is when string_to_update is ALL UPPER CASE or set as CONSTANT.
"""

if len(string_to_update) > 0:
return f"{string_to_update[0].lower()}{string_to_update[1:]}"
if string_to_update.isupper():
# CONSTANTS will go all lower case
return string_to_update.lower()
else:
return f"{string_to_update[0].lower()}{string_to_update[1:]}"
else:
return string_to_update

Expand Down Expand Up @@ -57,48 +65,54 @@ def str_to_uc_first(string_to_update: str):
# Helper function, which will make sure that passed string_to_update
# will be converted in camel case,
# based on this guide: https://google.github.io/styleguide/javaguide.html#s5.3-camel-case
# NOTE: If the passed string is ALL UPPER or LOWER case, it will be returned as it is.
def str_to_camel_case(string_to_update: str) -> str:
updated_str = ""

string_to_update_length = len(string_to_update)

if string_to_update.isupper() or string_to_update.islower():
# ALL UPPER / LOWER Case strings will be returned as they are
return string_to_update

for char_index in range(0, string_to_update_length, 1):
char_to_read = string_to_update[char_index]
current_char = string_to_update[char_index]

if char_to_read.isnumeric() or len(updated_str) == 0:
if current_char.isnumeric() or len(updated_str) == 0:
# Read numeric characters and first one as they are
updated_str = updated_str + char_to_read
updated_str = updated_str + current_char

else:
# Handle any subsequent character in camel case
prev_char = updated_str[char_index - 1]
prev_char = string_to_update[char_index - 1]

next_index = char_index + 1
if string_to_update_length == next_index:
# Rollback next_index as we will get index out of boundaries error.
# In this case the next_char will be same as char_to_read.
# In this case the next_char will be same as current_char.
next_index = next_index - 1

next_char = string_to_update[next_index]

if (
char_to_read.isupper()
and (prev_char.isupper() or prev_char.islower() or len(prev_char.strip()) > 0)
and (next_char.isupper() or next_char.isnumeric())
current_char.isupper()
and len(prev_char.strip()) > 0
and prev_char.isupper()
and (len(next_char.strip()) == 0 or next_char.isupper())
):
# Convert case of char_to_read from UPPER to LOWER
char_to_read = char_to_read.lower()
# Convert case of current_char from UPPER to LOWER
current_char = current_char.lower()

elif len(prev_char.strip()) == 0 and next_char.islower():
# Convert case of char_to_read from LOWER to UPPER
char_to_read = char_to_read.upper()
elif current_char.islower() and len(prev_char.strip()) == 0 and next_char.islower():
# Convert case of current_char from LOWER to UPPER when prev was empty_string and next is lower
current_char = current_char.upper()

# ELSE: read char_to_read as it is
# ELSE: read current_char as it is

updated_str = updated_str + char_to_read
updated_str = updated_str + current_char

# Make sure to remove any empty space from updated_str
return updated_str.replace(" ", "")
# Make sure to remove any empty space or dot '.' characters from updated_str
return updated_str.replace(" ", "").replace(".", "")


def str_to_uc_first_camel_case(string_to_update: str):
Expand Down Expand Up @@ -132,12 +146,26 @@ def str_camel_case_split(string_to_update: str) -> str:
else:
# Read next character so to define whether to split or not
# NOTE: abbreviations in ALL UPPER CASE should not be split
next_index = char_index + 1
if string_to_update_length == next_index:
next_index = next_index - 1
next_char = string_to_update[next_index]
prev_char = None
if char_index >= 1:
prev_index = char_index - 1
prev_char = string_to_update[prev_index]

next_char = None
if (char_index + 1) < string_to_update_length:
next_char = string_to_update[char_index + 1]

if char.isupper() and next_char.islower():
if (
char.isupper()
and prev_char
and (
(prev_char.islower() or prev_char.isnumeric())
or (prev_char.isupper() and next_char and next_char.islower())
)
):
# Split between lower case character or number, followed by an upper one
# OR when a constant was followed by upper and lower case characters.
# Example: ABSIsEnabled should become: ABS Is Enabled
updated_str = updated_str + " " + char
else:
updated_str = updated_str + char
Expand Down
39 changes: 32 additions & 7 deletions src/vss_tools/exporters/samm/helpers/ttl_builder_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .data_types_and_units import DataTypes
from .namespaces import Namespaces, get_node_name_from_vspec_uri, get_vspec_uri
from .samm_concepts import SammCConcepts, SammConcepts, VSSConcepts
from .string_helper import str_camel_case_split, str_to_lc_first_camel_case, str_to_uc_first_camel_case
from .string_helper import str_camel_case_split, str_to_lc_first_camel_case, str_to_uc_first, str_to_uc_first_camel_case

#
# Builder helper, which provides a set of functions, to set up a TTL Graph,
Expand Down Expand Up @@ -91,16 +91,37 @@ def add_graph_node(graph: Graph, vss_node: VSSNode, is_aspect: bool) -> URIRef:

if is_aspect:
add_node_aspect(graph, vss_node, node_uri)

# ELSE: just build a simple property node as usual

# Preferred name should be white space in front of each upper case letter
# i.e. more human friendly / readable name
# EXAMPLE:
# node.name : IsStrongCrossWindDetected
# should be like: Is Strong Cross Wind Detected
__add_node_tuple(
graph, node_uri, SammConcepts.PREFERRED_NAME.uri, Literal(str_camel_case_split(vss_node.ttl_name), "en")
)
#
# In case when VSSNode ttl_name has been prefixed with Parent node name, we should preserve the parent name casing
# and still keep the human friendly format, as per above.
#
# EXAMPLE:
# node.name : IsEnabled
# node.ttl_name : AbsIsEnabled
# node.parent.name: ABS
# PREFERRED NAME : ABS Is Enabled
preferred_name = str_camel_case_split(vss_node.ttl_name)
if vss_node.name != vss_node.ttl_name:
if len(vss_node.ttl_name) > len(vss_node.name):
# Set preferred name by preserving VSSNode and its parent names' casing
parent_prefix = vss_helper.get_parent_prefix_for_ttl_name(vss_node, vss_node.name)

preferred_name = str_camel_case_split(parent_prefix + vss_node.name)

elif len(vss_node.ttl_name) == len(vss_node.name):
# VssNode TTL Name is same as its name, but they differ in casing
# => preserve VSSNode name casing and just split it
preferred_name = str_camel_case_split(vss_node.name)

__add_node_tuple(graph, node_uri, SammConcepts.PREFERRED_NAME.uri, Literal(preferred_name, "en"))

log.debug("Created graph node with URI: '%s'.\n", node_uri)

Expand Down Expand Up @@ -327,7 +348,9 @@ def add_node_leaf(graph: Graph, node_uri: URIRef, vss_node: VSSNode):
def add_node_leaf_constraint(graph: Graph, node_char_name: str, node_char_uri: URIRef, vss_node: VSSNode):
log.debug("Add leaf-node constraint")

constraint_name = str_to_uc_first_camel_case(vss_node.ttl_name + "Constraint")
constraint_name = str_to_uc_first_camel_case(
vss_node.ttl_name + str_to_uc_first(SammCConcepts.CONSTRAINT.vsso_name)
)
constraint_node_uri = get_vspec_uri(constraint_name)

# Default Constraint URI is for Range (min/max) constraints
Expand Down Expand Up @@ -371,7 +394,9 @@ def add_node_leaf_constraint(graph: Graph, node_char_name: str, node_char_uri: U

# Set the RegExp value for constraint_node_uri

base_c_name = str_to_uc_first_camel_case(vss_node.ttl_name + "BaseCharacteristic")
base_c_name = str_to_uc_first_camel_case(
vss_node.ttl_name + str_to_uc_first(SammCConcepts.BASE_CHARACTERISTICS.vsso_name)
)
base_c_uri = get_vspec_uri(base_c_name)

__add_node_tuple(graph, node_char_uri, SammCConcepts.BASE_CHARACTERISTICS.uri, base_c_uri)
Expand Down Expand Up @@ -473,7 +498,7 @@ def get_node_characteristic_name(node_uri: URIRef, has_limits: bool):
# Node characteristic name is based on the node property URI, and should be in the form:
# NodePropertyNameCharacteristic or NodePropertyNameTrait, in case if the node has some constraints
node_name = get_node_name_from_vspec_uri(node_uri)
characteristic_name_suffix = "Trait" if has_limits else "Characteristic"
characteristic_name_suffix = SammCConcepts.TRAIT.vsso_name if has_limits else SammConcepts.CHARACTERISTIC.vsso_name

return str_to_uc_first_camel_case(node_name + characteristic_name_suffix)

Expand Down
47 changes: 33 additions & 14 deletions src/vss_tools/exporters/samm/helpers/vss_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


# A DICT collection of key => value entries, where:
# :key - vss_node.name of a node from provided VSSNote (tree) for parsing.
# :key - UPPER CASE vss_node.name of a node from provided VSSNote (tree) for parsing.
# :value - object of type: { counter: int, vss_paths: [str]}
# where:
# - counter - holds number of occurrences of the corresponding :key in the main VSSNode
Expand All @@ -52,13 +52,16 @@ def count_vss_tree_unique_node_names(vss_node: VSSNode) -> None:
# their number of occurrences and vss_paths for their duplicates if there is any.
# For more details, check comment for: top_vss_tree_unique_node_names field on lines: 33-44.
def populate_unique_node_names(node_names_dict: dict[str, Any], vss_node: VSSNode) -> None:
if not node_names_dict.get(vss_node.name):
# Make sure that VSSNode names are all in same case to avoid any mix of lower/upper case nodes with same name.
node_key = vss_node.name.upper()

if not node_names_dict.get(node_key):
# ADD vss_node to node_names_dict
node_names_dict.__setitem__(vss_node.name, {"counter": 1, "vss_paths": [vss_node.get_fqn()]})
node_names_dict.__setitem__(node_key, {"counter": 1, "vss_paths": [vss_node.get_fqn()]})
else:
# UPDATE vss_node counters in the node_names_dict
node_names_dict[vss_node.name]["counter"] += 1 # type: ignore
node_names_dict[vss_node.name]["vss_paths"].append(vss_node.get_fqn()) # type: ignore
node_names_dict[node_key]["counter"] += 1 # type: ignore
node_names_dict[node_key]["vss_paths"].append(vss_node.get_fqn()) # type: ignore

# Process vss_node children
if vss_node.children and len(vss_node.children) > 0:
Expand All @@ -82,12 +85,12 @@ def get_parent_prefix_for_ttl_name(vss_node: VSSNode, ttl_name: str, use_vehicle
# in order it can be loaded across different models.
#
# This is specially, when a user is using the split option
node_key = vss_node.name.upper()
if (
vss_node.parent.name != vss_node.parent.ttl_name
and top_vss_tree_unique_node_names[vss_node.name]["counter"] > 0
and top_vss_tree_unique_node_names[node_key]["counter"] > 0
and sum(
vss_node.parent.name in vss_path
for vss_path in top_vss_tree_unique_node_names[vss_node.name]["vss_paths"]
vss_node.parent.name in vss_path for vss_path in top_vss_tree_unique_node_names[node_key]["vss_paths"]
)
> 1
):
Expand Down Expand Up @@ -122,21 +125,37 @@ def set_ttl_name(vss_node: VSSNode, use_parent_prefix: bool, overwrite_ttl_name=
log.debug(" -- node ttl name: '%s' is already set.", vss_node.ttl_name)

else:
ttl_name = vss_node.ttl_name if vss_node.ttl_name and preserve_ttl_name else vss_node.name
ttl_name: str = vss_node.ttl_name if vss_node.ttl_name and preserve_ttl_name else vss_node.name

if not ttl_name.isupper():
# Set ttl_name to camel case if it was not a constant or ALL_UPPER_CASE
ttl_name = str_to_uc_first_camel_case(ttl_name)

# else: read it as it is

if use_parent_prefix:
parent_prefix = get_parent_prefix_for_ttl_name(vss_node, ttl_name)
vss_node.ttl_name = parent_prefix + ttl_name
parent_prefix: str = get_parent_prefix_for_ttl_name(vss_node, ttl_name)

if parent_prefix.isupper():
# If parent name is a CONSTANT or ALL UPPER CASE, set it to all lower case
# to preserve casing when converting it to UC First CamelCase
parent_prefix = parent_prefix.lower()

# else: read it as it is

# Make sure that parent prefix is UC First CamelCase
vss_node.__setattr__("ttl_name", str_to_uc_first_camel_case(parent_prefix) + ttl_name)

else:
vss_node.ttl_name = ttl_name
vss_node.__setattr__("ttl_name", ttl_name)

log.debug(" -- %s node ttl name: '%s'.", "updated" if overwrite_ttl_name else "added", vss_node.ttl_name)


# Helper function, to check whether to use parent prefix for a vss_node or not
def should_use_parent_prefix(vss_node: VSSNode) -> bool:
if top_vss_tree_unique_node_names[vss_node.name]["counter"] > 1 or (
node_key = vss_node.name.upper()
if top_vss_tree_unique_node_names[node_key]["counter"] > 1 or (
vss_node.is_leaf
and hasattr(vss_node.data, "datatype")
and vss_node.data.datatype is Datatypes.BOOLEAN
Expand Down Expand Up @@ -275,7 +294,7 @@ def get_node_description(vss_node: VSSNode) -> str:
spacer = spacer if description else " "

# Align 'Comment:' with 'Description:' and 'VSS path:'
description = f"{description}\n\nComment{' ' if description else ''}: {vss_node.data.comment}"
description = f"{description}\n\nComment{' ' if description else ''}: {vss_node.data.comment}"

if hasattr(vss_node.data, "unit") and vss_node.data.unit and len(vss_node.data.unit.strip()) > 0:
description = f"{description}\n\nUnit{' ' if description else ''}: {vss_node.data.unit}"
Expand Down
Loading