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