Сохранение пользовательских атрибутов в NSAttributedString - PullRequest
10 голосов
/ 13 апреля 2010

Мне нужно добавить пользовательский атрибут к выделенному тексту в NSTextView. Так что я могу сделать это, получив атрибутированную строку для выделения, добавив в нее собственный атрибут, а затем заменив выделение моей новой атрибутированной строкой.

Так что теперь я получаю строку с атрибутом текстового представления как NSData и записываю ее в файл. Позже, когда я открою этот файл и восстановлю его в текстовом представлении, мои пользовательские атрибуты исчезнут! После разработки всей схемы для моего пользовательского атрибута я обнаружил, что пользовательские атрибуты не сохранены для вас. Посмотрите на ВАЖНОЕ примечание здесь: http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/AttributedStrings/Tasks/RTFAndAttrStrings.html

Так что я не знаю, как сохранить и восстановить мои документы с помощью этого пользовательского атрибута. Любая помощь?

Ответы [ 2 ]

16 голосов
/ 13 апреля 2010

Обычным способом сохранения NSAttributedString является использование RTF, а данные RTF - это то, что генерирует метод -dataFromRange:documentAttributes:error: NSAttributedString.

Однако формат RTF не поддерживает настраиваемые атрибуты. Вместо этого вы должны использовать протокол NSCoding для архивации вашей атрибутивной строки, которая сохранит пользовательские атрибуты:

//asssume attributedString is your NSAttributedString
//encode the string as NSData
NSData* stringData = [NSKeyedArchiver archivedDataWithRootObject:attributedString];
[stringData writeToFile:pathToFile atomically:YES];

//read the data back in and decode the string
NSData* newStringData = [NSData dataWithContentsOfFile:pathToFile];
NSAttributedString* newString = [NSKeyedUnarchiver unarchiveObjectWithData:newStringData];
6 голосов
/ 25 мая 2011

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