/src/serenity/AK/String.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Array.h> |
8 | | #include <AK/Checked.h> |
9 | | #include <AK/FlyString.h> |
10 | | #include <AK/Format.h> |
11 | | #include <AK/MemMem.h> |
12 | | #include <AK/Stream.h> |
13 | | #include <AK/String.h> |
14 | | #include <AK/Vector.h> |
15 | | #include <stdlib.h> |
16 | | |
17 | | namespace AK { |
18 | | |
19 | | String String::from_utf8_with_replacement_character(StringView view, WithBOMHandling with_bom_handling) |
20 | 734 | { |
21 | 734 | if (auto bytes = view.bytes(); with_bom_handling == WithBOMHandling::Yes && bytes.size() >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) |
22 | 1 | view = view.substring_view(3); |
23 | | |
24 | 734 | if (Utf8View(view).validate()) |
25 | 75 | return String::from_utf8_without_validation(view.bytes()); |
26 | | |
27 | 659 | StringBuilder builder; |
28 | | |
29 | 659 | for (auto c : Utf8View { view }) |
30 | 42.8M | builder.append_code_point(c); |
31 | | |
32 | 659 | return builder.to_string_without_validation(); |
33 | 734 | } |
34 | | |
35 | | String String::from_utf8_without_validation(ReadonlyBytes bytes) |
36 | 6.18M | { |
37 | 6.18M | String result; |
38 | 6.18M | MUST(result.replace_with_new_string(bytes.size(), [&](Bytes buffer) { |
39 | 6.18M | bytes.copy_to(buffer); |
40 | 6.18M | return ErrorOr<void> {}; |
41 | 6.18M | })); |
42 | 6.18M | return result; |
43 | 6.18M | } |
44 | | |
45 | | ErrorOr<String> String::from_utf8(StringView view) |
46 | 52.5M | { |
47 | 52.5M | if (!Utf8View { view }.validate()) |
48 | 286k | return Error::from_string_literal("String::from_utf8: Input was not valid UTF-8"); |
49 | | |
50 | 52.2M | String result; |
51 | 52.2M | TRY(result.replace_with_new_string(view.length(), [&](Bytes buffer) { |
52 | 52.2M | view.bytes().copy_to(buffer); |
53 | 52.2M | return ErrorOr<void> {}; |
54 | 52.2M | })); |
55 | 52.2M | return result; |
56 | 52.2M | } |
57 | | |
58 | | ErrorOr<String> String::from_stream(Stream& stream, size_t byte_count) |
59 | 12.9k | { |
60 | 12.9k | String result; |
61 | 12.9k | TRY(result.replace_with_new_string(byte_count, [&](Bytes buffer) -> ErrorOr<void> { |
62 | 12.6k | TRY(stream.read_until_filled(buffer)); |
63 | 12.6k | if (!Utf8View { StringView { buffer } }.validate()) |
64 | 12.6k | return Error::from_string_literal("String::from_stream: Input was not valid UTF-8"); |
65 | 12.6k | return {}; |
66 | 12.6k | })); |
67 | 12.6k | return result; |
68 | 12.9k | } |
69 | | |
70 | | ErrorOr<String> String::repeated(u32 code_point, size_t count) |
71 | 0 | { |
72 | 0 | VERIFY(is_unicode(code_point)); |
73 | | |
74 | 0 | Array<u8, 4> code_point_as_utf8; |
75 | 0 | size_t i = 0; |
76 | |
|
77 | 0 | size_t code_point_byte_length = UnicodeUtils::code_point_to_utf8(code_point, [&](auto byte) { |
78 | 0 | code_point_as_utf8[i++] = static_cast<u8>(byte); |
79 | 0 | }); |
80 | |
|
81 | 0 | auto total_byte_count = code_point_byte_length * count; |
82 | |
|
83 | 0 | String result; |
84 | 0 | TRY(result.replace_with_new_string(total_byte_count, [&](Bytes buffer) { |
85 | 0 | if (code_point_byte_length == 1) { |
86 | 0 | buffer.fill(code_point_as_utf8[0]); |
87 | 0 | } else { |
88 | 0 | for (i = 0; i < count; ++i) |
89 | 0 | memcpy(buffer.data() + (i * code_point_byte_length), code_point_as_utf8.data(), code_point_byte_length); |
90 | 0 | } |
91 | 0 | return ErrorOr<void> {}; |
92 | 0 | })); |
93 | 0 | return result; |
94 | 0 | } |
95 | | |
96 | | StringView String::bytes_as_string_view() const& |
97 | 425M | { |
98 | 425M | return StringView(bytes()); |
99 | 425M | } |
100 | | |
101 | | bool String::is_empty() const |
102 | 24.5M | { |
103 | 24.5M | return bytes().size() == 0; |
104 | 24.5M | } |
105 | | |
106 | | ErrorOr<String> String::vformatted(StringView fmtstr, TypeErasedFormatParams& params) |
107 | 3.49M | { |
108 | 3.49M | StringBuilder builder; |
109 | 3.49M | TRY(vformat(builder, fmtstr, params)); |
110 | 3.49M | return builder.to_string(); |
111 | 3.49M | } |
112 | | |
113 | | ErrorOr<Vector<String>> String::split(u32 separator, SplitBehavior split_behavior) const |
114 | 416 | { |
115 | 416 | return split_limit(separator, 0, split_behavior); |
116 | 416 | } |
117 | | |
118 | | ErrorOr<Vector<String>> String::split_limit(u32 separator, size_t limit, SplitBehavior split_behavior) const |
119 | 3.56k | { |
120 | 3.56k | Vector<String> result; |
121 | | |
122 | 3.56k | if (is_empty()) |
123 | 569 | return result; |
124 | | |
125 | 3.00k | bool keep_empty = has_flag(split_behavior, SplitBehavior::KeepEmpty); |
126 | | |
127 | 3.00k | size_t substring_start = 0; |
128 | 27.9k | for (auto it = code_points().begin(); it != code_points().end() && (result.size() + 1) != limit; ++it) { |
129 | 24.9k | u32 code_point = *it; |
130 | 24.9k | if (code_point == separator) { |
131 | 3.34k | size_t substring_length = code_points().iterator_offset(it) - substring_start; |
132 | 3.34k | if (substring_length != 0 || keep_empty) |
133 | 2.66k | TRY(result.try_append(TRY(substring_from_byte_offset_with_shared_superstring(substring_start, substring_length)))); |
134 | 3.34k | substring_start = code_points().iterator_offset(it) + it.underlying_code_point_length_in_bytes(); |
135 | 3.34k | } |
136 | 24.9k | } |
137 | 3.00k | size_t tail_length = code_points().byte_length() - substring_start; |
138 | 3.00k | if (tail_length != 0 || keep_empty) |
139 | 2.86k | TRY(result.try_append(TRY(substring_from_byte_offset_with_shared_superstring(substring_start, tail_length)))); |
140 | 3.00k | return result; |
141 | 3.00k | } |
142 | | |
143 | | Optional<size_t> String::find_byte_offset(u32 code_point, size_t from_byte_offset) const |
144 | 738k | { |
145 | 738k | auto code_points = this->code_points(); |
146 | 738k | if (from_byte_offset >= code_points.byte_length()) |
147 | 0 | return {}; |
148 | | |
149 | 22.7M | for (auto it = code_points.iterator_at_byte_offset(from_byte_offset); it != code_points.end(); ++it) { |
150 | 22.7M | if (*it == code_point) |
151 | 737k | return code_points.byte_offset_of(it); |
152 | 22.7M | } |
153 | | |
154 | 851 | return {}; |
155 | 738k | } |
156 | | |
157 | | Optional<size_t> String::find_byte_offset(StringView substring, size_t from_byte_offset) const |
158 | 0 | { |
159 | 0 | auto view = bytes_as_string_view(); |
160 | 0 | if (from_byte_offset >= view.length()) |
161 | 0 | return {}; |
162 | | |
163 | 0 | auto index = memmem_optional( |
164 | 0 | view.characters_without_null_termination() + from_byte_offset, view.length() - from_byte_offset, |
165 | 0 | substring.characters_without_null_termination(), substring.length()); |
166 | |
|
167 | 0 | if (index.has_value()) |
168 | 0 | return *index + from_byte_offset; |
169 | 0 | return {}; |
170 | 0 | } |
171 | | |
172 | | bool String::operator==(FlyString const& other) const |
173 | 0 | { |
174 | 0 | return static_cast<StringBase const&>(*this) == other.data({}); |
175 | 0 | } |
176 | | |
177 | | bool String::operator==(StringView other) const |
178 | 382M | { |
179 | 382M | return bytes_as_string_view() == other; |
180 | 382M | } |
181 | | |
182 | | ErrorOr<String> String::substring_from_byte_offset(size_t start, size_t byte_count) const |
183 | 977 | { |
184 | 977 | if (!byte_count) |
185 | 477 | return String {}; |
186 | 500 | return String::from_utf8(bytes_as_string_view().substring_view(start, byte_count)); |
187 | 977 | } |
188 | | |
189 | | ErrorOr<String> String::substring_from_byte_offset(size_t start) const |
190 | 977 | { |
191 | 977 | VERIFY(start <= bytes_as_string_view().length()); |
192 | 977 | return substring_from_byte_offset(start, bytes_as_string_view().length() - start); |
193 | 977 | } |
194 | | |
195 | | ErrorOr<String> String::substring_from_byte_offset_with_shared_superstring(size_t start, size_t byte_count) const |
196 | 482k | { |
197 | 482k | return String { TRY(StringBase::substring_from_byte_offset_with_shared_superstring(start, byte_count)) }; |
198 | 482k | } |
199 | | |
200 | | ErrorOr<String> String::substring_from_byte_offset_with_shared_superstring(size_t start) const |
201 | 224k | { |
202 | 224k | VERIFY(start <= bytes_as_string_view().length()); |
203 | 224k | return substring_from_byte_offset_with_shared_superstring(start, bytes_as_string_view().length() - start); |
204 | 224k | } |
205 | | |
206 | | bool String::operator==(char const* c_string) const |
207 | 7.29M | { |
208 | 7.29M | return bytes_as_string_view() == c_string; |
209 | 7.29M | } |
210 | | |
211 | | u32 String::ascii_case_insensitive_hash() const |
212 | 0 | { |
213 | 0 | return case_insensitive_string_hash(reinterpret_cast<char const*>(bytes().data()), bytes().size()); |
214 | 0 | } |
215 | | |
216 | | Utf8View String::code_points() const& |
217 | 13.3M | { |
218 | 13.3M | return Utf8View(bytes_as_string_view()); |
219 | 13.3M | } |
220 | | |
221 | | ErrorOr<void> Formatter<String>::format(FormatBuilder& builder, String const& utf8_string) |
222 | 252k | { |
223 | 252k | return Formatter<StringView>::format(builder, utf8_string.bytes_as_string_view()); |
224 | 252k | } |
225 | | |
226 | | ErrorOr<String> String::replace(StringView needle, StringView replacement, ReplaceMode replace_mode) const |
227 | 0 | { |
228 | 0 | return StringUtils::replace(*this, needle, replacement, replace_mode); |
229 | 0 | } |
230 | | |
231 | | ErrorOr<String> String::reverse() const |
232 | 0 | { |
233 | | // FIXME: This handles multi-byte code points, but not e.g. grapheme clusters. |
234 | | // FIXME: We could avoid allocating a temporary vector if Utf8View supports reverse iteration. |
235 | 0 | auto code_point_length = code_points().length(); |
236 | |
|
237 | 0 | Vector<u32> code_points; |
238 | 0 | TRY(code_points.try_ensure_capacity(code_point_length)); |
239 | |
|
240 | 0 | for (auto code_point : this->code_points()) |
241 | 0 | code_points.unchecked_append(code_point); |
242 | |
|
243 | 0 | auto builder = TRY(StringBuilder::create(code_point_length * sizeof(u32))); |
244 | 0 | while (!code_points.is_empty()) |
245 | 0 | TRY(builder.try_append_code_point(code_points.take_last())); |
246 | |
|
247 | 0 | return builder.to_string(); |
248 | 0 | } |
249 | | |
250 | | ErrorOr<String> String::trim(Utf8View const& code_points_to_trim, TrimMode mode) const |
251 | 0 | { |
252 | 0 | auto trimmed = code_points().trim(code_points_to_trim, mode); |
253 | 0 | return String::from_utf8(trimmed.as_string()); |
254 | 0 | } |
255 | | |
256 | | ErrorOr<String> String::trim(StringView code_points_to_trim, TrimMode mode) const |
257 | 0 | { |
258 | 0 | return trim(Utf8View { code_points_to_trim }, mode); |
259 | 0 | } |
260 | | |
261 | | ErrorOr<String> String::trim_ascii_whitespace(TrimMode mode) const |
262 | 0 | { |
263 | 0 | return trim(" \n\t\v\f\r"sv, mode); |
264 | 0 | } |
265 | | |
266 | | bool String::contains(StringView needle, CaseSensitivity case_sensitivity) const |
267 | 5.52M | { |
268 | 5.52M | return StringUtils::contains(bytes_as_string_view(), needle, case_sensitivity); |
269 | 5.52M | } |
270 | | |
271 | | bool String::contains(u32 needle, CaseSensitivity case_sensitivity) const |
272 | 5.52M | { |
273 | 5.52M | auto needle_as_string = String::from_code_point(needle); |
274 | 5.52M | return contains(needle_as_string.bytes_as_string_view(), case_sensitivity); |
275 | 5.52M | } |
276 | | |
277 | | bool String::starts_with(u32 code_point) const |
278 | 1.29M | { |
279 | 1.29M | if (is_empty()) |
280 | 0 | return false; |
281 | | |
282 | 1.29M | return *code_points().begin() == code_point; |
283 | 1.29M | } |
284 | | |
285 | | bool String::starts_with_bytes(StringView bytes, CaseSensitivity case_sensitivity) const |
286 | 1.29M | { |
287 | 1.29M | return bytes_as_string_view().starts_with(bytes, case_sensitivity); |
288 | 1.29M | } |
289 | | |
290 | | bool String::ends_with(u32 code_point) const |
291 | 361k | { |
292 | 361k | if (is_empty()) |
293 | 0 | return false; |
294 | | |
295 | 361k | u32 last_code_point = 0; |
296 | 10.8M | for (auto it = code_points().begin(); it != code_points().end(); ++it) |
297 | 10.5M | last_code_point = *it; |
298 | | |
299 | 361k | return last_code_point == code_point; |
300 | 361k | } |
301 | | |
302 | | bool String::ends_with_bytes(StringView bytes, CaseSensitivity case_sensitivity) const |
303 | 0 | { |
304 | 0 | return bytes_as_string_view().ends_with(bytes, case_sensitivity); |
305 | 0 | } |
306 | | |
307 | | unsigned Traits<String>::hash(String const& string) |
308 | 1.07M | { |
309 | 1.07M | return string.hash(); |
310 | 1.07M | } |
311 | | |
312 | | ByteString String::to_byte_string() const |
313 | 1 | { |
314 | 1 | return ByteString(bytes_as_string_view()); |
315 | 1 | } |
316 | | |
317 | | ErrorOr<String> String::from_byte_string(ByteString const& byte_string) |
318 | 15.9k | { |
319 | 15.9k | return String::from_utf8(byte_string.view()); |
320 | 15.9k | } |
321 | | |
322 | | String String::to_ascii_lowercase() const |
323 | 0 | { |
324 | 0 | bool const has_ascii_uppercase = [&] { |
325 | 0 | for (u8 const byte : bytes()) { |
326 | 0 | if (AK::is_ascii_upper_alpha(byte)) |
327 | 0 | return true; |
328 | 0 | } |
329 | 0 | return false; |
330 | 0 | }(); |
331 | |
|
332 | 0 | if (!has_ascii_uppercase) |
333 | 0 | return *this; |
334 | | |
335 | 0 | Vector<u8> lowercase_bytes; |
336 | 0 | lowercase_bytes.ensure_capacity(bytes().size()); |
337 | 0 | for (u8 const byte : bytes()) { |
338 | 0 | if (AK::is_ascii_upper_alpha(byte)) |
339 | 0 | lowercase_bytes.unchecked_append(AK::to_ascii_lowercase(byte)); |
340 | 0 | else |
341 | 0 | lowercase_bytes.unchecked_append(byte); |
342 | 0 | } |
343 | 0 | return String::from_utf8_without_validation(lowercase_bytes); |
344 | 0 | } |
345 | | |
346 | | String String::to_ascii_uppercase() const |
347 | 0 | { |
348 | 0 | bool const has_ascii_lowercase = [&] { |
349 | 0 | for (u8 const byte : bytes()) { |
350 | 0 | if (AK::is_ascii_lower_alpha(byte)) |
351 | 0 | return true; |
352 | 0 | } |
353 | 0 | return false; |
354 | 0 | }(); |
355 | |
|
356 | 0 | if (!has_ascii_lowercase) |
357 | 0 | return *this; |
358 | | |
359 | 0 | Vector<u8> uppercase_bytes; |
360 | 0 | uppercase_bytes.ensure_capacity(bytes().size()); |
361 | 0 | for (u8 const byte : bytes()) { |
362 | 0 | if (AK::is_ascii_lower_alpha(byte)) |
363 | 0 | uppercase_bytes.unchecked_append(AK::to_ascii_uppercase(byte)); |
364 | 0 | else |
365 | 0 | uppercase_bytes.unchecked_append(byte); |
366 | 0 | } |
367 | 0 | return String::from_utf8_without_validation(uppercase_bytes); |
368 | 0 | } |
369 | | |
370 | | bool String::equals_ignoring_ascii_case(String const& other) const |
371 | 0 | { |
372 | 0 | return StringUtils::equals_ignoring_ascii_case(bytes_as_string_view(), other.bytes_as_string_view()); |
373 | 0 | } |
374 | | |
375 | | bool String::equals_ignoring_ascii_case(StringView other) const |
376 | 0 | { |
377 | 0 | return StringUtils::equals_ignoring_ascii_case(bytes_as_string_view(), other); |
378 | 0 | } |
379 | | |
380 | | ErrorOr<String> String::repeated(String const& input, size_t count) |
381 | 0 | { |
382 | 0 | if (Checked<u32>::multiplication_would_overflow(count, input.bytes().size())) |
383 | 0 | return Error::from_errno(EOVERFLOW); |
384 | | |
385 | 0 | String result; |
386 | 0 | size_t input_size = input.bytes().size(); |
387 | 0 | TRY(result.replace_with_new_string(count * input_size, [&](Bytes buffer) { |
388 | 0 | if (input_size == 1) { |
389 | 0 | buffer.fill(input.bytes().first()); |
390 | 0 | } else { |
391 | 0 | for (size_t i = 0; i < count; ++i) |
392 | 0 | input.bytes().copy_to(buffer.slice(i * input_size, input_size)); |
393 | 0 | } |
394 | 0 | return ErrorOr<void> {}; |
395 | 0 | })); |
396 | |
|
397 | 0 | return result; |
398 | 0 | } |
399 | | |
400 | | } |