xref: /aosp_15_r20/system/media/audio_utils/StringUtils.cpp (revision b9df5ad1c9ac98a7fefaac271a55f7ae3db05414)
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