Convertir entre C enum y XML

6 minutos de lectura

¿Cuál es la forma más limpia de almacenar una enumeración en XML y volver a leerla? Di que tengo:

enum ETObjectType {ETNormalObjectType, ETRareObjectType, ETEssentialObjectType};

… y quiero tomar una variable, enum ETObjectType objectType = ETNormalObjectType;y conviértalo a XML que se ve así: <objectType>ETNormalObjectType</objectType>.

Actualmente lo que estoy haciendo es algo como esto:

NSString* const ETObjectTypeAsString[] = {@"ETNormalObjectType",@"ETRareObjectType",@"ETEssentialObjectType"};

[anXMLElement addChild:[NSXMLElement elementWithName:@"objectType" stringValue:ETObjectTypeAsString[objectType]]];

…pero eso no es del todo ideal; No estoy feliz de actualizar ambas listas cada vez que cambio mi enumeración. Pero es aceptable. Mucho, mucho peor es volver a leer XML, por lo que actualmente estoy haciendo esto:

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

¡Puaj! Esto me disgusta. ¿Tiene que haber una forma más limpia de leer, al menos, o tal vez una forma unificada de leer y escribir?

Estoy usando Obj-C y Cocoa, pero no me importaría algunas funciones C puras. Incluso usaría cosas de preprocesador, si es la única forma.

avatar de usuario
pedro n lewis

No he encontrado una mejor manera que duplicar la enumeración en una cadena. Sin embargo, lo hago de manera ligeramente diferente, a saber:

typedef enum {
    kManipulateWindowTargetFrontWindow,
    kManipulateWindowTargetNamedWindow, 
    kManipulateWindowTargetWindowNameContaining, 
    kManipulateWindowTargetDEFAULT = kManipulateWindowTargetFrontWindow, 
} ManipulateWindowTargetType;
#define kManipulateWindowTargetTypeNamesArray @"FrontWindow", @"NamedWindow", @"WindowNameContaining", nil

luego en la implementación:

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

La razón por la que uso #define es para evitar declarar la matriz en el archivo de encabezado, pero sería una locura separar la definición de la enumeración de la definición de la secuencia de cadenas, por lo que este es el mejor compromiso que he encontrado.

Dado que el código es repetitivo, puede convertirlos en una categoría en 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

y luego:

NSLog( @"s is %@", [kManipulateWindowTargetTypeArray stringWithEnum:kManipulateWindowTargetNamedWindow] );
ManipulateWindowTargetType mwtt = (ManipulateWindowTargetType)[kManipulateWindowTargetTypeArray enumFromString:@"WindowNameContaining" default:kManipulateWindowTargetDEFAULT];
NSLog( @"e is %d", mwtt );

  • Gran idea para mantener la enumeración y la secuencia de cadenas en el archivo de encabezado. Algo me molesta sobre hacer que esos sean métodos NSArray, pero probablemente lo haré de todos modos. ¡Gracias!

    – andyvn22

    7 de agosto de 2009 a las 8:55

  • ¿Dónde se define la función de verificación?

    – zekel

    18 mayo 2010 a las 15:30

  • check se define en AssertMacros.h, junto con verificar, requerir y las variantes _noerr, _action, _quiet, _string. Deben esparcirse con frecuencia a lo largo de su código para que encuentre sus errores antes de que lo hagan sus usuarios.

    – Peter N Lewis

    19 de mayo de 2010 a las 7:04

  • Por lo general, trato la separación de la lista de cadenas de la enumeración al tener un ‘MyEnumCount’ al final de la lista de enumeración, y luego una afirmación en el método EnumToString de que la cantidad de elementos en la matriz es igual a ‘MyEnumCount – 1’. De esa manera, si alguien agrega una enumeración y no actualiza la matriz, obtendrá un error de afirmación inmediato. Compilar aserciones de tiempo en lugar de aserciones de tiempo de ejecución puede hacer que eso sea aún más autocorregible.

    – Jon Hess

    26 de noviembre de 2010 a las 6:57

  • Va en mis archivos .mm, pero tenga en cuenta que siempre compilo todo como Objective-C++. C probablemente no permita la inicialización estática con llamadas a funciones, en cuyo caso deberá crear una función de acceso estática con una variable local estática que inicialice una vez. Esa es una función estática con cuerpo estático kManipulateWindowTargetTypeArray = nil; if (! kManipulateWindowTargetTypeArray) kManipulateWindowTargetTypeArray = …; devuelve kManipulateWindowTargetTypeArray; Alternativamente, cambie a Objective-C++ (el único inconveniente real es que clang aún no funciona con C++).

    – Peter N Lewis

    9 de diciembre de 2010 a las 6:44

Así es como normalmente escribo estos estilos de métodos:

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

Termina teniendo que colocar sus valores de enumeración en dos lugares, la enumeración original y la matriz que asigna valores enteros a sus nombres de cadena. Sin embargo, las dos funciones que realmente hacen el mapeo no tienen copias de los mapas.

Me hago eco de la solución de Jon, pero puedes usar el temido X-macro para evitar repetirse en absoluto. No sé cómo comentar la respuesta de Jon con formato de código, así que aquí está como una nueva respuesta.

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

  • Esto es… verdaderamente repugnante. 😀 ¡Gracias! (Voy a usar la categoría en la estrategia NSArray, pero es muy divertido jugar con esto).

    – andyvn22

    11 de agosto de 2009 a las 1:43

  • ¿”Verdaderamente repugnante”? Yo diría poco convencionalmente elegante.

    – Jon Hess

    14 de agosto de 2009 a las 10:38

  • @JonHess Te estás haciendo viejo, eso fue un cumplido

    – Madbreaks

    28 de diciembre de 2012 a las 0:59

Lo bueno de XML es que puede transformarse en casi cualquier cosa, incluso en código. Solo se necesita (mucho) esfuerzo para hacer la traducción una vez. He trabajado en varios proyectos donde se tradujo XML a código. Y esto ahorró mucho tiempo. Esta técnica se trata, por ejemplo, en el capítulo 12 del libro “XSLT Cookbook 2nd edition, S. Mangano, O’Reilley”.

No es una solución fácil, pero si tiene un buen mapeo, tiene un único punto de definición (su xml), puede generar archivos .h con enumeración, puede generar tablas o funciones para leer/escribir valores en xml.

Depende de la cantidad de enumeraciones y de la frecuencia con la que cambien si vale la pena. ¡Buena suerte!

Estoy experimentando con esta solución –

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

Me gusta porque verifica la longitud de la matriz de cadenas en el momento de la compilación, y la función enumFromStrings es genérica y reutilizable. Lo llamas así:

-(void)setType:(NSString*)typeString{
    type = enumFromStrings(typeString,stepTypeEnum,kStepTypeCount);
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad