Goby3 3.7.0
2026.11.12
Loading...
Searching...
No Matches
key_expression.h
Go to the documentation of this file.
1// Copyright 2026:
2// GobySoft, LLC (2013-)
3// Community contributors (see AUTHORS file)
4// File authors:
5// Toby Schneider <toby@gobysoft.org>
6//
7//
8// This file is part of the Goby Underwater Autonomy Project Libraries
9// ("The Goby Libraries").
10//
11// The Goby Libraries are free software: you can redistribute them and/or modify
12// them under the terms of the GNU Lesser General Public License as published by
13// the Free Software Foundation, either version 2.1 of the License, or
14// (at your option) any later version.
15//
16// The Goby Libraries are distributed in the hope that they will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19// GNU Lesser General Public License for more details.
20//
21// You should have received a copy of the GNU Lesser General Public License
22// along with Goby. If not, see <http://www.gnu.org/licenses/>.
23
24#ifndef GOBY_ZENOH_TRANSPORT_DETAIL_KEY_EXPRESSION_H
25#define GOBY_ZENOH_TRANSPORT_DETAIL_KEY_EXPRESSION_H
26
27#include <string>
28#include <vector>
29
31
32namespace goby
33{
34namespace zenoh
35{
36namespace detail
37{
38
44inline bool is_reserved(char c)
45{
46 return c == '*' || c == '?' || c == '#' || c == '$' || c == '/' || c == '@' || c == '%';
47}
48
52constexpr const char* empty_chunk = "%";
53
55inline std::string escape_chunk(const std::string& chunk)
56{
57 if (chunk.empty())
58 return empty_chunk;
59
60 static constexpr const char hex[] = "0123456789ABCDEF";
61 std::string escaped;
62 escaped.reserve(chunk.size());
63 for (char c : chunk)
64 {
65 if (is_reserved(c))
66 {
67 escaped += '%';
68 escaped += hex[(static_cast<unsigned char>(c) >> 4) & 0xF];
69 escaped += hex[static_cast<unsigned char>(c) & 0xF];
70 }
71 else
72 {
73 escaped += c;
74 }
75 }
76 return escaped;
77}
78
80inline std::string unescape_chunk(const std::string& chunk)
81{
82 if (chunk == empty_chunk)
83 return {};
84
85 auto hex_value = [](char c) -> int
86 {
87 if (c >= '0' && c <= '9')
88 return c - '0';
89 if (c >= 'A' && c <= 'F')
90 return c - 'A' + 10;
91 if (c >= 'a' && c <= 'f')
92 return c - 'a' + 10;
93 return -1;
94 };
95
96 std::string unescaped;
97 unescaped.reserve(chunk.size());
98 for (std::size_t i = 0; i < chunk.size(); ++i)
99 {
100 int hi = -1, lo = -1;
101 if (chunk[i] == '%' && i + 2 < chunk.size() && (hi = hex_value(chunk[i + 1])) >= 0 &&
102 (lo = hex_value(chunk[i + 2])) >= 0)
103 {
104 unescaped += static_cast<char>((hi << 4) | lo);
105 i += 2;
106 }
107 else
108 {
109 unescaped += chunk[i];
110 }
111 }
112 return unescaped;
113}
114
121inline std::vector<std::string> split_identifier(const std::string& identifier)
122{
125 if (end == std::string::npos)
126 end = identifier.size();
127
128 std::size_t start = 0;
129 if (start < end && identifier[start] == delimiter)
130 ++start;
131 const bool framed = end > start && identifier[end - 1] == delimiter;
132 if (framed)
133 --end;
134
135 std::vector<std::string> components;
136 if (start == end && !framed)
137 return components;
138
139 while (true)
140 {
141 auto next = identifier.find(delimiter, start);
142 if (next == std::string::npos || next > end)
143 next = end;
144 components.push_back(identifier.substr(start, next - start));
145 if (next == end)
146 break;
147 start = next + 1;
148 }
149 return components;
150}
151
153inline std::string join_identifier(const std::vector<std::string>& components)
154{
156 std::string identifier;
157 for (const auto& component : components)
158 {
159 identifier += delimiter;
160 identifier += component;
161 }
162 identifier += delimiter;
163 return identifier;
164}
165
172inline std::string identifier_to_key(const std::string& root, const std::string& identifier,
173 bool wildcard_suffix)
174{
175 std::string key = root;
176 for (const auto& component : split_identifier(identifier))
177 {
178 key += '/';
179 key += escape_chunk(component);
180 }
181 if (wildcard_suffix)
182 key += "/**";
183 return key;
184}
185
189inline std::string key_to_identifier(const std::string& root, const std::string& key)
190{
191 if (key.size() <= root.size() || key.compare(0, root.size(), root) != 0 ||
192 key[root.size()] != '/')
193 return {};
194
195 std::vector<std::string> components;
196 std::size_t start = root.size() + 1;
197 while (start <= key.size())
198 {
199 auto next = key.find('/', start);
200 components.push_back(
201 unescape_chunk(key.substr(start, next == std::string::npos ? next : next - start)));
202 if (next == std::string::npos)
203 break;
204 start = next + 1;
205 }
206 return join_identifier(components);
207}
208
209} // namespace detail
210} // namespace zenoh
211} // namespace goby
212
213#endif
detail namespace with internal helper functions
Definition json.hpp:263
constexpr const char * empty_chunk
The chunk standing for an empty identifier component, which Zenoh would reject.
std::string identifier_to_key(const std::string &root, const std::string &identifier, bool wildcard_suffix)
Map a Goby identifier onto a key expression below root.
std::vector< std::string > split_identifier(const std::string &identifier)
Split a Goby interprocess identifier into its '/'-delimited components.
std::string escape_chunk(const std::string &chunk)
Percent-encode the characters a key expression chunk cannot carry.
std::string join_identifier(const std::vector< std::string > &components)
Rebuild a Goby identifier from key expression chunks, with the trailing delimiter Goby expects.
std::string key_to_identifier(const std::string &root, const std::string &key)
Invert identifier_to_key() for a received sample, whose key is always fully qualified.
bool is_reserved(char c)
Characters that cannot appear in a key expression chunk.
std::string unescape_chunk(const std::string &chunk)
Invert escape_chunk(). A trailing or malformed escape is passed through unchanged.
The global namespace for the Goby project.