1 /*
2 * Copyright (C) 2016 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 "StringHelper.h"
18
19 #include <cctype>
20 #include <regex>
21 #include <sstream>
22 #include <string>
23 #include <vector>
24
25 #include <android-base/logging.h>
26 #include <android-base/macros.h>
27
28 #define UPPERCASE "[A-Z0-9]"
29 #define LOWERCASE "[a-z][a-z0-9]*"
30 #define NUMCASE "[0-9]"
31 #define CAPCASE "[A-Z][a-z][a-z0-9]*"
32 static const std::regex kStartUppercase("^" UPPERCASE);
33 static const std::regex kStartLowercase("^" LOWERCASE);
34 static const std::regex kStartCapcase("^" CAPCASE);
35 static const std::regex kStartNumcase("^" CAPCASE);
36
37 namespace android {
38
Uppercase(const std::string & in)39 std::string StringHelper::Uppercase(const std::string &in) {
40 std::string out{in};
41
42 for (auto &ch : out) {
43 ch = toupper(ch);
44 }
45
46 return out;
47 }
48
Lowercase(const std::string & in)49 std::string StringHelper::Lowercase(const std::string &in) {
50 std::string out{in};
51
52 for (auto &ch : out) {
53 ch = tolower(ch);
54 }
55
56 return out;
57 }
58
Capitalize(const std::string & in)59 std::string StringHelper::Capitalize(const std::string &in) {
60 std::string out{in};
61
62 if(!out.empty()) {
63 out[0] = toupper(out[0]);
64 }
65
66 return out;
67 }
68
69 // Combines multiple single character upper case tokens together
70 // {"U", "I", "Error"} becomes {"UI", "Error"}
combineSingleCharTokens(const std::vector<std::string> & from,std::vector<std::string> * to)71 static void combineSingleCharTokens(const std::vector<std::string>& from,
72 std::vector<std::string>* to) {
73 std::string current;
74 for (const std::string& str : from) {
75 if (str.size() == 1 && (isupper(str[0]) || isdigit(str[0]))) {
76 current += str;
77 } else {
78 if (!current.empty()) {
79 to->push_back(current);
80 current = "";
81 }
82
83 to->push_back(str);
84 }
85 }
86
87 if (!current.empty()) to->push_back(current);
88 }
89
90 // Tokenizes strings first based on "_"s and then based on case
91 // PascalCase (CAPCASE) regex is given the highest priority and the remaining uppercase characters
92 // are grouped together. Digits are added to the preceding group, whichever it may be.
93 // Ipv4Addr => {"Ipv4", "Addr"}, V3Bool => {"V3", "Bool"}
Tokenize(const std::string & in,std::vector<std::string> * vec)94 void StringHelper::Tokenize(const std::string& in, std::vector<std::string>* vec) {
95 vec->clear();
96 if (in.empty()) return;
97
98 if (in.find('_') != std::string::npos) {
99 std::vector<std::string> snakeCaseComponents;
100 SplitString(in, '_', &snakeCaseComponents);
101 for (const std::string& comp : snakeCaseComponents) {
102 std::vector<std::string> tokens;
103 Tokenize(comp, &tokens);
104
105 vec->insert(vec->end(), tokens.begin(), tokens.end());
106 }
107
108 return;
109 }
110
111 std::smatch match;
112 std::string copy(in);
113 std::vector<std::string> matches;
114 std::vector<std::string> tmpVec;
115 while (!copy.empty()) {
116 if (std::regex_search(copy, match, kStartLowercase)) matches.push_back(match.str(0));
117 if (std::regex_search(copy, match, kStartCapcase)) matches.push_back(match.str(0));
118 if (std::regex_search(copy, match, kStartUppercase)) matches.push_back(match.str(0));
119 if (std::regex_search(copy, match, kStartNumcase)) matches.push_back(match.str(0));
120
121 if (!matches.empty()) {
122 std::string& maxmatch = matches[0];
123 for (std::string& match : matches)
124 if (match.length() > maxmatch.length()) maxmatch = match;
125 tmpVec.push_back(maxmatch);
126 copy = copy.substr(maxmatch.length());
127 matches.clear();
128 } else {
129 LOG(WARNING) << "Could not stylize \"" << in << "\"";
130 // don't know what to do, so push back the rest of the string.
131 tmpVec.push_back(copy);
132 }
133 }
134
135 combineSingleCharTokens(tmpVec, vec);
136 }
137
ToCamelCase(const std::string & in)138 std::string StringHelper::ToCamelCase(const std::string &in) {
139 std::vector<std::string> components;
140 Tokenize(in, &components);
141 if (components.empty()) {
142 if (!in.empty())
143 LOG(WARNING) << "Could not stylize \"" << in << "\"";
144 return in;
145 }
146 components[0] = Lowercase(components[0]);
147 for (size_t i = 1; i < components.size(); i++) {
148 components[i] = Capitalize(components[i]);
149 }
150 return JoinStrings(components, "");
151 }
152
ToPascalCase(const std::string & in)153 std::string StringHelper::ToPascalCase(const std::string &in) {
154 std::vector<std::string> components;
155 Tokenize(in, &components);
156 for (size_t i = 0; i < components.size(); i++) {
157 components[i] = Capitalize(components[i]);
158 }
159 return JoinStrings(components, "");
160 }
161
ToUpperSnakeCase(const std::string & in)162 std::string StringHelper::ToUpperSnakeCase(const std::string &in) {
163 std::vector<std::string> components;
164 Tokenize(in, &components);
165 for (size_t i = 0; i < components.size(); i++) {
166 components[i] = Uppercase(components[i]);
167 }
168 return JoinStrings(components, "_");
169 }
170
ToLowerSnakeCase(const std::string & in)171 std::string StringHelper::ToLowerSnakeCase(const std::string &in) {
172 std::vector<std::string> components;
173 Tokenize(in, &components);
174 for (size_t i = 0; i < components.size(); i++) {
175 components[i] = Lowercase(components[i]);
176 }
177 return JoinStrings(components, "_");
178 }
179
ToCase(StringHelper::Case c,const std::string & in)180 std::string StringHelper::ToCase(StringHelper::Case c, const std::string &in) {
181 switch(c) {
182 case kCamelCase:
183 return ToCamelCase(in);
184 case kPascalCase:
185 return ToPascalCase(in);
186 case kUpperSnakeCase:
187 return ToUpperSnakeCase(in);
188 case kLowerSnakeCase:
189 return ToLowerSnakeCase(in);
190 case kNoCase:
191 return in;
192 }
193 LOG(FATAL) << "Should not reach here.";
194 return in;
195 }
196
EndsWith(const std::string & in,const std::string & suffix)197 bool StringHelper::EndsWith(const std::string &in, const std::string &suffix) {
198 return in.size() >= suffix.size() &&
199 in.substr(in.size() - suffix.size()) == suffix;
200 }
201
StartsWith(const std::string & in,const std::string & prefix)202 bool StringHelper::StartsWith(const std::string &in, const std::string &prefix) {
203 return in.size() >= prefix.size() &&
204 in.substr(0, prefix.size()) == prefix;
205 }
206
RTrim(const std::string & in,const std::string & suffix)207 std::string StringHelper::RTrim(const std::string &in, const std::string &suffix) {
208 if (EndsWith(in, suffix)) {
209 return in.substr(0, in.size() - suffix.size());
210 }
211
212 return in;
213 }
214
LTrim(const std::string & in,const std::string & prefix)215 std::string StringHelper::LTrim(const std::string &in, const std::string &prefix) {
216 if (StartsWith(in, prefix)) {
217 return in.substr(prefix.size());
218 }
219
220 return in;
221 }
222
RTrimAll(const std::string & in,const std::string & suffix)223 std::string StringHelper::RTrimAll(const std::string &in, const std::string &suffix) {
224 if (suffix.empty()) {
225 return in;
226 }
227
228 std::string copy(in);
229 while (EndsWith(copy, suffix)) {
230 copy = copy.substr(0, copy.size() - suffix.size());
231 }
232
233 return copy;
234 }
235
LTrimAll(const std::string & in,const std::string & prefix)236 std::string StringHelper::LTrimAll(const std::string &in, const std::string &prefix) {
237 if (prefix.empty()) {
238 return in;
239 }
240
241 std::string copy(in);
242 while (StartsWith(copy, prefix)) {
243 copy = copy.substr(prefix.size());
244 }
245
246 return copy;
247 }
248
SplitString(const std::string & s,char c,std::vector<std::string> * components)249 void StringHelper::SplitString(
250 const std::string &s, char c, std::vector<std::string> *components) {
251 components->clear();
252
253 size_t startPos = 0;
254 size_t matchPos;
255 while ((matchPos = s.find(c, startPos)) != std::string::npos) {
256 components->push_back(s.substr(startPos, matchPos - startPos));
257 startPos = matchPos + 1;
258 }
259
260 if (startPos <= s.length()) {
261 components->push_back(s.substr(startPos));
262 }
263 }
264
JoinStrings(const std::vector<std::string> & components,const std::string & separator)265 std::string StringHelper::JoinStrings(
266 const std::vector<std::string> &components,
267 const std::string &separator) {
268 std::string out;
269 bool first = true;
270 for (const auto &component : components) {
271 if (!first) {
272 out += separator;
273 }
274 out += component;
275
276 first = false;
277 }
278
279 return out;
280 }
281
282 } // namespace android
283
284