Tu veux promouvoir ton application ici ? Ce service est gratuit pour le moment alors profites en !


Tutoriel n°12 : Utiliser XML

Bonjour à tous !

Aujourd'hui, nous allons parler de XML, et de parsage.
Nous allons lire un fichier xml (un exemple très simple) puis nous l'afficherons.

http://www.ipup.fr/forum/userimages/Image-39-1.jpg



1) Qu'est-ce que XML ?

Si vous ne savez pas ce qu'est XML, je vous invite à faire deux trois recherches sous Google

- Wikipedia
- Communauté française autour du XML
- Cours sur altruiste.com
- Tutoriel sur developpez.com

En gros, ce qu'il faut retenir : XML pour eXtensible Markup Language est un langage utilisant des balises, et ce de manière simplifiée. Vous pouvez en effet définir vos propres balises.

Voici à quoi peut ressembler un fichier XML :

<Book id="1">
      <title>Circumference</title>
      <author>Nicholas Nicastro</author>
      <summary>Eratosthenes and the Ancient Quest to Measure the Globe.</summary>
</Book>

2) Et sur iPhone alors ?

  1. Quel fichier xml

Nous allons partir de ceci :

<?xml version="1.0" encoding="UTF-8"?>
<Monde>
       <pays>
           <nom>France</nom>
           <capitale>Paris</capitale>
           <habitants>60M</habitants>
       </pays>
       <pays>
           <nom>Suisse</nom>
           <capitale>Berne</capitale>
           <habitants>7,5M</habitants>
       </pays>
</Monde>

Copier/coller ce code dans un fichier txt, puis changer l'extension en .xml

  2. Nouveau projet

Créons tout d'abord un projet "Navigation Based Application".

Ajoutez un fichier "NSObject Subclass" que nous nommerons pays.m
NB : pays correspond à la balise <pays>

Ajoutez le code suivant :

#import <Foundation/Foundation.h>


@interface pays : NSObject {

        // Les NSStrings correspondent aux balises dans pays. Attention, mettre le même nom que vos balises
    NSString *nom;
    NSString *capitale;
    NSString *habitants;
}

@property (nonatomic, retain) NSString *nom;
@property (nonatomic, retain) NSString *capitale;
@property (nonatomic, retain) NSString *habitants;

@end

N'oubliez pas les @synthesize dans le .m big_smile

  3. Attaquons-nous au Parser

(D'après le blog d'Atrexis)

Créez une nouvelle subclass NSObject "XMLToObjectParser". Cette classe va permettre d'ouvrir et fermer les balises pour récupérer les données.

#import <Foundation/Foundation.h>


@interface XMLToObjectParser : NSObject {
    NSString *className;
    NSMutableArray *items;
    NSObject *item; // stands for any class    
    NSString *currentNodeName;
    NSMutableString *currentNodeContent;
}
- (NSArray *)items;
- (id)parseXMLAtURL:(NSURL *)url 
           toObject:(NSString *)aClassName 
         parseError:(NSError **)error;
@end

Dans le .m

#import "XMLToObjectParser.h"

@implementation XMLToObjectParser

- (NSArray *)items
{
    return items;
}

- (id)parseXMLAtURL:(NSURL *)url 
           toObject:(NSString *)aClassName 
            parseError:(NSError **)error
{
    [items release];
    items = [[NSMutableArray alloc] init];
    className = aClassName;
    NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
    [parser setDelegate:self];
    [parser parse];
    
    if([parser parserError] && error) {
        *error = [parser parserError];
    }
    [parser release];
    return self;
}

- (void)parser:(NSXMLParser *)parser
                didStartElement:(NSString *)elementName
                namespaceURI:(NSString *)namespaceURI
                qualifiedName:(NSString *)qName
                attributes:(NSDictionary *)attributeDict
{
    NSLog(@"Open tag: %@", elementName);
    if([elementName isEqualToString:className]) {
        // create an instance of a class on run-time  
        item = [[NSClassFromString(className) alloc] init];
    }
    else {
        currentNodeName = [elementName copy];
        currentNodeContent = [[NSMutableString alloc] init];
    }
}

- (void)parser:(NSXMLParser *)parser 
            didEndElement:(NSString *)elementName
            namespaceURI:(NSString *)namespaceURI
            qualifiedName:(NSString *)qName
{
    NSLog(@"Close tag: %@", elementName);    
    if([elementName isEqualToString:className]) {
        [items addObject:item];        
        [item release];
        item = nil;
    }
    else if([elementName isEqualToString:currentNodeName]) {
        // use key-value coding     
        [item setValue:currentNodeContent forKey:elementName];
        
        [currentNodeContent release];
        currentNodeContent = nil;
        
        [currentNodeName release];
        currentNodeName = nil;
    }
}

- (void)parser:(NSXMLParser *)parser
                foundCharacters:(NSString *)string
{   
    [currentNodeContent appendString:string];
}

- (void)dealloc
{
    [items release];
    [super dealloc];
}

@end

J'ai gardé la méthode
- (id)parseXMLAtURL:(NSURL *)url
           toObject:(NSString *)aClassName
            parseError:(NSError **)error

qui parse un document à partir d'une adresse url, même si dans ce tutoriel je prendrai un document en local.

Notez cette ligne : [parser setDelegate:self];. On délègue donc la gestion des événements pour le NSXMLParser à XMLToObjectParser. En observant la documentation de NSXMLParser, vous retrouverez les méthodes
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict,

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName

et - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string.

Cette classe va donc parcourir le xml. Ensuite, si elle trouve une balise qui porte le nom de la classe pays que nous avons créée, elle copiera les informations et retournera un objet de ce type.

Maintenant, nous retournons dans notre viewController, et on import les .h (

#import "XMLToObjectParser.h"
#import "pays.h"

)

Dans la méthode viewDidLoad,

- (void)viewDidLoad {
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Monde" ofType:@"xml"];
    NSURL *url = [NSURL fileURLWithPath:filePath]; // pour convertir un filePath en NSUrl
    XMLToObjectParser *myParser = [[XMLToObjectParser alloc] 
                                   parseXMLAtURL:url toObject:@"pays" parseError:nil];
    
    NSLog(@"Pays");
    
    for(int i = 0; i < [[myParser items] count]; i++) {
        NSLog(@"    %@", [(pays *)[[myParser items] objectAtIndex:i] nom]);
    }
    
    
    [super viewDidLoad];
}

Voici ce que vous obtenez :

http://www.ipup.fr/forum/userimages/Image-33-1.jpg



Affichez maintenant le pays, la capitale, et le nombre d'habitants.

[réponse]



3) Affichons tout ceci dans une TableView

Pour commencer, il nous faut remplir notre TableView :
Dans le rootViewController, nous allons créer un    NSMutableArray *tableauPays;

Je vous invite à aller faire un tour sur le tuto de Ceetix si vous n'êtes pas familier avec les TablesView

Ensuite, dans viewDidLoad, nous allons remplir ce tableau avec des objets de type "pays" :

- (void)loadView {
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Monde" ofType:@"xml"];
    NSURL *url = [NSURL fileURLWithPath:filePath];
    XMLToObjectParser *myParser = [[XMLToObjectParser alloc] 
                                   parseXMLAtURL:url toObject:@"pays" parseError:nil];
    
    tableauPays = [[NSMutableArray alloc] init];

    for(int i = 0; i < [[myParser items] count]; i++) {
        pays *nouveauPays;
        nouveauPays = (pays *)[[myParser items] objectAtIndex:i];
        [tableauPays addObject:nouveauPays];
    }
        
    [super viewDidLoad];
}

Ensuite, ajoutez une tableView dans ParserXMLViewController.xib. Dans l'onglet "Connections" de la tableView ainsi mise, reliez dataSource et delegate à File's Owner.

Modifiez ensuite ces méthodes :

#pragma mark Table view methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [tableauPays count];
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
       cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"MyIdentifier"] autorelease];
    }
    NSString *ligneTableau = [NSString stringWithFormat:@"%@", [[tableauPays objectAtIndex:indexPath.row] nom]];
    cell.text=ligneTableau;
    return cell;
}

#pragma mark end

Compilez, lancez, vous devriez obtenir ceci :

http://www.ipup.fr/forum/userimages/Image-35-1.jpg


Il serait judicieux d'afficher les informations correspondants à ces pays !

Pour cela, créer une classe UIViewController subclass qui servira à gérer la vue affichant les caractéristiques du pays choisis.

#import <UIKit/UIKit.h>
#import "pays.h"

@interface PaysViewController : UIViewController {
    
    IBOutlet UILabel *nom;
    IBOutlet UILabel *capitale;
    IBOutlet UILabel *habitants;
    pays *unPays;

}

@property (nonatomic, retain) UILabel *nom;
@property (nonatomic, retain) UILabel *capitale;
@property (nonatomic, retain) UILabel *habitants;
@property (nonatomic, retain) pays *unPays;


@end


- (void)viewDidLoad {
    NSLog(@"viewDidLoad");
    nom.text = unPays.nom;
    capitale.text = unPays.capitale;
    habitants.text = unPays.habitants;
    [super viewDidLoad];
}

Créons le .xib correspondant :

clic droit sur resources / new File puis

http://www.ipup.fr/forum/userimages/Image-36.jpg


Nommez-la "PaysView"

Ouvrez ensuite ce .xib, spécifiez la classe :

http://www.ipup.fr/forum/userimages/Image-37-1.jpg



Puis faites les connexions et agencements nécessaires :

http://www.ipup.fr/forum/userimages/Image-38-1.jpg



Compilez, lancez, il ne se passe rien...

En effet, il faut modifier la méthode suivante :

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // Navigation logic may go here -- for example, create and push another view controller.
    PaysViewController *paysViewController = [[PaysViewController alloc] initWithNibName:@"PaysView" bundle:nil];
    paysViewController.unPays=[tableauPays objectAtIndex:indexPath.row];
    self.navigationItem.title=[[tableauPays objectAtIndex:indexPath.row] nom];
    [self.navigationController pushViewController:paysViewController animated:YES];
    [paysViewController release];
}

Et voilà !

http://www.ipup.fr/forum/userimages/Image-39-1.jpg



Sources

@ bientôt

PS : rajoutez cette ligne pour avoir des flèches :

http://www.ipup.fr/forum/userimages/tre42.jpg




- (UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewCellAccessoryDisclosureIndicator;
}

Ipodishima

Copyright © 2009 - ipup.fr • création de Jérémy Lagrue • design de Loann Fraillon • contact