Projet

Général

Profil

lxml_parser.py

Nouvelle version avec des optimisation reste un pb avec <value></value> - Joël Cuissinat, 18/01/2012 17:06

Télécharger (13,7 ko)

 
1
# -*- coding: UTF-8 -*-
2
"""
3
Parseur LXML des fichiers XML de collecte des variables EOLE
4
"""
5

    
6
import xml.dom.minidom
7
from lxml import etree
8
#from creole.error import *
9
from creole.error import ConfigError
10
from creole.utils import string_to_bool #, get_text_node
11
from creole.config import VIRTMASTER
12

    
13
service_level = 0
14
file_level = 0
15

    
16

    
17
def parse_xml_file(filename):
18
    """
19
    @param filename: nom du fichier xml source
20
    @return: structure de données permettant de créer les objets Eole
21
    """
22
    try:
23
        document = etree.iterparse(filename, events=('end',), tag='creole')
24
        #document = xml.dom.minidom.parse(filename)
25
        return _parse_root_node(document)
26
    except Exception, err:
27
        import traceback
28
        traceback.print_exc()
29
        raise ConfigError, "Erreur lors du parsing du fichier %s : %s" % (filename, str(err))
30

    
31
def parse_string(string):
32
    # FIXME !!!
33
    try:
34
        document = xml.dom.minidom.parseString(string)
35
        return _parse_root_node(document)
36
    except Exception, err:
37
        raise ConfigError, "Erreur lors du parsing : %s" % str(err)
38

    
39

    
40
def _parse_root_node(document):
41
    """
42
    @param document: le document DOM contenant l'arborescence xml
43
    """
44
    for _, first_node in document:
45
        root_node = first_node
46

    
47
    variables = parse_variables(root_node.find('variables'))
48
    families, order = parse_families(root_node)
49

    
50
    # balise <files> (données sur le maître)
51
    file_node = root_node.findall('files')
52
    softwares = {}
53
    if file_node != []:
54
        if len(file_node) != 1:
55
            raise Exception("Erreur plusieurs balises <files> par dictionnaire")
56
        file_root_node = file_node[0]
57
        files = parse_files(file_root_node)
58
        file_packages = parse_packages(file_root_node)
59
        file_services = parse_services(file_root_node)
60
        softwares[VIRTMASTER] = [files, file_packages, file_services, {}, '1', '', '']
61

    
62
    # balise <containers> (données dans les conteneurs)
63
    containers_node = root_node.findall('containers')
64
    if containers_node != []:
65
        for container in containers_node:
66
            softwares.update(parse_containers(container))
67

    
68
    ## gestion des contraintes
69
    constraints = parse_constraints(root_node)
70

    
71
    # gestion des groupes de variables
72
    groups = parse_groups(root_node)
73

    
74
    # gestion de l'aide
75
    helps = parse_help(root_node)
76

    
77
    # gestion des séparateurs
78
    separators = parse_separators(root_node)
79

    
80
    return softwares, variables, families, order, constraints, groups, helps, separators
81

    
82

    
83
def _get_boolean_attr(node, attr_name, default=False):
84
    """
85
    Gestion spécifique pour les attributs booléens
86
    Ils sont à False par défaut
87
    """
88
    val = node.get(attr_name)
89
    if default:
90
        return str(val).lower() != 'false'
91
    else:
92
        return str(val).lower() == 'true'
93

    
94
def _get_optionnal(node, attr_name):
95
    """
96
    Valeur d'un attribut optionnel
97
    renvoie une chaine vide au lieu de None
98
    """
99
    val = node.get(attr_name)
100
    if val is None:
101
        val = ''
102
    return val
103

    
104
def _parse_value(varnode, attr='value'):
105
    """
106
    récupération des valeurs d'une variable
107
    """
108
    return [val.text for val in varnode.findall(attr)]
109

    
110

    
111
def parse_variables(var_node):
112
    """
113
    traitement des variables
114
    @param var_node: noeud <variables>
115
    """
116
    result = {}
117
    for var in var_node.getiterator('variable'):
118
        hidden = _get_boolean_attr(var, 'hidden')
119
        multi = _get_boolean_attr(var, 'multi')
120
        redefine = _get_boolean_attr(var, 'redefine')
121
        exists = _get_boolean_attr(var, 'exists', default=True)
122
        # FIXME: mode='' était admis avec domparser
123
        mode = _get_optionnal(var, 'mode')
124
        name = var.attrib['name']
125
        value = _parse_value(var)
126
        typ = _get_optionnal(var, 'type')
127
        desc = _get_optionnal(var, 'description')
128
        result[name] = dict(value=value,
129
                            type=typ,
130
                            description=desc,
131
                            hidden=hidden,
132
                            multi=multi,
133
                            auto='',
134
                            redefine=redefine,
135
                            exists=exists,
136
                            mode=mode
137
                            )
138
    return result
139

    
140

    
141
def _get_vars_name(family_node, family_name, family_parse):
142
    """
143
    récupération de la liste des variables associées à une famille
144
    """
145
    varsname = []
146
    for var in family_node.getiterator('variable'):
147
        varsname.append(var.attrib['name'])
148
    return varsname
149

    
150

    
151
def parse_families(var_node):
152
    """
153
    traitement des familles
154
    @param var_node: noeud <variables>
155
    """
156
    result = {}
157
    order = []
158
    for family in var_node.findall('variables/family'): #: getiterator('family'):
159
        family_name = family.attrib['name']
160
        order.append(family_name)
161
        hidden = _get_boolean_attr(family, 'hidden')
162
        # FIXME: mode='' était admis avec domparser
163
        mode = _get_optionnal(family, 'mode')
164
        result[family_name] = {'hidden':hidden, 'mode':mode,
165
                               'vars':_get_vars_name(family,
166
                                                 family_name,
167
                                                 result)}
168
    return result, order
169

    
170

    
171
def parse_files(file_node):
172
    """
173
    Parsing des balises <file>
174
    @file_node : noeud xml
175
    """
176
    global file_level
177
    result = {}
178
    for fic in file_node.getiterator('file'):
179
        file_level += 1
180

    
181
        result[fic.attrib['name']] = {'hidden':False,
182
                                      'source': _get_optionnal(fic,'source'),
183
                                      'mode': _get_optionnal(fic, 'mode'),
184
                                      'owner': _get_optionnal(fic, 'owner'),
185
                                      'group': _get_optionnal(fic, 'group'),
186
                                      'mkdir': _get_boolean_attr(fic, 'mkdir'),
187
                                      'filelist': _get_optionnal(fic, 'filelist'),
188
                                      'rm': _get_boolean_attr(fic, 'rm'),
189
                                      'del_comment': _get_optionnal(fic, 'del_comment'),
190
                                      'container_only': _get_boolean_attr(fic, 'container_only'),
191
                                      'level': file_level
192
                                      }
193
    return result
194

    
195

    
196
def parse_packages(package_node):
197
    """
198
    Traitement de balises <package>
199
    """
200
    return _parse_value(package_node, 'package')
201

    
202

    
203
def parse_services(service_node):
204
    """
205
    Traitement des balises <service>
206
    """
207
    global service_level
208
    result = {}
209
    for serv in service_node.getiterator('service'):
210
        service_level += 1
211
        method = serv.get('method')
212
        if not method:
213
            method = 'service'
214
        # FIXME : cas inverse de d'habitude !
215
        try:
216
            pty = string_to_bool(serv.get('pty'))
217
        except (TypeError, ValueError):
218
            pty = True
219
        # FIXME : cas encore différent !
220
        try:
221
            #True : seulement en mode conteneur
222
            #False : seulement en mode non conteneur
223
            #None : dans les deux modes
224
            in_container = string_to_bool(serv.get('in_container'))
225
        except (TypeError, ValueError):
226
            in_container = None
227
        #result[str(get_text_node(serv))] = {'servicelist':serv.getAttribute('servicelist'),
228
        result[serv.text] = {'servicelist': _get_optionnal(serv, 'servicelist'),
229
                             'startlevel': _get_optionnal(serv, 'startlevel'),
230
                             'stoplevel': _get_optionnal(serv, 'stoplevel'),
231
                             'method': method,
232
                             'pty': pty,
233
                             'level': service_level,
234
                             'in_container': in_container}
235
    return result
236

    
237

    
238
def parse_disknod(disknod_node):
239
    """
240
    Traitement de balises <disknod>
241
    """
242
    return _parse_value(disknod_node, 'disknod')
243

    
244

    
245
def parse_interfaces(interface_node):
246
    """
247
    Traitement des balises <interface>
248
    """
249
    result = {}
250
    for interface in interface_node.getiterator('interface'):
251
        method = interface.get('method')
252
        if not method:
253
            method = 'macvlan'
254
        if method not in ['macvlan', 'bridge']:
255
            raise Exception('method pour une interface doit être macvlan ou bridge et pas {0}'.format(method))
256
        result[interface.text] = {'interfacelist': _get_optionnal(interface, 'interfacelist'),
257
                                  'linkto': _get_optionnal(interface, 'linkto'),
258
                                  'ip': _get_optionnal(interface, 'ip'),
259
                                  'mask': _get_optionnal(interface, 'mask'),
260
                                  'bcast': _get_optionnal(interface, 'bcast'),
261
                                  'method': method
262
                                 }
263
    return result
264

    
265

    
266
def parse_containers(containers_node):
267
    result = {}
268
    for container_node in containers_node.getiterator('container'):
269
        name = container_node.attrib['name']
270
        containerid = _get_optionnal(container_node, 'id')
271
        groupid = _get_optionnal(container_node, 'group')
272
        if name == VIRTMASTER:
273
            raise Exception("Le nom '%s' est interdit pour une balise <container>" % VIRTMASTER)
274
        if name in result:
275
            raise Exception("Le nom %s doit etre unique par dictionnaire" % name)
276

    
277
        file_nodes = parse_files(container_node)
278
        packages = parse_packages(container_node)
279
        services = parse_services(container_node)
280
        interfaces = parse_interfaces(container_node)
281
        disknods = parse_disknod(container_node)
282
        result[name] = [file_nodes, packages, services, interfaces, containerid, groupid, disknods]
283
    return result
284

    
285

    
286
def parse_constraints(node):
287
    """
288
    @param node: node des contraintes
289
    """
290
    constraints = {'checks' : parse_funcs(node,'check'),
291
                   'fills' : parse_funcs(node,'fill'),
292
                   'auto' : parse_funcs(node,'auto'),
293
                   'conditions' : parse_conditions(node)
294
                  }
295
    return constraints
296

    
297

    
298
def _parse_param(param_node):
299
    return {'name'  : _get_optionnal(param_node, 'name'),
300
            'type'  : _get_optionnal(param_node, 'type'),
301
            'value' : param_node.text,
302
            'optional' : _get_optionnal(param_node, 'optional'),
303
            'hidden' : _get_optionnal(param_node, 'hidden'),
304
            }
305

    
306

    
307
def parse_funcs(node, func_type):
308
    """
309
    @param node: node des fonctions
310
    @param func_type: TagName of the functions to find
311
    """
312
    # fonctions de vérification
313
    funcs = {}
314
    #for func in node.getiterator(func_type):
315
    for func in node.findall('constraints/%s' % func_type):
316
        # lecture des paramètres
317
        # XXX (adim): est-ce que le fait que params ne soit pas réinitialisé
318
        #             à chaque tour de boucle "for target in targets" est un bug ?
319
        params = []
320
        targets = _parse_value(func, 'target') # [ get_text_node(t) for t in func.getElementsByTagName('target') ]
321
        if not targets:
322
            targets = [_get_optionnal(func, 'target')]
323
        for target in targets:
324
            if target is not None:
325
                for param in func.getiterator('param'):
326
                    params.append(_parse_param(param))
327
                funcs.setdefault(target, []).append([func.attrib['name'],
328
                                                     params])
329
    return funcs
330

    
331

    
332
def parse_conditions(node):
333
    """
334
    @param node: node des fonctions
335
    """
336
    # fonctions de vérification
337
    funcs = {}
338
    for func in node.getiterator('condition'):
339
        # lecture des paramètres
340
        targets = []
341
        family_targets = []
342
        file_targets = []
343
        filelist_targets = []
344
        servicelist_targets = []
345
        interfacelist_targets = []
346
        # paramètres de la fonction
347
        params = [_parse_param(param)
348
                  for param in func.getiterator('param')]
349
        # cibles de la dépendance
350
        for target in func.getiterator('target'):
351
            ttype = target.get('type')
352
            if ttype == 'family':
353
                family_targets.append(target.text)
354
            elif ttype == 'file':
355
                file_targets.append(target.text)
356
            elif ttype == 'filelist':
357
                filelist_targets.append(target.text)
358
            elif ttype == 'servicelist':
359
                servicelist_targets.append(target.text)
360
            elif ttype == 'interfacelist':
361
                interfacelist_targets.append(target.text)
362
            else:
363
                # sinon, c'est une variable EOLE
364
                targets.append(target.text)
365
        funcdef = [func.attrib['name'], family_targets, targets,
366
                   file_targets, filelist_targets, servicelist_targets, interfacelist_targets, params]
367
        funcs.setdefault(_get_optionnal(func, 'source'), []).append(funcdef)
368
    return funcs
369

    
370

    
371
def parse_groups(node):
372
    """
373
    Traitement des groupes de variables
374
    """
375
    result = {}
376
    for group in node.findall('constraints/group'):
377
        slaves = _parse_value(group, 'slave')
378
        result[group.attrib['master']] = slaves
379
    return result
380

    
381

    
382
def parse_help(node):
383
    """
384
    Traitement de l'aide
385
    """
386
    var_help = dict( (var.attrib['name'], var.text.strip())
387
                     for var in node.findall('help/variable') )
388
    fam_help = dict( (var.attrib['name'], var.text.strip())
389
                     for var in node.findall('help/family') )
390
    return {'variables':var_help, 'families': fam_help}
391

    
392

    
393
def parse_separators(node):
394
    """dictionnaire des séparateurs, format {'variable':'text'}
395
    variable : nom de la première variable après le sépateur"""
396
    var_sep = {}
397
    for var in node.findall('separators/separator'):
398
        var_sep[var.attrib['name']] = (var.text.strip(), _get_boolean_attr(var, 'never_hidden'))
399
    return var_sep
400