1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "core/components_ng/pattern/text_field/content_controller.h"
16
17 #include <algorithm>
18 #include <cstdint>
19 #include <string>
20
21 #include "base/utils/string_utils.h"
22 #include "base/utils/utf_helper.h"
23 #include "base/utils/utils.h"
24 #include "core/text/text_emoji_processor.h"
25 #include "core/components_ng/pattern/text/typed_text.h"
26 #include "core/components_ng/pattern/text_field/text_field_pattern.h"
27
28 namespace OHOS::Ace::NG {
29 namespace {
30 const std::u16string DIGIT_WHITE_LIST = u"[0-9]";
31 const std::u16string DIGIT_DECIMAL_WHITE_LIST = u"[0-9.]";
32 const std::u16string PHONE_WHITE_LIST = uR"([0-9 \+\-\*\#\(\)])";
33 const std::u16string EMAIL_WHITE_LIST = uR"([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~@"-])";
34 // when do ai analaysis, we should list the left and right of the string
35 constexpr static int32_t AI_TEXT_RANGE_LEFT = 50;
36 constexpr static int32_t AI_TEXT_RANGE_RIGHT = 50;
37 constexpr static int32_t EMOJI_RANGE_LEFT = 150;
38 constexpr static int32_t EMOJI_RANGE_RIGHT = 150;
39
ContentToWstring(const std::u16string & str)40 inline std::wstring ContentToWstring(const std::u16string& str)
41 {
42 auto utf16Len = str.length();
43 std::unique_ptr<wchar_t[]> pBuf16 = std::make_unique<wchar_t[]>(utf16Len);
44 wchar_t *wBuf = pBuf16.get();
45 for (uint32_t i = 0; i < utf16Len; i++) {
46 wBuf[i] = static_cast<wchar_t>(str[i]);
47 }
48
49 return std::wstring(wBuf, utf16Len);
50 }
51
52 inline std::u16string ContentToU16string(const std::wstring& str)
53 {
54 auto utf16Len = str.length();
55 std::unique_ptr<char16_t[]> pBuf16 = std::make_unique<char16_t[]>(utf16Len);
56 char16_t *buf16 = pBuf16.get();
57 for (uint32_t i = 0; i < utf16Len; i++) {
58 buf16[i] = static_cast<char16_t>(str[i]);
59 }
60
61 return std::u16string(buf16, utf16Len);
62 }
63 } // namespace
64
65 std::u16string ContentController::PreprocessString(int32_t startIndex, int32_t endIndex, const std::u16string& value)
66 {
67 auto tmp = value;
68 auto pattern = pattern_.Upgrade();
69 CHECK_NULL_RETURN(pattern, value);
70 auto textField = DynamicCast<TextFieldPattern>(pattern);
71 CHECK_NULL_RETURN(textField, value);
72 if (textField->GetIsPreviewText()) {
73 return tmp;
74 }
75 auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
76 CHECK_NULL_RETURN(property, value);
77 auto selectValue = GetSelectedValue(startIndex, endIndex);
78 bool hasInputFilter =
79 property->GetInputFilter().has_value() && !property->GetInputFilter().value().empty() && !content_.empty();
80 if (!hasInputFilter && property->GetTextInputType().has_value() &&
81 (property->GetTextInputType().value() == TextInputType::NUMBER_DECIMAL ||
82 property->GetTextInputType().value() == TextInputType::EMAIL_ADDRESS)) {
83 char16_t specialChar = property->GetTextInputType().value() == TextInputType::NUMBER_DECIMAL ? u'.' : u'@';
84 if (content_.find(specialChar) != std::u16string::npos && value.find(specialChar) != std::u16string::npos &&
85 GetSelectedValue(startIndex, endIndex).find(specialChar) == std::u16string::npos) {
86 tmp.erase(std::remove_if(tmp.begin(), tmp.end(), [&specialChar](char16_t c) { return c == specialChar; }),
87 tmp.end());
88 }
89 }
90 FilterValueType(tmp);
91 auto maxLength = static_cast<uint32_t>(textField->GetMaxLength());
92 auto curLength = static_cast<uint32_t>(content_.length());
93 auto addLength = static_cast<uint32_t>(tmp.length());
94 auto delLength = static_cast<uint32_t>(std::abs(endIndex - startIndex));
95 addLength = std::min(addLength, maxLength - curLength + delLength);
96 tmp = TextEmojiProcessor::SubU16string(0, addLength, tmp); // clamp emoji
97 return tmp;
98 }
99
100 bool ContentController::InsertValue(int32_t index, const std::u16string& value)
101 {
102 return ReplaceSelectedValue(index, index, value);
103 }
104
105 bool ContentController::ReplaceSelectedValue(int32_t startIndex, int32_t endIndex, const std::u16string& value)
106 {
107 FormatIndex(startIndex, endIndex);
108 auto tmp = PreprocessString(startIndex, endIndex, value);
109 auto str = content_;
110 endIndex = std::clamp(endIndex, 0, static_cast<int32_t>(content_.length()));
111 content_ = content_.substr(0, startIndex) + tmp +
112 content_.substr(endIndex, static_cast<int32_t>(content_.length()) - endIndex);
113 auto len = content_.length();
114 FilterValue();
115 insertValue_ = tmp;
116 if (value.length() == 1 && content_.length() < len) {
117 content_ = str;
118 insertValue_ = u"";
119 }
120 return !tmp.empty();
121 }
122
123 std::u16string ContentController::GetSelectedValue(int32_t startIndex, int32_t endIndex)
124 {
125 FormatIndex(startIndex, endIndex);
126 startIndex = std::clamp(startIndex, 0, static_cast<int32_t>(content_.length()));
127 auto selectedValue = content_.substr(startIndex, endIndex - startIndex);
128 if (selectedValue.empty()) {
129 selectedValue = TextEmojiProcessor::SubU16string(startIndex, endIndex - startIndex, content_);
130 }
131 return selectedValue;
132 }
133
134 void ContentController::FormatIndex(int32_t& startIndex, int32_t& endIndex)
135 {
136 startIndex = std::min(startIndex, endIndex);
137 endIndex = std::max(startIndex, endIndex);
138 startIndex = std::clamp(startIndex, 0, static_cast<int32_t>(content_.length()));
139 endIndex = std::clamp(endIndex, 0, static_cast<int32_t>(content_.length()));
140 }
141
142 void ContentController::FilterTextInputStyle(bool& textChanged, std::u16string& result)
143 {
144 auto pattern = pattern_.Upgrade();
145 CHECK_NULL_VOID(pattern);
146 auto textField = DynamicCast<TextFieldPattern>(pattern);
147 CHECK_NULL_VOID(textField);
148 auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
149 CHECK_NULL_VOID(property);
150 if (!property->GetTextInputType().has_value()) {
151 return;
152 }
153 switch (property->GetTextInputType().value()) {
154 case TextInputType::NUMBER: {
155 textChanged |= FilterWithEvent(DIGIT_WHITE_LIST, result);
156 break;
157 }
158 case TextInputType::PHONE: {
159 textChanged |= FilterWithEvent(PHONE_WHITE_LIST, result);
160 break;
161 }
162 case TextInputType::EMAIL_ADDRESS: {
163 textChanged |= FilterWithEvent(EMAIL_WHITE_LIST, result);
164 textChanged |= FilterWithEmail(result);
165 break;
166 }
167 case TextInputType::VISIBLE_PASSWORD:
168 break;
169 case TextInputType::NEW_PASSWORD:
170 break;
171 case TextInputType::NUMBER_PASSWORD: {
172 textChanged |= FilterWithEvent(DIGIT_WHITE_LIST, result);
173 break;
174 }
175 case TextInputType::SCREEN_LOCK_PASSWORD: {
176 textChanged |= FilterWithAscii(result);
177 break;
178 }
179 case TextInputType::NUMBER_DECIMAL: {
180 textChanged |= FilterWithEvent(DIGIT_DECIMAL_WHITE_LIST, result);
181 textChanged |= FilterWithDecimal(result);
182 break;
183 }
184 default: {
185 break;
186 }
187 }
188 }
189
190 bool ContentController::FilterValue()
191 {
192 bool textChanged = false;
193 auto result = content_;
194 auto pattern = pattern_.Upgrade();
195 CHECK_NULL_RETURN(pattern, false);
196 auto textField = DynamicCast<TextFieldPattern>(pattern);
197 CHECK_NULL_RETURN(textField, false);
198 if (textField->GetIsPreviewText()) {
199 return false;
200 }
201
202 auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
203 CHECK_NULL_RETURN(property, false);
204
205 bool hasInputFilter =
206 property->GetInputFilter().has_value() && !property->GetInputFilter().value().empty() && !content_.empty();
207 if (!hasInputFilter) {
208 FilterTextInputStyle(textChanged, result);
209 } else {
210 textChanged |= FilterWithEvent(StringUtils::Str8ToStr16(property->GetInputFilter().value()), result);
211 if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
212 FilterTextInputStyle(textChanged, result);
213 }
214 }
215 if (textChanged) {
216 content_ = result;
217 }
218 auto maxLength =
219 property->HasMaxLength() ? property->GetMaxLengthValue(Infinity<uint32_t>()) : Infinity<uint32_t>();
220 auto textWidth = static_cast<int32_t>(content_.length());
221 if (GreatNotEqual(textWidth, maxLength)) {
222 // clamp emoji
223 content_ = TextEmojiProcessor::SubU16string(0, maxLength, content_);
224 return true;
225 }
226 return textChanged;
227 }
228
229 void ContentController::FilterValueType(std::u16string& value)
230 {
231 bool textChanged = false;
232 auto result = value;
233 auto pattern = pattern_.Upgrade();
234 CHECK_NULL_VOID(pattern);
235 auto textField = DynamicCast<TextFieldPattern>(pattern);
236 CHECK_NULL_VOID(textField);
237 auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
238 CHECK_NULL_VOID(property);
239
240 bool hasInputFilter = property->GetInputFilter().has_value() && !property->GetInputFilter().value().empty();
241 if (!hasInputFilter) {
242 FilterTextInputStyle(textChanged, result);
243 } else {
244 textChanged = FilterWithEvent(StringUtils::Str8ToStr16(property->GetInputFilter().value()), result) ||
245 textChanged;
246 if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
247 FilterTextInputStyle(textChanged, result);
248 }
249 }
250 if (textChanged) {
251 value = result;
252 }
253 }
254
255 std::u16string ContentController::RemoveErrorTextFromValue(const std::u16string& value, const std::u16string& errorText)
256 {
257 std::u16string result;
258 int32_t valuePtr = 0;
259 int32_t errorTextPtr = 0;
260 auto valueSize = static_cast<int32_t>(value.length());
261 auto errorTextSize = static_cast<int32_t>(errorText.length());
262 while (errorTextPtr < errorTextSize) {
263 while (value[valuePtr] != errorText[errorTextPtr] && valuePtr < valueSize) {
264 result += value[valuePtr];
265 valuePtr++;
266 }
267 // no more text left to remove in value
268 if (valuePtr >= valueSize) {
269 return result;
270 }
271 // increase both value ptr and error text ptr if char in value is removed
272 valuePtr++;
273 errorTextPtr++;
274 }
275 valuePtr = std::clamp(valuePtr, 0, static_cast<int32_t>(value.length()));
276 result += value.substr(valuePtr);
277 return result;
278 }
279
280 std::u16string ContentController::FilterWithRegex(const std::u16string& filter, std::u16string& result)
281 {
282 // convert wstring for processing unicode characters
283 std::wstring wFilter = ContentToWstring(filter);
284 std::wstring wResult = ContentToWstring(result);
285 std::wregex wFilterRegex(wFilter);
286 std::wstring wErrorText = std::regex_replace(wResult, wFilterRegex, L"");
287 std::u16string errorText = ContentToU16string(wErrorText);
288 result = RemoveErrorTextFromValue(result, errorText);
289 return errorText;
290 }
291
292 bool ContentController::FilterWithEmail(std::u16string& result)
293 {
294 auto valueToUpdate = result;
295 bool first = true;
296 std::replace_if(
297 result.begin(), result.end(),
298 [&first](const char16_t c) {
299 if (c == u'@' && !first) {
300 return true;
301 }
302 if (c == u'@') {
303 first = false;
304 }
305 return false;
306 },
307 u' ');
308
309 // remove the spaces
310 result.erase(std::remove(result.begin(), result.end(), u' '), result.end());
311 return result != valueToUpdate;
312 }
313
314 bool ContentController::FilterWithAscii(std::u16string& result)
315 {
316 if (result.empty()) {
317 return false;
318 }
319 auto valueToUpdate = result;
320 bool textChange = true;
321 std::u16string errorText;
322 result.clear();
323 for (char16_t valuePtr : valueToUpdate) {
324 if (isascii(valuePtr)) {
325 result += valuePtr;
326 } else {
327 errorText += valuePtr;
328 }
329 }
330 if (errorText.empty()) {
331 textChange = false;
332 } else {
333 LOGI("FilterWithAscii Error text size %{public}zu", UtfUtils::Str16DebugToStr8(errorText).size());
334 }
335 return textChange;
336 }
337
FilterWithDecimal(std::u16string & result)338 bool ContentController::FilterWithDecimal(std::u16string& result)
339 {
340 auto valueToUpdate = result;
341 bool first = true;
342 std::replace_if(
343 result.begin(), result.end(),
344 [&first](const char16_t c) {
345 if (c == u'.' && !first) {
346 return true;
347 }
348 if (c == u'.') {
349 first = false;
350 }
351 return false;
352 },
353 u' ');
354 result.erase(std::remove(result.begin(), result.end(), u' '), result.end());
355 return result != valueToUpdate;
356 }
357
358 bool ContentController::FilterWithEvent(const std::u16string& filter, std::u16string& result)
359 {
360 auto errorValue = FilterWithRegex(filter, result);
361 if (!errorValue.empty()) {
362 auto pattern = pattern_.Upgrade();
363 CHECK_NULL_RETURN(pattern, false);
364 auto textField = DynamicCast<TextFieldPattern>(pattern);
365 CHECK_NULL_RETURN(textField, false);
366 auto host = textField->GetHost();
367 CHECK_NULL_RETURN(host, false);
368 auto eventHub = host->GetEventHub<TextFieldEventHub>();
369 CHECK_NULL_RETURN(eventHub, false);
370 eventHub->FireOnInputFilterError(errorValue);
371 auto textFieldAccessibilityProperty = host->GetAccessibilityProperty<TextFieldAccessibilityProperty>();
372 CHECK_NULL_RETURN(textFieldAccessibilityProperty, false);
373 textFieldAccessibilityProperty->SetErrorText(UtfUtils::Str16DebugToStr8(errorValue));
374 }
375 return !errorValue.empty();
376 }
377
378 void ContentController::erase(int32_t startIndex, int32_t length)
379 {
380 if (startIndex < 0 || startIndex >= static_cast<int32_t>(content_.length())) {
381 return;
382 }
383 content_.erase(startIndex, length);
384 }
385
386 int32_t ContentController::Delete(int32_t startIndex, int32_t length, bool isBackward)
387 {
388 int32_t result = TextEmojiProcessor::Delete(startIndex, length, content_, isBackward);
389 if (length > 0 && result == 0) {
390 // try delete whole emoji
391 if (isBackward) {
392 TextEmojiSubStringRange range = TextEmojiProcessor::CalSubU16stringRange(
393 startIndex - length, length, content_, true, true);
394 result = TextEmojiProcessor::Delete(range.endIndex,
395 length, content_, true);
396 } else {
397 TextEmojiSubStringRange range = TextEmojiProcessor::CalSubU16stringRange(
398 startIndex, length, content_, true, true);
399 result = TextEmojiProcessor::Delete(range.startIndex,
400 length, content_, true);
401 }
402 }
403 return result;
404 }
405
406 int32_t ContentController::GetDeleteLength(int32_t startIndex, int32_t length, bool isBackward)
407 {
408 auto content = content_;
409 return TextEmojiProcessor::Delete(startIndex, length, content, isBackward);
410 }
411
412 bool ContentController::IsIndexBeforeOrInEmoji(int32_t index)
413 {
414 int32_t startIndex = index - EMOJI_RANGE_LEFT;
415 int32_t endIndex = index + EMOJI_RANGE_RIGHT;
416 FormatIndex(startIndex, endIndex);
417 index = index - startIndex;
418 return TextEmojiProcessor::IsIndexBeforeOrInEmoji(index, GetSelectedValue(startIndex, endIndex));
419 }
420
421 std::u16string ContentController::GetValueBeforeIndex(int32_t index)
422 {
423 index = std::clamp(index, 0, static_cast<int32_t>(content_.length()));
424 return content_.substr(0, index);
425 }
426
427 std::u16string ContentController::GetValueAfterIndex(int32_t index)
428 {
429 index = std::clamp(index, 0, static_cast<int32_t>(content_.length()));
430 return content_.substr(index, content_.length() - index);
431 }
432
433 std::string ContentController::GetSelectedLimitValue(int32_t& index, int32_t& startIndex)
434 {
435 startIndex = index - AI_TEXT_RANGE_LEFT;
436 int32_t endIndex = index + AI_TEXT_RANGE_RIGHT;
437 FormatIndex(startIndex, endIndex);
438 index = index - startIndex;
439 return UtfUtils::Str16DebugToStr8(GetSelectedValue(startIndex, endIndex));
440 }
441
442 } // namespace OHOS::Ace::NG