2009-06-30 2 views
71

C# имеет синтаксис, который позволяет указать индекс аргумента в струнном спецификаторе формата, например .:Есть ли способ указать позицию/индекс аргумента в NSString stringWithFormat?

string message = string.Format("Hello, {0}. You are {1} years old. How does it feel to be {1}?", name, age); 

Вы можете использовать аргументы более чем один раз, а также опустить аргументы, которые предоставляются от использования. Another question упоминает одно и то же форматирование для C/C++ в форме %[index]$[format], например. %1$i. Я не смог получить NSString до полностью уважать этот синтаксис, потому что он хорошо себя ведет, когда опускает аргументы из формата. Ниже не работает, как ожидался (EXC_BAD_ACCESS, потому что он пытается разыменование параметра age как NSObject *):

int age = 23; 
NSString * name = @"Joe"; 
NSString * message = [NSString stringWithFormat:@"Age: %2$i", name, age]; 

позиционных аргументов соблюдается только если нет отсутствующих аргументов из формата (который, кажется, нечетное требование):

NSString * message = [NSString stringWithFormat:@"Age: %2$i; Name: %[email protected]", name, age]; 

Все эти вызовы работают должным образом в OS X:

printf("Age: %2$i", [name UTF8String], age); 
printf("Age: %2$i %1$s", [name UTF8String], age); 

есть ли способ accomplishin g, используя NSString в Objective-C/Cocoa? Это было бы полезно для целей локализации.

+0

Сообщите об ошибке (и сообщите нам об ошибке #). –

ответ

110

NSString и CFString поддерживают перезаписываемые/позиционные аргументы.

NSString *string = [NSString stringWithFormat: @"Second arg: %[email protected], First arg %[email protected]", @"<1111>", @"<22222>"]; 
NSLog(@"String = %@", string); 

Кроме того, обратитесь к документации на Apple: String Resources

+3

Я уточнил вопрос с некоторыми разъяснениями. Похоже, что Cocoa не учитывает пропущенные аргументы из формата, что является побочным эффектом нарушения доступа, которое я получал. – Jason

+2

Уважение пропущенных аргументов невозможно из-за того, как работают varargs в C. Нет стандартного способа узнать количество аргументов или их размер. Анализ строк обрабатывает это путем вывода информации из спецификаторов формата, что требует наличия спецификаторов. –

+1

Я понимаю, как работает va_args; однако, похоже, это работает так, как ожидалось: printf («Возраст:% 2 $ i», [имя UTF8String], возраст); Я пробовал другие printf с переупорядоченными/отсутствующими аргументами, и все они дают ожидаемый результат, тогда как NSString этого не делает. – Jason

0

После проведения большего количества исследований, кажется, что Cocoa уважает позиционный синтаксис в printf. Поэтому альтернативный шаблон будет:

char msg[512] = {0}; 
NSString * format = @"Age %2$i, Name: %1$s"; // loaded from resource in practice 
sprintf(msg, [format UTF8String], [name UTF8String], age); 
NSString * message = [NSString stringWithCString:msg encoding:NSUTF8StringEncoding]; 

Однако, было бы неплохо, если бы была реализация этого на NSString.

+1

'sprintf' не является частью Cocoa, это часть стандартной библиотеки C, а реализация этого -' stringWithFormat: '/' initWithFormat: '. –

+0

Уточнение моего предыдущего комментария: версия Cocoa - 'stringWithFormat:'/'initWithFormat:'. Это отдельная реализация (в настоящее время 'CFStringCreateWithFormat') от версии' sprintf' и друзей. –

+4

Я полагаю, что мало пользы, чтобы прокомментировать тот факт, что инициализация msg ровно 512 байт примерно так же безопасна, как выполнение случайного селектора на случайном объекте, но в любом случае. Для всех, кто не знает: буферы фиксированного размера - одни из самых простых способов уволить. google: переполнение буфера –

1

Следующий код исправляет ошибку, указанную в этом вопросе. Это временное решение и позволяет заполнителям заполнить пробелы.

+ (id)stringWithFormat:(NSString *)format arguments:(NSArray*) arguments 
{ 
    NSMutableArray *filteredArguments = [[NSMutableArray alloc] initWithCapacity:arguments.count]; 
    NSMutableString *correctedFormat = [[NSMutableString alloc ] initWithString:format]; 
    NSString *placeHolderFormat = @"%%%d$"; 

    int actualPlaceholderIndex = 1; 

    for (int i = 1; i <= arguments.count; ++i) { 
     NSString *placeHolder = [[NSString alloc] initWithFormat:placeHolderFormat, i]; 
     if ([format rangeOfString:placeHolder].location != NSNotFound) { 
      [filteredArguments addObject:[arguments objectAtIndex:i - 1]]; 

      if (actualPlaceholderIndex != i) { 
       NSString *replacementPlaceHolder = [[NSString alloc] initWithFormat:placeHolderFormat, actualPlaceholderIndex]; 
       [correctedFormat replaceAllOccurrencesOfString:placeHolder withString:replacementPlaceHolder];  
       [replacementPlaceHolder release]; 
      } 
      actualPlaceholderIndex++; 
     } 
     [placeHolder release]; 
    } 

    if (filteredArguments.count == 0) { 
     //No numbered arguments found: just copy the original arguments. Mixing of unnumbered and numbered arguments is not supported. 
     [filteredArguments setArray:arguments]; 
    } 

    NSString* result; 
    if (filteredArguments.count == 0) { 
     //Still no arguments: don't use initWithFormat in this case because it will crash: just return the format string 
     result = [NSString stringWithString:format]; 
    } else { 
     char *argList = (char *)malloc(sizeof(NSString *) * [filteredArguments count]); 
     [filteredArguments getObjects:(id *)argList]; 
     result = [[[NSString alloc] initWithFormat:correctedFormat arguments:argList] autorelease]; 
     free(argList);  
    } 

    [filteredArguments release]; 
    [correctedFormat release]; 

    return result; 
}