Skip to content

Commit 2f7feef

Browse files
authored
feat: iOS Html entities handling (#258)
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please follow the template so that the reviewers can easily understand what the code changes affect --> # Summary Fixes #235; an issue that the iOS parser didn't handle special Html entities, `<`, `>` and `&` both ways. ## Test Plan - Run example app on iOS - Input a text consisting of these characters and see that they are properly switched to their codenames in emitted html - For the other way around, paste the following HTML into either default value pr `Set input's value` field and see the codenames are properly translated to the characters: ``` <html> <p>&lt;html&gt;<b>BOLD</b>&lt;/html&gt;</p> </html> ``` Also worth double checking the same on Android, its system parser handles that well. ## Screenshots / Videos <img width="814" height="346" alt="image" src="https://github.com/user-attachments/assets/68a4672d-5d4d-437f-ad14-7a48f141b9b5" /> ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | Android | ✅ (system) |
1 parent 2788beb commit 2f7feef

4 files changed

Lines changed: 39 additions & 19 deletions

File tree

ios/inputParser/InputParser.mm

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#import "StyleHeaders.h"
44
#import "UIView+React.h"
55
#import "TextInsertionUtils.h"
6+
#import "StringExtension.h"
67

78
@implementation InputParser {
89
EnrichedTextInputView *_input;
@@ -204,8 +205,8 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
204205
[result appendString: [NSString stringWithFormat:@"<%@>", tagContent]];
205206
}
206207

207-
// append the letter
208-
[result appendString:currentCharacterStr];
208+
// append the letter and escape it if needed
209+
[result appendString: [NSString stringByEscapingHtml:currentCharacterStr]];
209210

210211
// save current styles for next character's checks
211212
previousActiveStyles = currentActiveStyles;
@@ -490,6 +491,7 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
490491
BOOL closingTag = NO;
491492
NSMutableString *currentTagName = [[NSMutableString alloc] initWithString:@""];
492493
NSMutableString *currentTagParams = [[NSMutableString alloc] initWithString:@""];
494+
NSDictionary *htmlEntitiesDict = [NSString getEscapedCharactersInfoFrom:fixedHtml];
493495

494496
// firstly, extract text and initially processed tags
495497
for(int i = 0; i < fixedHtml.length; i++) {
@@ -511,7 +513,7 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
511513
} else if(!closingTag) {
512514
// we finish opening tag - get its location and optionally params and put them under tag name key in ongoingTags
513515
NSMutableArray *tagArr = [[NSMutableArray alloc] init];
514-
[tagArr addObject:[NSNumber numberWithInt:plainText.length]];
516+
[tagArr addObject:[NSNumber numberWithInteger:plainText.length]];
515517
if(currentTagParams.length > 0) {
516518
[tagArr addObject:[currentTagParams copy]];
517519
}
@@ -550,8 +552,19 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
550552
currentTagParams = [[NSMutableString alloc] initWithString:@""];
551553
} else {
552554
if(!insideTag) {
553-
// no tags logic - just append text
554-
[plainText appendString:currentCharacterStr];
555+
// no tags logic - just append the right text
556+
557+
// html entity on the index; use unescaped character and forward iterator accordingly
558+
NSArray *entityInfo = htmlEntitiesDict[@(i)];
559+
if(entityInfo != nullptr) {
560+
NSString *escaped = entityInfo[0];
561+
NSString *unescaped = entityInfo[1];
562+
[plainText appendString:unescaped];
563+
// the iterator will forward by 1 itself
564+
i += escaped.length - 1;
565+
} else {
566+
[plainText appendString:currentCharacterStr];
567+
}
555568
} else {
556569
if(gettingTagName) {
557570
if(currentCharacterChar == ' ') {

ios/inputTextView/InputTextView.mm

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ - (void)copy:(id)sender {
1414
NSString *plainText = [typedInput->textView.textStorage.string substringWithRange:typedInput->textView.selectedRange];
1515
NSString *fixedPlainText = [plainText stringByReplacingOccurrencesOfString:@"\u200B" withString:@""];
1616

17-
NSString *escapedHtml = [NSString stringByEscapingHtml:[typedInput->parser parseToHtmlFromRange:typedInput->textView.selectedRange]];
17+
NSString *parsedHtml = [typedInput->parser parseToHtmlFromRange:typedInput->textView.selectedRange];
1818

1919
NSMutableAttributedString *attrStr = [[typedInput->textView.textStorage attributedSubstringFromRange:typedInput->textView.selectedRange] mutableCopy];
2020
NSRange fullAttrStrRange = NSMakeRange(0, attrStr.length);
@@ -28,7 +28,7 @@ - (void)copy:(id)sender {
2828
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
2929
[pasteboard setItems:@[@{
3030
UTTypeUTF8PlainText.identifier : fixedPlainText,
31-
UTTypeHTML.identifier : escapedHtml,
31+
UTTypeHTML.identifier : parsedHtml,
3232
UTTypeRTF.identifier : rtfData
3333
}]];
3434
}
@@ -53,9 +53,7 @@ - (void)paste:(id)sender {
5353
htmlString = htmlValue;
5454
}
5555

56-
// unescape the html
57-
htmlString = [NSString stringByUnescapingHtml:htmlString];
58-
// validate it
56+
// validate the html
5957
NSString *initiallyProcessedHtml = [typedInput->parser initiallyProcessHtml:htmlString];
6058

6159
if(initiallyProcessedHtml != nullptr) {

ios/utils/StringExtension.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- (std::string)toCppString;
77
+ (NSString *)fromCppString:(std::string)string;
88
+ (NSString *)stringByEscapingHtml:(NSString *)html;
9-
+ (NSString *)stringByUnescapingHtml:(NSString *)html;
9+
+ (NSDictionary *)getEscapedCharactersInfoFrom:(NSString *)text;
1010
@end
1111

1212
@interface NSMutableString (StringExtension)

ios/utils/StringExtension.mm

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ + (NSString *)stringByEscapingHtml:(NSString *)html {
1616
@"&": @"&amp;",
1717
@"<": @"&lt;",
1818
@">": @"&gt;",
19-
@"\"": @"&quot;",
20-
@"'": @"&apos;"
2119
};
2220

2321
for(NSString *key in escapeMap) {
@@ -26,20 +24,31 @@ + (NSString *)stringByEscapingHtml:(NSString *)html {
2624
return escaped;
2725
}
2826

29-
+ (NSString *)stringByUnescapingHtml:(NSString *)html {
30-
NSMutableString *unescaped = [html mutableCopy];
27+
+ (NSDictionary *)getEscapedCharactersInfoFrom:(NSString *)text {
3128
NSDictionary *unescapeMap = @{
3229
@"&amp;": @"&",
3330
@"&lt;": @"<",
3431
@"&gt;": @">",
35-
@"&quot;": @"\"",
36-
@"&apos;": @"'",
3732
};
3833

34+
NSMutableDictionary *results = [[NSMutableDictionary alloc] init];
35+
3936
for(NSString *key in unescapeMap) {
40-
[unescaped replaceOccurrencesOfString:key withString:unescapeMap[key] options:NSLiteralSearch range:NSMakeRange(0, unescaped.length)];
37+
NSRange searchRange = NSMakeRange(0, text.length);
38+
NSRange foundRange;
39+
40+
while(searchRange.location < text.length) {
41+
foundRange = [text rangeOfString:key options:0 range:searchRange];
42+
if(foundRange.location == NSNotFound) {
43+
break;
44+
}
45+
results[@(foundRange.location)] = @[key, unescapeMap[key]];
46+
searchRange.location = foundRange.location + foundRange.length;
47+
searchRange.length = text.length - searchRange.location;
48+
}
4149
}
42-
return unescaped;
50+
51+
return results;
4352
}
4453

4554
@end

0 commit comments

Comments
 (0)