Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "stringutils.h"
23 :
24 : #include <cctype>
25 : #include <algorithm>
26 :
27 12 : bool muse::strings::replace(std::string& str, const std::string& from, const std::string& to)
28 : {
29 12 : size_t start_pos = str.find(from);
30 12 : if (start_pos == std::string::npos) {
31 10 : return false;
32 : }
33 2 : str.replace(start_pos, from.length(), to);
34 2 : return true;
35 : }
36 :
37 : namespace {
38 80333 : void split_simple(const std::string& str, std::vector<std::string>& out, const std::string& delim)
39 : {
40 80333 : if (delim.empty()) {
41 1 : out.push_back(str);
42 1 : return;
43 : }
44 :
45 80332 : std::size_t current, previous = 0;
46 80332 : current = str.find(delim);
47 80332 : std::size_t delimLen = delim.length();
48 :
49 247100 : while (current != std::string::npos) {
50 166768 : out.push_back(str.substr(previous, current - previous));
51 166768 : previous = current + delimLen;
52 166768 : current = str.find(delim, previous);
53 : }
54 80332 : out.push_back(str.substr(previous, current - previous));
55 : }
56 :
57 : //! Split that handles an escape character, which causes the following escape or separator to be emitted verbatim.
58 7 : void split_handle_escape(const std::string& str, std::vector<std::string>& out, const std::string& delim, const std::string& esc)
59 : {
60 7 : const size_t delimLen = delim.size();
61 7 : const size_t escLen = esc.size();
62 7 : std::string current;
63 :
64 138 : for (size_t i = 0; i < str.size();) {
65 131 : if (escLen > 0 && str.compare(i, escLen, esc) == 0) {
66 : // Escaped sequence: drop the escape, then emit the following escape
67 : // or separator token literally (mirrors what join() produced). Any
68 : // other (or dangling) escape consumes a single following character.
69 16 : i += escLen;
70 16 : if (str.compare(i, escLen, esc) == 0) {
71 5 : current += esc;
72 5 : i += escLen;
73 11 : } else if (str.compare(i, delimLen, delim) == 0) {
74 10 : current += delim;
75 10 : i += delimLen;
76 1 : } else if (i < str.size()) {
77 0 : current += str[i];
78 0 : ++i;
79 : }
80 115 : } else if (delimLen > 0 && str.compare(i, delimLen, delim) == 0) {
81 16 : out.push_back(current);
82 16 : current.clear();
83 16 : i += delimLen;
84 : } else {
85 99 : current += str[i];
86 99 : ++i;
87 : }
88 : }
89 7 : out.push_back(current);
90 7 : }
91 : }
92 :
93 80340 : void muse::strings::split(const std::string& str, std::vector<std::string>& out, const std::string& delim,
94 : const std::string& esc)
95 : {
96 80340 : if (esc.empty()) {
97 80333 : split_simple(str, out, delim);
98 : } else {
99 7 : split_handle_escape(str, out, delim, esc);
100 : }
101 80340 : }
102 :
103 : namespace {
104 30 : std::string join_simple(const std::vector<std::string>& strs, const std::string& sep)
105 : {
106 30 : std::string str;
107 30 : bool first = true;
108 73 : for (const std::string& s : strs) {
109 43 : if (!first) {
110 15 : str += sep;
111 : }
112 43 : first = false;
113 43 : str += s;
114 : }
115 30 : return str;
116 0 : }
117 :
118 : //! Join that prefixes any separator or escape characters in the input with the escape string.
119 8 : std::string join_handle_escape(const std::vector<std::string>& strs, const std::string& sep, const std::string& esc)
120 : {
121 8 : std::string str;
122 8 : bool first = true;
123 34 : for (const std::string& s : strs) {
124 26 : if (!first) {
125 18 : str += sep;
126 : }
127 26 : first = false;
128 145 : for (size_t i = 0; i < s.size(); ++i) {
129 119 : if (esc.size() > 0 && s.compare(i, esc.size(), esc) == 0) {
130 6 : str += esc;
131 6 : str += esc;
132 6 : i += esc.size() - 1;
133 113 : } else if (!sep.empty() && s.compare(i, sep.size(), sep) == 0) {
134 11 : str += esc;
135 11 : str += sep;
136 11 : i += sep.size() - 1;
137 : } else {
138 102 : str += s[i];
139 : }
140 : }
141 : }
142 8 : return str;
143 0 : }
144 : }
145 :
146 38 : std::string muse::strings::join(const std::vector<std::string>& strs, const std::string& sep, const std::string& esc)
147 : {
148 38 : if (esc.empty()) {
149 30 : return join_simple(strs, sep);
150 : } else {
151 8 : return join_handle_escape(strs, sep, esc);
152 : }
153 : }
154 :
155 94 : void muse::strings::ltrim(std::string& s)
156 : {
157 94 : s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
158 94 : return !std::isspace(ch);
159 : }));
160 94 : }
161 :
162 94 : void muse::strings::rtrim(std::string& s)
163 : {
164 188 : s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
165 94 : return !std::isspace(ch);
166 188 : }).base(), s.end());
167 94 : }
168 :
169 94 : void muse::strings::trim(std::string& s)
170 : {
171 94 : ltrim(s);
172 94 : rtrim(s);
173 94 : }
174 :
175 34664 : std::string muse::strings::toLower(const std::string& source)
176 : {
177 34664 : std::string str = source;
178 34664 : std::for_each(str.begin(), str.end(), [](char& c) {
179 234782 : c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
180 234782 : });
181 34664 : return str;
182 : }
183 :
184 0 : std::string muse::strings::toUpper(const std::string& source)
185 : {
186 0 : std::string str = source;
187 0 : std::for_each(str.begin(), str.end(), [](char& c) {
188 0 : c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
189 0 : });
190 0 : return str;
191 : }
192 :
193 0 : bool muse::strings::startsWith(const std::string& str, const std::string& start)
194 : {
195 0 : if (str.size() < start.size()) {
196 0 : return false;
197 : }
198 :
199 0 : for (size_t i = 0; i < start.size(); ++i) {
200 0 : if (str.at(i) != start.at(i)) {
201 0 : return false;
202 : }
203 : }
204 :
205 0 : return true;
206 : }
207 :
208 46 : bool muse::strings::startsWith(const std::string& str, const std::string_view& start)
209 : {
210 46 : if (str.size() < start.size()) {
211 0 : return false;
212 : }
213 :
214 46 : for (size_t i = 0; i < start.size(); ++i) {
215 46 : if (str.at(i) != start.at(i)) {
216 46 : return false;
217 : }
218 : }
219 :
220 0 : return true;
221 : }
222 :
223 46 : bool muse::strings::startsWith(const std::string& str, const char* start)
224 : {
225 46 : return startsWith(str, std::string_view(start));
226 : }
227 :
228 0 : bool muse::strings::endsWith(const std::string& str, const std::string& ending)
229 : {
230 0 : if (ending.size() > str.size()) {
231 0 : return false;
232 : }
233 0 : std::string ss = str.substr(str.size() - ending.size());
234 0 : return ss == ending;
235 0 : }
236 :
237 0 : bool muse::strings::endsWith(const std::string& str, const std::string_view& ending)
238 : {
239 0 : if (ending.size() > str.size()) {
240 0 : return false;
241 : }
242 0 : std::string ss = str.substr(str.size() - ending.size());
243 0 : return ss == ending;
244 0 : }
245 :
246 0 : bool muse::strings::endsWith(const std::string& str, const char* end)
247 : {
248 0 : return endsWith(str, std::string_view(end));
249 : }
250 :
251 0 : std::string muse::strings::leftJustified(const std::string& val, size_t width)
252 : {
253 0 : std::string str;
254 0 : str.resize(width, ' ');
255 0 : size_t length = width < val.size() ? width : val.size();
256 0 : for (size_t i = 0; i < length; ++i) {
257 0 : str[i] = val[i];
258 : }
259 0 : return str;
260 0 : }
261 :
262 0 : bool muse::strings::lessThanCaseInsensitive(const std::string& lhs, const std::string& rhs)
263 : {
264 0 : int cmp = toLower(lhs).compare(toLower(rhs));
265 0 : if (cmp == 0) {
266 0 : return lhs < rhs;
267 : }
268 :
269 0 : return cmp < 0;
270 : }
271 :
272 0 : bool muse::strings::lessThanCaseInsensitive(const String& lhs, const String& rhs)
273 : {
274 0 : String lhsLower = lhs.toLower(), rhsLower = rhs.toLower();
275 0 : if (lhsLower == rhsLower) {
276 0 : return lhs < rhs;
277 : }
278 :
279 0 : return lhsLower < rhsLower;
280 0 : }
281 :
282 4337 : size_t muse::strings::levenshteinDistance(const std::string& s1, const std::string& s2)
283 : {
284 4337 : size_t N1 = s1.length();
285 4337 : size_t N2 = s2.length();
286 : size_t i, j;
287 4337 : std::vector<size_t> V(N2 + 1);
288 :
289 49046 : for (i = 0; i <= N2; i++) {
290 44709 : V[i] = i;
291 : }
292 :
293 33603 : for (i = 0; i < N1; i++) {
294 29266 : V[0] = i + 1;
295 29266 : size_t corner = i;
296 321845 : for (j = 0; j < N2; j++) {
297 292579 : size_t upper = V[j + 1];
298 292579 : if (s1[i] == s2[j]) {
299 22773 : V[j + 1] = corner;
300 : } else {
301 269806 : V[j + 1] = std::min(V[j], std::min(upper, corner)) + 1;
302 : }
303 292579 : corner = upper;
304 : }
305 : }
306 8674 : return V[N2];
307 4337 : }
|