Чи є спосіб вказати позицію аргументу / індекс у NSString stringWithFormat?


83

C # має синтаксис, який дозволяє вказати індекс аргументу у специфікаторі рядкового формату, наприклад:

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

Ви можете використовувати аргументи більше одного разу, а також опустити аргументи, які надані від використання. В іншому питанні згадується те саме форматування для 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: %1$@", name, age];

Усі ці дзвінки працюють належним чином в OS X:

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

Чи є спосіб досягти цього за допомогою NSString в Objective-C / Cocoa? Це було б корисно для цілей локалізації.


Подайте звіт про помилку (і повідомте нам про помилку №).
Дірк Тейзен

Відповіді:


126

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

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

Також див. Документацію на Apple: String Resources


5
Я оновив питання з деякими роз’ясненнями. Здається, Cocoa не поважає пропущені аргументи з формату, що було побічним ефектом порушення доступу, яке я отримував.
Джейсон

2
Поважати пропущені аргументи неможливо через те, як працюють varargs у C. Не існує стандартного способу дізнатися кількість аргументів або їх розмір. Синтаксичний розбір обробляє це, виводячи інформацію із специфікаторів формату, що вимагає, щоб специфікатори були там.
Jens Ayton

1
Я розумію, як працює va_args; однак, здається, це працює належним чином: printf ("Вік:% 2 $ i", [ім'я UTF8String], вік); Я спробував інші printf's із впорядкованими / відсутніми аргументами, і всі вони дають очікуваний результат, тоді як NSString - ні.
Джейсон

7
Тільки для того, щоб повторити свої висновки, тоді: stringWithFormat:підтримує позиційні аргументи, якщо всі аргументи вказані у рядку формату.
Джейсон


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;
}

0

Провівши додаткові дослідження, виявляється, що какао поважає позиційний синтаксис у Росії 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не входить до складу какао, це частина стандартної бібліотеки C, і реалізація цього є stringWithFormat:/ initWithFormat:.
Пітер Хоузі,

Уточнюючи мій попередній коментар: версія какао - це stringWithFormat:/ initWithFormat:. Це окрема реалізація (на даний момент CFStringCreateWithFormat) від програми sprintfand of friends.
Пітер Хосі

4
Я припускаю, що мало користі коментувати той факт, що ініціалізація повідомлення з рівним 512 байтами приблизно така ж безпечна, як виконання випадкового селектора на випадковому об'єкті, але в будь-якому випадку. Хто не знає: буфери фіксованого розміру - це найпростіші способи звільнення. google: переповнення буфера
Джордж Пенн.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.