parser un xml avec arbres avec touchxml
Bonjour à tous,
je m'adresse à la communauté de ce forum, je rencontre des difficultés techniques à parser mon fichier xml dans un projet de developpement d'application pour ipad. Je me suis orienté vers touchxml parceque je n'ai que de la lecture à réaliser sur ce fichier xml.
J'ai parcouru pas mal de sites mais les exemples fournis sur ces sites sont toujours très basiques et ne m'aident pas vraiment.
le xml est composé de cette facon:
<data>
<category>
<id></id>
<name></name>
<image></image>
<products>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
<option> </option>
</options>
</product>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
</options>
</product>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
<option> </option>
<option> </option>
</options>
</product>
</products>
</category>
<category>
<id></id>
<name></name>
<image></image>
<products>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
<option> </option>
</options>
</product>
</products>
</category>
</data>
j'utilise ceci pour me connecter au fichier xml et parser les categories
CXMLDocument *xmlParser = [[[CXMLDocument alloc] initWithContentsOfURL:[NSURL URLWithString:@"http://xxxx.xml"] options:0 error:nil] autorelease];
NSArray *categories = [xmlParser nodesForXPath:@/data/category error:nil];
ma question est la suivante:
comment récupérer le nom de chaque catégorie? (je souhaite les afficher dans un tableview)
et ensuite, lorsque je clique sur une categorie, je souhaite afficher la liste des produits de cette categorie.
comment recupérer la liste des produits ainsi que ses options pour une catégorie??
j'espère vraiment que vous pourrez m'aider, je bloque sur ce problème depuis plusieurs jours ce qui m'empeche de continuer mon developpement.
dans l'attente d'une réponse, je vous remercie!
je m'adresse à la communauté de ce forum, je rencontre des difficultés techniques à parser mon fichier xml dans un projet de developpement d'application pour ipad. Je me suis orienté vers touchxml parceque je n'ai que de la lecture à réaliser sur ce fichier xml.
J'ai parcouru pas mal de sites mais les exemples fournis sur ces sites sont toujours très basiques et ne m'aident pas vraiment.
le xml est composé de cette facon:
<data>
<category>
<id></id>
<name></name>
<image></image>
<products>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
<option> </option>
</options>
</product>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
</options>
</product>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
<option> </option>
<option> </option>
</options>
</product>
</products>
</category>
<category>
<id></id>
<name></name>
<image></image>
<products>
<product>`
<id></id>
<title></title>
<price></price>
<options>
<option> </option>
<option> </option>
</options>
</product>
</products>
</category>
</data>
j'utilise ceci pour me connecter au fichier xml et parser les categories
CXMLDocument *xmlParser = [[[CXMLDocument alloc] initWithContentsOfURL:[NSURL URLWithString:@"http://xxxx.xml"] options:0 error:nil] autorelease];
NSArray *categories = [xmlParser nodesForXPath:@/data/category error:nil];
ma question est la suivante:
comment récupérer le nom de chaque catégorie? (je souhaite les afficher dans un tableview)
et ensuite, lorsque je clique sur une categorie, je souhaite afficher la liste des produits de cette categorie.
comment recupérer la liste des produits ainsi que ses options pour une catégorie??
j'espère vraiment que vous pourrez m'aider, je bloque sur ce problème depuis plusieurs jours ce qui m'empeche de continuer mon developpement.
dans l'attente d'une réponse, je vous remercie!
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Pour parser du XML moi je suis parti du tutos suivant : http://ipup.fr/page.php?id=332
Peut-être que ça peut t'aider.
Bonne continuation,
Pierre
P.S.: n'hésite pas à te présenter ici : http://forum.pommedev.com/index.php?board=55.0
Je te remercie pour ta rapidité. (Je vais compléter ma présentation dès que possible
Cependant, le site vers lequel tu m'as orienté offre une fois de plus un exemple très basique,
ce qui ne correspond pas à mon problème.
Il me semble que ton problème n'est pas de parser du XML, mais de garder les données en mémoire. Il va donc falloir penser ta couche "modèle".
Regarde le diagramme de classes UML que j'ai joint. (Les relations 1->* sont typiquement implémentées sous forme de NSMutableArray).
En parsant le XML, tu vas créer la hiérarchie des objets, et renseigner leurs propriétés.
La tableview n'aura plus alors qu'à configurer correctement ses cellules en piochant les informations dans le Modèle. Encore une fois, nous revenons au fameux principe du MVC (Modèle-Vue-Contrôleur) avec lequel les vétérans comme moi vous vont chier systématiquement.
si je comprend bien, la logique que je dois appliquer est la suivante:
@interface category : NSObject {
// Les NSStrings correspondent aux balises dans category.
NSString *id;
NSString *name;
NSString *image;
// définir les produits ici
@property (nonatomic, retain) NSString *id;
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *image;
@end
je n'ai pas encore tout assimilé en objective c, c'est certainement une chose très simple à réaliser
mais je ne sais pas comment définir mes produits dans l'interface category pour les récupérer simplement? (j'aurai du ouvrir la discussion de cette facon)
(J'ai remplacé id par ident, car id est un type Objective-C.)
Le but est d'avoir un modèle exploitable directement par le reste de l'appli, alors on traduit les données. Par exemple, dans la balise <image>, tu vas sans doute trouver l'URL de l'image. Du coup, on crée tout de suite une instance de UIImage initialisée avec cette URL.
Je tiens à te remercier, je commence à voir le bout du tunnel!
cependant je rencontre un problème
exception:
exception 'NSUnknownKeyException', reason: '[<category 0x4b72d30> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key id.'
voici mes fichiers:
//Categorie.h
#import <Foundation/Foundation.h>
@interface category : NSObject {
NSInteger *ident;
NSString *name;
UIImage *image;
NSMutableArray *products;
}
@property (readwrite) NSInteger *ident;
@property (retain) NSString *name;
@property (retain) UIImage *image;
@end
//category.m
#import "category.h"
@implementation category
@synthesize ident;
@synthesize name;
@synthesize image;
@end
//viewcontroller.h
#import "category.h"
#import "XMLToObjectParser.h"
- (void)viewDidLoad {
XMLToObjectParser *myParserCat = [[XMLToObjectParser alloc] parseXMLAtURL:url toObject:@category parseError:nil];
}
je ne comprend pas, j'ai bien défini un integer id dans mon category.h
NSInteger au lieu de NSInteger*
le pire c'est que je ne la voyais pas !
merci FKDEV
mtn je rencontre une erreur que j'ai deja eu aupparavant quand j'ai commencé a passer d'exemples avec des xml très simples à des xml plus complexes.
cette erreur rejoin ma question de base:
2010-06-10 16:04:54.946 lavachesurletoit[3808:207] Open tag: data
2010-06-10 16:04:54.947 lavachesurletoit[3808:207] Open tag: category
2010-06-10 16:04:54.948 lavachesurletoit[3808:207] Open tag: id
2010-06-10 16:04:54.950 lavachesurletoit[3808:207] Open tag: name
2010-06-10 16:04:54.950 lavachesurletoit[3808:207] Open tag: image
2010-06-10 16:04:54.951 lavachesurletoit[3808:207] Open tag: products
2010-06-10 16:04:54.952 lavachesurletoit[3808:207] Open tag: product
2010-06-10 16:04:54.952 lavachesurletoit[3808:207] Open tag: id
2010-06-10 16:04:54.953 lavachesurletoit[3808:207] Open tag: title
2010-06-10 16:04:54.954 lavachesurletoit[3808:207] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<category 0x49263e0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key title.'
en ayant déclaré un nsmutablearray products, je pensais que les produits seraient stockés dans ce tableau,
mais apparement ca ne se passe pas comme ca, je ne vois pas comment faire.
Soit dans la manière dont tu l'utilises.
Normalement pour mettre des objets dans un NSMutableArray tu n'as pas besoin d'être "KVC compliant".
Là apparemment tu utilises un objet ou un message qui s'attends à ce que ta classe soit Key-Value Coding compliant.
Pour répondre à ta question initiale :
peux tu m'expliquer comment définir cette classe correctement? et comment l'utiliser correctement?
ok pour cette partie mais ensuite, pourrait tu me donner un exemple pour afficher les noms des produits par catégories?(retour a la question initiale)
je ne suis pas obligé d'utiliser touchxml, je recherche simplement une solution a mon problème.
merci de l'intêret que vous portez à mes questions!
Pour ce que tu veux faire il faut utiliser la classe UITableView.
Je te conseille de télécharger tous les exemples du SDK et de les executer. Quand tu en trouves qui affiche des trucs un peu similaires à ce que tu veux faire, tu regardes comme c'est fait.
Pour débuter il vaut mieux éviter les exemples qui utilisent CoreData.
j'ai passé pas mal de temps sur le net à chercher un exemple avec un fichier xml qui possède une structure ressemblante au mien mais je n'ai pas réussi à trouver!
c'est la partie récupération des produits que je n'arrive pas à mettre en place (au niveau du code) j'ai compris la logique a utiliser mais je n'arrive pas à l'implémenter. J'ai suivi les conseils défini ci-dessus, l'utilisation d'un mutable mais ca ne fonctionne pas....
Tu peux même travailler chaque sujet dans un projet différent sinon tu risques de cumuler les problèmes.
Une fois que tu es parvenu à faire fonctionner chaque partie alors tu peux mettre le tout ensemble.
L'une des compétences de base à acquérir c'est de savoir comment découper un projet en morceaux et ensuite comment établir des liens entre ces morceaux.
Si tu as réussi à faire fonctionner l'UITableView comme tu le souhaites, alors garde ton projet au chaud et repart de zéro pour la partie XML.
Il ne faut pas hésiter à repartir de très bas quand tu découpes les problèmes.
Par exemple si tu ne peux pas continuer travailler sur ta lecture du fichier XML tant que tu n'es pas certain d'avoir bien compris le fonctionnement de la classe NSMutableArray.
Le NSMutableArray est un container générique, tu peux y mettre des NSString* ou des products* ça fonctionne de la même manière. Le tableau en lui-même ne sait pas ce qu'il contient.
Donc fais-toi un exemple où tu mets des strings dans le tableau. Fais-le en dur ou avec une boucle très simple.
Si ça marche, affiche les strings du tableau dans UITableView.
Ensuite créer une classe qui contient juste une string, mets la dans un tableau et affiche ton string dans le table view.
Ensuite mets un NSMutableArray à côté de ton string dans ta nouvelle classe. Et Voilà tu auras une structure similaire à ce que tu veux obtenir.
dernière question avant le retour au départ:
voila ce qui est censé représenter mes produits dans mon interface category:
NSMutableArray *products;
est ce que je dois avoir une interface "products"? qui va définir un produit c-a-d: son id, son nom,...
est ce que cette interface va être automatiquement utilisée par le parser pour remplir le tableau "products"?
je vais reconstruire ca petit à petit cette journée,
merci à toi pour tes conseils !
- soit tu utilises un NSDictionary (enfin plutôt un NSMutableDictionary) pour stocker chaque produit sous la forme de paris clé/valeur (clé "id" => valeur de l'ID du produit, clé "name" => nom du produit, ...), l'avantage c'est que c'est rapide à mettre en oeuvre, la classe NSDictionary existant déjà .
- soit tu crées une classe "Product" pour stocker un produit, avec les propriétés "productID" ("id" est un mot réservé...), "name", etc, ce qui est plus propre côté objet quand même !
Dans les deux cas de toute façon le parseur n'a aucun moyen de connaà®tre tes classes, tout ce qu'il fait lui c'est te dire "dans ton XML là t'as un noeud/tag XML qui s'appelle "<product>" avec tels attributs, puis ensuite dans ce tag "<product>" tu as un tag "<id>" et un tag "<name>", ... à toi de lui dire quoi en faire après (genre créer un nouvel objet Produit quand tu rencontres le tag <produit>, stocker la valeur du texte qui est à l'intérieur du tag "<name>" dans le nom du produit que tu viens de créer, ajouter l'objet Produit à ton tableau de produits quand tu as fini de parser le contenu du tag <produit> (quand tu rencontres son tag fermant </produit>), etc.
D'ailleurs pour ce genre de cas je me demande bien l'utilité d'utiliser un parseur DOM comme touchxml plutôt que du parsing SAX comme NSXMLParser (ou libxml) : le DOM n'ayant un intérêt que si tu veux réaccéder à l'arbre XML une fois parsé, pour aller refouiller dans l'arbre... mais comme de toute façon lors du parsing tu auras construit un NSArray de Products pour mémoriser tes produits, une fois que le parsing est fait et les produits stockés tu ne devrais plus avoir besoin de l'arbre DOM XML...
pour ce point, mon choix s'oriente vers la deuxième solution.
donc ici je devrais modifier le parserxml et rajouter une condition ds le parser dans ce genre:
if ( balise = <products>){
products* monproduit = contenubalise;
tableproduct: ajouter monproduit;
}
pour ce qui est du parser DOM (touchxml) comme j'en ai parlé d'en un premier temps,
je me suis rendu compte qu'il n'étais pas nécessaire pour moi, c'est pour ca qu'ensuite je me suis
réorienté vers le parser NSXML.
Merci Aligator
Pour ma part quand j'ai ce genre de truc à faire, je fais plutôt un truc comme ça :
- Une variable d'instance currentParsedProduct de type "Product"
- Une variable d'instance currentParsedText de type NSMutableString
- quand je rencontre une balise <product> (methode de delegate "didStartElement" + un if pour tester le elementName) ==> créer un nouveau product : currentParsedProduct = [[Product alloc] init];
- quand je rencontre une balise <name> (didStartElement + if(elementName==<name>)), se préparer à parser du texte (le nom) : currentParsedText = [[NSMutableString alloc] init]
- quand je rencontre du texte (didParseString ou un nom de delegate method qui ressemble), si je suis en attente de texte (currentParsedText != nil), ajouter le texte lu par le parseur à ma chaà®ne : [currentParsedText appendString:leTexteLuParLeParseur]. Cette méthode pouvant être appelée plusieurs fois de suite (le parseur peut ne pas lire la chaà®ne de texte "d'un coup" on sait jamais c'est un cas à prévoir), d'où le "appendString" pour concaténer
- quand je rencontre la fin de la balise </name> (didEndElement + if(elementName=='name')), stocker le texte lu dans le nom du produit (currentParsedProduct.name = currentParsedText) puis libérer la mémoire du texte parsé ([currentParsedText release]; puis currentParsedText = nil pour éviter que si le parseur rencontre d'autre texte qui n'a plus rien à voir avec le name, il le lise pas)
- même principe pour les autres sous-balises de <product> comme <id>, etc...
- quand je rencontre la fin de la balise </product>, c'est qu'on a fini de remplir le produit "en cours de construction". On peut alors le rajouter au tableau [tableauProducts addObject:currentParsedProduct], puis libérer la mémoire associée à cet objet [currentParsedProduct release])
Voilà pour le principe générique de parsing SAX.
PS : pourquoi tu mets un "s" au nom de ta classe "products" alors qu'elle représente un produit unique ? c'est tout bête mais ça pourrait te valoir des incompréhensions lors de la relecture de ton code plus tard... et pour info les noms des classes commencent tjs par une majuscule, par convention
je mets un "s" à "products" parceque la structure de mon xml fait que les balises <product> sont des enfants de <products>
le nombre de produit n'étant pas un nombre fixe pour une <category>
c'est vrai que je peu modifier ca.
Quand à la naming convention des classes, je l'avais respectée mais en mettant des majuscule, ( sauf erreur de ma part)
le parser étant case sensitive; si j'écris Category je pense qu'il ne trouvait pas la balise <category>...
Mais rien ne t'oblige à avoir une classe Cocoa qui a le même nom que ton tag XML (évidemment c'est plus clair, mais passer la première lettre en majuscule pour respecter les conventions Cocoa est donc mieux).
D'ailleurs par exemple pour le tag "<id>" qui se trouve si j'imagine bien dans le tag "<product>", tu ne pourras pas le faire correspondre à une variable d'instance que tu nommerais "id" dans ta classe "Product", car "id" est un mot réservé en Cocoa (c'est un type dynamique). Il faudra donc nommer autrement la variable d'instance dans laquelle tu stockeras la valeur du tag <id>. Par exemple "NSString* productId" au lieu de "NSString* id" qui serait invalide.
Sinon je réitère la remarque que si tu veux rester logique, ta classe Product représente un produit unique, qui va correspondre à un tag <product> dans ton XML (et non à un tag <products>. Donc je ne mettrais pas de "s", car une instance de "Product" représente un produit unique.
Par contre, tu peux appeler ton "NSMutableArray* products" avec un "s", et ce NSMutableArray contiendra bien ta liste de produits, chacun étant un objet "Product" représentant un seul produit.
Bon après ce ne sont que des considérations de nommage, mais mieux vaut prendre les bonnes habitudes de suite et utiliser des noms de variables et de classes logiques et lisibles, surtout pour la relecture et compréhension du code ensuite.
merci beaucoup pour tes explications très claires et précises qui me sont d'une grande aide!
La confusion vient peut-être du fait que les parsers DOM te fournissent effectivement des objets.
D'ailleurs si tu veux juste afficher le contenu des tags, les objets DOM pourraient très bien te suffire.
Cela dit je pense que c'est un meilleur exercice de créer tes propres classes et, en plus, cela te permettra de faire évoluer plus facilement ton logiciel.
Moi j'avais proposé TouchXML pour son support du langage XPath qui te permet très simplement de récupérer des noeuds dans l'arborescence XML.
En plus c'est une approche séquentiel, c'est-à -dire que tout le code est visible, tu peux le suivre facilement au debugger.
Avec l'approche SAX, il y a une notion de "callback" qui me parait plus complexe pour un débutant. Mais bon...
Bon courage
je vais me débrouiller maintenant, je pense avoir compris l'essentiel pour avancer un max.
j'aurai probablement des questions qui vont suivre qd j'aurai un peu avancé dans mon projet.
je solliciterai probablement votre aide précieuse!
merci AliGator et FKDEV pour la précision et la rapidité de vos réponses, rien à redire.