Преобразование между C перечислением и XML
Какой самый чистый способ сохранить перечисление в XML и снова прочитать его? Скажем, у меня есть:
enum ETObjectType {ETNormalObjectType, ETRareObjectType, ETEssentialObjectType};
... и я хочу взять переменную enum ETObjectType objectType = ETNormalObjectType;
и преобразовать ее в XML, который выглядит следующим образом: <objectType>ETNormalObjectType</objectType>
.
В настоящее время я делаю что-то вроде этого:
NSString* const ETObjectTypeAsString[] = {@"ETNormalObjectType",@"ETRareObjectType",@"ETEssentialObjectType"};
[anXMLElement addChild:[NSXMLElement elementWithName:@"objectType" stringValue:ETObjectTypeAsString[objectType]]];
... но это не совсем идеально; Я не доволен обновлением обоих списков каждый раз, когда я меняю свой enum. Но это приемлемо. Многое, гораздо хуже читает XML, в котором я сейчас это делаю:
if ([[[anXMLElement childNamed:@"objectType"] stringValue] isEqualToString:@"ETRareObjectType"])
{
[self initObjectType:ETRareObjectType];
}
else if ([[[anXMLElement childNamed:@"objectType"] stringValue] isEqualToString:@"ETEssentialObjectType"])
{
[self initObjectType:ETEssentialObjectType];
}
else
{
[self initObjectType:ETNormalObjectType];
}
Тьфу! Это отвратительно. Должен быть более чистый способ читать, по крайней мере, или, возможно, единый способ читать и писать?
Я использую Obj-C и Cocoa, но я бы не возражал против некоторых чистых функций C. Я бы даже использовал материал препроцессора, если это единственный способ.
Ответы
Ответ 1
Я не нашел лучшего способа, чем дублирование перечисления в строке. Однако я делаю это несколько иначе, а именно:
typedef enum {
kManipulateWindowTargetFrontWindow,
kManipulateWindowTargetNamedWindow,
kManipulateWindowTargetWindowNameContaining,
kManipulateWindowTargetDEFAULT = kManipulateWindowTargetFrontWindow,
} ManipulateWindowTargetType;
#define kManipulateWindowTargetTypeNamesArray @"FrontWindow", @"NamedWindow", @"WindowNameContaining", nil
то в реализации:
static NSArray* kManipulateWindowTargetTypeArray = [[NSArray alloc] initWithObjects: kManipulateWindowTargetTypeNamesArray];
NSString* ManipulateWindowTargetTypeToString( ManipulateWindowTargetType mwtt )
{
return [kManipulateWindowTargetTypeArray objectAtIndex:mwtt];
}
ManipulateWindowTargetType ManipulateWindowTargetTypeFromString( NSString* s )
{
NSUInteger n = [kManipulateWindowTargetTypeArray indexOfObject:s];
check( n != NSNotFound );
if ( n == NSNotFound ) {
n = kManipulateWindowTargetDEFAULT;
}
return (ManipulateWindowTargetType) n;
}
Причина, по которой я использую #define, заключается в том, чтобы избежать объявления массива в файле заголовка, но было бы безумным отделить определение перечисления от определения последовательности строк, так что это лучший компромисс я ' вы нашли.
Поскольку код является шаблоном, вы можете сделать их категорией в NSArray.
@interface NSArray (XMLExtensions)
- (NSString*) stringWithEnum: (NSUInteger) e;
- (NSUInteger) enumFromString: (NSString*) s default: (NSUInteger) def;
- (NSUInteger) enumFromString: (NSString*) s;
@end
@implementation NSArray (XMLExtensions)
- (NSString*) stringWithEnum: (NSUInteger) e;
{
return [self objectAtIndex:e];
}
- (NSUInteger) enumFromString: (NSString*) s default: (NSUInteger) def;
{
NSUInteger n = [self indexOfObject:s];
check( n != NSNotFound );
if ( n == NSNotFound ) {
n = def;
}
return n;
}
- (NSUInteger) enumFromString: (NSString*) s;
{
return [self enumFromString:s default:0];
}
@end
а затем:
NSLog( @"s is %@", [kManipulateWindowTargetTypeArray stringWithEnum:kManipulateWindowTargetNamedWindow] );
ManipulateWindowTargetType mwtt = (ManipulateWindowTargetType)[kManipulateWindowTargetTypeArray enumFromString:@"WindowNameContaining" default:kManipulateWindowTargetDEFAULT];
NSLog( @"e is %d", mwtt );
Ответ 2
Вот как я обычно пишу эти стили методов:
#define countof(array) (sizeof(array)/sizeof(array[0]))
enum {
ETNormalObjectType,
ETRareObjectType,
ETEssentialObjectType
};
typedef NSInteger ETObjectType;
NSString *ETObjectTypesAsStrings[] = {[ETNormalObjectType] = @"ETNormalObjectType",
[ETRareObjectType] = @"ETRareObjectType",
[ETEssentialObjectType] = @"ETEssentialObjectType"};
NSString *ETStringFromObjectType(ETObjectType type) {
return ETObjectTypesAsStrings[type];
}
ETObjectType ETObjectTypeFromString(NSString *string) {
NSString *match = nil;
for(NSInteger idx = 0; !match && (idx < countof(ETObjectTypesAsStrings)); idx += 1) {
if ([string isEqualToString:ETObjectTypesAsStrings[idx]]) {
match = ETObjectTypesAsStrings[idx];
}
}
return match;
}
В конечном итоге вам нужно поместить свои значения перечисления в два места, исходное перечисление и массив, который отображает целочисленные значения в их имена строк. Две функции, которые фактически выполняют отображение, не имеют копий карт.
Ответ 3
Я использую решение Jon, но вы можете использовать страшный X-macro, чтобы вообще не повторять себя. Я не знаю, как комментировать ответ Джона с форматированием кода, так что вот это новый ответ.
#define ETObjectTypeEntries \
ENTRY(ETNormalObjectType) \
ENTRY(ETRareObjectType) \
ENTRY(ETEssentialObjectType)
typedef enum ETObjectType {
#define ENTRY(objectType) objectType,
ETObjectTypeEntries
#undef ENTRY
} ETObjectType;
NSString *ETObjectTypesAsStrings[] = {
#define ENTRY(objectType) [objectType] = @"" # objectType,
ETObjectTypeEntries
#undef ENTRY
};
#define countof(array) (sizeof(array)/sizeof(array[0]))
NSString *ETStringFromObjectType(ETObjectType type) {
return ETObjectTypesAsStrings[type];
}
NSString *ETObjectTypeFromString(NSString *string) {
NSString *match = nil;
for(NSInteger idx = 0; !match && (idx < countof(ETObjectTypesAsStrings)); idx += 1) {
if ([string isEqualToString:ETObjectTypesAsStrings[idx]]) {
match = ETObjectTypesAsStrings[idx];
}
}
return match;
}
Ответ 4
Великая вещь XML заключается в том, что он может преобразовать почти все, даже в код. Это просто требуется (много) усилий, чтобы сделать перевод один раз. Я работал в нескольких проектах, где XML был переведен в код. И это сэкономило много времени. Этот метод, например, рассматривается в главе 12 книги "XSLT Cookbook 2nd edition, S. Mangano, O'Reilley".
Это не простое решение, но если у вас есть хорошее картографирование, вы
- иметь одну точку определения (ваш xml)
- может генерировать файлы .h с перечислением
- может генерировать таблицы или функции для чтения/записи значений в xml
Это зависит от количества перечислений и того, как часто они меняются, если это стоит.
Удачи!
Ответ 5
Я экспериментирую с этим решением -
static NSString *stepTypeEnum[kStepTypeCount] = {@"one",@"two",@"three",@"four"};
int enumFromStrings(NSString*findString,NSString *strings[],int enumMax){
for (int i=0;i<enumMax;i++){
if ([findString isEqual: strings[i]]) {
return i;
}
}
NSLog(@"enum was not found for string %@", findString);
assert(false);
return INT_MAX;
}
Мне это нравится, потому что он проверяет длину строкового массива во время компиляции, а функция enumFromStrings является универсальной и многоразовой. Вы называете это так:
-(void)setType:(NSString*)typeString{
type = enumFromStrings(typeString,stepTypeEnum,kStepTypeCount);
}