我想将NSAttributedString转换为这样的html:
This is a <i>string</i> with some <b>simple</b> <i><b>html</b></i> tags in it.
不幸的是,如果您使用Apple的内置系统,它会生成详细的基于CSS的html。 (以下示例供参考。)
那么如何从NSAttributedString生成简单标记的html?
我写了一篇非常详细,脆弱的电话来做这件事,这是一个糟糕的解决方案。
func simpleTagStyle(fromNSAttributedString att: NSAttributedString)->String {
// verbose, fragile solution
// essentially, iterate all the attribute ranges in the attString
// make a note of what style they are, bold italic etc
// (totally ignore any not of interest to us)
// then basically get the plain string, and munge it for those ranges.
// be careful with the annoying "multiple attribute" case
// (an alternative would be to repeatedly munge out attributed ranges
// one by one until there are none left.)
let rangeAll = NSRange(location: 0, length: att.length)
// make a note of all of the ranges of bold/italic
// (use a tuple to remember which is which)
var allBlocks: [(NSRange, String)] = []
att.enumerateAttribute(
NSFontAttributeName,
in: rangeAll,
options: .longestEffectiveRangeNotRequired
)
{ value, range, stop in
handler: if let font = value as? UIFont {
let b = font.fontDescriptor.symbolicTraits.contains(.traitBold)
let i = font.fontDescriptor.symbolicTraits.contains(.traitItalic)
if b && i {
allBlocks.append( (range, "bolditalic") )
break handler // take care not to duplicate
}
if b {
allBlocks.append( (range, "bold") )
break handler
}
if i {
allBlocks.append( (range, "italic") )
break handler
}
}
}
// traverse those backwards and munge away
var plainString = att.string
for oneBlock in allBlocks.reversed() {
let r = oneBlock.0.range(for: plainString)!
let w = plainString.substring(with: r)
if oneBlock.1 == "bolditalic" {
plainString.replaceSubrange(r, with: "<b><i>" + w + "</i></b>")
}
if oneBlock.1 == "bold" {
plainString.replaceSubrange(r, with: "<b>" + w + "</b>")
}
if oneBlock.1 == "italic" {
plainString.replaceSubrange(r, with: "<i>" + w + "</i>")
}
}
return plainString
}
所以这里是如何使用Apple的内置系统,不幸的是,它会生成完整的CSS等。
x = ... your NSAttributedText
var resultHtmlText = ""
do {
let r = NSRange(location: 0, length: x.length)
let att = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]
let d = try x.data(from: r, documentAttributes: att)
if let h = String(data: d, encoding: .utf8) {
resultHtmlText = h
}
}
catch {
print("utterly failed to convert to html!!! \n>\(x)<\n")
}
print(resultHtmlText)
示例输出....
<!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>
答案 0 :(得分:6)
根据enumerateAttribute:inRange:options:usingBlock:
的文档,特别是讨论部分,其中说明:
如果将此方法发送到NSMutableAttributedString的实例, 允许变异(删除,添加或更改),只要它是 在提供给街区的范围内;突变后, 枚举继续在紧随其后的范围内 处理范围,经过处理范围的长度调整后 对于突变。 (调查员基本上假设有任何变化 length出现在指定的范围内。)例如,如果调用了block 从位置N开始的范围,该块删除所有 在提供的范围内的字符,下一个调用也将传递N as 范围的索引。
换句话说,在闭包/块中,使用range
,您可以删除/替换那里的字符。操作系统会在该范围的末端放置一个标记。完成修改后,它将计算标记新范围,以便枚举的下一次迭代将从该新标记开始。
因此,您不必将所有范围保留在数组中,然后通过执行向后替换来应用更改,而不是修改范围。不要打扰你,方法已经做到了。
我不是Swift开发者,我更像是Objective-C。所以我的Swift代码可能不会尊重所有“Swift规则”,并且可能有点丑陋(可选,包装等等做得不好,if let
未完成等等。)
这是我的解决方案:
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
}
输出(对于最后一个,其他两个打印仅用于调试):
$> taggedString: So, <i><b>here is</b></i> some stuff
修改强> Objective-C版本(快速编写,可能是一些问题)。
-(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;
}
答案 1 :(得分:1)
我有很好的方法将 NSAttributedString 转换为简单的 HTML 字符串。
1)使用 UIWebView 和 UITextView 。
2)在 WebView 中设置属性字符串。
[webView loadHTMLString:[yourAttributedString stringByReplacingOccurrencesOfString:@"\n" withString:@"<br/>"] baseURL:nil];
3)从 UIWebView 获取您的 HTML 字符串。
NSString *simpleHtmlString = [webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"];
答案 2 :(得分:1)
这是保留了更多样式和链接的更完整的解决方案。
如果要保留颜色和字距调整信息,请查看NSAttributedString.h中的其他键。
@implementation NSAttributedString (SimpleHTML)
- (NSString*) simpleHTML
{
NSMutableAttributedString* attr = [self mutableCopy];
[attr enumerateAttributesInRange: NSMakeRange(0, [self length])
options: 0
usingBlock: ^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop)
{
for (NSString* aKey in attrs.allKeys.copy)
{
NSString* format = nil;
if ([aKey compare: NSFontAttributeName] == NSOrderedSame) //UIFont, default Helvetica(Neue) 12
{
UIFont* font = attrs[aKey];
BOOL isBold = UIFontDescriptorTraitBold & [[font fontDescriptor] symbolicTraits];
BOOL isItalic = UIFontDescriptorTraitItalic & [[font fontDescriptor] symbolicTraits];
if (isBold && isItalic)
{
format = @"<b><i>%@</i></b>";
}
else if (isBold)
{
format = @"<b>%@</b>";
}
else if (isItalic)
{
format = @"<i>%@</i>";
}
}
else if ([aKey compare: NSStrikethroughStyleAttributeName] == NSOrderedSame) //NSNumber containing integer, default 0: no strikethrough
{
NSNumber* strike = (id) attrs[aKey];
if (strike.boolValue)
{
format = @"<strike>";
}
else
{
format = @"</strike>";
}
}
else if ([aKey compare: NSUnderlineStyleAttributeName] == NSOrderedSame) //NSNumber containing integer, default 0: no underline
{
if ([attrs.allKeys containsObject: NSLinkAttributeName] == NO)
{
NSNumber* underline = (id) attrs[aKey];
if (underline.boolValue)
{
format = @"<u>%@</u>";
}
}
}
else if ([aKey compare: NSLinkAttributeName] == NSOrderedSame) //NSURL (preferred) or NSString
{
NSObject* value = (id) attrs[aKey];
NSString* absolute = @"";
if ([value isKindOfClass: NSURL.class])
{
NSURL* url = (id) value;
absolute = url.absoluteString;
}
else if ([value isKindOfClass: NSString.class])
{
absolute = (id) value;
}
format = [NSString stringWithFormat: @"<a href=\"%@\">%%@</a>", absolute];
}
if (format)
{
NSString* occurence = [[attr attributedSubstringFromRange: range] string];
NSString* replacement = [NSString stringWithFormat: format, occurence];
[attr replaceCharactersInRange: range
withString: replacement];
}
}
}];
NSMutableString* result = [[NSString stringWithFormat: @"<html>%@</html>", attr.string] mutableCopy];
[result replaceOccurrencesOfString: @"\n"
withString: @"<br>"
options: 0
range: NSMakeRange(0, result.length)];
return result;
}
@end
编辑: 我添加了在处理URL时需要检查以启用/禁用下划线检测的条件。