Bonjour/Bonsoir à tous,

Avant toute chose, je tiens à préciser que je suis presque débutant en ce qui concerne le parsing XML.
Je sais que cette partie du forum est réservée au parsing du XML sous des langages plus connus comme C++ ou Java. Mais je veux l'appliquer sous IDL (Interactive Data Language). Il existe le parser IDLffXMLSAX dans ce cas là.

Exemple d'application
J'ai trouvé un modèle plutôt simple sur le net. A partir du fichier Planets.xml suivant :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
<Solar_System>   
   <Planet NAME='Mercury'> 
      <Orbit UNITS='kilometers' TYPE='ulong64'>579100000</Orbit> 
      <Period UNITS='days' TYPE='float'>87.97</Period> 
      <Moons TYPE='int'>0</Moons> 
   </Planet> 
  ... 
</Solar_System>
On définit dans un script xml_to_struct__define.pro (l'extension IDL est .pro) une classe d'objet afin de parser ce XML.
Voici le script :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
;---------------------------------------------------------------------------
; Init method
; Called when the xmlstruct object is created.
 
FUNCTION xml_to_struct::Init
   ; Initialize the value of the planetNum counter. This
   ; will be incremented as elements are added to the
   ; Planets array.
   self.planetNum = 0
   RETURN, self->IDLffxmlsax::Init()
END
 
;---------------------------------------------------------------------------
; Characters method
; Called when parsing character data within an element.
; Adds data to the charBuffer field.
 
PRO xml_to_struct::characters, data
   self.charBuffer = self.charBuffer + data
END
 
;---------------------------------------------------------------------------
; StartElement method
; Called when the parser encounters the start of an element.
 
PRO xml_to_struct::startElement, URI, local, strName, attrName, attrValue
 
   CASE strName OF
      "Solar_System":   ; Do nothing
      ; Initialize the PLANET structure held in currentPlanet,
      ; and set the Name field of the structure equal to the
      ; value of the first attribute of the <Planet> element.
      "Planet" : BEGIN
         self.currentPlanet = {PLANET, "", 0ull, 0.0, 0}
         self.currentPlanet.Name = attrValue[0]
      END
      ; Reinitialize the charBuffer.
      "Orbit" : self.charBuffer = ''
      "Period" : self.charBuffer = ''
      "Moons" : self.charBuffer = ''
   ENDCASE
 
END
 
;---------------------------------------------------------------------------
; EndElement method
; Called when the parser encounters the end of an element.
 
PRO xml_to_struct::EndElement, URI, Local, strName
 
   CASE strName of
      "Solar_System":   ; Do nothing
      "Planet": BEGIN
         ; Set element 'planetNum' of the Planets array equal
         ; to the PLANET structure held in currentPlanet.
         self.Planets[self.planetNum] = self.currentPlanet
         ; Increment planetNum counter
         self.planetNum = self.planetNum + 1
      END
      ; Set the value of the appropriate field in the current
      ; PLANET structure equal to the value of charBuffer.
      "Orbit" : self.currentPlanet.Orbit = self.charBuffer
      "Period" : self.currentPlanet.Period = self.charBuffer
      "Moons" : self.currentPlanet.Moons= self.charBuffer
   ENDCASE
 
END
 
;---------------------------------------------------------------------------
; GetArray method
; Returns the current array stored internally. If
; no data is available, returns -1.
 
FUNCTION xml_to_struct::GetArray
   IF (self.planetNum EQ 0) THEN $
      RETURN, -1 $
   ELSE RETURN, self.Planets[0:self.planetNum-1]
END
 
;---------------------------------------------------------------------------
; Object class definition method.
 
PRO xml_to_struct__define
 
   ; Define a useful structure
   void = {PLANET, NAME: "", Orbit: 0ull, period:0.0, Moons:0}
 
   ; Define the class data structure
   void = {xml_to_struct, $
            INHERITS IDLffXMLSAX, $
            charBuffer : "", $
            planetNum : 0, $
            currentPlanet: {PLANET}, $
            Planets : MAKE_ARRAY(9, VALUE={PLANET}) }
 
END
Bien. Ça c'était l'exemple qu'on trouve sur le net et surtout dans le répertoire où est installé IDL. Donc on peut faire confiance au script xml_to_struct__define.pro.


Le XML à parser
Je l'appelle config.xml
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 
<parametres>
	<zones_geographiques>
		<zone_geo id=1>
			<BG_lat>45.0</BG_lat>
                        <BG_lon>0.0</BG_lon>
			<HD_lat>50.0</HD_lat>
                        <HD_lon>5.0</HD_lon>
		</zone_geo>
		<zone_geo id=2>
			<BG_lat>50.0</BG_lat>
                        <BG_lon>0.0</BG_lon>
			<HD_lat>55.0</HD_lat>
                        <HD_lon>5.0</HD_lon>
		</zone_geo>
	</zones_geographiques>
	<liste_num_orbite_cycle>1 10 12 14 17 96</liste_num_orbite_cycle>
	<seuils>
		<canal num=1>
			<indices_colonnes_debut>75</indices_colonnes_debut>
                        <indices_colonnes_fin>111</indices_colonnes_fin>
			<seuil_max_TB_elem>200</seuil_max_TB_elem>
			<seuil_max_TB_ecart_type>4</seuil_max_TB_ecart_type>
			<seuil_min_nb_TB_valides_jour>20</seuil_min_nb_TB_valides_jour>
			<seuil_max_TB_moy>220</seuil_max_TB_moy>
		</canal>
		<canal num=6>						     
                         <indices_colonnes_debut>75</indices_colonnes_debut>
                        <indices_colonnes_fin>111</indices_colonnes_fin>
			<seuil_max_TB_elem>180</seuil_max_TB_elem>
			<seuil_max_TB_ecart_type>2</seuil_max_TB_ecart_type>
			<seuil_min_nb_TB_valides_jour>15</seuil_min_nb_TB_valides_jour>
			<seuil_max_TB_moy>240</seuil_max_TB_moy>
		</canal>
	</seuils>
</parametres>
C'est un peu plus compliqué à parser que le Planets.xml, pour plusieurs raisons :
* Les balises zone_geo et canal_num ont différents attributs, portant pour nomenclature "id" et "num" (respectivement). Le problème, c'est que ce ne seront pas toujours les mêmes. Le fichier ici présent utilise les zones geo 1 et 2, et les canaux 1 et 6, mais ça aurait très bien pu être les zones de 1 à 15, et les canaux 2, 3, 4 et 5 (je sais simplement que les numéros de zones vont de 1 à 15 maxi et ceux de canaux de 1 à 6)
Ainsi, sans parler du contenu des balises, l' ""ossature"" de ce fichier XML est elle même sujette à variations.
* L'arborescence est un peu plus complexe. Dans le planets.xml, il y avait la balise <Planet> répétée 9 fois, avec trois "sous balises" <Orbites>, <Period> et <Moons>. Et c'est tout.



Voici ce que JE propose, en langage IDL, pour parser config.xml. J'adapte le xml_to_struct__define.pro à mon propre fichier XML.
Je vais l'appeler xml2struct_saphir__define.pro (ne cherchez pas à comprendre le "saphir", c'est en lien avec mon travail. )

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
;---------------------------------------------------------------------------
; Init method
; Called when the xmlstruct object is created.
 
FUNCTION xml2struct_saphir::Init
   ; initialise les valeurs des compteurs zoneNum et canalNum
   ; C'est incremente des qu'un element est ajoute à l'array zonesGeo
   self.zoneNum = 0
   self.canalNum = 0
   RETURN, self->IDLffxmlsax::Init()
END
 
;---------------------------------------------------------------------------
; Characters method
; Called when parsing character data within an element.
; Adds data to the charBuffer field.
 
PRO xml2struct_saphir::characters, data
   self.charBuffer = self.charBuffer + data
END
 
;---------------------------------------------------------------------------
; StartElement method
; Called when the parser encounters the start of an element.
 
PRO xml2struct_saphir::startElement, URI, local, strName, attrName, attrValue
 
   CASE strName OF
      "parametres":   ; Do nothing
      "zones_geographiques":   ; Do nothing
      ; Initialize the ZONE_GEO structure held in currentZoneGeo,
      ; and set the ID field of the structure equal to the
      ; value of the first attribute of the <zone_geo> element.
      "zone_geo" : BEGIN
         self.currentZoneGeo = {ZONE_GEO, 0, 0.0, 0.0, 0.0, 0.0}
         self.currentZoneGeo.ID = attrValue[0]
      END
      ; Reinitialize the charBuffer.
      "BG_lat" : self.charBuffer = ''
      "BG_lon" : self.charBuffer = ''
      "HD_lat" : self.charBuffer = ''
      "HD_lon" : self.charBuffer = ''
      "liste_num_orbite_cycle" : self.charBuffer = ''
       "seuils":   ; Do nothing
      ; Initialize the CANAL structure held in currentCanal,
      ; and set the NUM field of the structure equal to the
      ; value of the first attribute of the <canal> element.
      "canal" : BEGIN
         self.currentCanal = {CANAL, 0, 0, 0, 0, 0, 0, 0}
         self.currentCanal.NUM = attrValue[0]
      END
      ; Reinitialize the charBuffer.
      "indices_colonnes_debut"        : self.charBuffer = ''
      "indices_colonnes_fin"             : self.charBuffer = ''
      "seuil_max_TB_elem"              : self.charBuffer = ''
      "seuil_max_TB_ecart_type"      : self.charBuffer = ''
      "seuil_min_nb_TB_valides_jour" : self.charBuffer = ''
      "seuil_max_TB_moy"                : self.charBuffer = ''
 
 
   ENDCASE
 
END
 
;---------------------------------------------------------------------------
; EndElement method
; Called when the parser encounters the end of an element.
 
PRO xml2struct_saphir::EndElement, URI, Local, strName
 
   CASE strName OF
      "parametres":   ; Do nothing
      "zones_geographiques":   ; Do nothing
      "zone_geo" : BEGIN
        ; Set element 'zoneNum' of the ZONES_GEOGRAPHIQUES array equal
        ; to the ZONE_GEO structure held in currentZoneGeo.
         self.ZONES_GEOGRAPHIQUES[self.zoneNum] = self.currentZoneGeo
         ; Increment zoneNum counter
         self.zoneNum = self.zoneNum + 1
      END
      ; Set the value of the appropriate field in the current
      ; ZONE_GEO structure equal to the value of charBuffer.
      "BG_lat" : self.currentZoneGeo.BG_LAT = self.charBuffer
      "BG_lon" : self.currentZoneGeo.BG_LON = self.charBuffer      
      "HD_lat" : self.currentZoneGeo.HD_LAT = self.charBuffer      
      "HD_lon" : self.currentZoneGeo.HD_LON = self.charBuffer   
       ; Set the value of the LISTE_NUM_ORBITE_CYCLE structure equal to the value of charBuffer.
      "liste_num_orbite_cycle" : self.LISTE_NUM_ORBITE_CYCLE = self.charBuffer   
      "seuils":   ; Do nothing
      "canal" : BEGIN
        ; Set element 'canlNum' of the SEUILS array equal
        ; to the CANAL structure held in currentCanal.
         self.SEUILS[self.canalNum] = self.currentCanal
         ; Increment canalNum counter
         self.canalNum = self.canalNum + 1
      END
      ; Set the value of the appropriate field in the current
      ; CANAL structure equal to the value of charBuffer.
      "indices_colonnes_debut" : self.currentCanal.indices_colonnes_debut = self.charBuffer
      "indices_colonnes_fin" : self.currentCanal.indices_colonnes_fin = self.charBuffer 
      "seuil_max_TB_elem" : self.currentCanal.seuil_max_TB_elem = self.charBuffer 
      "seuil_max_TB_ecart_type" : self.currentCanal.seuil_max_TB_ecart_type = self.charBuffer 
      "seuil_min_nb_TB_valides_jour" : self.currentCanal.seuil_min_nb_TB_valides_jour = self.charBuffer 
      "seuil_max_TB_moy" : self.currentCanal.seuil_max_TB_moy = self.charBuffer 
   ENDCASE
 
END
 
;---------------------------------------------------------------------------
; GetArray method
; Returns the current array stored internally. If
; no data is available, returns -1.
 
FUNCTION xml2struct_saphir::GetArray
   IF (self.zoneNum EQ 0) THEN $
      RETURN, -1 $
   ELSE RETURN, self.ZONES_GEOGRAPHIQUES[0:self.zoneNum-1]
   IF (self.canalNum EQ 0) THEN $
      RETURN, -1 $
   ELSE RETURN, self.SEUILS[0:self.canalNum-1]
END
 
;---------------------------------------------------------------------------
; Object class definition method.
 
PRO xml2struct_saphir__define, nb_zones_geo, nb_canaux
; Par rapport à la procedure analogue pour les planetes, je passe les nombres de zones_geo et de canaux en parametres. 
; ces parametres sont definis en amont dans le script qui va appeler le parsing. 
 
   ; Define useful structures
   void = {ZONE_GEO, ID:0, BG_LAT:0.0, BG_LON:0.0, HD_LAT:0.0, HD_LON:0.0}
   void = {CANAL, NUM:0, indices_colonnes_debut: 0, indices_colonnes_fin : 0,  seuil_max_TB_elem:0, seuil_max_TB_ecart_type:0, seuil_min_nb_TB_valides_jour:0, seuil_max_TB_moy:0}
  void = {LISTE_NUM_ORBITE_CYCLE, sList:""}
 
 
   ; Define the class data structure
   void = {xml2struct_saphir, $
            INHERITS IDLffXMLSAX, $
            charBuffer : "", $
            zoneNum : 0, $
            currentZoneGeo : {ZONE_GEO}, $
            ZONES_GEOGRAPHIQUES : MAKE_ARRAY(nb_zones_geo, VALUE={ZONE_GEO}), $
            LISTE_NUM_ORBITE_CYCLE: {LISTE_NUM_ORBITE_CYCLE}          
            canalNum : 0, $
            currentCanal: {CANAL}, $
            SEUILS: MAKE_ARRAY(nb_canaux, VALUE={CANAL}) }
 
END
(Je suis désolé pour l'amalgame de commentaires en anglais et en français. ^^ )

Alors voilà j'aimerais avoir votre avis sur cette adaptation du .pro basé sur les planètes.
Je sais bien que c'est pas parfait du premier coup, mais juste savoir si je suis pas carrément à côté de la plaque... (je peux pas encore débugger ce code IDL, je ne peux que l'éditer)


Notamment, j'aimerais savoir si c'est "légal" d'introduire trois structures dans la procédure finale. Je parle des trois void = {ZONE_GEO puis CANAL puis LISTE_NUM_ORBITE_CYCLE.
Il n'y avait qu'une seule structure dans le précédent script : PLANET mais là, la structure du XML me l'impose un peu.


Et enfin, ne sachant pas comment faire autrement, j'ai été contraint de spécifier les paramètres variables nb_zones_geo, nb_canaux AVANT que le parsing détecte par lui même qu'il y a nb_zones_geo et nb_canaux.
C'est pas forcément très grave, mais je voudrais que le fichier de config XML soit auto-suffisant et puisse gérer ces aspects dynamiques sans qu'on le rende artificiellement "figé".


Voilà, désolé pour le roman. J'étais frais au début du post et j'écris/programme depuis plus de 2heures donc j'espère que la fin du post est intelligible.
Merci pour tout avis.

EDIT : je vois déjà que les deux RETURN dans le xml2struct_saphir, c'est pas terrible... mais je corrigerai ça demain, trop fatigué, je vais laisser ça tel quel, au risque de choquer.