chart_xml.py 15.4 KB
Newer Older
1
# -*- coding: utf-8 -*-
VIGNET Pierre's avatar
VIGNET Pierre committed
2
3
4
## Filename    : chart_xml.py
## Author(s)   : Geoffroy Andrieux
## Created     : 04/2010
5
6
## Revision    :
## Source      :
VIGNET Pierre's avatar
VIGNET Pierre committed
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
##
## Copyright 2010 : IRISA
##
## This library is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published
## by the Free Software Foundation; either version 2.1 of the License, or
## any later version.
##
## This library is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF
## MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  The software and
## documentation provided here under is on an "as is" basis, and IRISA has
## no obligations to provide maintenance, support, updates, enhancements
## or modifications.
## In no event shall IRISA be liable to any party for direct, indirect,
## special, incidental or consequential damages, including lost profits,
## arising out of the use of this software and its documentation, even if
## IRISA have been advised of the possibility of such damage.  See
## the GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this library; if not, write to the Free Software Foundation,
## Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
##
## The original code contained here was initially developed by:
##
##     Geoffroy Andrieux.
##     IRISA/IRSET
##     Symbiose team
##     IRISA  Campus de Beaulieu
37
38
##     35042 RENNES Cedex, FRANCE
##
VIGNET Pierre's avatar
VIGNET Pierre committed
39
40
41
42
43
44
##
## Contributor(s): Michel Le Borgne, Nolwenn Le Meur
##
"""
Load and generate Cadbiom xml files
"""
45
46
from __future__ import unicode_literals
from __future__ import print_function
VIGNET Pierre's avatar
VIGNET Pierre committed
47

48
from cadbiom.models.guard_transitions.chart_model import ChartModel
VIGNET Pierre's avatar
VIGNET Pierre committed
49
50
from xml.sax import make_parser
from xml.sax import parseString as PS
51
from xml.sax.handler import ContentHandler
VIGNET Pierre's avatar
VIGNET Pierre committed
52
53
54
55
56
from lxml import etree
from lxml import objectify


class XmlVisitor:
57
    """Visitor used to generate xml cadbiom code when the model is exported."""
58

VIGNET Pierre's avatar
VIGNET Pierre committed
59
60
61
62
63
64
65
66
67
68
69
70
71
    def __init__(self, model):
        self.model_name = model.name
        self.model = model
        self.fact_list = []
        self.xml = ""      # string: xml representation of model
        self.symb = dict() # symbol table to check double naming of nodes
        self.visit_chart_model()

    def visit_chart_model(self):
        """
        Entrance point
        """
        self.visit_ctop_node(self.model.get_root())
72

VIGNET Pierre's avatar
VIGNET Pierre committed
73
74
75
76
77
78
79
80
81
#CNode:
#    def write_xml(self):
#        pass

    def check_name(self, name):
        """
        Detect double declarations
        """
        try:
82
            self.symb[name]
VIGNET Pierre's avatar
VIGNET Pierre committed
83
84
85
86
        except:
            self.symb[name] = "ok"
            return
        raise XmlException("Node double declaration")
87

VIGNET Pierre's avatar
VIGNET Pierre committed
88
89
90
91
    def visit_cstart_node(self, snode):
        """
        Generate xml representation of a start node
        """
92
93

        tag = "CStartNode"
VIGNET Pierre's avatar
VIGNET Pierre committed
94
        attrname = ["name", "xloc", "yloc"]
95

VIGNET Pierre's avatar
VIGNET Pierre committed
96
97
        attr = [snode.name, snode.xloc, snode.yloc]
        return [tag, attrname, attr]
98

VIGNET Pierre's avatar
VIGNET Pierre committed
99
100
101
102
    def visit_ctrap_node(self, tnode):
        """
        Generate xml representation of a trap node
        """
103
        tag = "CTrapNode"
VIGNET Pierre's avatar
VIGNET Pierre committed
104
105
106
        attrname = ["name", "xloc", "yloc"]
        attr = [tnode.name, tnode.xloc, tnode.yloc]
        return [tag, attrname, attr]
107

VIGNET Pierre's avatar
VIGNET Pierre committed
108
109
110
111
112
    def visit_csimple_node(self, sin):
        """
        Generate xml representation of a simple node
        """
        self.check_name(sin.name)
113
        tag = "CSimpleNode"
VIGNET Pierre's avatar
VIGNET Pierre committed
114
        attrname = ["name", "xloc", "yloc"]
115
        attr = [sin.name, sin.xloc, sin.yloc]
VIGNET Pierre's avatar
VIGNET Pierre committed
116
        return [tag, attrname, attr]
117

VIGNET Pierre's avatar
VIGNET Pierre committed
118
119
120
121
122
    def visit_cperm_node(self, pnode):
        """
        Generate xml representation of a perm node
        """
        self.check_name(pnode.name)
123
        tag = "CPermNode"
VIGNET Pierre's avatar
VIGNET Pierre committed
124
125
        attrname = ["name", "xloc", "yloc"]
        attr = [pnode.name, pnode.xloc, pnode.yloc]
126
127
        return [tag, attrname, attr]

VIGNET Pierre's avatar
VIGNET Pierre committed
128
129
130
    def visit_cinput_node(self, inn):
        """
        Generate xml representation of an input node
131
        """
VIGNET Pierre's avatar
VIGNET Pierre committed
132
        # double declaration of input nodes is allowed"
133
        tag = "CInputNode"
VIGNET Pierre's avatar
VIGNET Pierre committed
134
135
        attrname = ["name", "xloc", "yloc"]
        attr = [inn.name, inn.xloc, inn.yloc]
136
137
        return [tag, attrname, attr]

VIGNET Pierre's avatar
VIGNET Pierre committed
138
139
140
141

    def visit_cmacro_node(self, mnode):
        """
        Generate xml representation of a macro node
142
        """
VIGNET Pierre's avatar
VIGNET Pierre committed
143
144
        self.check_name(mnode.name)
        save_macro = self.current_element
145
        tag = "CMacroNode"
VIGNET Pierre's avatar
VIGNET Pierre committed
146
        attrname = ["name", "xloc", "yloc", "wloc", "hloc"]
147
        attr = [mnode.name, mnode.xloc, mnode.yloc, mnode.wloc, mnode.hloc]
VIGNET Pierre's avatar
VIGNET Pierre committed
148
        properties = [tag, attrname, attr]
149

VIGNET Pierre's avatar
VIGNET Pierre committed
150
151
        macro = etree.SubElement(self.current_element, properties[0])
        self.current_element = macro
152
        if len(properties) > 1:
VIGNET Pierre's avatar
VIGNET Pierre committed
153
154
155
156
157
            attrname = properties[1]
            attr = properties[2]
            attributes = macro.attrib
            for i in range(0, len(attrname)):
                attributes[attrname[i]] =  str(attr[i])
158

VIGNET Pierre's avatar
VIGNET Pierre committed
159
160
161
162
163
        # nodes
        for snode in mnode.sub_nodes:
            properties = snode.accept(self)
            if properties[0] == 'CMacroNode':
                self.current_element = macro
164

VIGNET Pierre's avatar
VIGNET Pierre committed
165
166
            if properties[0] != 'CMacroNode':
                subel = etree.SubElement(self.current_element, properties[0])
167
                if len(properties) > 1:
VIGNET Pierre's avatar
VIGNET Pierre committed
168
169
170
171
172
173
174
175
176
177
178
                    attrname = properties[1]
                    attr = properties[2]
                    attributes = subel.attrib
                    for i in range(0, len(attrname)):
                        attributes[attrname[i]] =  str(attr[i])

        # transitions
        for gtr in mnode.transitions:
            for trunlist in gtr:
                properties = trunlist.accept(self)
                sub_tr = etree.SubElement(self.current_element, properties[0])
179
                if len(properties) > 1:
VIGNET Pierre's avatar
VIGNET Pierre committed
180
181
182
183
184
                    attrname = properties[1]
                    attr = properties[2]
                    attributes = sub_tr.attrib
                    for i in range(0, len(attrname)):
                        attributes[attrname[i]] =  str(attr[i])
185
        self.current_element = save_macro
VIGNET Pierre's avatar
VIGNET Pierre committed
186
        return [tag, attrname, attr]
187
188

    def visit_ctop_node(self, tnode):
VIGNET Pierre's avatar
VIGNET Pierre committed
189
190
191
        """
        interative build of xml tree for model saving
        """
192
193
194
195
196
        header = objectify.ElementMaker(
            annotate=False,
            namespace="http://cadbiom.genouest.org/",
            nsmap={None : "http://cadbiom.genouest.org/"}
        )
197
        xmodel = header.model(name=self.model_name)
VIGNET Pierre's avatar
VIGNET Pierre committed
198
        self.current_element = xmodel
199
200
201
202
203

        def create_xml_element(entity):
            """Create XML element and add it to root object"""
            # get node or transition properties
            properties = entity.accept(self)
204
            if properties[0] != 'CMacroNode':
205
                element = etree.Element(properties[0])
206
                if len(properties) > 1:
VIGNET Pierre's avatar
VIGNET Pierre committed
207
208
                    attrname = properties[1]
                    attr = properties[2]
209
210
                    attributes = element.attrib
                    # Set attributes and values (name, event, coords...)
VIGNET Pierre's avatar
VIGNET Pierre committed
211
                    for i in range(0, len(attrname)):
212
213
214
215
216
217
218
219
220
221
                        attributes[attrname[i]] = str(attr[i])
                # Add notes/text of element
                if entity.note:
                    element.text = entity.note
                # Attach element to the model
                xmodel.append(element)

        # nodes
        for snode in tnode.sub_nodes:
            create_xml_element(snode)
222

VIGNET Pierre's avatar
VIGNET Pierre committed
223
224
225
        # transitions
        for gtr in tnode.transitions:
            for trans in gtr:
226
                create_xml_element(trans)
227

VIGNET Pierre's avatar
VIGNET Pierre committed
228
229
230
231
232
        # constraints
        if len(tnode.model.constraints) > 0:
            const = etree.Element("constraints")
            const.text = tnode.model.constraints
            xmodel.append(const)
233

VIGNET Pierre's avatar
VIGNET Pierre committed
234
235
        self.xml = etree.tostring(xmodel, pretty_print=True)
#        print (etree.tostring(xmodel,pretty_print=True))
236
237


VIGNET Pierre's avatar
VIGNET Pierre committed
238
239
240
    def visit_ctransition(self, trans):
        """
        Generate xml representation of a transition
241
242
243
        """
        tag = "transition"
        attrname = ["name", "ori", "ext", "event",
VIGNET Pierre's avatar
VIGNET Pierre committed
244
245
246
247
248
249
250
                    "condition", "action", "fact_ids"]
        attr = [trans.name, trans.ori.name, trans.ext.name, trans.event,
                trans.condition, trans.action, trans.fact_ids]

        fact_ids = trans.fact_ids
        for fact in fact_ids:
            self.fact_list.append(fact)
251
252
253

        return [tag, attrname, attr]

VIGNET Pierre's avatar
VIGNET Pierre committed
254
    def return_xml(self):
255
256
257
        """Return the model as xml string.

        .. note:: Used when the model is saved in a .bcx file.
VIGNET Pierre's avatar
VIGNET Pierre committed
258
        """
259
260
        return self.xml

VIGNET Pierre's avatar
VIGNET Pierre committed
261
262
    def get_fact_ids(self):
        """
263
        get litterature references
VIGNET Pierre's avatar
VIGNET Pierre committed
264
265
266
267
268
269
270
271
        """
        model_fact = []
        for i in self.fact_list:
            if i in model_fact:
                continue
            else :
                model_fact.append(i)
        return model_fact
272
273


VIGNET Pierre's avatar
VIGNET Pierre committed
274
275
276
277
class MakeHandler(ContentHandler):
    """
    make a handler for the parser
    """
278

VIGNET Pierre's avatar
VIGNET Pierre committed
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    def __init__(self, model = None):
        self.pile_node = []
        self.top_pile = None
        self.pile_dict = []
        self.node_dict = dict()
        self.in_constraints = False
        self.in_transition = False
        self.constraints = ""
        self.model = model

    def startElement(self, name, att):
        if name == "model":
            if not self.model:
                self.model = ChartModel(att.get('name',''))
            root = self.model.get_root()
            self.pile_node.append(root)
            self.top_pile = root
            new_dict = dict()
            self.pile_dict.append(new_dict)
            self.node_dict = new_dict

        elif name == "CStartNode":
            name = att.get('name', '').encode('ascii')
            xloc = float(att.get('xloc', ''))
            yloc = float(att.get('yloc', ''))
            node = self.top_pile.add_start_node(xloc, yloc, name)
            self.node_dict[name] = node
306

VIGNET Pierre's avatar
VIGNET Pierre committed
307
308
309
310
311
312
        elif name == "CTrapNode":
            name = att.get('name', '').encode('ascii')
            xloc = float(att.get('xloc', '') )
            yloc = float(att.get('yloc', '') )
            node = self.top_pile.add_trap_node(xloc, yloc, name)
            self.node_dict[name] = node
313

VIGNET Pierre's avatar
VIGNET Pierre committed
314
315
316
317
318
319
        elif name == "CSimpleNode":
            name = att.get('name', '').encode('ascii')
            xloc = float(att.get('xloc', '') )
            yloc = float(att.get('yloc', '') )
            node = self.top_pile.add_simple_node(name, xloc, yloc)
            self.node_dict[name] = node
320

VIGNET Pierre's avatar
VIGNET Pierre committed
321
322
323
324
325
326
        elif name == "CPermNode":
            name = att.get('name', '').encode('ascii')
            xloc = float(att.get('xloc', '') )
            yloc = float(att.get('yloc', '') )
            node = self.top_pile.add_perm_node(name, xloc, yloc)
            self.node_dict[name] = node
327

VIGNET Pierre's avatar
VIGNET Pierre committed
328
329
330
331
332
        elif name == "CInputNode":
            name = att.get('name', '').encode('ascii')
            xloc = float(att.get('xloc', '') )
            yloc = float(att.get('yloc', '') )
            node = self.top_pile.add_input_node(name, xloc, yloc)
333
334
335
            self.node_dict[name] = node


VIGNET Pierre's avatar
VIGNET Pierre committed
336
337
338
339
340
341
        elif name == "CMacroNode":
            name = att.get('name', '').encode('ascii')
            xloc = float(att.get('xloc', '') )
            yloc = float(att.get('yloc', '') )
            wloc = float(att.get('wloc', '') )
            hloc = float(att.get('hloc', '') )
342
343

            node = self.top_pile.add_macro_subnode(name, xloc, yloc,
VIGNET Pierre's avatar
VIGNET Pierre committed
344
345
346
347
348
349
350
351
352
                                                   wloc, hloc)
            self.node_dict[name] = node

            self.pile_node.append(node)
            # symbol table put on stack to preserve macro scope for inputs
            new_node_dict = dict()
            self.pile_dict.append(new_node_dict)
            self.top_pile = node
            self.node_dict = new_node_dict
353
354

        elif name == "transition":
VIGNET Pierre's avatar
VIGNET Pierre committed
355
356
357
358
359
360
361
            name = att.get('name', '').encode('ascii')
            ori = att.get('ori', '')
            ext = att.get('ext', '')
            event = att.get('event', '')
            condition = att.get('condition', '')
            action = att.get('action', '')
            fact_ids_text = att.get('fact_ids','')[1:-1]
362
            if len(fact_ids_text) > 0:
VIGNET Pierre's avatar
VIGNET Pierre committed
363
364
365
366
367
368
                fact_ids_split = fact_ids_text.split(',')
                fact_ids = []
                for fid in fact_ids_split:
                    fact_ids.append(int(fid))
            else:
                fact_ids = []
369

VIGNET Pierre's avatar
VIGNET Pierre committed
370
371
372
373
374
375
376
            try:
                node_ori = self.node_dict[ori]
                node_ext = self.node_dict[ext]
            except Exception, exc:
                print 'Bad xml file - missing nodes', ori, ' ', ext
                print self.node_dict
                print exc
377

VIGNET Pierre's avatar
VIGNET Pierre committed
378
379
380
381
382
383
384
            self.transition = self.top_pile.add_transition(node_ori, node_ext)
            # the transition may not be created (origin = ext for example)
            if self.transition:
                self.transition.set_event(event)
                self.transition.set_condition(condition)
                self.transition.set_action(action)
                self.transition.fact_ids = fact_ids
385

VIGNET Pierre's avatar
VIGNET Pierre committed
386
387
                self.in_transition = True
                self.transition.note = ""
388

VIGNET Pierre's avatar
VIGNET Pierre committed
389
390
391
        elif name == 'constraints':
            self.in_constraints = True
            self.constraints = ""
392

VIGNET Pierre's avatar
VIGNET Pierre committed
393
394
        else: # ignore
            pass
395

VIGNET Pierre's avatar
VIGNET Pierre committed
396
397
398
399
400
401
402
403
    def characters(self, chr):
        """
        xxx
        """
        if self.in_constraints:
            self.constraints = self.constraints + chr
        if self.in_transition:
            self.transition.note = self.transition.note + chr
404

VIGNET Pierre's avatar
VIGNET Pierre committed
405
406
407
408
409
    def endElement (self, name):
        """
        xxx
        """
        if name == "model":
410
411
            return

VIGNET Pierre's avatar
VIGNET Pierre committed
412
413
414
415
416
417
418
419
420
421
422
        elif name == "CMacroNode":
            #self.top_pile = self.pile_node.pop()
            self.pile_node.remove(self.top_pile)
            self.top_pile = self.pile_node[-1]
            #self.node_dict = self.pile_dict.pop()
            self.pile_dict.remove(self.node_dict)
            self.node_dict = self.pile_dict[-1]

        elif name == 'constraints':
            self.in_constraints = False
            self.model.constraints = self.constraints + '\n'
423

VIGNET Pierre's avatar
VIGNET Pierre committed
424
425
426
427
428
429
        elif name == 'transition':
            self.in_transition = False
#            if self.transition:
#                self.transition.note = self.transition.note + '\n'
        else:
            pass
430
431
432


class MakeModelFromXmlFile:
VIGNET Pierre's avatar
VIGNET Pierre committed
433
434
435
436
437
438
    """
    parse a xml file
    """
    def __init__(self, xml_file, model = None):
        self.model = model
        self.handler = MakeHandler(model=self.model)
439
        self.parser = make_parser()
VIGNET Pierre's avatar
VIGNET Pierre committed
440
        self.parser.setContentHandler(self.handler)
441
442
443

        try:
            self.parser.parse(xml_file)
VIGNET Pierre's avatar
VIGNET Pierre committed
444
445
446
        except Exception, exc:
            print 'ERROR while xml parsing'
            print exc
447
448


VIGNET Pierre's avatar
VIGNET Pierre committed
449
450
451
452
453
    def get_model(self):
        """
        As it says
        """
        return self.handler.model
454
455

class MakeModelFromXmlString:
VIGNET Pierre's avatar
VIGNET Pierre committed
456
457
458
459
460
461
    """
    parse a xml description as string
    """
    def __init__(self, xml_string):
        self.model = None
        self.handler = MakeHandler()
462
        self.parser = make_parser()
VIGNET Pierre's avatar
VIGNET Pierre committed
463
        self.parser.setContentHandler(self.handler)
464

VIGNET Pierre's avatar
VIGNET Pierre committed
465
        try:
466
            PS(xml_string, self.handler)
VIGNET Pierre's avatar
VIGNET Pierre committed
467
468
469
        except Exception, exc:
            print 'ERROR while xml parsing'
            print exc
470
471


VIGNET Pierre's avatar
VIGNET Pierre committed
472
473
474
475
476
    def get_model(self):
        """
        As it says
        """
        return self.handler.model
477
478


VIGNET Pierre's avatar
VIGNET Pierre committed
479
480
481
482
483
484
class XmlException(Exception):
    """
    For exception identification
    """
    def __init__(self, mess):
        self.message = mess
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506