"""String utility functions."""
from __future__ import annotations
from ..exceptions import RobotValidationError, ValidationErrorCode
[docs]
def sanitize_name(name: str | None, allow_hyphen: bool = True) -> str:
"""Sanitize a name for robot model and Python identifier compatibility.
Replaces invalid characters with underscores and ensures it doesn't
start with a digit.
Args:
name: Original name
allow_hyphen: Whether to allow hyphens (valid in many formats like URDF/SDF, invalid in Python)
Returns:
Sanitized name
"""
if not name:
return ""
# Prevent ReDoS: limit input length before processing
if len(name) > 1000:
raise RobotValidationError(
ValidationErrorCode.OUT_OF_RANGE,
f"Name length {len(name)} exceeds 1000 characters",
target="NameLength",
value=len(name),
)
# Replace spaces with underscores
name = name.replace(" ", "_")
# Character iteration (safer than regex)
allowed_special = ("_", "-") if allow_hyphen else ("_",)
sanitized = "".join(c if c.isalnum() or c in allowed_special else "_" for c in name)
# Ensure it doesn't start with a digit
if sanitized and sanitized[0].isdigit():
sanitized = f"_{sanitized}"
return sanitized
def is_valid_name(name: str, allow_hyphen: bool = True) -> bool:
"""Check if a name is valid for robot components.
A valid name:
- Is not empty
- Does not start with a digit
- Contains only alphanumeric characters, underscores, and optionally hyphens
Args:
name: Name to validate
allow_hyphen: Whether to allow hyphens (valid in URDF/SDF, invalid in Python)
Returns:
True if name is valid, False otherwise
Examples:
>>> is_valid_name("base_link")
True
>>> is_valid_name("base-link")
True
>>> is_valid_name("base-link", allow_hyphen=False)
False
>>> is_valid_name("2nd_link")
False
>>> is_valid_name("base link")
False
"""
if not name:
return False
# Must not start with a digit
if name[0].isdigit():
return False
# All characters must be alphanumeric or allowed special chars
allowed_special = ("_", "-") if allow_hyphen else ("_",)
return all(c.isalnum() or c in allowed_special for c in name)
def format_scientific(value: float) -> str:
"""Format a float to scientific notation for UI display."""
return f"{value:.2e}"
def parse_scientific(value: str, fallback: float) -> float:
"""Parse a scientific notation string back to float."""
try:
return float(value)
except ValueError:
return fallback