/src/serenity/Userland/Libraries/LibWeb/Infra/Strings.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org> |
3 | | * Copyright (c) 2022, networkException <networkexception@serenityos.org> |
4 | | * Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org> |
5 | | * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org> |
6 | | * Copyright (c) 2024, Andreas Kling <andreas@ladybird.org> |
7 | | * |
8 | | * SPDX-License-Identifier: BSD-2-Clause |
9 | | */ |
10 | | |
11 | | #include <AK/CharacterTypes.h> |
12 | | #include <AK/FlyString.h> |
13 | | #include <AK/GenericLexer.h> |
14 | | #include <AK/String.h> |
15 | | #include <AK/Utf16View.h> |
16 | | #include <AK/Utf8View.h> |
17 | | #include <LibWeb/Infra/CharacterTypes.h> |
18 | | #include <LibWeb/Infra/Strings.h> |
19 | | |
20 | | namespace Web::Infra { |
21 | | |
22 | | // https://infra.spec.whatwg.org/#ascii-case-insensitive |
23 | | bool is_ascii_case_insensitive_match(StringView a, StringView b) |
24 | 0 | { |
25 | | // A string A is an ASCII case-insensitive match for a string B, |
26 | | // if the ASCII lowercase of A is the ASCII lowercase of B. |
27 | 0 | return AK::StringUtils::equals_ignoring_ascii_case(a, b); |
28 | 0 | } |
29 | | |
30 | | // https://infra.spec.whatwg.org/#normalize-newlines |
31 | | String normalize_newlines(String const& string) |
32 | 0 | { |
33 | | // To normalize newlines in a string, replace every U+000D CR U+000A LF code point pair with a single U+000A LF |
34 | | // code point, and then replace every remaining U+000D CR code point with a U+000A LF code point. |
35 | 0 | if (!string.contains('\r')) |
36 | 0 | return string; |
37 | | |
38 | 0 | StringBuilder builder; |
39 | 0 | GenericLexer lexer { string }; |
40 | |
|
41 | 0 | while (!lexer.is_eof()) { |
42 | 0 | builder.append(lexer.consume_until('\r')); |
43 | |
|
44 | 0 | if (lexer.peek() == '\r') { |
45 | 0 | lexer.ignore(1 + static_cast<size_t>(lexer.peek(1) == '\n')); |
46 | 0 | builder.append('\n'); |
47 | 0 | } |
48 | 0 | } |
49 | |
|
50 | 0 | return MUST(builder.to_string()); |
51 | 0 | } |
52 | | |
53 | | // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace |
54 | | ErrorOr<String> strip_and_collapse_whitespace(StringView string) |
55 | 0 | { |
56 | | // Replace any sequence of one or more consecutive code points that are ASCII whitespace in the string with a single U+0020 SPACE code point. |
57 | 0 | StringBuilder builder; |
58 | 0 | for (auto code_point : Utf8View { string }) { |
59 | 0 | if (Infra::is_ascii_whitespace(code_point)) { |
60 | 0 | if (!builder.string_view().ends_with(' ')) |
61 | 0 | builder.append(' '); |
62 | 0 | continue; |
63 | 0 | } |
64 | 0 | TRY(builder.try_append_code_point(code_point)); |
65 | 0 | } |
66 | | |
67 | | // ...and then remove any leading and trailing ASCII whitespace from that string. |
68 | 0 | return String::from_utf8(builder.string_view().trim(Infra::ASCII_WHITESPACE)); |
69 | 0 | } |
70 | | |
71 | | // https://infra.spec.whatwg.org/#code-unit-prefix |
72 | | bool is_code_unit_prefix(StringView potential_prefix, StringView input) |
73 | 0 | { |
74 | 0 | auto potential_prefix_utf16 = utf8_to_utf16(potential_prefix).release_value_but_fixme_should_propagate_errors(); |
75 | 0 | auto input_utf16 = utf8_to_utf16(input).release_value_but_fixme_should_propagate_errors(); |
76 | | |
77 | | // 1. Let i be 0. |
78 | 0 | size_t i = 0; |
79 | | |
80 | | // 2. While true: |
81 | 0 | while (true) { |
82 | | // 1. If i is greater than or equal to potentialPrefix’s length, then return true. |
83 | 0 | if (i >= potential_prefix.length()) |
84 | 0 | return true; |
85 | | |
86 | | // 2. If i is greater than or equal to input’s length, then return false. |
87 | 0 | if (i >= input.length()) |
88 | 0 | return false; |
89 | | |
90 | | // 3. Let potentialPrefixCodeUnit be the ith code unit of potentialPrefix. |
91 | 0 | auto potential_prefix_code_unit = Utf16View(potential_prefix_utf16).code_unit_at(i); |
92 | | |
93 | | // 4. Let inputCodeUnit be the ith code unit of input. |
94 | 0 | auto input_code_unit = Utf16View(input_utf16).code_unit_at(i); |
95 | | |
96 | | // 5. Return false if potentialPrefixCodeUnit is not inputCodeUnit. |
97 | 0 | if (potential_prefix_code_unit != input_code_unit) |
98 | 0 | return false; |
99 | | |
100 | | // 6. Set i to i + 1. |
101 | 0 | ++i; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | // https://infra.spec.whatwg.org/#scalar-value-string |
106 | | ErrorOr<String> convert_to_scalar_value_string(StringView string) |
107 | 0 | { |
108 | | // To convert a string into a scalar value string, replace any surrogates with U+FFFD. |
109 | 0 | StringBuilder scalar_value_builder; |
110 | 0 | auto utf8_view = Utf8View { string }; |
111 | 0 | for (u32 code_point : utf8_view) { |
112 | 0 | if (is_unicode_surrogate(code_point)) |
113 | 0 | code_point = 0xFFFD; |
114 | 0 | scalar_value_builder.append_code_point(code_point); |
115 | 0 | } |
116 | 0 | return scalar_value_builder.to_string(); |
117 | 0 | } |
118 | | |
119 | | // https://infra.spec.whatwg.org/#ascii-lowercase |
120 | | ErrorOr<String> to_ascii_lowercase(StringView string) |
121 | 0 | { |
122 | | // To ASCII lowercase a string, replace all ASCII upper alphas in the string with their |
123 | | // corresponding code point in ASCII lower alpha. |
124 | 0 | StringBuilder string_builder; |
125 | 0 | auto utf8_view = Utf8View { string }; |
126 | 0 | for (u32 code_point : utf8_view) { |
127 | 0 | code_point = AK::to_ascii_lowercase(code_point); |
128 | 0 | string_builder.append_code_point(code_point); |
129 | 0 | } |
130 | 0 | return string_builder.to_string(); |
131 | 0 | } |
132 | | |
133 | | // https://infra.spec.whatwg.org/#ascii-uppercase |
134 | | ErrorOr<String> to_ascii_uppercase(StringView string) |
135 | 0 | { |
136 | | // To ASCII uppercase a string, replace all ASCII lower alphas in the string with their |
137 | | // corresponding code point in ASCII upper alpha. |
138 | 0 | StringBuilder string_builder; |
139 | 0 | auto utf8_view = Utf8View { string }; |
140 | 0 | for (u32 code_point : utf8_view) { |
141 | 0 | code_point = AK::to_ascii_uppercase(code_point); |
142 | 0 | string_builder.append_code_point(code_point); |
143 | 0 | } |
144 | 0 | return string_builder.to_string(); |
145 | 0 | } |
146 | | |
147 | | } |