Есть способ сохранить пользовательские атрибуты в 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)];
}