I am using this code to convert HTML strings to NSAttributedString using RegEx. The only problem I am having is with nested bold and italic tags. Is RegEx the correct way to do it?
Also I want to avoid using an HTML parser because all I need is Bold, Italic and Underline attributes and StrikeThrough if Possible.
Any suggestions?
- (NSMutableAttributedString *)applyStylesToString:(NSString *)string searchRange:(NSRange)searchRange {
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
[attributedString addAttribute:NSFontAttributeName value:[StylesConfig regularLargeFont] range:searchRange];
NSDictionary *boldAttributes = @{ NSFontAttributeName : [StylesConfig regularBoldLargeFont] };
NSDictionary *italicAttributes = @{ NSFontAttributeName : [StylesConfig regularItalicLargeFont] };
NSDictionary *underlineAttributes = @{ NSUnderlineStyleAttributeName : @1};
NSDictionary *strikeThroughAttributes = @{ NSStrikethroughStyleAttributeName : @1};
NSDictionary *replacements = @{
@"<b>(.*?)</b>" : boldAttributes,
@"<i>(.*?)</i>" : italicAttributes,
@"<u>(.*?)</u>" : underlineAttributes,
@"<s>(.*?)</s>" : strikeThroughAttributes
};
for (NSString *key in replacements) {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:key options:0 error:nil];
[regex enumerateMatchesInString:string options:0 range:searchRange usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
NSRange matchRange = [match rangeAtIndex:1];
[attributedString addAttributes:replacements[key] range:matchRange];
if ([key isEqualToString:@"<b>(.*?)</b>"]) {
[self makeBoldItalic:attributedString matchRange:matchRange font:@"SourceSansPro-It"];
} else if ([key isEqualToString:@"<i>(.*?)</i>"]) {
[self makeBoldItalic:attributedString matchRange:matchRange font:@"SourceSansPro-Semibold"];
}
}];
}
[[attributedString mutableString] replaceOccurrencesOfString:@"<b>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
[[attributedString mutableString] replaceOccurrencesOfString:@"</b>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
[[attributedString mutableString] replaceOccurrencesOfString:@"<i>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
[[attributedString mutableString] replaceOccurrencesOfString:@"</i>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
[[attributedString mutableString] replaceOccurrencesOfString:@"<u>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
[[attributedString mutableString] replaceOccurrencesOfString:@"</u>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
[[attributedString mutableString] replaceOccurrencesOfString:@"<s>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
[[attributedString mutableString] replaceOccurrencesOfString:@"</s>" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, attributedString.length)];
return attributedString;
}
- (void)makeBoldItalic:(NSMutableAttributedString *)attributedString matchRange:(NSRange)matchRange font:(NSString *)font {
[attributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
UIFont *oldFont = (UIFont *)value;
if ([oldFont.fontName isEqualToString:font]) {
[attributedString removeAttribute:NSFontAttributeName range:range];
NSDictionary *boldItalicAttributes = @{ NSFontAttributeName : [StylesConfig regularBoldItalicLargeFont] };
[attributedString addAttributes:boldItalicAttributes range:matchRange];
}
}];
}
The thing is that Bold and Italic (and other attributes) are part of the font, unlike underlining. iOS doesn't give a "fake bold" or "fake italic" like Photoshop can do for example.
So, I guess from your StylesConfig
class utilities that you have:
boldFont
, italicFont
and boldItalicFont
.
boldFont = [StylesConfig regularBoldLargeFont]; //SourceSansPro-Semibold
italicFont = [StylesConfig regularItalicLargeFont];//SourceSansPro-It
boldItalicFont = [StylesConfig regularBoldItalicLargeFont];//SourceSansPro-SemiboldIt (or something like this)
So to change the font, expecting that the font in its symbolicTraits
fontDescriptor
contains the correct one, you can do something like that:
-(void)boldText:(NSMutableAttributedText *)attributeString forRange:(NSRange)range
{
[attributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
UIFont *currentFont = (UIFont *)value;
if ([[currentFont fontDescriptor] symbolicTraits] & UIFontDescriptorTraitBold)
{
NSLog(@"Font is already bold);
}
else
{
UIFont *newFont = [self boldFontFromFont:currentFont];
[attributedString addAttribute:newFont range:range];
}
}];
}
-(void)italicText:(NSMutableAttributedText *)attributeString forRange:(NSRange)range
{
[attributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
UIFont *currentFont = (UIFont *)value;
if ([[currentFont fontDescriptor] symbolicTraits] & UIFontDescriptorTraitItalic)
{
NSLog(@"Font is already italic);
}
else
{
UIFont *newFont = [self italicFontFromFont:currentFont];
[attributedString addAttribute:newFont range:range];
}
}];
}
-(UIFont *)boldFontFromFont:(UIFont *)font
{
if ([[currentFont fontDescriptor] symbolicTraits] & UIFontDescriptorTraitItalic)
return boldItalicFont;
else
return boldFont;
}
-(UIFont *)italicFontFromFont:(UIFont *)font
{
if ([[currentFont fontDescriptor] symbolicTraits] & UIFontDescriptorTraitBold)
return boldItalicFont;
else
return italicFont;
}
In your current code:
if ([key isEqualToString:@"<b>(.*?)</b>"])
{
[self boldText: attributedString forRange:matchRange];
}
else if ([key isEqualToString:@"<i>(.*?)</i>"])
{
[self italicText: attributedString forRange:matchRange];
}
If your font doesn't pass the symbolicTraits
tests, you may have to look into the UIFont
fontName
and check if its Bold, Italic, or BoldItalic.
Note:
I didn't test the code, I write it only here, I don't even know if it compiles, but that should give you the whole idea.