4

I have written a function that converts HTML text to NSAttributedString. It is working fine. However, I have noticed that some tags when nested inside another tag, their fonts get overwritten.

Here's my code.

+(NSMutableAttributedString*) replaceHTMLTags : (NSString*) text : (NSString*) fontName : (CGFloat) fontSize
{
    UIFont* font = [UIFont fontWithName:fontName size:fontSize];
    NSMutableParagraphStyle* paragraphStyle = [[NSMutableParagraphStyle alloc]init];
    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
    paragraphStyle.alignment = NSTextAlignmentJustified;

    text = [text stringByReplacingOccurrencesOfString:@"<br>" withString:@"\n"];
    NSMutableAttributedString* finalText = [[NSMutableAttributedString alloc]initWithString:text];

    [finalText setAttributes:@{NSFontAttributeName:font} range:NSMakeRange(0, [finalText string].length)];

    finalText = [self recurseFunc:finalText :@"" : font : paragraphStyle];
    return finalText;
}

+(NSMutableAttributedString*) recurseFunc : (NSMutableAttributedString*) text : (NSString*) tag : (UIFont*) font : (NSMutableParagraphStyle*) paragraphStyle
{
    NSMutableAttributedString* finalText = text;

    NSRange newOpenTagRange;
    //RECURSE IF THERE ARE MORE TAGS
    while((newOpenTagRange = [[text string] rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location != NSNotFound)
    {
        NSString* openTagName = [[text string] substringWithRange:newOpenTagRange];
        NSString* closeTagName = [self getCloseTagName: openTagName];
        NSRange newCloseTagRange = [[text string ]rangeOfString:closeTagName];

        if(newCloseTagRange.location != NSNotFound)
        {
            NSString* textWithTags = [[text string] substringWithRange:NSMakeRange(newOpenTagRange.location, newCloseTagRange.location - newOpenTagRange.location + newCloseTagRange.length)];
            NSString* newPlainText = [textWithTags stringByReplacingOccurrencesOfString:openTagName withString:@""];
            newPlainText = [newPlainText stringByReplacingOccurrencesOfString:closeTagName withString:@""];

            NSMutableAttributedString* newText = [[NSMutableAttributedString alloc]initWithString:newPlainText attributes:@{NSFontAttributeName:font,  NSParagraphStyleAttributeName:paragraphStyle}];

            newText = [self recurseFunc:newText :openTagName : font : paragraphStyle];
            [finalText replaceCharactersInRange:NSMakeRange(newOpenTagRange.location, newCloseTagRange.location - newOpenTagRange.location + newCloseTagRange.length) withAttributedString:newText];
        }
        else
        {
            NSLog(@"Cannot find closing tag for tag %@", openTagName);
        }
    }

    //FORMAT HTML TAGS
    if([tag containsString:@"<p"])
    {
        [finalText.mutableString appendString:@"\n\n"];
    }

    else if ([tag isEqualToString:@"<i>"])
    {
        UIFont* italicFont = [UIFont fontWithName:@"Arial-ItalicMT" size:DEFAULT_FONT_SIZE];
        [finalText addAttribute:NSFontAttributeName value:italicFont range:NSMakeRange(0, [finalText string].length)];
    }
    else if ([tag isEqualToString:@"<b>"])
    {
        UIFont* boldFont = [UIFont fontWithName:@"Arial-BoldMT" size:DEFAULT_FONT_SIZE];
        [finalText addAttribute:NSFontAttributeName value:boldFont range:NSMakeRange(0, [finalText string].length)];

    }
    else if([tag isEqualToString:@"<ul>"])
    {
        NSMutableParagraphStyle* tempStyle = [[NSMutableParagraphStyle alloc]init];
        tempStyle.headIndent = 30;
        tempStyle.firstLineHeadIndent = 10;
        tempStyle.lineBreakMode = NSLineBreakByWordWrapping;
        tempStyle.alignment = NSTextAlignmentJustified;

        NSString* temp = [[finalText string]stringByReplacingOccurrencesOfString:@"###" withString:@"•\t"];
        temp = [NSString stringWithFormat:@"\n%@", temp];
        [finalText setAttributedString:[[NSAttributedString alloc] initWithString:temp]];

        [finalText addAttribute:NSParagraphStyleAttributeName value:tempStyle range:NSMakeRange(0, [finalText string].length)];


    }
    else if ([tag isEqualToString:@"<li>"])
    {
        NSMutableAttributedString* tempAS = [[NSMutableAttributedString alloc]initWithString:@"###$$$\n"];
        NSRange r = [[tempAS string]rangeOfString:@"$$$"];
        [tempAS replaceCharactersInRange:r withAttributedString:finalText];
        [finalText setAttributedString:tempAS];

    }
    return finalText;
}

This does exactly what it is supposed to do, except for one specific case.

For instance, if I have a <b> or an <i> tag inside a <ul><li> tag, the <b> or <i> don't get rendered.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
user2771150
  • 722
  • 4
  • 10
  • 33
  • Didn't tested your whole code, but with a quicklook, I'd guess that a `someText` won't work, not giving italic AND bold. – Larme Mar 14 '17 at 21:42
  • @Larme I have tested the whole code. It all works fine. The problem is in a scenario like this. `
    • SomeText
    `. Where "Text" does not become bold. However, if it is just `

    Some Text

    ` it WORKS fine.
    – user2771150 Mar 15 '17 at 02:21
  • 1
    Replace `NSString* temp = [[finalText string]stringByReplacingOccurrencesOfString:@"###" withString:@"•\t"]; temp = [NSString stringWithFormat:@"\n%@", temp]; [finalText setAttributedString:[[NSAttributedString alloc] initWithString:temp]];` with `[finalText replaceCharactersInRange:[[finalText string] rangeOfString:@"###"] withString:@"•\t"];` and that should solve one part of your issue. – Larme Mar 15 '17 at 13:49
  • 1
    Your second issue lies there: `NSMutableAttributedString* newText = [[NSMutableAttributedString alloc]initWithString:newPlainText attributes:@{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle}];` your are overriding the font, so if it was "Arial-Bold", it will be override by the one in parameter. – Larme Mar 15 '17 at 13:53
  • @Larme, your solution worked partially. The `###`s appear as they are when rendered. They are not being replaced by `@"•\t"` – user2771150 Mar 16 '17 at 07:53
  • 1
    Hi, just a small question, why can't you use this solution http://stackoverflow.com/a/18886718/3899770 ? – Aris Mar 27 '17 at 07:49
  • @Aris 's method is the easiest one and it works even in very old macOS systems. – VitorMM Mar 27 '17 at 13:25
  • Everybody, especially @Aris, but also Larme: Please make an answer, it would make my heart ache to see a perfectly nice bounty be lost... – Yunnosch Apr 01 '17 at 19:11

1 Answers1

2

For converting HTML to NSAttributedString you can use the following code:

[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding] 
                             options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                       NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)} 
                  documentAttributes:nil error:nil];
Aris
  • 1,529
  • 9
  • 17