Skip to content

Commit a253348

Browse files
fix(iOS): remove hacks for last element parsing (#621)
# Summary Fixes: #483 This PR remove hacks for last element parsing in lists, codeblock and blockquote. After removing the hacks also those issues should not occur: #505 #207 ## Test Plan Run reproduction step from #483 The issue should be resolved. ## Screenshots / Videos https://github.com/user-attachments/assets/2d982551-90da-49fe-82c7-21fa3660dce6 ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | Android | ❌ | ## Checklist - [x] E2E tests are passing - [x] Required E2E tests have been added (if applicable)
1 parent 34fcc8d commit a253348

3 files changed

Lines changed: 64 additions & 34 deletions

File tree

.maestro/enrichedInput/flows/empty_element_parsing.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
appId: swmansion.enriched.example
2-
tags:
3-
- android-only
42
---
53
# PR #284 - fix: parsing empty elements
64
- launchApp
11.8 KB
Loading

ios/htmlParser/HtmlParser.mm

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -409,34 +409,6 @@ + (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html
409409
inString:fixedHtml
410410
leading:NO
411411
trailing:YES];
412-
413-
// this is more like a hack but for some reason the last <br> in
414-
// <blockquote> and <codeblock> are not properly changed into zero width
415-
// space so we do that manually here
416-
fixedHtml = [fixedHtml
417-
stringByReplacingOccurrencesOfString:@"<br>\n</blockquote>"
418-
withString:@"<p>\u200B</p>\n</blockquote>"];
419-
fixedHtml = [fixedHtml
420-
stringByReplacingOccurrencesOfString:@"<br>\n</codeblock>"
421-
withString:@"<p>\u200B</p>\n</codeblock>"];
422-
423-
// The same like above for (blockquote and codeblock) this is more like a
424-
// hack but for some reason the last <li></li> in <ul> and <ol> are not
425-
// properly changed into zero width space so we do that manually here
426-
// TODO: investigate this further, issue is already described here:
427-
// https://github.com/software-mansion/react-native-enriched/issues/505
428-
fixedHtml = [fixedHtml
429-
stringByReplacingOccurrencesOfString:@"<li></li>\n</ul>"
430-
withString:@"<li>\u200B</li>\n</ul>"];
431-
fixedHtml = [fixedHtml
432-
stringByReplacingOccurrencesOfString:@"<li></li>\n</ol>"
433-
withString:@"<li>\u200B</li>\n</ol>"];
434-
435-
// replace "<br>" at the end with "<br>\n" if input is not empty to properly
436-
// handle last <br> in html
437-
if ([fixedHtml hasSuffix:@"<br>"] && fixedHtml.length != 4) {
438-
fixedHtml = [fixedHtml stringByAppendingString:@"\n"];
439-
}
440412
}
441413

442414
return fixedHtml;
@@ -455,6 +427,7 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
455427
BOOL gettingTagName = NO;
456428
BOOL gettingTagParams = NO;
457429
BOOL closingTag = NO;
430+
BOOL lastTagWasBr = NO;
458431
NSMutableString *currentTagName =
459432
[[NSMutableString alloc] initWithString:@""];
460433
NSMutableString *currentTagParams =
@@ -490,12 +463,36 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
490463
}
491464

492465
if ([currentTagName isEqualToString:@"br"]) {
466+
lastTagWasBr = YES;
493467
// do nothing, we don't include these tags in styles
494468
} else if ([currentTagName isEqualToString:@"li"]) {
495-
// Only track checkbox state if we're inside a checkbox list
496-
if (insideCheckboxList && !closingTag) {
497-
BOOL isChecked = [currentTagParams containsString:@"checked"];
498-
checkboxStates[@(plainText.length)] = @(isChecked);
469+
if (!closingTag) {
470+
// Opening tag <li>
471+
// Track checkbox state if we're inside a checkbox list
472+
if (insideCheckboxList) {
473+
BOOL isChecked = [currentTagParams containsString:@"checked"];
474+
checkboxStates[@(plainText.length)] = @(isChecked);
475+
}
476+
// Record the start location so we can check if it's empty when
477+
// closing
478+
ongoingTags[@"li"] = @[ @(plainText.length) ];
479+
} else {
480+
// Closing tag </li>
481+
NSArray *tagData = ongoingTags[@"li"];
482+
if (tagData != nil) {
483+
NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
484+
NSString *innerContent = [plainText substringFromIndex:tagLocation];
485+
486+
// If the li is completely empty (or just contains layout newlines),
487+
// inject ZWS
488+
if ([innerContent
489+
stringByTrimmingCharactersInSet:[NSCharacterSet
490+
newlineCharacterSet]]
491+
.length == 0) {
492+
[plainText appendString:@"\u200B"];
493+
}
494+
[ongoingTags removeObjectForKey:@"li"];
495+
}
499496
}
500497
} else if (!closingTag) {
501498
BOOL isPlainParagraph = [currentTagName isEqualToString:@"p"] &&
@@ -529,6 +526,12 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
529526
i += 1;
530527
}
531528

529+
if ([currentTagName isEqualToString:@"img"]) {
530+
// Images have no inner text, so we manually break the <br> streak
531+
// here.
532+
lastTagWasBr = NO;
533+
}
534+
532535
if (isSelfClosing) {
533536
[self finalizeTagEntry:currentTagName
534537
ongoingTags:ongoingTags
@@ -549,12 +552,36 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
549552

550553
BOOL isBlockTag = [self isBlockTag:currentTagName];
551554

555+
// ZWS logic for blockquote and codeblock
556+
BOOL needsZWS = [currentTagName isEqualToString:@"blockquote"] ||
557+
[currentTagName isEqualToString:@"codeblock"];
558+
BOOL isEmptyBlock = NO;
559+
if (needsZWS) {
560+
NSArray *tagData = ongoingTags[currentTagName];
561+
if (tagData != nil) {
562+
NSInteger tagLoc = [tagData[0] intValue];
563+
NSString *inner = [plainText substringFromIndex:tagLoc];
564+
if ([inner stringByTrimmingCharactersInSet:[NSCharacterSet
565+
newlineCharacterSet]]
566+
.length == 0) {
567+
isEmptyBlock = YES;
568+
}
569+
}
570+
}
571+
552572
// skip one newline if it was added before some closing tags that are
553573
// in separate lines
554574
if (isBlockTag && plainText.length > 0 &&
555575
[[NSCharacterSet newlineCharacterSet]
556576
characterIsMember:[plainText
557577
characterAtIndex:plainText.length - 1]]) {
578+
579+
// If the last thing processed was a <br>, or the block is totally
580+
// empty, inject a \u200B before trimming the trailing newline to save
581+
// the empty line.
582+
if (lastTagWasBr || isEmptyBlock) {
583+
[plainText insertString:@"\u200B" atIndex:plainText.length - 1];
584+
}
558585
plainText = [[plainText
559586
substringWithRange:NSMakeRange(0, plainText.length - 1)]
560587
mutableCopy];
@@ -589,6 +616,11 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
589616
i += escaped.length - 1;
590617
} else {
591618
[plainText appendString:currentCharacterStr];
619+
// Any typed character that isn't a newline breaks the <br> streak
620+
if (![[NSCharacterSet newlineCharacterSet]
621+
characterIsMember:currentCharacterChar]) {
622+
lastTagWasBr = NO;
623+
}
592624
}
593625
} else {
594626
if (gettingTagName) {

0 commit comments

Comments
 (0)