• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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