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