将嵌套的粗体斜体 HTML 标签转换为 NSAttributedString

Convert nested bold italic HTML tags to NSAttributedString

我正在使用此代码使用 RegEx 将 HTML 字符串转换为 NSAttributedString。我遇到的唯一问题是嵌套的粗体和斜体标签。 RegEx 是正确的方法吗?

我还想避免使用 HTML 解析器,因为我需要的只是粗体、斜体和下划线属性以及删除线(如果可能的话)。

有什么建议吗?

- (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];
        }
    }];
}

问题在于,与下划线不同,粗体和斜体(以及其他属性)是字体的一部分。 iOS 不会像 Photoshop 那样提供 "fake bold" 或 "fake italic"。

所以,根据您的 StylesConfig class 实用程序,我猜您拥有: boldFontitalicFontboldItalicFont

boldFont = [StylesConfig regularBoldLargeFont]; //SourceSansPro-Semibold
italicFont = [StylesConfig regularItalicLargeFont];//SourceSansPro-It
boldItalicFont = [StylesConfig regularBoldItalicLargeFont];//SourceSansPro-SemiboldIt (or something like this)

所以要更改字体,希望其 symbolicTraits fontDescriptor 中的字体包含正确的字体,您可以这样做:

-(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;
}

在您当前的代码中:

if ([key isEqualToString:@"<b>(.*?)</b>"]) 
{
    [self boldText: attributedString forRange:matchRange];
}
 else if ([key isEqualToString:@"<i>(.*?)</i>"]) 
{
    [self italicText: attributedString forRange:matchRange];
}

如果您的字体没有通过 symbolicTraits 测试,您可能需要查看 UIFont fontName 并检查它是粗体、斜体还是粗斜体。

注:
我没有测试代码,我只写在这里,我什至不知道它是否编译,但这应该给你整个想法。