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/utils.h"
23 #include "core/text/text_emoji_processor.h"
24 #include "core/components_ng/pattern/text/typed_text.h"
25 #include "core/components_ng/pattern/text_field/text_field_pattern.h"
26 
27 namespace OHOS::Ace::NG {
28 namespace {
29 const std::string DIGIT_WHITE_LIST = "[0-9]";
30 const std::string DIGIT_DECIMAL_WHITE_LIST = "[0-9.]";
31 const std::string PHONE_WHITE_LIST = R"([0-9 \+\-\*\#\(\)])";
32 const std::string EMAIL_WHITE_LIST = R"([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~@-])";
33 // when do ai analaysis, we should list the left and right of the string
34 constexpr static int32_t AI_TEXT_RANGE_LEFT = 50;
35 constexpr static int32_t AI_TEXT_RANGE_RIGHT = 50;
36 constexpr static int32_t EMOJI_RANGE_LEFT = 150;
37 constexpr static int32_t EMOJI_RANGE_RIGHT = 150;
38 } // namespace
39 
PreprocessString(int32_t startIndex,int32_t endIndex,const std::string & value)40 std::string ContentController::PreprocessString(int32_t startIndex, int32_t endIndex, const std::string& value)
41 {
42     auto tmp = value;
43     auto pattern = pattern_.Upgrade();
44     CHECK_NULL_RETURN(pattern, value);
45     auto textField = DynamicCast<TextFieldPattern>(pattern);
46     CHECK_NULL_RETURN(textField, value);
47     if (textField->GetIsPreviewText()) {
48         TAG_LOGI(AceLogTag::ACE_TEXT_FIELD, "no PreprocessString at previewing text");
49         return tmp;
50     }
51     auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
52     auto selectValue = GetSelectedValue(startIndex, endIndex);
53     bool hasInputFilter =
54         property->GetInputFilter().has_value() && !property->GetInputFilter().value().empty() && !content_.empty();
55     if (!hasInputFilter && property->GetTextInputType().has_value() &&
56         (property->GetTextInputType().value() == TextInputType::NUMBER_DECIMAL ||
57             property->GetTextInputType().value() == TextInputType::EMAIL_ADDRESS)) {
58         char specialChar = property->GetTextInputType().value() == TextInputType::NUMBER_DECIMAL ? '.' : '@';
59         if (content_.find(specialChar) != std::string::npos && value.find(specialChar) != std::string::npos &&
60             GetSelectedValue(startIndex, endIndex).find(specialChar) == std::string::npos) {
61             tmp.erase(
62                 std::remove_if(tmp.begin(), tmp.end(), [&specialChar](char c) { return c == specialChar; }), tmp.end());
63         }
64     }
65     FilterValueType(tmp);
66     auto wideText = GetWideText();
67     auto wideTmp = StringUtils::ToWstring(tmp);
68     auto maxLength = static_cast<uint32_t>(textField->GetMaxLength());
69     auto curLength = static_cast<uint32_t>(wideText.length());
70     auto addLength = static_cast<uint32_t>(wideTmp.length());
71     auto delLength = static_cast<uint32_t>(std::abs(endIndex - startIndex));
72     addLength = std::min(addLength, maxLength - curLength + delLength);
73     wideTmp = TextEmojiProcessor::SubWstring(0, addLength, wideTmp); // clamp emoji
74     tmp = StringUtils::ToString(wideTmp);
75     return tmp;
76 }
77 
InsertValue(int32_t index,const std::string & value)78 bool ContentController::InsertValue(int32_t index, const std::string& value)
79 {
80     return ReplaceSelectedValue(index, index, value);
81 }
82 
ReplaceSelectedValue(int32_t startIndex,int32_t endIndex,const std::string & value)83 bool ContentController::ReplaceSelectedValue(int32_t startIndex, int32_t endIndex, const std::string& value)
84 {
85     FormatIndex(startIndex, endIndex);
86     auto tmp = PreprocessString(startIndex, endIndex, value);
87     auto wideText = GetWideText();
88     auto str = content_;
89     content_ = StringUtils::ToString(wideText.substr(0, startIndex)) + tmp +
90                StringUtils::ToString(wideText.substr(endIndex, static_cast<int32_t>(wideText.length()) - endIndex));
91     auto len = content_.length();
92     FilterValue();
93     insertValue_ = tmp;
94     if (value.length() == 1 && content_.length() < len) {
95         content_ = str;
96         insertValue_ = "";
97     }
98     return !tmp.empty();
99 }
100 
GetSelectedValue(int32_t startIndex,int32_t endIndex)101 std::string ContentController::GetSelectedValue(int32_t startIndex, int32_t endIndex)
102 {
103     FormatIndex(startIndex, endIndex);
104     auto wideText = GetWideText();
105     return StringUtils::ToString(wideText.substr(startIndex, endIndex - startIndex));
106 }
107 
FormatIndex(int32_t & startIndex,int32_t & endIndex)108 void ContentController::FormatIndex(int32_t& startIndex, int32_t& endIndex)
109 {
110     startIndex = std::min(startIndex, endIndex);
111     endIndex = std::max(startIndex, endIndex);
112     auto wideText = GetWideText();
113     startIndex = std::clamp(startIndex, 0, static_cast<int32_t>(wideText.length()));
114     endIndex = std::clamp(endIndex, 0, static_cast<int32_t>(wideText.length()));
115 }
116 
FilterTextInputStyle(bool & textChanged,std::string & result)117 void ContentController::FilterTextInputStyle(bool& textChanged, std::string& result)
118 {
119     auto pattern = pattern_.Upgrade();
120     CHECK_NULL_VOID(pattern);
121     auto textField = DynamicCast<TextFieldPattern>(pattern);
122     CHECK_NULL_VOID(textField);
123     auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
124     if (!property->GetTextInputType().has_value()) {
125         return;
126     }
127     switch (property->GetTextInputType().value()) {
128         case TextInputType::NUMBER: {
129             textChanged |= FilterWithEvent(DIGIT_WHITE_LIST, result);
130             break;
131         }
132         case TextInputType::PHONE: {
133             textChanged |= FilterWithEvent(PHONE_WHITE_LIST, result);
134             break;
135         }
136         case TextInputType::EMAIL_ADDRESS: {
137             textChanged |= FilterWithEvent(EMAIL_WHITE_LIST, result);
138             textChanged |= FilterWithEmail(result);
139             break;
140         }
141         case TextInputType::VISIBLE_PASSWORD:
142             break;
143         case TextInputType::NEW_PASSWORD:
144             break;
145         case TextInputType::NUMBER_PASSWORD: {
146             textChanged |= FilterWithEvent(DIGIT_WHITE_LIST, result);
147             break;
148         }
149         case TextInputType::SCREEN_LOCK_PASSWORD: {
150             textChanged |= FilterWithAscii(result);
151             break;
152         }
153         case TextInputType::NUMBER_DECIMAL: {
154             textChanged |= FilterWithEvent(DIGIT_DECIMAL_WHITE_LIST, result);
155             textChanged |= FilterWithDecimal(result);
156             break;
157         }
158         default: {
159             break;
160         }
161     }
162 }
163 
FilterValue()164 void ContentController::FilterValue()
165 {
166     bool textChanged = false;
167     auto result = content_;
168     auto pattern = pattern_.Upgrade();
169     CHECK_NULL_VOID(pattern);
170     auto textField = DynamicCast<TextFieldPattern>(pattern);
171     CHECK_NULL_VOID(textField);
172     if (textField->GetIsPreviewText()) {
173         TAG_LOGI(AceLogTag::ACE_TEXT_FIELD, "no filter at previewing text");
174         return;
175     }
176 
177     auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
178 
179     bool hasInputFilter =
180         property->GetInputFilter().has_value() && !property->GetInputFilter().value().empty() && !content_.empty();
181     if (!hasInputFilter) {
182         FilterTextInputStyle(textChanged, result);
183     } else {
184         textChanged |= FilterWithEvent(property->GetInputFilter().value(), result);
185         if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
186             FilterTextInputStyle(textChanged, result);
187         }
188     }
189     if (textChanged) {
190         content_ = result;
191     }
192     auto maxLength =
193         property->HasMaxLength() ? property->GetMaxLengthValue(Infinity<uint32_t>()) : Infinity<uint32_t>();
194     auto textWidth = static_cast<int32_t>(GetWideText().length());
195     if (GreatNotEqual(textWidth, maxLength)) {
196         // clamp emoji
197         auto subWstring = TextEmojiProcessor::SubWstring(0, maxLength, GetWideText());
198         content_ = StringUtils::ToString(subWstring);
199     }
200 }
201 
FilterValueType(std::string & value)202 void ContentController::FilterValueType(std::string& value)
203 {
204     bool textChanged = false;
205     auto result = value;
206     auto pattern = pattern_.Upgrade();
207     CHECK_NULL_VOID(pattern);
208     auto textField = DynamicCast<TextFieldPattern>(pattern);
209     CHECK_NULL_VOID(textField);
210     auto property = textField->GetLayoutProperty<TextFieldLayoutProperty>();
211 
212     bool hasInputFilter = property->GetInputFilter().has_value() && !property->GetInputFilter().value().empty();
213     if (!hasInputFilter) {
214         FilterTextInputStyle(textChanged, result);
215     } else {
216         textChanged = FilterWithEvent(property->GetInputFilter().value(), result) || textChanged;
217         if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
218             FilterTextInputStyle(textChanged, result);
219         }
220     }
221     if (textChanged) {
222         value = result;
223     }
224 }
225 
RemoveErrorTextFromValue(const std::string & value,const std::string & errorText)226 std::string ContentController::RemoveErrorTextFromValue(const std::string& value, const std::string& errorText)
227 {
228     std::string result;
229     int32_t valuePtr = 0;
230     int32_t errorTextPtr = 0;
231     auto valueSize = static_cast<int32_t>(value.length());
232     auto errorTextSize = static_cast<int32_t>(errorText.length());
233     while (errorTextPtr < errorTextSize) {
234         while (value[valuePtr] != errorText[errorTextPtr] && valuePtr < valueSize) {
235             result += value[valuePtr];
236             valuePtr++;
237         }
238         // no more text left to remove in value
239         if (valuePtr >= valueSize) {
240             return result;
241         }
242         // increase both value ptr and error text ptr if char in value is removed
243         valuePtr++;
244         errorTextPtr++;
245     }
246     result += value.substr(valuePtr);
247     return result;
248 }
249 
FilterWithRegex(const std::string & filter,std::string & result)250 std::string ContentController::FilterWithRegex(const std::string& filter, std::string& result)
251 {
252     // convert wstring for processing unicode characters
253     std::wstring wFilter = StringUtils::ToWstring(filter);
254     std::wstring wResult = StringUtils::ToWstring(result);
255     std::wregex wFilterRegex(wFilter);
256     std::wstring wErrorText = std::regex_replace(wResult, wFilterRegex, L"");
257     std::string errorText = StringUtils::ToString(wErrorText);
258     result = RemoveErrorTextFromValue(result, errorText);
259     return errorText;
260 }
261 
FilterWithEmail(std::string & result)262 bool ContentController::FilterWithEmail(std::string& result)
263 {
264     auto valueToUpdate = result;
265     bool first = true;
266     std::replace_if(
267         result.begin(), result.end(),
268         [&first](const char c) {
269             if (c == '@' && !first) {
270                 return true;
271             }
272             if (c == '@') {
273                 first = false;
274             }
275             return false;
276         },
277         ' ');
278 
279     // remove the spaces
280     result.erase(std::remove(result.begin(), result.end(), ' '), result.end());
281     return result != valueToUpdate;
282 }
283 
FilterWithAscii(std::string & result)284 bool ContentController::FilterWithAscii(std::string& result)
285 {
286     if (result.empty()) {
287         return false;
288     }
289     auto valueToUpdate = result;
290     bool textChange = true;
291     std::string errorText;
292     result.clear();
293     for (char valuePtr : valueToUpdate) {
294         if (isascii(valuePtr)) {
295             result += valuePtr;
296         } else {
297             errorText += valuePtr;
298         }
299     }
300     if (errorText.empty()) {
301         textChange = false;
302     } else {
303         LOGI("FilterWithAscii Error text %{private}s", errorText.c_str());
304     }
305     return textChange;
306 }
307 
FilterWithDecimal(std::string & result)308 bool ContentController::FilterWithDecimal(std::string& result)
309 {
310     auto valueToUpdate = result;
311     bool first = true;
312     std::replace_if(
313         result.begin(), result.end(),
314         [&first](const char c) {
315             if (c == '.' && !first) {
316                 return true;
317             }
318             if (c == '.') {
319                 first = false;
320             }
321             return false;
322         },
323         ' ');
324     result.erase(std::remove(result.begin(), result.end(), ' '), result.end());
325     return result != valueToUpdate;
326 }
327 
FilterWithEvent(const std::string & filter,std::string & result)328 bool ContentController::FilterWithEvent(const std::string& filter, std::string& result)
329 {
330     auto errorValue = FilterWithRegex(filter, result);
331     if (!errorValue.empty()) {
332         auto pattern = pattern_.Upgrade();
333         CHECK_NULL_RETURN(pattern, false);
334         auto textField = DynamicCast<TextFieldPattern>(pattern);
335         CHECK_NULL_RETURN(textField, false);
336         auto host = textField->GetHost();
337         CHECK_NULL_RETURN(host, false);
338         auto eventHub = host->GetEventHub<TextFieldEventHub>();
339         eventHub->FireOnInputFilterError(errorValue);
340         auto textFieldAccessibilityProperty = host->GetAccessibilityProperty<TextFieldAccessibilityProperty>();
341         CHECK_NULL_RETURN(textFieldAccessibilityProperty, false);
342         textFieldAccessibilityProperty->SetErrorText(errorValue);
343     }
344     return !errorValue.empty();
345 }
346 
erase(int32_t startIndex,int32_t length)347 void ContentController::erase(int32_t startIndex, int32_t length)
348 {
349     if (startIndex < 0 || startIndex >= static_cast<int32_t>(GetWideText().length())) {
350         return;
351     }
352     auto wideText = GetWideText().erase(startIndex, length);
353     content_ = StringUtils::ToString(wideText);
354 }
355 
Delete(int32_t startIndex,int32_t length,bool isBackward)356 int32_t ContentController::Delete(int32_t startIndex, int32_t length, bool isBackward)
357 {
358     int32_t result = TextEmojiProcessor::Delete(startIndex, length, content_, isBackward);
359     if (length > 0 && result == 0) {
360         // try delete whole emoji
361         if (isBackward) {
362             TextEmojiSubStringRange range = TextEmojiProcessor::CalSubWstringRange(
363                 startIndex - length, length, GetWideText(), true);
364             result = TextEmojiProcessor::Delete(range.endIndex,
365                 length, content_, true);
366         } else {
367             TextEmojiSubStringRange range = TextEmojiProcessor::CalSubWstringRange(
368                 startIndex, length, GetWideText(), true);
369             result = TextEmojiProcessor::Delete(range.startIndex,
370                 length, content_, true);
371         }
372     }
373     return result;
374 }
375 
GetDeleteLength(int32_t startIndex,int32_t length,bool isBackward)376 int32_t ContentController::GetDeleteLength(int32_t startIndex, int32_t length, bool isBackward)
377 {
378     auto content = content_;
379     return TextEmojiProcessor::Delete(startIndex, length, content, isBackward);
380 }
381 
IsIndexBeforeOrInEmoji(int32_t index)382 bool ContentController::IsIndexBeforeOrInEmoji(int32_t index)
383 {
384     int32_t startIndex = index - EMOJI_RANGE_LEFT;
385     int32_t endIndex = index + EMOJI_RANGE_RIGHT;
386     FormatIndex(startIndex, endIndex);
387     index = index - startIndex;
388     return TextEmojiProcessor::IsIndexBeforeOrInEmoji(index, GetSelectedValue(startIndex, endIndex));
389 }
390 
GetValueBeforeIndex(int32_t index)391 std::string ContentController::GetValueBeforeIndex(int32_t index)
392 {
393     return StringUtils::ToString(GetWideText().substr(0, index));
394 }
395 
GetValueAfterIndex(int32_t index)396 std::string ContentController::GetValueAfterIndex(int32_t index)
397 {
398     return StringUtils::ToString(GetWideText().substr(index, GetWideText().length() - index));
399 }
400 
GetSelectedLimitValue(int32_t & index,int32_t & startIndex)401 std::string ContentController::GetSelectedLimitValue(int32_t& index, int32_t& startIndex)
402 {
403     startIndex = index - AI_TEXT_RANGE_LEFT;
404     int32_t endIndex = index + AI_TEXT_RANGE_RIGHT;
405     FormatIndex(startIndex, endIndex);
406     index = index - startIndex;
407     return GetSelectedValue(startIndex, endIndex);
408 }
409 
410 } // namespace OHOS::Ace::NG