有一种方法可以使用 Cocoa 将自定义属性保存到 RTF。它依赖于 RTF 是一种文本格式这一事实,因此即使您不了解 RTF 的所有规则并且没有自定义 RTF 阅读器/编写器,也可以将其作为字符串进行操作。我在下面概述的过程在编写和阅读时对 RTF 进行后处理,我个人也使用过这种技术。需要非常小心的一件事是您插入到 RTF 中的文本仅使用 7 位 ASCII 并且没有未转义的控制字符,其中包括“\ { }”。
以下是对数据进行编码的方式:
NSData *GetRtfFromAttributedString(NSAttributedString *text)
{
NSData *rtfData = nil;
NSMutableString *rtfString = nil;
NSString *customData = nil, *encodedData = nil;
NSRange range;
NSUInteger dataLocation;
// Convert the attributed string to RTF
if ((rtfData = [text RTFFromRange:NSMakeRange(0, [text length]) documentAttributes:nil]) == nil)
return(nil);
// Find and encode your custom attributes here. In this example the data is a string and there's at most one of them
if ((customData = [text attribute:@"MyCustomData" atIndex:0 effectiveRange:&range]) == nil)
return(rtfData); // No custom data, return RTF as is
dataLocation = range.location;
// Get a string representation of the RTF
rtfString = [[NSMutableString alloc] initWithData:rtfData encoding:NSASCIIStringEncoding];
// Find the anchor where we'll put our data, namely just before the first paragraph property reset
range = [rtfString rangeOfString:@"\\pard" options:NSLiteralSearch];
if (range.location == NSNotFound)
{
NSLog(@"Custom data dropped; RTF has no paragraph properties");
[rtfString release];
return(rtfData);
}
// Insert the starred group containing the custom data and its location
encodedData = [NSString stringWithFormat:@"{\\*\\my_custom_keyword %d,%@}\n", dataLocation, customData];
[rtfString insertString:encodedData atIndex:range.location];
// Convert the amended RTF back to a data object
rtfData = [rtfString dataUsingEncoding:NSASCIIStringEncoding];
[rtfString release];
return(rtfData);
}
这种技术之所以有效,是因为所有符合标准的 RTF 阅读器都会忽略他们无法识别其关键字的“已加星标的组”。因此,您要确保您的控制字不会被任何其他阅读器识别,因此请使用可能是唯一的东西,例如您的公司或产品名称的前缀。如果您的数据是复杂的、二进制的,或者可能包含您不想转义的非法 RTF 字符,请将其编码为 base64。确保在关键字后放置一个空格。
同样,在阅读 RTF 时,您搜索控制字、提取数据并恢复属性。该例程将属性字符串和创建它的 RTF 作为参数。
void RestoreCustomAttributes(NSMutableAttributedString *text, NSData *rtfData)
{
NSString *rtfString = [[NSString alloc] initWithData:rtfData encoding:NSASCIIStringEncoding];
NSArray *components = nil;
NSRange range, endRange;
// Find the custom data and its end
range = [rtfString rangeOfString:@"{\\*\\my_custom_keyword " options:NSLiteralSearch];
if (range.location == NSNotFound)
{
[rtfString release];
return;
}
range.location += range.length;
endRange = [rtfString rangeOfString:@"}" options:NSLiteralSearch
range:NSMakeRange(range.location, [rtfString length] - endRange.location)];
if (endRange.location == NSNotFound)
{
[rtfString release];
return;
}
// Get the location and the string data, which are separated by a comma
range.length = endRange.location - range.location;
components = [[rtfString substringWithRange:range] componentsSeparatedByString:@","];
[rtfString release];
// Assign the custom data back to the attributed string. You should do range checking here (omitted for clarity)
[text addAttribute:@"MyCustomData" value:[components objectAtIndex:1]
range:NSMakeRange([[components objectAtIndex:0] integerValue], 1)];
}