Skip to content

Instantly share code, notes, and snippets.

@robnadin
Last active May 29, 2019 04:56
Show Gist options
  • Save robnadin/bc780da7edb3aaaa5443 to your computer and use it in GitHub Desktop.
Save robnadin/bc780da7edb3aaaa5443 to your computer and use it in GitHub Desktop.
NSAttributedString-HTMLStyle
//
// NSAttributedString+HTMLStyle.h
// QRContentMobilizer
//
// Created by Wojciech Czekalski on 22.03.2014.
// Copyright (c) 2014 Wojciech Czekalski. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NSString QRHTMLAttribute;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeParagraph;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeLink;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeHeader1;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeHeader2;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeHeader3;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeHeader4;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeHeader5;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeHeader6;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeEmphasis;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeStrong;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeBold;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeAlternateVoice;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeSmall;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeSubscripted;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeSuperscripted;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeInserted;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeDeleted;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeHighlighted;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeCode;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeKeyboardText;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeSampleCode;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeVariable;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributePreformatted;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeAbbreviation;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeAddress;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeBlockQuote;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeInlineQuote;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeTitle;
FOUNDATION_EXPORT QRHTMLAttribute * const QRHTMLAttributeDefinition;
/**
* Creation of NSAttributedString out of HTML with per-tag attributes made easy.
*/
@interface NSAttributedString (HTMLStyle)
/**
* Creates an attributed string from HTML data. Assumes `NSUTF8StringEncoding`.
*
* @param data Data to be processed.
*
* @return Returns a new instance of `NSAttributedString` or nil if given data was invalid.
*/
+ (instancetype)attributedStringFromHTMLData:(NSData *)data;
/**
* Creates an attributed string from HTML string and CSS string with attributes.
*
* @param html A string containing HTML.
* @param css A CSS string. If nil, use attributedStringFromHTMLData: instead.
*
* @abstract Don't convert your HTML and CSS into NSStrings from NSData objects. Use `attributedStringFromHTMLData:CSSData:` instead.
*
* @return Returns a new instance of `NSAttributedString` or nil if given data was invalid.
*/
+ (instancetype)attributedStringFromHTMLString:(NSString *)html CSSString:(NSString *)css;
/**
* Creates an attributed string from merged HTML and CSS data.
*
* @param html HTML data to be processed
* @param css CSS data to be processed
*
* @return Returns a new instance of NSAttributedString or nil if given data was invalid.
*/
+ (instancetype)attributedStringFromHTMLData:(NSData *)html CSSData:(NSData *)css;
/**
* Creates an attributed string from merged HTML and CSS files.
*
* @param html URL of a CSS file.
* @param css URL of a CSS file.
*
* @return Returns a new instance of NSAttributedString or nil if given data was invalid.
*/
+ (instancetype)attributedStringFromHTMLFileAtURL:(NSURL *)html CSSURL:(NSURL *)css;
/**
* Creates an attributed string from HTML data and attributes parsed from attributes dict.
*
* @param data HTML data to be processed
* @param attributes Key-Value pairs of NSAttributedString attributes keyed under HTML tags.
*
* @return Returns a new instance of NSAttributedString or nil if given data was invalid.
*/
+ (instancetype)attributedStringFromHTMLData:(NSData *)data attributes:(NSDictionary *)attributes;
@end
/**
* Convinience methods for dealing with adding attributes to `NSAttributedStrings` from HTML.
*/
@interface NSMutableDictionary (CSS)
/**
* Sets attributes for a given HTML tag.
*
* @param attributes NSDictionary containing key-value pairs of `NSAttributedString` attributes.
* @param tag `HTML` tag to be assigned to the attributes
* @param flatten if flatten set to yes, the dictionary is converted into CSS string before being added to the receiver.
*/
- (void)addAttributes:(NSDictionary *)attributes forHTMLAttribute:(QRHTMLAttribute *)tag flatten:(BOOL)flatten;
@end
@interface NSDictionary (CSS)
/**
* Parses receiver and creates a `NSString` with `CSS` attributes.
*
* @return Returns a `NSString` with `CSS` attributes.
*/
- (NSString *)CSSStringFromAttributes;
@end
/**
* NSData category which adds method allowing to replace occurencies of given data with another data.
*/
@interface NSData (HTMLAdditions)
/**
* Creates `NSData` instance with replaced occurencies.
*
* @param data `NSData` to be replaced.
* @param replacementData `NSData` to be inserted in place of `data`.
*
* This method replaces all occurencies of `data` with `replacementData`.
*
* @return Returns `NSData` with replaced contents.
*/
- (NSData *)dataByReplacingOccurrencesOfData:(NSData *)data withData:(NSData *)replacementData;
@end
//
// NSAttributedString+HTMLStyle.m
// QRContentMobilizer
//
// Created by Wojciech Czekalski on 22.03.2014.
// Copyright (c) 2014 Wojciech Czekalski. All rights reserved.
//
#import "NSAttributedString+HTMLStyle.h"
#import "regex.h"
QRHTMLAttribute * const QRHTMLAttributeParagraph = @"p";
QRHTMLAttribute * const QRHTMLAttributeLink = @"a";
QRHTMLAttribute * const QRHTMLAttributeHeader1 = @"h1";
QRHTMLAttribute * const QRHTMLAttributeHeader2 = @"h2";
QRHTMLAttribute * const QRHTMLAttributeHeader3 = @"h3";
QRHTMLAttribute * const QRHTMLAttributeHeader4 = @"h4";
QRHTMLAttribute * const QRHTMLAttributeHeader5 = @"h5";
QRHTMLAttribute * const QRHTMLAttributeHeader6 = @"h6";
QRHTMLAttribute * const QRHTMLAttributeEmphasis = @"em";
QRHTMLAttribute * const QRHTMLAttributeStrong = @"strong";
QRHTMLAttribute * const QRHTMLAttributeBold = @"b";
QRHTMLAttribute * const QRHTMLAttributeAlternateVoice = @"i";
QRHTMLAttribute * const QRHTMLAttributeSmall = @"small";
QRHTMLAttribute * const QRHTMLAttributeSubscripted = @"sub";
QRHTMLAttribute * const QRHTMLAttributeSuperscripted = @"sup";
QRHTMLAttribute * const QRHTMLAttributeInserted = @"ins";
QRHTMLAttribute * const QRHTMLAttributeDeleted = @"del";
QRHTMLAttribute * const QRHTMLAttributeHighlighted = @"mark";
QRHTMLAttribute * const QRHTMLAttributeCode = @"code";
QRHTMLAttribute * const QRHTMLAttributeKeyboardText = @"kbd";
QRHTMLAttribute * const QRHTMLAttributeSampleCode = @"samp";
QRHTMLAttribute * const QRHTMLAttributeVariable = @"var";
QRHTMLAttribute * const QRHTMLAttributePreformatted = @"pre";
QRHTMLAttribute * const QRHTMLAttributeAbbreviation = @"abbr";
QRHTMLAttribute * const QRHTMLAttributeAddress = @"address";
QRHTMLAttribute * const QRHTMLAttributeBlockQuote = @"blockquote";
QRHTMLAttribute * const QRHTMLAttributeInlineQuote = @"q";
QRHTMLAttribute * const QRHTMLAttributeTitle = @"cite";
QRHTMLAttribute * const QRHTMLAttributeDefinition = @"dfn";
@implementation NSAttributedString (HTMLStyle)
+ (instancetype)attributedStringFromHTMLFileAtURL:(NSURL *)html CSSURL:(NSURL *)css {
return [self attributedStringFromHTMLData:[NSData dataWithContentsOfURL:html] CSSData:[NSData dataWithContentsOfURL:css]];
}
+ (instancetype)attributedStringFromHTMLString:(NSString *)html CSSString:(NSString *)css {
return [self attributedStringFromHTMLData:[html dataUsingEncoding:NSUTF8StringEncoding] CSSData:[css dataUsingEncoding:NSUTF8StringEncoding]];
}
+ (instancetype)attributedStringFromHTMLData:(NSData *)data {
return [[self alloc] initWithData:data options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)} documentAttributes:NULL error:nil];
}
+ (instancetype)attributedStringFromHTMLData:(NSData *)html CSSData:(NSData *)css {
if (css) {
NSMutableData *newHTMLData = [css mutableCopy];
[newHTMLData appendData:html];
return [self attributedStringFromHTMLData:newHTMLData];
}
return [self attributedStringFromHTMLData:html];
}
+ (instancetype)attributedStringFromHTMLData:(NSData *)data attributes:(NSDictionary *)attributes {
return [self attributedStringFromHTMLData:data CSSData:[[attributes CSSStringFromAttributes] dataUsingEncoding:NSUTF8StringEncoding]];
}
@end
@interface UIColor (HTMLRGBString)
- (NSString *)HTMLRGBString;
@end
@implementation UIColor (HTMLRGBString)
- (NSString *)HTMLRGBString {
UIColor *color = self;
CGFloat red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0;
if (CGColorGetNumberOfComponents(color.CGColor) == 2) {
CGFloat white = 0.f, alpha = 0.f;
[color getWhite:&white alpha:&alpha];
red = white;
green = white;
blue = white;
} else {
[color getRed:&red green:&green blue:&blue alpha:&alpha];
}
return [NSString stringWithFormat:@"rgb(%li, %li, %li)", lroundf(red*255.f), lroundf(green*255.f), lroundf(blue*255.f)];
}
@end
@implementation NSDictionary (CSS)
+ (NSString *)cssStringWithValues:(NSDictionary *)attributes HTMLTag:(QRHTMLAttribute *)tag {
NSMutableString *cssString = [NSMutableString stringWithFormat:@"%@ {", tag];
for (NSString *key in attributes) {
#if DEBUG
NSSet *set = [NSSet setWithObjects:NSLigatureAttributeName, NSKernAttributeName, NSStrikethroughStyleAttributeName, NSStrikethroughColorAttributeName, NSUnderlineStyleAttributeName, NSStrokeColorAttributeName, NSStrokeWidthAttributeName, NSTextEffectAttributeName, NSObliquenessAttributeName, NSExpansionAttributeName, NSWritingDirectionAttributeName, NSVerticalGlyphFormAttributeName, NSUnderlineColorAttributeName, NSAttachmentAttributeName, NSLinkAttributeName, NSBaselineOffsetAttributeName, nil];
NSAssert(![set containsObject:key], @"Invalid Key. This library doesn't support %@ yet.", key);
#endif
if (key == NSFontAttributeName) {
UIFont *font = attributes[NSFontAttributeName];
[cssString appendFormat:@"font-family:'%@'; font-size: %0.fpx; ", font.fontName, roundf(font.pointSize)];;
} else if (key == NSParagraphStyleAttributeName) {
NSParagraphStyle *style = attributes[key];
NSString *alignment;
switch (style.alignment) {
case NSTextAlignmentCenter:
alignment = @"center";
break;
case NSTextAlignmentJustified:
alignment = @"justify";
break;
case NSTextAlignmentLeft:
alignment = @"left";
break;
case NSTextAlignmentNatural:
alignment = @"initial";
break;
case NSTextAlignmentRight:
alignment = @"right";
break;
default:
alignment = @"left";
break;
}
[cssString appendFormat:@"line-height:%0.1fem; text-align:%@;", style.lineHeightMultiple, alignment];
} else if (key == NSForegroundColorAttributeName) {
[cssString appendFormat:@"color:%@; ", [attributes[key] HTMLRGBString]];
} else if (key == NSBackgroundColorAttributeName ) {
[cssString appendFormat:@"background-color:%@; ", [attributes[key] HTMLRGBString]];;
} else if (key == NSShadowAttributeName) {
NSShadow *shadow = attributes[NSShadowAttributeName];
[cssString appendFormat:@"text-shadow: %fpx %fpx %fpx %@; ", shadow.shadowOffset.width, shadow.shadowOffset.height, shadow.shadowBlurRadius, [shadow.shadowColor HTMLRGBString]];
}
}
[cssString appendString:@"}"];
return cssString;
}
- (NSString *)CSSStringFromAttributes {
NSMutableString *string = [NSMutableString stringWithString:@"<style>"];
for (NSString *key in self) {
id object = self[key];
if ([object isKindOfClass:[NSDictionary class]]) {
[string appendString:[NSDictionary cssStringWithValues:object HTMLTag:key]];
} else if ([object isKindOfClass:[NSString class]]) {
[string appendString:object];
}
}
[string appendString:@"</style>"];
return string;
}
@end
@implementation NSMutableDictionary (CSS)
- (void)addAttributes:(NSDictionary *)attributes forHTMLAttribute:(QRHTMLAttribute *)tag flatten:(BOOL)flatten {
if (!flatten) {
[self setObject:attributes forKey:tag];
} else {
[self setObject:[NSDictionary cssStringWithValues:attributes HTMLTag:tag] forKey:tag];
}
}
@end
@implementation NSData (HTMLAdditions)
- (NSData *)dataByReplacingOccurrencesOfData:(NSData *)data withData:(NSData *)replacementData {
NSMutableData *mutableSelf = [self mutableCopy];
const void *replacementBytes = [replacementData bytes];
NSUInteger replacementBytesLength = [replacementData length];
NSRange rangeOfCharacters = [mutableSelf rangeOfData:data options:0 range:NSMakeRange(0, [mutableSelf length])];
while (rangeOfCharacters.location != NSNotFound) {
[mutableSelf replaceBytesInRange:rangeOfCharacters withBytes:replacementBytes];
NSUInteger searchLocation = replacementBytesLength + rangeOfCharacters.location;
rangeOfCharacters = [mutableSelf rangeOfData:data options:0 range:NSMakeRange(searchLocation, [mutableSelf length]-searchLocation)];
}
return [NSData dataWithData:mutableSelf];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment