diff --git a/.maestro/enrichedInput/flows/empty_element_parsing.yaml b/.maestro/enrichedInput/flows/empty_element_parsing.yaml
index 4429bf8e..981cd4da 100644
--- a/.maestro/enrichedInput/flows/empty_element_parsing.yaml
+++ b/.maestro/enrichedInput/flows/empty_element_parsing.yaml
@@ -1,6 +1,4 @@
appId: swmansion.enriched.example
-tags:
- - android-only
---
# PR #284 - fix: parsing empty elements
- launchApp
diff --git a/.maestro/enrichedInput/screenshots/ios/empty_element_parsing.png b/.maestro/enrichedInput/screenshots/ios/empty_element_parsing.png
new file mode 100644
index 00000000..ea9dd331
Binary files /dev/null and b/.maestro/enrichedInput/screenshots/ios/empty_element_parsing.png differ
diff --git a/ios/htmlParser/HtmlParser.mm b/ios/htmlParser/HtmlParser.mm
index d2ded44c..a6e84dd6 100644
--- a/ios/htmlParser/HtmlParser.mm
+++ b/ios/htmlParser/HtmlParser.mm
@@ -409,34 +409,6 @@ + (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html
inString:fixedHtml
leading:NO
trailing:YES];
-
- // this is more like a hack but for some reason the last
in
- //
and are not properly changed into zero width
- // space so we do that manually here
- fixedHtml = [fixedHtml
- stringByReplacingOccurrencesOfString:@"
\n
"
- withString:@"\u200B
\n"];
- fixedHtml = [fixedHtml
- stringByReplacingOccurrencesOfString:@"
\n"
- withString:@"\u200B
\n"];
-
- // The same like above for (blockquote and codeblock) this is more like a
- // hack but for some reason the last in and are not
- // properly changed into zero width space so we do that manually here
- // TODO: investigate this further, issue is already described here:
- // https://github.com/software-mansion/react-native-enriched/issues/505
- fixedHtml = [fixedHtml
- stringByReplacingOccurrencesOfString:@"\n
"
- withString:@"\u200B\n"];
- fixedHtml = [fixedHtml
- stringByReplacingOccurrencesOfString:@"\n"
- withString:@"\u200B\n"];
-
- // replace "
" at the end with "
\n" if input is not empty to properly
- // handle last
in html
- if ([fixedHtml hasSuffix:@"
"] && fixedHtml.length != 4) {
- fixedHtml = [fixedHtml stringByAppendingString:@"\n"];
- }
}
return fixedHtml;
@@ -455,6 +427,7 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
BOOL gettingTagName = NO;
BOOL gettingTagParams = NO;
BOOL closingTag = NO;
+ BOOL lastTagWasBr = NO;
NSMutableString *currentTagName =
[[NSMutableString alloc] initWithString:@""];
NSMutableString *currentTagParams =
@@ -490,12 +463,36 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
}
if ([currentTagName isEqualToString:@"br"]) {
+ lastTagWasBr = YES;
// do nothing, we don't include these tags in styles
} else if ([currentTagName isEqualToString:@"li"]) {
- // Only track checkbox state if we're inside a checkbox list
- if (insideCheckboxList && !closingTag) {
- BOOL isChecked = [currentTagParams containsString:@"checked"];
- checkboxStates[@(plainText.length)] = @(isChecked);
+ if (!closingTag) {
+ // Opening tag
+ // Track checkbox state if we're inside a checkbox list
+ if (insideCheckboxList) {
+ BOOL isChecked = [currentTagParams containsString:@"checked"];
+ checkboxStates[@(plainText.length)] = @(isChecked);
+ }
+ // Record the start location so we can check if it's empty when
+ // closing
+ ongoingTags[@"li"] = @[ @(plainText.length) ];
+ } else {
+ // Closing tag
+ NSArray *tagData = ongoingTags[@"li"];
+ if (tagData != nil) {
+ NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
+ NSString *innerContent = [plainText substringFromIndex:tagLocation];
+
+ // If the li is completely empty (or just contains layout newlines),
+ // inject ZWS
+ if ([innerContent
+ stringByTrimmingCharactersInSet:[NSCharacterSet
+ newlineCharacterSet]]
+ .length == 0) {
+ [plainText appendString:@"\u200B"];
+ }
+ [ongoingTags removeObjectForKey:@"li"];
+ }
}
} else if (!closingTag) {
BOOL isPlainParagraph = [currentTagName isEqualToString:@"p"] &&
@@ -529,6 +526,12 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
i += 1;
}
+ if ([currentTagName isEqualToString:@"img"]) {
+ // Images have no inner text, so we manually break the
streak
+ // here.
+ lastTagWasBr = NO;
+ }
+
if (isSelfClosing) {
[self finalizeTagEntry:currentTagName
ongoingTags:ongoingTags
@@ -549,12 +552,36 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
BOOL isBlockTag = [self isBlockTag:currentTagName];
+ // ZWS logic for blockquote and codeblock
+ BOOL needsZWS = [currentTagName isEqualToString:@"blockquote"] ||
+ [currentTagName isEqualToString:@"codeblock"];
+ BOOL isEmptyBlock = NO;
+ if (needsZWS) {
+ NSArray *tagData = ongoingTags[currentTagName];
+ if (tagData != nil) {
+ NSInteger tagLoc = [tagData[0] intValue];
+ NSString *inner = [plainText substringFromIndex:tagLoc];
+ if ([inner stringByTrimmingCharactersInSet:[NSCharacterSet
+ newlineCharacterSet]]
+ .length == 0) {
+ isEmptyBlock = YES;
+ }
+ }
+ }
+
// skip one newline if it was added before some closing tags that are
// in separate lines
if (isBlockTag && plainText.length > 0 &&
[[NSCharacterSet newlineCharacterSet]
characterIsMember:[plainText
characterAtIndex:plainText.length - 1]]) {
+
+ // If the last thing processed was a
, or the block is totally
+ // empty, inject a \u200B before trimming the trailing newline to save
+ // the empty line.
+ if (lastTagWasBr || isEmptyBlock) {
+ [plainText insertString:@"\u200B" atIndex:plainText.length - 1];
+ }
plainText = [[plainText
substringWithRange:NSMakeRange(0, plainText.length - 1)]
mutableCopy];
@@ -589,6 +616,11 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
i += escaped.length - 1;
} else {
[plainText appendString:currentCharacterStr];
+ // Any typed character that isn't a newline breaks the
streak
+ if (![[NSCharacterSet newlineCharacterSet]
+ characterIsMember:currentCharacterChar]) {
+ lastTagWasBr = NO;
+ }
}
} else {
if (gettingTagName) {