From 419bf4fd1ace7e7d40b6420e2936e746dacf6c2b Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Wed, 30 Jul 2025 15:31:02 +0200 Subject: [PATCH] feat: improve parser newline sensitivity and fix empty list items issues --- example/ios/Podfile.lock | 2 +- ios/parser/EditorParser.mm | 142 ++++++++++++++++++++++++++++--------- 2 files changed, 110 insertions(+), 34 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 4077701da..5de90b547 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1940,7 +1940,7 @@ SPEC CHECKSUMS: fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 hermes-engine: c32f2e405098bc1ebe30630a051ddce6f21d3c2e - RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: 0ada4fb1e5c5637bff940dc40b94e2d3bf96b0ab RCTRequired: 76ca80ff10acb3834ed0dacba9645645009578a2 RCTTypeSafety: 01b27f48153eb2d222c0ad4737fe291e9549946a diff --git a/ios/parser/EditorParser.mm b/ios/parser/EditorParser.mm index 1c69d0368..e0bff1035 100644 --- a/ios/parser/EditorParser.mm +++ b/ios/parser/EditorParser.mm @@ -53,40 +53,53 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { unichar currentCharacterChar = [_editor->textView.textStorage.string characterAtIndex:currentRange.location]; if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentCharacterChar]) { - // just an empty line - only br tag and no style tags if(newLine) { - // keep the newLine at YES - - // br is not a valid child of lists: - if(inOrderedList || inUnorderedList) { - [result appendString:@"\n
  • "]; + // we can either have an empty list item OR need to close the list and put a BR in such a situation + // the existence of the list must be checked on 0 length range, not on the newline character + if(inOrderedList) { + OrderedListStyle *oStyle = _editor->stylesDict[@(OrderedList)]; + BOOL detected = [oStyle detectStyle: NSMakeRange(currentRange.location, 0)]; + if(detected) { + [result appendString:@"\n
  • "]; + } else { + [result appendString:@"\n\n
    "]; + inOrderedList = NO; + } + } else if(inUnorderedList) { + UnorderedListStyle *uStyle = _editor->stylesDict[@(UnorderedList)]; + BOOL detected = [uStyle detectStyle: NSMakeRange(currentRange.location, 0)]; + if(detected) { + [result appendString:@"\n
  • "]; + } else { + [result appendString:@"\n\n
    "]; + inUnorderedList = NO; + } } else { [result appendString:@"\n
    "]; } - continue; - } - - // newline finishes a paragraph and all style tags need to be closed - // we use previous styles - NSArray *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]]; - - // append closing tags - for(NSNumber *style in sortedEndedStyles) { - NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location]; - [result appendString: [NSString stringWithFormat:@"", tagContent]]; - } - - // append closing paragraph tag - if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])] || - [previousActiveStyles containsObject:@([OrderedListStyle getStyleType])] || - [previousActiveStyles containsObject:@([H1Style getStyleType])] || - [previousActiveStyles containsObject:@([H2Style getStyleType])] || - [previousActiveStyles containsObject:@([H3Style getStyleType])] || - [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] - ) { - // do nothing, proper closing paragraph tags have been already appended } else { - [result appendString:@"

    "]; + // newline finishes a paragraph and all style tags need to be closed + // we use previous styles + NSArray *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]]; + + // append closing tags + for(NSNumber *style in sortedEndedStyles) { + NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location]; + [result appendString: [NSString stringWithFormat:@"", tagContent]]; + } + + // append closing paragraph tag + if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])] || + [previousActiveStyles containsObject:@([OrderedListStyle getStyleType])] || + [previousActiveStyles containsObject:@([H1Style getStyleType])] || + [previousActiveStyles containsObject:@([H2Style getStyleType])] || + [previousActiveStyles containsObject:@([H3Style getStyleType])] || + [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] + ) { + // do nothing, proper closing paragraph tags have been already appended + } else { + [result appendString:@"

    "]; + } } // clear the previous styles @@ -392,12 +405,24 @@ - (NSString * _Nullable)initiallyProcessHtml:(NSString * _Nonnull)html { if(html.length >= 15) { NSString *firstSix = [html substringWithRange:NSMakeRange(0, 6)]; NSString *lastSeven = [html substringWithRange:NSMakeRange(html.length-7, 7)]; + NSInteger newlinesCount = [[html componentsSeparatedByString:@"\n"] count] - 1; if([firstSix isEqualToString:@""] && [lastSeven isEqualToString:@""]) { - // looks like our format - // we want to get the string without and and their newlines - // so we skip first 7 characters and get the string 7+8 = 15 characters shorter - fixedHtml = [html substringWithRange: NSMakeRange(7, html.length - 15)]; + if(newlinesCount >= 2) { + // looks like our format + // we want to get the string without and and their newlines + // so we skip first 7 characters and get the string 7+8 = 15 characters shorter + fixedHtml = [html substringWithRange: NSMakeRange(7, html.length - 15)]; + } else { + // most likely a valid html but with some newline differences + fixedHtml = [html copy]; + // firstly remove newlined html tags if any: + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" withString:@""]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n" withString:@""]; + // fallback; remove html tags without their newlines + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"" withString:@""]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"" withString:@""]; + } } else { // in other case we are most likely working with some external html - try getting the styles from between body tags NSRange openingBodyRange = [html rangeOfString:@""]; @@ -411,9 +436,60 @@ - (NSString * _Nullable)initiallyProcessHtml:(NSString * _Nonnull)html { } } + // second processing - try fixing htmls with wrong newlines' setup + if(fixedHtml != nullptr) { + NSInteger newlinesCount = [[fixedHtml componentsSeparatedByString:@"/n"] count] - 1; + if(newlinesCount == 0) { + // add
    tag wherever needed + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

    " withString:@"
    "]; + + // remove

    tags inside of

  • + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
  • " withString:@"

  • "]; + fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"

  • " withString:@""]; + + // tags that have to be in separate lines + fixedHtml = [self stringByAddingNewlinesToTag:@"
    " inString:fixedHtml leading:YES trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:YES trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
      " inString:fixedHtml leading:YES trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
    " inString:fixedHtml leading:YES trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
    " inString:fixedHtml leading:YES trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
    " inString:fixedHtml leading:YES trailing:YES]; + + // line opening tags + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

  • " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + + // line closing tags + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

  • " inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + } + } + return fixedHtml; } +- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag inString:(NSString *)html leading:(BOOL)leading trailing:(BOOL)trailing { + NSString *str = [html copy]; + if(leading) { + NSString *formattedTag = [NSString stringWithFormat:@">%@", tag]; + NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag]; + str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag]; + } + if(trailing) { + NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag]; + NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag]; + str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag]; + } + return str; +} + - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml { NSMutableString *plainText = [[NSMutableString alloc] initWithString: @""]; NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];