diff --git a/include/sdf/Types.hh b/include/sdf/Types.hh index 493fcb9f9..d1bc921a4 100644 --- a/include/sdf/Types.hh +++ b/include/sdf/Types.hh @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -60,6 +61,8 @@ namespace sdf inline namespace SDF_VERSION_NAMESPACE { // + const std::string kSdfScopeDelimiter = "::"; + /// \brief Split a string using the delimiter in splitter. /// \param[in] str The string to split. /// \param[in] splitter The delimiter to use. @@ -228,6 +231,23 @@ namespace sdf /// \param[in] _in String to convert to lowercase /// \return Lowercase equilvalent of _in. std::string SDFORMAT_VISIBLE lowercase(const std::string &_in); + + /// \brief Split a name into a two strings based on the '::' delimeter + /// \param[in] _absoluteName The fully qualified absolute name + /// \return A pair with the absolute name minus the leaf node name, and the + /// leaf name + SDFORMAT_VISIBLE + std::pair SplitName( + const std::string &_absoluteName); + + /// \brief Join two strings with the '::' delimiter. + /// This checks for edge cases and is safe to use with any valid names + /// \param[in] _scopeName the left-hand-side component + /// \param[in] _localName the right-hand-side component + /// \return A full string with the names joined by the '::' delimeter. + SDFORMAT_VISIBLE + std::string JoinName( + const std::string &_scopeName, const std::string &_localName); } } #endif diff --git a/src/Types.cc b/src/Types.cc index ff724394b..278497796 100644 --- a/src/Types.cc +++ b/src/Types.cc @@ -88,5 +88,60 @@ std::ostream &operator<<(std::ostream &_out, const sdf::Errors &_errs) } return _out; } + +// Split a given absolute name into the parent model name and the local name. +// If the give name is not scoped, this will return an empty string for the +// parent model name and the given name as the local name. +std::pair SplitName( + const std::string &_absoluteName) +{ + const auto pos = _absoluteName.rfind(kSdfScopeDelimiter); + if (pos != std::string::npos) + { + const std::string first = _absoluteName.substr(0, pos); + const std::string second = + _absoluteName.substr(pos + kSdfScopeDelimiter.size()); + return {first, second}; + } + return {"", _absoluteName}; +} + +static bool EndsWithDelimiter(const std::string &_s) +{ + if (_s.size() < kSdfScopeDelimiter.size()) + return false; + + const size_t startPosition = _s.size() - kSdfScopeDelimiter.size(); + return _s.compare( + startPosition, kSdfScopeDelimiter.size(), kSdfScopeDelimiter) == 0; +} + +static bool StartsWithDelimiter(const std::string &_s) +{ + if (_s.size() < kSdfScopeDelimiter.size()) + return false; + + return _s.compare(0, kSdfScopeDelimiter.size(), kSdfScopeDelimiter) == 0; +} + +// Join a scope name prefix with a local name using the scope delimeter +std::string JoinName( + const std::string &_scopeName, const std::string &_localName) +{ + if (_scopeName.empty()) + return _localName; + if (_localName.empty()) + return _scopeName; + + const bool scopeNameEndsWithDelimiter = EndsWithDelimiter(_scopeName); + const bool localNameStartsWithDelimiter = StartsWithDelimiter(_localName); + + if (scopeNameEndsWithDelimiter && localNameStartsWithDelimiter) + return _scopeName + _localName.substr(kSdfScopeDelimiter.size()); + else if (scopeNameEndsWithDelimiter || localNameStartsWithDelimiter) + return _scopeName + _localName; + else + return _scopeName + kSdfScopeDelimiter + _localName; +} } } diff --git a/src/Types_TEST.cc b/src/Types_TEST.cc index bf1f80930..a486025d8 100644 --- a/src/Types_TEST.cc +++ b/src/Types_TEST.cc @@ -120,6 +120,85 @@ TEST(Types, ErrorsOutputStream) EXPECT_EQ(expected, output.str()); } +TEST(Types, SplitName) +{ + { + const auto[basePath, tipName] = sdf::SplitName("a::b"); + EXPECT_EQ(basePath, "a"); + EXPECT_EQ(tipName, "b"); + } + { + const auto[basePath, tipName] = sdf::SplitName("a::b::c"); + EXPECT_EQ(basePath, "a::b"); + EXPECT_EQ(tipName, "c"); + } + { + const auto[basePath, tipName] = sdf::SplitName("b"); + EXPECT_EQ(basePath, ""); + EXPECT_EQ(tipName, "b"); + } + { + const auto[basePath, tipName] = sdf::SplitName("a::b::"); + EXPECT_EQ(basePath, "a::b"); + EXPECT_EQ(tipName, ""); + } + { + const auto[basePath, tipName] = sdf::SplitName("::b"); + EXPECT_EQ(basePath, ""); + EXPECT_EQ(tipName, "b"); + } + { + const auto[basePath, tipName] = sdf::SplitName(""); + EXPECT_EQ(basePath, ""); + EXPECT_EQ(tipName, ""); + } + { + const auto[basePath, tipName] = sdf::SplitName("a::b::c::d"); + EXPECT_EQ(basePath, "a::b::c"); + EXPECT_EQ(tipName, "d"); + } +} + +TEST(Types, JoinName) +{ + { + const auto joinedName = sdf::JoinName("a", "b"); + EXPECT_EQ(joinedName, "a::b"); + } + { + const auto joinedName = sdf::JoinName("a::b", "c"); + EXPECT_EQ(joinedName, "a::b::c"); + } + { + const auto joinedName = sdf::JoinName("a", "b::c"); + EXPECT_EQ(joinedName, "a::b::c"); + } + { + const auto joinedName = sdf::JoinName("a::", "b"); + EXPECT_EQ(joinedName, "a::b"); + } + { + const auto joinedName = sdf::JoinName("a", "::b"); + EXPECT_EQ(joinedName, "a::b"); + } + { + const auto joinedName = sdf::JoinName("a::", "::b"); + EXPECT_EQ(joinedName, "a::b"); + } + { + const auto joinedName = sdf::JoinName("", "b"); + EXPECT_EQ(joinedName, "b"); + } + { + const auto joinedName = sdf::JoinName("a", ""); + EXPECT_EQ(joinedName, "a"); + } + { + const auto joinedName = sdf::JoinName("", ""); + EXPECT_EQ(joinedName, ""); + } +} + ///////////////////////////////////////////////// /// Main int main(int argc, char **argv)