Tutorial Parser XML (recorrer y analizar) en iPhone

Cuando tenemos aplicaciones que se comunican con servicios web, las respuestas suelen por normal general en XML o JSON.

 

En este tutorial vamos a apprender como parsear (recorrer y analizar) este XML, mostrando los datos en una tabla.

 

 

Supongamos que enviamos con nuestra aplicación una petición a un servidor que en función del parámetro nos devuelve unos resultados u otros, pero siempre con el mismo formato.

Pongamos que queremos recuperar la lista de peliculas de dos actores distintos.

Nuestra aplicación cuenta con una lista de actores que tienen unos “id_cator” asociado.

 

Si por ejemplo queremos saber las películas de Bruce Willis, pasaremos al servicio el id_actor de Bruce, de forma similar a esto:

http://www.apprendemos.com/webservices/get_peliculas?id_actor=145 

 

Pero si queremos las películas de Nicolas Cage, será:

http://www.apprendemos.com/webservices/get_peliculas?id_actor=37

*Estas url no existen, son solo ejemplos.

 

La respuesta podría ser similar a:

 

<listado>
    <pelicula id="1">
        <titulo>La Jungla 4.0</titulo>
        <descripcion>La mejor pelicula de los ultimos tiempos. El agente Jonh McCain...</descripcion>
        <anio>2010</anio>
    </pelicula>
    <pelicula id="2">
        <titulo>El sexto sentido</titulo>
        <descripcion>La película con un final prometedor e insesperado.</descripcion>
        <anio>1998</anio>
    </pelicula>
    <pelicula id="3">
        <titulo>Falsas Apariencias</titulo>
        <descripcion>Mezcla de acción y comedia junto a Mathew Perry</descripcion>
        <anio>2004</anio>
    </pelicula>
</listado>

 

Como vemos la respuesta contiene un Nodo principal. Por norma, todo XML debe tener un nodo padre principal.

 

Después vemos claramente cuales son los distintos items, en este caso, delimitados por “película”

 

Dentro tenemos 4 elementos: 

    – El id de la pelicula (dentro de “pelicula id=”id_pelicula”” Utilizamos un atributo para mostrar después como recuperarlo. Los atributos hacen ahorrar bastante código en el XML, lo que para las respuestas web son más eficientes).

    – El título (‘<'titulo'>”<'/titulo'>‘).

    – La descripción (‘<'descripcion'>”<'/descripcion'>‘).

    – El año de estreno (‘<'anio'>”<'/anio'>‘).

 

 

Para realizar este tutorial será necesario tener conocimiento de UITableview (tutorial disponible) y UINavigationController (tutorial disponible)

 

En otro tutorial explicaremos como conectar a un servicio web de forma asíncrona y recuperar sus valores parapoder interacturar.

 

En este ejemplo, pondemos crear en un NSString cada XML.

 

 

Creamos un nuevo proyecto de tipo Single View Application.

 

Single View Application - Apprendemos

Lo primero que vamos a hacer es crear una clase que contendrá los valores de las películas.

 

Creamos un NSObject.

 

New File - Apprendemos.com

 

En Pelicula.h:

#import <Foundation/Foundation.h>
 
@interface Pelicula : NSObject
{
 
}
 
@property(nonatomic) int id_pelicula;
@property(nonatomic, retain) NSString *titulo;
@property(nonatomic, retain) NSString *descripcion;
@property(nonatomic) int anio;
 
@end

 

En Pelicula.m:

#import "Pelicula.h"
 
@implementation Pelicula
@synthesize id_pelicula, titulo, descripcion, anio;
 
 
@end

 

 

Como nuestra aplicación va a navegar entre ventanas le añadimos al AppDelegate un UINavigationController, como ya se ha hecho en el tutorial correspondiente.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
 
    self.viewController = [[ViewController alloc] initWithNibName:nil bundle:nil];
 
    UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:self.viewController];
 
    self.window.rootViewController = navController;
    [self.window makeKeyAndVisible];
    return YES;
}

 

 

Én viewController.xib insertamos dos botones.

 

Interface Builder - Apprendemos.com

 

Y les asignamos el tag 0 y 1 respectivamente.

 

Set tag UIButton - Apprendemos.com

 

 

Creamos un UIViewController ListadoViewController en el proyecto.

 

ListadoViewController - Apprendemos.com

 

Dejaremos el código de ListadoViewController.h así:

#import <UIKit/UIKit.h>
#import "Pelicula.h"
 
@interface ListadoViewController : UIViewController <NSXMLParserDelegate, UITableViewDataSource, UITableViewDelegate>
{
    NSMutableArray *peliculas;
    Pelicula *auxPelicula;
 
    NSMutableString *currentElementValue;
 
    IBOutlet UITableView *tableView;
}
 
@property(nonatomic, retain) NSString *stringXML;
 
 
@end

 

 

Como vemos hemos importado el objeto Pelicula. Hemos declarado al controlador como delegado de UITableView y de NSXMLParser, que será el que implementaremos en el tutorial.

Además declaramos el array que contendrá las películas del actor, el string que almacenará el contenido de cada etiqueta del XML, y la tabla para mostrar los resultados.

 

stringXMl hará del contenido XML que en una situación real  recuperaríamos de un servicio web (o de un fichero local).

 

 

Ahora volvemos a viewController.m, y creamos un método que llame al listado, y en función del tag que tenga el botón que hace la llamada, mostraremos un xml u otro. 

 

- (IBAction)mostrarPeliculasPorActor:(id)sender
{
    UIButton *auxButton = (UIButton*)sender;
 
    NSString *stringXML = @"";
 
    switch (auxButton.tag) {
        case 0:
        {
            stringXML = [stringXML stringByAppendingString:@"<listado>"];
                stringXML = [stringXML stringByAppendingString:@"<pelicula id=\"1\">"];
                    stringXML = [stringXML stringByAppendingString:@"<titulo>La Jungla 4.0</titulo>"];
                    stringXML = [stringXML stringByAppendingString:@"<descripcion>La mejor pelicula de los ultimos tiempos. El agente Jonh McCain...</descripcion>"];
                    stringXML = [stringXML stringByAppendingString:@"<anio>2010</anio>"];
                stringXML = [stringXML stringByAppendingString:@"</pelicula>"];
                stringXML = [stringXML stringByAppendingString:@"<pelicula id=\"2\">"];
                    stringXML = [stringXML stringByAppendingString:@"<titulo>El sexto sentido</titulo>"];
                    stringXML = [stringXML stringByAppendingString:@"<descripcion>La película con un final prometedor e insesperado.</descripcion>"];
                    stringXML = [stringXML stringByAppendingString:@"<anio>1998</anio>"];
                stringXML = [stringXML stringByAppendingString:@"</pelicula>"];
                stringXML = [stringXML stringByAppendingString:@"<pelicula id=\"3\">"];
                    stringXML = [stringXML stringByAppendingString:@"<titulo>Falsas Apariencias</titulo>"];
                    stringXML = [stringXML stringByAppendingString:@"<descripcion>Mezcla de acción y comedia junto a Mathew Perry</descripcion>"];
                    stringXML = [stringXML stringByAppendingString:@"<anio>2004</anio>"];
                stringXML = [stringXML stringByAppendingString:@"</pelicula>"];
            stringXML = [stringXML stringByAppendingString:@"</listado>"];
 
            break;
        }
 
        case 1:
        {
            stringXML = [stringXML stringByAppendingString:@"<listado>"];
                stringXML = [stringXML stringByAppendingString:@"<pelicula id=\"35\">"];
                    stringXML = [stringXML stringByAppendingString:@"<titulo>Cara a Cara</titulo>"];
                    stringXML = [stringXML stringByAppendingString:@"<descripcion>Dos personas enfrentadas, a si mismas...</descripcion>"];
                    stringXML = [stringXML stringByAppendingString:@"<anio>1995</anio>"];
                stringXML = [stringXML stringByAppendingString:@"</pelicula>"];
                stringXML = [stringXML stringByAppendingString:@"<pelicula id=\"56\">"];
                    stringXML = [stringXML stringByAppendingString:@"<titulo>El motorista fantasma</titulo>"];
                    stringXML = [stringXML stringByAppendingString:@"<descripcion>Porque morir con una moto no es decir adios</descripcion>"];
                    stringXML = [stringXML stringByAppendingString:@"<anio>2007</anio>"];
                stringXML = [stringXML stringByAppendingString:@"</pelicula>"];
                stringXML = [stringXML stringByAppendingString:@"<pelicula id=\"42\">"];
                    stringXML = [stringXML stringByAppendingString:@"<titulo>La búsqueda</titulo>"];
                    stringXML = [stringXML stringByAppendingString:@"<descripcion>Nicolas Cage hace temblar al Sr.Jones</descripcion>"];
                    stringXML = [stringXML stringByAppendingString:@"<anio>2002</anio>"];
                stringXML = [stringXML stringByAppendingString:@"</pelicula>"];
            stringXML = [stringXML stringByAppendingString:@"</listado>"];
            break;
        }
 
        default:
            break;
    }
 
    ListadoViewController *listadoController = [[ListadoViewController alloc]initWithNibName:nil bundle:nil];
    NSLog(@"XML = %@", stringXML);
    [listadoController setStringXML:stringXML];
 
    [self.navigationController pushViewController:listadoController animated:YES];
 
 
 
}

        

        

Ahora tenemos que llamar desde el listado al parser XML (NSXMLParser).

 

Vamos a ListadoViewController.m y en viewDidLoad añadimos lo siguiente:

 

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Establecemos el delegate de la tabla, el dataSource y el alto de tabla.
    tableView.delegate = self;
    tableView.dataSource = self;
    tableView.rowHeight = 90.0;
 
 
 
    // Simulamos la respuesta del servidor con nuestro stringXML.
    // Lo convertimos a NSData para evitar errores de codificación.
    NSData *responseData = [stringXML dataUsingEncoding:NSUTF8StringEncoding];
 
    // Si tuvieramos el XML en un archivo local se podría hacer así:
    // NSData *responseData = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"peliculas.xml"]]
 
    // Creamos el parser:
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:responseData];
 
    // Lo establecemos como delegado, ya que nuestro ListadoViewController implementará el protocolo.
    [parser setDelegate:self];
 
    // Y parseamos el XML, a falta de algún error:
    // - Etiquetas sin cerrar
    // - Mala sintaxis
    // - Problemas de codificación
    // - XML sin etiqueta raiz
 
    if(![parser parse])
        NSLog(@"Error al parsear");
    else
        NSLog(@"OK Parsing");
 
 
}

 

    

Añadimos el método que detecta las aperturas de los nodos (listado,pelicula,titulo,descripcion y anio):

 

 

// Este método detecta las etiquetas que abren.
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
    // Comprobamos que etiqueta se ha detectado
    if([elementName isEqualToString:@"listado"]){
 
        // Iniciamos el listado
        // En principio este objeto es nil, pero no está de más comprobar.
        if(peliculas == nil)
        {
            peliculas = [[NSMutableArray alloc]init];
 
        } else {
 
            [peliculas removeAllObjects];
 
        }
 
    } else if([elementName isEqualToString:@"pelicula"]){
 
        // Al detectar que abre la etiqueta película creamos el objeto que contendrá los datos.
        auxPelicula = [[Pelicula alloc] init];
 
        // En XMl los datos puedes formar parte de una etiqueta, siendo su atributo. Esto ahorra en tamaño para el XML cuando tenemos muchos datos.
        if([attributeDict objectForKey:@"id"] != nil)
        {
            // Obtenemos el id de la película.
            auxPelicula.id_pelicula = [[attributeDict objectForKey:@"id"]intValue];
        }
    }
 
}

 

Añadimos ahora el método que lee y almacena el contenido de las etiquetas. Este podemos dejarlo tal cual en cualquier aplicación que hagamos:

// Este método detecta contenido entre etiquetas, siempre lo dejaremos así.

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    if(!currentElementValue)
    {
        currentElementValue = [[NSMutableString alloc]initWithString:string];
    }
    else
    {
        [currentElementValue appendString:string];
    }
 
}

 

Pasamos ahora al que seguro es más importante. 

// Este método detecta las etiquetas que cierran y es donde se comprueban los valores entre etiquetas para completar los objetos inicializados.
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    // Si es el raiz, sabemos que ha terminado el XML, y por tanto es tonteria, seguir haciendo comprobaciones. Finalizamos el parser.
    if([elementName isEqualToString:@"listado"]){
        return;
 
        // Si es el objeto el que cierra, podemos añadir el objeto al listado y finalizarlo, pues ya tendrá todas las propiedades.
    }  else if([elementName isEqualToString:@"pelicula"]){
 
 
        [peliculas addObject:auxPelicula];
        auxPelicula = nil;
 
        // Si es un parametro del objeto, se lo añadimos.
    } else if([elementName isEqualToString:@"titulo"])
    {
        [auxPelicula setTitulo:currentElementValue];
 
        // Y así con todos....
    } else if([elementName isEqualToString:@"descripcion"])
    {
        [auxPelicula setDescripcion:currentElementValue];
 
    } else if([elementName isEqualToString:@"anio"])
    {
        [auxPelicula setAnio:[currentElementValue intValue]];
 
    }
 
    // Es importante liberar el contenido de currentElementValue para que no siga concatenando el valor actual con el siguiente.
    currentElementValue = nil;
 
}

 

 

Una vez finalizado solo hay que mostrar los resultados en la tabla, estos métodos están explicados en el correspondiente tutorial de listar datos en iOS con UITableView.

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return[peliculas count];
}
 
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
 
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Normalmente recuperaremos del array, según la posicion de la fila..
    Pelicula *aPelicula = [peliculas objectAtIndex:indexPath.row];
 
    // Creamos la celda (o fila).
    UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"] ;
 
    /*-------------------------------------*/
    /* TÍTULO DE PELÍCULA                  */
    /*-------------------------------------*/
    // Creamos una etiqueta para la celda.
    UILabel *tituloLabel =  [[UILabel alloc] initWithFrame: CGRectMake( 10.0, 0.0, self.view.frame.size.width - 20.0, 20.0)];
 
    tituloLabel.textAlignment = NSTextAlignmentCenter;
 
    // Insertamos el texto de la etiqueta.
    tituloLabel.text = [NSString stringWithFormat:@"%@",aPelicula.titulo];
 
    // Añadimos el label a la celda.
    [cell.contentView addSubview:tituloLabel];
 
 
    /*-------------------------------------*/
    /* DESCRIPCIÓN DE PELÍCULA             */
    /*-------------------------------------*/
    // Creamos una etiqueta para la celda.
    UILabel *descTextView =  [[UILabel alloc] initWithFrame: CGRectMake( 10.0, 25.0, self.view.frame.size.width - 20.0, 40.0)];
 
    descTextView.numberOfLines = 2;
 
    // Insertamos el texto de la etiqueta.
    descTextView.text = [NSString stringWithFormat:@"%@",aPelicula.descripcion];
 
    // Añadimos el label a la celda.
    [cell.contentView addSubview:descTextView];
 
    /*-------------------------------------*/
    /* AÑO DE PELÍCULA                     */
    /*-------------------------------------*/
    // Creamos una etiqueta para la celda.
    UILabel *anioLabel =  [[UILabel alloc] initWithFrame: CGRectMake( self.view.frame.size.width - 60.0, 70.0, 60.0, 20.0)];
 
    // Insertamos el texto de la etiqueta.
    anioLabel.text = [NSString stringWithFormat:@"%d",aPelicula.anio];
 
    // Añadimos el label a la celda.
    [cell.contentView addSubview:anioLabel];
    /*-------------------------------------*/
 
 
 
 
    // Y finalizamos devolviendo la celda 
    return cell;
}

 

 

Antes de terminar el código, vamos a viewDidLoad y justo después del parseo recargamos los datos de la tabla:

if(![parser parse])
        NSLog(@"Error al parsear");
    else
        NSLog(@"OK Parsing");
 
    [tableView reloadData];

 

Por último queda ir al ListadoViewController.xib y arrastrar un tableView.

Le asignamos el IBOutlet de tableView, y las propiedades delegate y datasource:

 

 

    

    

 

Podéis probar como al iniciar al simulador.

 

MainView - Apprendemos.com

 

Si pulsamos sobre el botón de Bruce Willis, nos muestra las que hemos añadido a su xml. Lo mismo pasa con Nicolas Cage.

 

Bruce Willis Films - Apprendemos.com

 

Esperamos que os haya gustado este tutorial de recorrido y análisis de XML en iOS, pues el parseo XML es muy útil para cualquier comunicación con servidores.

Recordar que si os gustan nuestros tutoriales, a nosotros nos gusta más aún que se difundan, compartir en las redes sociales nos viene bien a todos 😉

Author Image
Author: ManuAlamar iOS & .NET Developer. Ingeniero Técnico Informático especialmente interesado en proyectos relacionados con la tecnología móvil y web
  • Promedi

    Buen tutorial, gracias por compartir tu trabajo.

  • Muy bueno, justo lo que andaba buscando. Gracias.

  • Tania Marquez

    me da error en el AppDelegate con self.viewController, me dice k no esta visible

  • Rodrigo Bustamante

    Me ocurre lo mismo que Tania, me dice que self.viewController no esta visible.

  • Mario Matinez

    Disculpen como se haria en caso de que el archivo XML este en una url?

  • Iván Rodríguez

    Una pregunta: ¿Podrían subir los códigos a un repositorio? Los que ponen vienen imcompletos y con múltiples fallos. Por ejemplo, en viewController.m me dice que no encuentra ListadoViewController (y eso que lo he importado expresamente). Muchas gracias.

  • Carlos

    Wow. me fue de mucha ayuda tu lógica para parsear el XML. Muchas gracias por el tutorial =D