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 " - 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) {