1 /*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <audio_utils/StringUtils.h>
18 #include <charconv>
19
20 namespace android::audio_utils::stringutils {
21
22 // Moved from frameworks/av/services/mediametrics
23
tokenizer(std::string::const_iterator & it,const std::string::const_iterator & end,const char * reserved)24 std::string tokenizer(std::string::const_iterator& it,
25 const std::string::const_iterator& end, const char* reserved)
26 {
27 // consume leading white space
28 for (; it != end && std::isspace(*it); ++it);
29 if (it == end) return {};
30
31 auto start = it;
32 // parse until we hit a reserved keyword or space
33 if (strchr(reserved, *it)) return {start, ++it};
34 for (;;) {
35 ++it;
36 if (it == end || std::isspace(*it) || strchr(reserved, *it)) return {start, it};
37 }
38 }
39
split(const std::string & flags,const char * delim)40 std::vector<std::string> split(const std::string& flags, const char* delim)
41 {
42 std::vector<std::string> result;
43 for (auto it = flags.begin(); ; ) {
44 auto flag = tokenizer(it, flags.end(), delim);
45 if (flag.empty() || !std::isalnum(flag[0])) return result;
46 result.emplace_back(std::move(flag));
47
48 // look for the delimiter and discard
49 auto token = tokenizer(it, flags.end(), delim);
50 if (token.size() != 1 || strchr(delim, token[0]) == nullptr) return result;
51 }
52 }
53
parseVector(const std::string & str,std::vector<int32_t> * vector)54 bool parseVector(const std::string& str, std::vector<int32_t>* vector) {
55 std::vector<int32_t> values;
56 const char* p = str.c_str();
57 const char* last = p + str.size();
58 while (p != last) {
59 if (*p == ',' || *p == '{' || *p == '}') {
60 p++;
61 }
62 int32_t value = -1;
63 auto [ptr, error] = std::from_chars(p, last, value);
64 if (error == std::errc::invalid_argument || error == std::errc::result_out_of_range) {
65 return false;
66 }
67 p = ptr;
68 values.push_back(value);
69 }
70 *vector = std::move(values);
71 return true;
72 }
73
74 std::vector<std::pair<std::string, std::string>>
getDeviceAddressPairs(const std::string & devices)75 getDeviceAddressPairs(const std::string& devices)
76 {
77 std::vector<std::pair<std::string, std::string>> result;
78
79 // Currently, the device format is
80 //
81 // devices = device_addr OR device_addr|devices
82 // device_addr = device OR (device, addr)
83 //
84 // EXAMPLE:
85 // device1|(device2, addr2)|...
86
87 static constexpr char delim[] = "()|,";
88 for (auto it = devices.begin(); ; ) {
89 std::string address;
90 std::string device = tokenizer(it, devices.end(), delim);
91 if (device.empty()) return result;
92 if (device == "(") { // it is a pair otherwise we consider it a device
93 device = tokenizer(it, devices.end(), delim); // get actual device
94 auto token = tokenizer(it, devices.end(), delim);
95 if (token != ",") return result; // malformed, must have a comma
96
97 // special handling here for empty addresses
98 address = tokenizer(it, devices.end(), delim);
99 if (address.empty()) return result;
100 if (address == ")") { // no address, just the ")"
101 address.clear();
102 } else {
103 token = tokenizer(it, devices.end(), delim);
104 if (token != ")") return result;
105 }
106 }
107 // misaligned token, device must start alphanumeric.
108 if (!std::isalnum(device[0])) return result;
109
110 result.emplace_back(std::move(device), std::move(address));
111
112 auto token = tokenizer(it, devices.end(), delim);
113 if (token != "|") return result; // this includes end of string detection
114 }
115 }
116
changeNameFormat(const std::string & name,NameFormat format)117 std::string changeNameFormat(const std::string& name, NameFormat format) {
118 std::string s;
119
120 char prevAlphaNum = 0; // last alphanum encountered, 0 starts new name.
121 for (auto it = name.begin(); it != name.end(); ++it) {
122
123 // underscores
124 bool prevUnderscore = false;
125 bool firstCharOfWord = false;
126 if (*it == '_') { // handle multiple _.
127 do {
128 ++it;
129 if (it == name.end()) return s; // trailing '_' stripped.
130 } while (*it == '_');
131 firstCharOfWord = true;
132 prevUnderscore = true;
133 }
134
135 // a digit
136 if (isdigit(*it)) {
137 if (prevUnderscore &&
138 ((format == NameFormat::kFormatLowerSnakeCase
139 || format == NameFormat::kFormatUpperSnakeCase) // preserve underscore
140 || (prevAlphaNum != 0 && isdigit(prevAlphaNum)))) {
141 s.push_back('_'); // do not concatenate 899_100 -> 899100, leave _
142 }
143 s.push_back(*it);
144 prevAlphaNum = *it;
145 continue;
146 }
147
148 // a non-alpha sequence. we copy as if '.' or ' '
149 if (!isalpha(*it)) {
150 s.push_back(*it);
151 prevAlphaNum = 0;
152 continue;
153 }
154
155 // an alpha char - determine whether to convert to upper or lower case.
156 if (!firstCharOfWord) {
157 if (prevAlphaNum == 0 || (prevAlphaNum
158 && (islower(prevAlphaNum) || isdigit(prevAlphaNum)) && isupper(*it))) {
159 firstCharOfWord = true;
160 }
161 }
162 switch (format) {
163 case NameFormat::kFormatLowerCamelCase:
164 if (firstCharOfWord && prevAlphaNum != 0) {
165 s.push_back(toupper(*it));
166 } else {
167 s.push_back(tolower(*it));
168 }
169 break;
170 case NameFormat::kFormatUpperCamelCase:
171 if (firstCharOfWord) {
172 s.push_back(toupper(*it));
173 } else {
174 s.push_back(tolower(*it));
175 }
176 break;
177 case NameFormat::kFormatLowerSnakeCase:
178 if (prevUnderscore || // preserve underscore
179 (firstCharOfWord && prevAlphaNum != 0 && !isdigit(prevAlphaNum))) {
180 s.push_back('_');
181 }
182 s.push_back(tolower(*it));
183 break;
184 case NameFormat::kFormatUpperSnakeCase:
185 if (prevUnderscore || // preserve underscore
186 (firstCharOfWord && prevAlphaNum != 0 && !isdigit(prevAlphaNum))) {
187 s.push_back('_');
188 }
189 s.push_back(toupper(*it));
190 break;
191 default:
192 s.push_back(*it);
193 break;
194 }
195 prevAlphaNum = *it;
196 }
197 return s;
198 }
199
200 } // namespace android::audio_utils::stringutils
201