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 void ContentController::FilterValue(std::u16string& value)
256 {
257 bool textChanged = false;
258 auto result = value;
259 auto pattern = pattern_.Upgrade();
260 CHECK_NULL_VOID(pattern);
261 auto textField = DynamicCast<TextFieldPattern>(pattern);
262 CHECK_NULL_VOID(textField);
263 auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
264 CHECK_NULL_VOID(property);
265
266 bool hasInputFilter = property->GetInputFilter().has_value() && !property->GetInputFilter().value().empty();
267 if (!hasInputFilter) {
268 FilterTextInputStyle(textChanged, result);
269 } else {
270 textChanged |= FilterWithEvent(StringUtils::Str8ToStr16(property->GetInputFilter().value()), result);
271 if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
272 FilterTextInputStyle(textChanged, result);
273 }
274 }
275 if (textChanged) {
276 value = result;
277 }
278 auto maxLength =
279 property->HasMaxLength() ? property->GetMaxLengthValue(Infinity<uint32_t>()) : Infinity<uint32_t>();
280 auto textWidth = static_cast<int32_t>(value.length());
281 if (GreatNotEqual(textWidth, maxLength)) {
282 // clamp emoji
283 value = TextEmojiProcessor::SubU16string(0, maxLength, value);
284 }
285 }
286
287 std::u16string ContentController::RemoveErrorTextFromValue(const std::u16string& value, const std::u16string& errorText)
288 {
289 std::u16string result;
290 int32_t valuePtr = 0;
291 int32_t errorTextPtr = 0;
292 auto valueSize = static_cast<int32_t>(value.length());
293 auto errorTextSize = static_cast<int32_t>(errorText.length());
294 while (errorTextPtr < errorTextSize) {
295 while (value[valuePtr] != errorText[errorTextPtr] && valuePtr < valueSize) {
296 result += value[valuePtr];
297 valuePtr++;
298 }
299 // no more text left to remove in value
300 if (valuePtr >= valueSize) {
301 return result;
302 }
303 // increase both value ptr and error text ptr if char in value is removed
304 valuePtr++;
305 errorTextPtr++;
306 }
307 valuePtr = std::clamp(valuePtr, 0, static_cast<int32_t>(value.length()));
308 result += value.substr(valuePtr);
309 return result;
310 }
311
312 std::u16string ContentController::FilterWithRegex(const std::u16string& filter, std::u16string& result)
313 {
314 // convert wstring for processing unicode characters
315 std::wstring wFilter = ContentToWstring(filter);
316 std::wstring wResult = ContentToWstring(result);
317 std::wregex wFilterRegex(wFilter);
318 std::wstring wErrorText = std::regex_replace(wResult, wFilterRegex, L"");
319 std::u16string errorText = ContentToU16string(wErrorText);
320 result = RemoveErrorTextFromValue(result, errorText);
321 return errorText;
322 }
323
324 bool ContentController::FilterWithEmail(std::u16string& result)
325 {
326 auto valueToUpdate = result;
327 bool first = true;
328 std::replace_if(
329 result.begin(), result.end(),
330 [&first](const char16_t c) {
331 if (c == u'@' && !first) {
332 return true;
333 }
334 if (c == u'@') {
335 first = false;
336 }
337 return false;
338 },
339 u' ');
340
341 // remove the spaces
342 result.erase(std::remove(result.begin(), result.end(), u' '), result.end());
343 return result != valueToUpdate;
344 }
345
346 bool ContentController::FilterWithAscii(std::u16string& result)
347 {
348 if (result.empty()) {
349 return false;
350 }
351 auto valueToUpdate = result;
352 bool textChange = true;
353 std::u16string errorText;
354 result.clear();
355 for (char16_t valuePtr : valueToUpdate) {
356 if (isascii(valuePtr)) {
357 result += valuePtr;
358 } else {
359 errorText += valuePtr;
360 }
361 }
362 if (errorText.empty()) {
363 textChange = false;
364 } else {
365 LOGI("FilterWithAscii Error text size %{public}zu", UtfUtils::Str16DebugToStr8(errorText).size());
366 }
367 return textChange;
368 }
369
FilterWithDecimal(std::u16string & result)370 bool ContentController::FilterWithDecimal(std::u16string& result)
371 {
372 auto valueToUpdate = result;
373 bool first = true;
374 std::replace_if(
375 result.begin(), result.end(),
376 [&first](const char16_t c) {
377 if (c == u'.' && !first) {
378 return true;
379 }
380 if (c == u'.') {
381 first = false;
382 }
383 return false;
384 },
385 u' ');
386 result.erase(std::remove(result.begin(), result.end(), u' '), result.end());
387 return result != valueToUpdate;
388 }
389
390 bool ContentController::FilterWithEvent(const std::u16string& filter, std::u16string& result)
391 {
392 auto errorValue = FilterWithRegex(filter, result);
393 if (!errorValue.empty()) {
394 auto pattern = pattern_.Upgrade();
395 CHECK_NULL_RETURN(pattern, false);
396 auto textField = DynamicCast<TextFieldPattern>(pattern);
397 CHECK_NULL_RETURN(textField, false);
398 auto host = textField->GetHost();
399 CHECK_NULL_RETURN(host, false);
400 auto eventHub = host->GetOrCreateEventHub<TextFieldEventHub>();
401 CHECK_NULL_RETURN(eventHub, false);
402 eventHub->FireOnInputFilterError(errorValue);
403 }
404 return !errorValue.empty();
405 }
406
407 void ContentController::erase(int32_t startIndex, int32_t length)
408 {
409 if (startIndex < 0 || startIndex >= static_cast<int32_t>(content_.length())) {
410 return;
411 }
412 content_.erase(startIndex, length);
413 }
414
415 int32_t ContentController::Delete(int32_t startIndex, int32_t length, bool isBackward)
416 {
417 int32_t result = TextEmojiProcessor::Delete(startIndex, length, content_, isBackward);
418 if (length > 0 && result == 0) {
419 // try delete whole emoji
420 if (isBackward) {
421 TextEmojiSubStringRange range = TextEmojiProcessor::CalSubU16stringRange(
422 startIndex - length, length, content_, true, true);
423 result = TextEmojiProcessor::Delete(range.endIndex,
424 length, content_, true);
425 } else {
426 TextEmojiSubStringRange range = TextEmojiProcessor::CalSubU16stringRange(
427 startIndex, length, content_, true, true);
428 result = TextEmojiProcessor::Delete(range.startIndex,
429 length, content_, true);
430 }
431 }
432 return result;
433 }
434
435 int32_t ContentController::GetDeleteLength(int32_t startIndex, int32_t length, bool isBackward)
436 {
437 auto content = content_;
438 return TextEmojiProcessor::Delete(startIndex, length, content, isBackward);
439 }
440
441 bool ContentController::IsIndexBeforeOrInEmoji(int32_t index)
442 {
443 int32_t startIndex = index - EMOJI_RANGE_LEFT;
444 int32_t endIndex = index + EMOJI_RANGE_RIGHT;
445 FormatIndex(startIndex, endIndex);
446 index = index - startIndex;
447 return TextEmojiProcessor::IsIndexBeforeOrInEmoji(index, GetSelectedValue(startIndex, endIndex));
448 }
449
450 std::u16string ContentController::GetValueBeforeIndex(int32_t index)
451 {
452 index = std::clamp(index, 0, static_cast<int32_t>(content_.length()));
453 return content_.substr(0, index);
454 }
455
456 std::u16string ContentController::GetValueAfterIndex(int32_t index)
457 {
458 index = std::clamp(index, 0, static_cast<int32_t>(content_.length()));
459 return content_.substr(index, content_.length() - index);
460 }
461
462 std::string ContentController::GetSelectedLimitValue(int32_t& index, int32_t& startIndex)
463 {
464 startIndex = index - AI_TEXT_RANGE_LEFT;
465 int32_t endIndex = index + AI_TEXT_RANGE_RIGHT;
466 FormatIndex(startIndex, endIndex);
467 index = index - startIndex;
468 return UtfUtils::Str16DebugToStr8(GetSelectedValue(startIndex, endIndex));
469 }
470
471 } // namespace OHOS::Ace::NG