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