Convert attributed string, to, simple tagged html
According to the documentation of enumerateAttribute:inRange:options:usingBlock:
, especially the Discussion part which states:
If this method is sent to an instance of NSMutableAttributedString,
mutation (deletion, addition, or change) is allowed, as long as it is
within the range provided to the block; after a mutation, the
enumeration continues with the range immediately following the
processed range, after the length of the processed range is adjusted
for the mutation. (The enumerator basically assumes any change in
length occurs in the specified range.) For example, if block is called
with a range starting at location N, and the block deletes all the
characters in the supplied range, the next call will also pass N as
the index of the range.
In other words, in the closure/block, with the range
, you can delete/replace characters there. The OS will put a marker on that end of the range. Once you did your modifications, it will compute the marker new range in order that the next iteration of the enumeration will start from that new marker.
So you don't have to keep all the ranges in an array and apply the changes afterwards by doing a backward replacement to not modify the range. Don't bother you with that, the methods does it already.
I'm not a Swift developper, I'm more an Objective-C one. So my Swift code may not respect all "Swift rules", and may be a little ugly (optionals, wrapping, etc badly done, if let
not done, etc.)
Here is my solution:
func attrStrSimpleTag() -> Void {
let htmlStr = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\"> <html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <meta http-equiv=\"Content-Style-Type\" content=\"text/css\"> <title></title> <meta name=\"Generator\" content=\"Cocoa HTML Writer\"> <style type=\"text/css\"> p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px 'Some Font'} span.s1 {font-family: 'SomeFont-ItalicOrWhatever'; font-weight: normal; font-style: normal; font-size: 14.00pt} span.s2 {font-family: 'SomeFont-SemiboldItalic'; font-weight: bold; font-style: italic; font-size: 14.00pt} </style> </head> <body> <p class=\"p1\"><span class=\"s1\">So, </span><span class=\"s2\">here is</span><span class=\"s1\"> some</span> stuff</p> </body></html>"
let attr = try! NSMutableAttributedString.init(data: htmlStr.data(using: .utf8)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil)
print("Attr: \(attr)")
attr.enumerateAttribute(NSFontAttributeName, in: NSRange.init(location: 0, length: attr.length), options: []) { (value, range, stop) in
if let font = value as? UIFont {
print("font found:\(font)")
let isBold = font.fontDescriptor.symbolicTraits.contains(.traitBold)
let isItalic = font.fontDescriptor.symbolicTraits.contains(.traitItalic)
let occurence = attr.attributedSubstring(from: range).string
let replacement = self.formattedString(initialString: occurence, bold: isBold, italic: isItalic)
attr.replaceCharacters(in: range, with: replacement)
}
};
let taggedString = attr.string
print("taggedString: \(taggedString)")
}
func formattedString(initialString:String, bold: Bool, italic: Bool) -> String {
var retString = initialString
if bold {
retString = "<b>".appending(retString)
retString.append("</b>")
}
if italic
{
retString = "<i>".appending(retString)
retString.append("</i>")
}
return retString
}
Output (for the last one, the other two prints are just for debug):
$> taggedString: So, <i><b>here is</b></i> some stuff
Edit:
Objective-C Version (quickly written, maybe some issue).
-(void)attrStrSimpleTag
{
NSString *htmlStr = @"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\"> <html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"> <meta http-equiv=\"Content-Style-Type\" content=\"text/css\"> <title></title> <meta name=\"Generator\" content=\"Cocoa HTML Writer\"> <style type=\"text/css\"> p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px 'Some Font'} span.s1 {font-family: 'SomeFont-ItalicOrWhatever'; font-weight: normal; font-style: normal; font-size: 14.00pt} span.s2 {font-family: 'SomeFont-SemiboldItalic'; font-weight: bold; font-style: italic; font-size: 14.00pt} </style> </head> <body> <p class=\"p1\"><span class=\"s1\">So, </span><span class=\"s2\">here is</span><span class=\"s1\"> some</span> stuff</p> </body></html>";
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithData:[htmlStr dataUsingEncoding:NSUTF8StringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}
documentAttributes:nil
error:nil];
NSLog(@"Attr: %@", attr);
[attr enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, [attr length]) options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = (UIFont *)value;
NSLog(@"Font found: %@", font);
BOOL isBold = UIFontDescriptorTraitBold & [[font fontDescriptor] symbolicTraits];
BOOL isItalic = UIFontDescriptorTraitItalic & [[font fontDescriptor] symbolicTraits];
NSString *occurence = [[attr attributedSubstringFromRange:range] string];
NSString *replacement = [self formattedStringWithString:occurence isBold:isBold andItalic:isItalic];
[attr replaceCharactersInRange:range withString:replacement];
}];
NSString *taggedString = [attr string];
NSLog(@"taggedString: %@", taggedString);
}
-(NSString *)formattedStringWithString:(NSString *)string isBold:(BOOL)isBold andItalic:(BOOL)isItalic
{
NSString *retString = string;
if (isBold)
{
retString = [NSString stringWithFormat:@"<b>%@</b>", retString];
}
if (isItalic)
{
retString = [NSString stringWithFormat:@"<i>%@</i>", retString];
}
return retString;
}
Edit Jan. 2020:
Updated code with easier modifications and Swift 5, adding support for two new effects (underline/strikethrough).
// MARK: In one loop
extension NSMutableAttributedString {
func htmlSimpleTagString() -> String {
enumerateAttributes(in: fullRange(), options: []) { (attributes, range, pointeeStop) in
let occurence = self.attributedSubstring(from: range).string
var replacement: String = occurence
if let font = attributes[.font] as? UIFont {
replacement = self.font(initialString: replacement, fromFont: font)
}
if let underline = attributes[.underlineStyle] as? Int {
replacement = self.underline(text: replacement, fromStyle: underline)
}
if let striked = attributes[.strikethroughStyle] as? Int {
replacement = self.strikethrough(text: replacement, fromStyle: striked)
}
self.replaceCharacters(in: range, with: replacement)
}
return self.string
}
}
// MARK: In multiple loop
extension NSMutableAttributedString {
func htmlSimpleTagString(options: [NSAttributedString.Key]) -> String {
if options.contains(.underlineStyle) {
enumerateAttribute(.underlineStyle, in: fullRange(), options: []) { (value, range, pointeeStop) in
let occurence = self.attributedSubstring(from: range).string
guard let style = value as? Int else { return }
if NSUnderlineStyle(rawValue: style) == NSUnderlineStyle.styleSingle {
let replacement = self.underline(text: occurence, fromStyle: style)
self.replaceCharacters(in: range, with: replacement)
}
}
}
if options.contains(.strikethroughStyle) {
enumerateAttribute(.strikethroughStyle, in: fullRange(), options: []) { (value, range, pointeeStop) in
let occurence = self.attributedSubstring(from: range).string
guard let style = value as? Int else { return }
let replacement = self.strikethrough(text: occurence, fromStyle: style)
self.replaceCharacters(in: range, with: replacement)
}
}
if options.contains(.font) {
enumerateAttribute(.font, in: fullRange(), options: []) { (value, range, pointeeStop) in
let occurence = self.attributedSubstring(from: range).string
guard let font = value as? UIFont else { return }
let replacement = self.font(initialString: occurence, fromFont: font)
self.replaceCharacters(in: range, with: replacement)
}
}
return self.string
}
}
//MARK: Replacing
extension NSMutableAttributedString {
func font(initialString: String, fromFont font: UIFont) -> String {
let isBold = font.fontDescriptor.symbolicTraits.contains(.traitBold)
let isItalic = font.fontDescriptor.symbolicTraits.contains(.traitItalic)
var retString = initialString
if isBold {
retString = "<b>" + retString + "</b>"
}
if isItalic {
retString = "<i>" + retString + "</i>"
}
return retString
}
func underline(text: String, fromStyle style: Int) -> String {
return "<u>" + text + "</u>"
}
func strikethrough(text: String, fromStyle style: Int) -> String {
return "<s>" + text + "</s>"
}
}
//MARK: Utility
extension NSAttributedString {
func fullRange() -> NSRange {
return NSRange(location: 0, length: self.length)
}
}
Simple HTML to test with mixed tags: "This is <i>ITALIC</i> with some <b>BOLD</b> <b><i>BOLDandITALIC</b></i> <b>BOLD<u>UNDERLINEandBOLD</b>RESTUNDERLINE</u> in it."
The solutions brings two approaches: One doing one loop, the other doing multiple loops, but for mixed tags, the result could be strange. Check with the sample provided previously the different rendering.
Convert attributed text to HTML in Swift 4
For Swift 4.x, the line:
let att = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]
should be:
let att = [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.html]
Swift - Convert HTML text to Attributed String
I tried this solution with your html and worked fine:
let htmlText = "<medium><b><font color='#2f3744'>IPL Tamil Web Series Episode #3 | யாருடா Swetha ? | Tamil Comedy Web Series | Being Thamizhan</font></b></medium> has been succesfully scheduled on <medium><b><font color='#2f3744'>2018-05-23 08:51 PM</font></b></medium>"
let encodedData = htmlText.data(using: String.Encoding.utf8)!
var attributedString: NSAttributedString
do {
attributedString = try NSAttributedString(data: encodedData, options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html,NSAttributedString.DocumentReadingOptionKey.characterEncoding:NSNumber(value: String.Encoding.utf8.rawValue)], documentAttributes: nil)
} catch let error as NSError {
print(error.localizedDescription)
} catch {
print("error")
}
attributedString
output:
IPL Tamil Web Series Episode #3 | யாருடா Swetha ? | Tamil Comedy Web Series | Being Thamizhan has been succesfully scheduled on 2018-05-23 08:45 PM
Convert HTML to NSAttributedString in iOS
In iOS 7, UIKit added an initWithData:options:documentAttributes:error:
method which can initialize an NSAttributedString
using HTML, eg:
[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
documentAttributes:nil error:nil];
In Swift:
let htmlData = NSString(string: details).data(using: String.Encoding.unicode.rawValue)
let options = [NSAttributedString.DocumentReadingOptionKey.documentType:
NSAttributedString.DocumentType.html]
let attributedString = try? NSMutableAttributedString(data: htmlData ?? Data(),
options: options,
documentAttributes: nil)
NSAttributedString: how to parse html tags and add attributes
Ok, I got the solution, maybe it will help anyone else.
Note, that my strings are strictly multi lined, so it's easy first split them, then add needed font and size to the parts, and then add paragraph styling.
I've played with order of parsing tags/styling fonts/styling paragraph, and at every case something was missed. If you don't need to separate line as multiline in strict order, just don't do mapping. Otherwise, you can miss breaking while styling or parsing tags.
descriptionLabel.attributedText = getAttributedDescriptionText(for: "Register and get <b>all</b>\n<b>rewards cards</b> of our partners\n<b>in one</b> universal <b>card</b>", fontDescription: "ProximaNova-Regular", fontSize: 15)
func getAttributedDescriptionText(for descriptionString: String, fontDescription: String, fontSize: Int) -> NSAttributedString? {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 1.0
paragraphStyle.alignment = .center
paragraphStyle.minimumLineHeight = 18.0
let attributedString = NSMutableAttributedString()
let splits = descriptionString.components(separatedBy: "\n")
_ = splits.map { string in
let modifiedFont = String(format:"<span style=\"font-family: '\(fontDescription)'; font-size: \(fontSize)\">%@</span>", string)
let data = modifiedFont.data(using: String.Encoding.unicode, allowLossyConversion: true)
let attr = try? NSMutableAttributedString(
data: data ?? Data(),
options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
],
documentAttributes: nil
)
attributedString.append(attr ?? NSMutableAttributedString())
if string != splits.last {
attributedString.append(NSAttributedString(string: "\n"))
}
}
attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
HTML tags in String, convert to normal String
You need to decode HTML entities first then you can use your current implementation to get your styled string.
For HTML entities decoding you can refer this:
https://stackoverflow.com/a/30141700/3867033
But I found that you can use NSAttributesString to achieve the same result.
let html1 = """
<span class="st"><em>Bread</em> is a staple food, usually by baking. Throughout ... <em>Sourdough</em> is a type of <em>bread</em> produced by dough using naturally occurring yeasts and lactobacilli. ... List of <em>toast</em> dishes</span>
"""
extension String {
var toAttributedString: NSAttributedString? {
return try? NSAttributedString(
data: data(using: .utf8)!,
options: [
.documentType: NSAttributedString.DocumentType.html,
],
documentAttributes: nil)
}
}
let output1 = html1.toAttributedString!.string
let output2 = output1.toAttributedString
It's a little bit weird for me too, but it works...
Related Topics
Why Does CSS Padding Increase Size of Element
How to Make Responsive Website for All Devices
CSS Text Underlining Too Long When Letter-Spacing Is Applied
How to Save a File from a Website as PDF
How to Change Bootstrap Select Arrows to Glyphicon
How to Create a Pricetag Shape in CSS and HTML
Twitter Bootstrap Select Arrow Missing
Absolute Position and Overflow:Hidden
How to Write Equations in HTML
Is There a Disadvantage of Using 'Display:Table-Cell'On Divs
Cross-Browser Embedded Font in Svg Image
How to Render a Field Request Without Refreshing the Page
Html/CSS Hack to Allow Overflow-X: Auto & Overflow-Y: Visible
Bootstrap 4 - Fullscreen Mobile Navbar
Percentage Height Not Working in Nested Flexbox Layout in Chrome