Salome HOME
updated copyright message
[modules/yacs.git] / src / salomeloader / salomeloader.py
1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2006-2023  CEA, EDF
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20
21 """This module is used to parse a supervision graph Salome (XML) and convert it into
22    YACS calculation schema
23
24    This parsing is done with SalomeLoader class and its method load.
25 """
26
27 import sys,os
28 try:
29   from xml.etree import cElementTree as ElementTree
30 except ImportError:
31   from xml.etree import ElementTree
32
33 #from sets import Set
34 Set=set
35 import graph
36 import pilot
37 import SALOMERuntime
38
39 class UnknownKind(Exception):pass
40
41 #global variables
42 debug=0
43 typeMap={}
44 objref=None
45 _containers={}
46 currentProc=None
47
48 def typeName(name):
49   """Replace :: in type name by /"""
50   return "/".join(name.split("::"))
51
52 streamTypes={
53              '0':"Unknown",
54              '1':"CALCIUM_integer",
55              '3':"CALCIUM_real",
56             }
57
58
59 class SalomeLoader:
60   """This class parses a Salome graph (version 3.2.x) and converts it into YACS schema.
61
62      The loadxml method parses xml file and returns a SalomeProc object
63
64      The load method calls the loadxml method and creates a YACS object of class Proc
65   """
66   def loadxml(self,filename):
67     """
68        Parse a XML file from Salome SUPERV and return a list of SalomeProc objects.
69     """
70     tree = ElementTree.ElementTree(file=filename)
71     root = tree.getroot()
72     if debug:print("root.tag:",root.tag,root)
73
74     procs=[]
75     if root.tag == "dataflow":
76       #only one dataflow
77       dataflow=root
78       if debug:print(dataflow)
79       proc=SalomeProc(dataflow)
80       procs.append(proc)
81     else:
82       #one or more dataflows. The graph contains macros.
83       #All macros are defined at the same level in the XML file.
84       for dataflow in root.findall("dataflow"):
85         if debug:print(dataflow)
86         proc=SalomeProc(dataflow)
87         if debug:print("dataflow name:",proc.name)
88         procs.append(proc)
89     return procs
90
91   def load(self,filename):
92     """Parse a SUPERV XML file (method loadxml) and return a YACS Proc object.
93     """
94     global typeMap,_containers,objref,currentProc
95     typeMap.clear()
96     objref=None
97     _containers.clear()
98     currentProc=None
99
100     procs=self.loadxml(filename)
101     #Split the master proc from the possible macros.
102     proc=procs.pop(0)
103     #proc.display()
104
105     #Put macros in macro_dict
106     macro_dict={}
107     for p in procs:
108       if debug:print("proc_name:",p.name,"coupled_node:",p.coupled_node)
109       macro_dict[p.name]=p
110
111     if debug:print(filename)
112     yacsproc=ProcNode(proc,macro_dict,filename)
113     return yacsproc.createNode()
114
115 class Container:
116   """Class that defines a Salome Container"""
117   def __init__(self,mach,name):
118     self.mach=mach
119     self.name=name
120     self.components={}
121   def getName(self):
122     return self.mach+"/"+self.name
123
124 def getContainer(name):
125   if not name:
126     name="localhost/FactoryServer"
127   elif "/" not in name:
128     #no machine name: use localhost
129     name="localhost/"+name
130   return _containers.get(name)
131
132 def addContainer(name):
133   if not name:
134     mach="localhost"
135     name="FactoryServer"
136   elif "/" not in name:
137     #no machine name: use localhost for mach
138     mach="localhost"
139   else:
140     mach,name=name.split("/")
141   c=Container(mach,name)
142   _containers[mach+"/"+name]=c
143   return c
144
145 class Service:
146     """Class for Service properties"""
147 class Parameter:
148     """Class for Parameter properties"""
149 class Link:
150     """Class for Link properties"""
151 class Data:
152     """Class for Data properties"""
153
154 class Node:
155     """Base class for all nodes """
156     label="Node: "
157     def __init__(self):
158       self.links=[]    # list to store inputs as links
159       # a link has two attributes : from_node, the starting node
160       # to_node, the end node
161       self.datas=[]
162       self.inStreamLinks=[] #list of dataStream links connected to this node (in)
163       self.outStreamLinks=[] #list of dataStream links connected to this node (out)
164       self.node=None
165     def createNode(self):
166       raise NotImplementedError
167     def getInputPort(self,p):
168       return self.node.getInputPort(".".join(p.split("__")))
169     def getOutputPort(self,p):
170       if not self.node:
171         self.createNode()
172       return self.node.getOutputPort(".".join(p.split("__")))
173     def getInputDataStreamPort(self,p):
174       return self.node.getInputDataStreamPort(p)
175     def getOutputDataStreamPort(self,p):
176       return self.node.getOutputDataStreamPort(p)
177
178     def initPort(self,l):
179       if l.type == 7:
180         #double (CORBA::tk_double)
181         try:
182           self.getInputPort(l.tonodeparam).edInitDbl(l.value)
183         except:
184           reason="Problem in initialization, not expected type (double): %s %s" % (l.tonodeparam,l.value)
185           currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
186       elif l.type == 3:
187         #int (CORBA::tk_long)
188         try:
189           self.getInputPort(l.tonodeparam).edInitInt(l.value)
190         except:
191           reason="Problem in initialization, not expected type (int): %s %s" % (l.tonodeparam,l.value)
192           currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
193       elif l.type == 14:
194         #objref (CORBA::tk_objref)
195         try:
196           self.getInputPort(l.tonodeparam).edInitString(l.value)
197         except:
198           reason="Problem in initialization, not expected type (objref): %s %s" % (l.tonodeparam,l.value)
199           currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
200       elif l.type == 18:
201         #string (CORBA::tk_string)
202         try:
203           self.getInputPort(l.tonodeparam).edInitString(l.value)
204         except:
205           reason="Problem in initialization, not expected type (string): %s %s" % (l.tonodeparam,l.value)
206           currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
207       else:
208         reason="Problem in initialization, not expected type (%s): %s %s" % (l.type,l.tonodeparam,l.value)
209         currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
210
211 class InlineNode(Node):
212     """Inline Node salome : python function in self.codes[0]"""
213     def __init__(self):
214       Node.__init__(self)
215       self.codes=[]
216     def createNode(self):
217       r = pilot.getRuntime()
218       if self.fnames[0] == "?":
219         n=r.createScriptNode("",self.name)
220       else:
221         n=r.createFuncNode("",self.name)
222         n.setFname(self.fnames[0])
223         n.setScript(self.codes[0])
224       self.node=n
225       for para in self.service.inParameters:
226         if para.type not in typeMap:
227           #create the missing type and add it in type map
228           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
229         if para.type not in currentProc.typeMap:
230           currentProc.typeMap[para.type]=typeMap[para.type]
231         n.edAddInputPort(para.name,typeMap[para.type])
232       for para in self.service.outParameters:
233         if para.type not in typeMap:
234           #create the missing type and add it in type map
235           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
236         if para.type not in currentProc.typeMap:
237           currentProc.typeMap[para.type]=typeMap[para.type]
238         n.edAddOutputPort(para.name,typeMap[para.type])
239
240       for d in self.datas:
241         self.initPort(d)
242
243       return n
244
245 class ComputeNode(Node):
246     """Compute Node Salome execute a component service"""
247     def createNode(self):
248       if self.node:
249         return self.node
250
251       r = pilot.getRuntime()
252       if self.sComponent in self.container.components:
253         #a node for this component already exists
254         compo_node=self.container.components[self.sComponent]
255         #It's a node associated with another node of the same component instance
256         #It is not sure that the yacs node has been created ????
257         master_node=compo_node.createNode()
258         n=master_node.createNode(self.name)
259       else:
260         #there is no node for this component. This node is first
261         self.container.components[self.sComponent]=self
262         #There is no component instance for this node
263         n=r.createCompoNode("",self.name)
264         n.setRef(self.sComponent)
265
266       n.setMethod(self.service.name)
267       self.node=n
268       #set the container for the node
269       if self.container:
270         n.getComponent().setContainer(currentProc.containerMap[self.container.getName()])
271
272       #add  dataflow ports in out 
273       for para in self.service.inParameters:
274         if para.type not in typeMap:
275           #Create the missing type and adds it into types table
276           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
277         if para.type not in currentProc.typeMap:
278           currentProc.typeMap[para.type]=typeMap[para.type]
279         n.edAddInputPort(para.name,typeMap[para.type])
280       for para in self.service.outParameters:
281         if para.type not in typeMap:
282           #Create the missing type and adds it into types table
283           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
284         if para.type not in currentProc.typeMap:
285           currentProc.typeMap[para.type]=typeMap[para.type]
286         pout=n.edAddOutputPort(para.name,typeMap[para.type])
287
288       #add datastream ports in and out
289       for para in self.inStreams:
290         if debug:print(para.name,para.type,para.dependency,para.schema, para.interpolation, end=' ')
291         if debug:print(para.extrapolation)
292         pin=n.edAddInputDataStreamPort(para.name,typeMap[streamTypes[para.type]])
293       for para in self.outStreams:
294         if debug:print(para.name,para.type,para.dependency,para.values)
295         pout=n.edAddOutputDataStreamPort(para.name,typeMap[streamTypes[para.type]])
296
297       for d in self.datas:
298         self.initPort(d)
299
300       return n
301
302 class ComposedNode(Node):
303   """Composed Node Salome (base class)"""
304
305   def reduceLoop(self):
306     """Transform a Salome graph with loops on one level
307        in a hierarchical graph.
308
309        The initial graph is in self.G. It is transformed in place.
310     """
311     G=self.G
312     if debug:graph.display(G)
313     #invert the graph
314     I=graph.invert(G)
315     #graph.display(I)
316
317     #Get all loops and their internal nodes
318     loops={}
319     for n in G:
320       if n.kind == 4:
321         #Beginning of loop
322         loops[n]=graph.reachable(G,n)&graph.reachable(I,n.endloop)
323         n.inner_nodes=loops[n]
324         n.G=graph.InducedSubgraph(loops[n],G)
325
326     if debug:print("all loops")
327     if debug:print(loops)
328
329     #Get most external loops 
330     outer_loops=list(loops.keys())
331     for l in loops:
332       for ll in outer_loops:
333         if loops[l] < loops[ll]:
334           #internal loop
335           outer_loops.remove(l)
336           ll.set_inner(l)
337           break
338
339     #In the end all remaining loops in outer_loops are the most external
340     if debug:print(outer_loops)
341
342     #We remove all internal nodes of most external loops 
343     for l in outer_loops:
344       #Remove internal nodes
345       for n in loops[l]:
346         del G[n]
347       #Remove endloop node
348       suiv=G[l.endloop]
349       del G[l.endloop]
350       #Replace neighbours of loop by those of endloop
351       G[l]= suiv
352
353       #Try to transform incoming and outcoming links of endloop in incoming and 
354       #outcoming links of internal nodes. Probably not complete.
355       inputs={}
356       for link in l.endloop.links:
357         if debug:print(link.from_node,link.to_node,link.from_param,link.to_param)
358         inputs[link.to_param]=link.from_node,link.from_param
359
360       for s in suiv:
361         for link in s.links:
362           if link.from_node == l.endloop:
363             link.from_node,link.from_param=inputs[link.from_param]
364           if debug:print(link.from_node,link.to_node,link.from_param,link.to_param)
365
366       if debug:graph.display(G)
367
368       #Apply the reduction treatment to most external loops (recurse)
369       for l in outer_loops:
370         l.reduceLoop()
371
372   def connect_macros(self,macro_dict):
373     """This method connects the salome macros in macro_dict to the master YACS Proc.
374        
375     """
376     if debug:print("connect_macros",self.node,macro_dict)
377     for node in self.G:
378       if isinstance(node,MacroNode):
379         #node is a macro, connect its definition to self.
380         #p is the Salome macro (class SalomeProc)
381         #node is the Salome MacroNode that has the subgraph p
382         #node.node is the YACS Bloc equivalent to node
383         p=macro_dict[node.coupled_node]
384         bloc=node.node
385         if debug:print("macronode:",node.name,node.coupled_node,p)
386         #Create a hierarchical graph from the salome graph
387         G=p.create_graph()
388         node.G=G
389         for n in G:
390           #create an equivalent YACS node from each salome node
391           nod=n.createNode()
392           bloc.edAddChild(nod)
393
394         #Connect macros to node
395         node.connect_macros(macro_dict)
396
397         #add control links
398         for n in G:
399           for v in G[n]:
400             bloc.edAddCFLink(n.node,v.node)
401         #add dataflow links and initializations
402         for n in G:
403           #dataflow links
404           for l in n.links:
405             bloc.edAddLink(l.from_node.getOutputPort(l.from_param),
406                         l.to_node.getInputPort(l.to_param))
407           #datastream links
408           for l in n.outStreamLinks:
409             pout=l.from_node.getOutputDataStreamPort(l.from_param)
410             pin=l.to_node.getInputDataStreamPort(l.to_param)
411             bloc.edAddLink(pout,pin)
412           #initializations
413           for l in n.datas:
414             if l.type == 7:
415               #double (CORBA::tk_double)
416               try:
417                 n.getInputPort(l.tonodeparam).edInitDbl(l.value)
418               except:
419                 reason="Problem in initialization, not expected type (double): %s %s" % (l.tonodeparam,l.value)
420                 currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
421             elif l.type == 3:
422               #int (CORBA::tk_long)
423               try:
424                 n.getInputPort(l.tonodeparam).edInitInt(l.value)
425               except:
426                 reason="Problem in initialization, not expected type (int): %s %s" % (l.tonodeparam,l.value)
427                 currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
428             elif l.type == 14:
429               #objref (CORBA::tk_objref)
430               try:
431                 n.getInputPort(l.tonodeparam).edInitString(l.value)
432               except:
433                 reason="Problem in initialization, not expected type (objref): %s %s" % (l.tonodeparam,l.value)
434                 currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
435             elif l.type == 18:
436               #string (CORBA::tk_string)
437               try:
438                 n.getInputPort(l.tonodeparam).edInitString(l.value)
439               except:
440                 reason="Problem in initialization, not expected type (string): %s %s" % (l.tonodeparam,l.value)
441                 currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
442             else:
443               reason="Problem in initialization, not expected type (%s): %s %s" % (l.type,l.tonodeparam,l.value)
444               currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
445
446 class LoopNode(ComposedNode):
447     """Objet qui simule le comportement d'une boucle Salome."""
448     def __init__(self):
449       ComposedNode.__init__(self)
450       self.inner_loops=[]
451       #inner_nodes contains internal nodes as in Salome (on one level with endloop nodes)
452       self.inner_nodes=[]
453
454     def set_node(self,node):
455       self.node=node
456
457     def set_inner(self,loop):
458       for i in self.inner_loops:
459         if loop.inner_nodes < i.inner_nodes:
460           #the loop is contained in i
461           i.set_inner(loop)
462           break
463       self.inner_loops.append(loop)
464
465     def createNode(self):
466       """Create the equivalent YACS loop and store it in attribute node
467
468          A Salome loop has n input ports and output ports with exactly same names.
469          The head of loop has 3 functions : init, next, more which have almost same 
470          interface. init and next have same interface : on input, input loop parameters
471          on output, output loop parameters (same as input). more has one more output parameter
472          in first place. This parameter says if the loop must go on or not.
473          The endloop has a function with the same interface as next.
474
475          To transform this node, create a YACS Bloc. In this bloc put a node for the init function
476          and a While node. In the while put all internal nodes plus 2 nodes for the next and more 
477          functions.
478       """
479
480       r = pilot.getRuntime()
481       bloop=r.createBloc(self.name)
482
483       #init node
484       init=r.createFuncNode("","init")
485       #print self.codes[0]
486       init.setScript(self.codes[0])
487       init.setFname(self.fnames[0])
488       for para in self.service.inParameters:
489         if para.type not in typeMap:
490           #create the missing type and add it in type map
491           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
492         if para.type not in currentProc.typeMap:
493           currentProc.typeMap[para.type]=typeMap[para.type]
494         init.edAddInputPort(para.name,typeMap[para.type])
495       for para in self.service.outParameters:
496         if para.type not in typeMap:
497           #create the missing type and add it in type map
498           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
499         if para.type not in currentProc.typeMap:
500           currentProc.typeMap[para.type]=typeMap[para.type]
501         init.edAddOutputPort(para.name,typeMap[para.type])
502       bloop.edAddChild(init)
503       self.init=init
504
505       wh=r.createWhileLoop(self.name)
506       bloop.edAddChild(wh)
507       blnode=r.createBloc(self.name)
508       wh.edSetNode(blnode)
509       cport=wh.edGetConditionPort()
510       cport.edInitBool(True)
511
512       #next node
513       next=r.createFuncNode("","next")
514       #print self.codes[2]
515       next.setScript(self.codes[2])
516       next.setFname(self.fnames[2])
517       for para in self.service.inParameters:
518         if para.type not in typeMap:
519           #create the missing type and add it in type map
520           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
521         if para.type not in currentProc.typeMap:
522           currentProc.typeMap[para.type]=typeMap[para.type]
523         next.edAddInputPort(para.name,typeMap[para.type])
524       for para in self.service.outParameters:
525         if para.type not in typeMap:
526           #create the missing type and add it in type map
527           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
528         if para.type not in currentProc.typeMap:
529           currentProc.typeMap[para.type]=typeMap[para.type]
530         next.edAddOutputPort(para.name,typeMap[para.type])
531       blnode.edAddChild(next)
532       self.next=next
533
534       #more node
535       more=r.createFuncNode("","more")
536       #print self.codes[1]
537       more.setScript(self.codes[1])
538       more.setFname(self.fnames[1])
539       for para in self.service.inParameters:
540         if para.type not in typeMap:
541           #create the missing type and add it in type map
542           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
543         if para.type not in currentProc.typeMap:
544           currentProc.typeMap[para.type]=typeMap[para.type]
545         more.edAddInputPort(para.name,typeMap[para.type])
546       more.edAddOutputPort("DoLoop",typeMap["int"])
547       for para in self.service.outParameters:
548         if para.type not in typeMap:
549           #create the missing type and add it in type map
550           typeMap[para.type]= currentProc.createInterfaceTc("",para.type,[objref])
551         if para.type not in currentProc.typeMap:
552           currentProc.typeMap[para.type]=typeMap[para.type]
553         more.edAddOutputPort(para.name,typeMap[para.type])
554       blnode.edAddChild(more)
555       self.more=more
556
557       for para in self.service.outParameters:
558         bloop.edAddDFLink(init.getOutputPort(para.name),next.getInputPort(para.name))
559
560       for para in self.service.outParameters:
561         blnode.edAddDFLink(next.getOutputPort(para.name),more.getInputPort(para.name))
562
563       wh.edAddLink(more.getOutputPort("DoLoop"),wh.getInputPort("condition"))
564
565       for para in self.service.outParameters:
566         wh.edAddLink(more.getOutputPort(para.name),next.getInputPort(para.name))
567
568       self.node=bloop
569
570       for n in self.G:
571         node=n.createNode()
572         blnode.edAddChild(node)
573
574       for n in self.G:
575         for v in self.G[n]:
576           blnode.edAddCFLink(n.node,v.node)
577
578       for n in self.G:
579         for l in n.links:
580           try:
581             blnode.edAddDFLink(l.from_node.getOutputPort(l.from_param),
582                              l.to_node.getInputPort(l.to_param))
583           except:
584             reason="Error while connecting output port: "+l.from_param+" from node: "+l.from_node.name
585             reason=reason+" to input port: "+l.to_param+" from node: "+l.to_node.name
586             currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
587
588       return bloop
589
590     def getInputPort(self,p):
591       return self.init.getInputPort(p)
592
593     def getOutputPort(self,p):
594       return self.more.getOutputPort(p)
595
596 class Bloc(ComposedNode):
597     """ Composed node containing a set of connected nodes
598     """
599     label="Bloc: "
600     def __init__(self):
601       Node.__init__(self)
602       self.nodes=[]
603
604     def addLink(self,node1,node2):
605       if node1 not in self.nodes:self.nodes.append(node1)
606       if node2 not in self.nodes:self.nodes.append(node2)
607
608 class MacroNode(Bloc):
609   """Objet that represents a Salome Macro
610   """
611   def createNode(self):
612     """Create a YACS node (Bloc) equivalent to a Salome Macro """
613     r = pilot.getRuntime()
614     macro=r.createBloc(self.name)
615     self.node=macro
616     return macro
617
618 def is_loop(n):
619   """Return true if n is a head loop node"""
620   return isinstance(n,LoopNode)
621
622 class ProcNode(ComposedNode):
623   """Salome proc with its macros
624
625      The Salome proc is stored in attribute proc
626      The Salome macros are stored in attribute macro_dict ({})
627   """
628   def __init__(self,proc,macro_dict,filename):
629     ComposedNode.__init__(self)
630     self.proc=proc
631     self.macro_dict=macro_dict
632     self.filename=filename
633
634   def createNode(self):
635     """Create the YACS node (Proc) equivalent a Salome proc"""
636     global currentProc,objref
637     r = pilot.getRuntime()
638
639     #create_graph gives a hierarchical graph equivalent to the Salome proc
640     G=self.proc.create_graph()
641     self.G=G
642
643     #Create the YACS proc with its elements (types, nodes, containers)
644     p=r.createProc("pr")
645     self.node=p
646     currentProc=p
647     p.filename=self.filename
648     typeMap["double"]=p.typeMap["double"]
649     typeMap["float"]=p.typeMap["double"]
650     typeMap["int"]=p.typeMap["int"]
651     typeMap["short"]=p.typeMap["int"]
652     typeMap["long"]=p.typeMap["int"]
653     typeMap["string"]=p.typeMap["string"]
654     typeMap["char"]=p.typeMap["string"]
655     typeMap["boolean"]=p.typeMap["bool"]
656     typeMap["bool"]=p.typeMap["bool"]
657
658     objref=p.createInterfaceTc("IDL:omg.org/CORBA/Object:1.0","Object",[])
659     typeMap["objref"]=objref
660     typeMap["Unknown"]=p.createInterfaceTc("","Unknown",[])
661     typeMap["GEOM_Object"]=p.createInterfaceTc("IDL:GEOM/GEOM_Object:1.0","GEOM_Object",[objref])
662     typeMap["GEOM_Shape"]=typeMap["GEOM_Object"]
663
664     typeMap["CALCIUM_integer"]=p.createInterfaceTc("IDL:Ports/Calcium_Ports/Calcium_Integer_Port:1.0","CALCIUM_integer",[])
665     typeMap["CALCIUM_real"]=p.createInterfaceTc("IDL:Ports/Calcium_Ports/Calcium_Real_Port:1.0","CALCIUM_real",[])
666     typeMap["CALCIUM_double"]=p.createInterfaceTc("IDL:Ports/Calcium_Ports/Calcium_Double_Port:1.0","CALCIUM_double",[])
667     typeMap["CALCIUM_string"]=p.createInterfaceTc("IDL:Ports/Calcium_Ports/Calcium_String_Port:1.0","CALCIUM_string",[])
668     typeMap["CALCIUM_boolean"]=p.createInterfaceTc("IDL:Ports/Calcium_Ports/Calcium_Logical_Port:1.0","CALCIUM_boolean",[])
669
670     typeMap["SuperVisionTest::Adder"]=p.createInterfaceTc("","SuperVisionTest/Adder",[objref])
671     typeMap["Adder"]=typeMap["SuperVisionTest::Adder"]
672
673     currentProc.typeMap["Object"]=typeMap["objref"]
674     currentProc.typeMap["Unknown"]=typeMap["Unknown"]
675     currentProc.typeMap["GEOM_Object"]=typeMap["GEOM_Object"]
676     currentProc.typeMap["GEOM_Shape"]=typeMap["GEOM_Shape"]
677     currentProc.typeMap["CALCIUM_integer"]=typeMap["CALCIUM_integer"]
678     currentProc.typeMap["CALCIUM_real"]=typeMap["CALCIUM_real"]
679
680     #create all containers
681     for name,container in list(_containers.items()):
682       cont=r.createContainer()
683       cont.setName(name)
684       cont.setProperty("hostname",container.mach)
685       cont.setProperty("container_name",container.name)
686       currentProc.containerMap[name]=cont
687
688     for n in G:
689       #each node in G creates an equivalent YACS node.
690       node=n.createNode()
691       p.edAddChild(node)
692
693     #Connect Salome macros to nodes of proc p.
694     self.connect_macros(self.macro_dict)
695
696     #add control links
697     for n in G:
698       for v in G[n]:
699         p.edAddCFLink(n.node,v.node)
700
701     #add dataflow links and initializations
702     for n in G:
703       #dataflow links
704       for l in n.links:
705         try:
706           p.edAddLink(l.from_node.getOutputPort(l.from_param),
707                     l.to_node.getInputPort(l.to_param))
708         except:
709           reason="Error while connecting output port: "+l.from_param+" from node: "+l.from_node.name
710           reason=reason+" to input port: "+l.to_param+" from node: "+l.to_node.name
711           currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
712
713       #datastream links
714       for l in n.outStreamLinks:
715         pout=l.from_node.getOutputDataStreamPort(l.from_param)
716         pin=l.to_node.getInputDataStreamPort(l.to_param)
717         p.edAddLink(pout,pin)
718       #initializations
719       for l in n.datas:
720         if l.type == 7:
721           #double (CORBA::tk_double)
722           try:
723             n.getInputPort(l.tonodeparam).edInitDbl(l.value)
724           except:
725             reason="Problem in initialization, not expected type (double): %s %s" % (l.tonodeparam,l.value)
726             currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
727         elif l.type == 3:
728           #int (CORBA::tk_long)
729           port=n.getInputPort(l.tonodeparam)
730           try:
731             port.edInitInt(l.value)
732           except:
733             reason="Problem in initialization, not expected type (int): %s %s" % (l.tonodeparam,l.value)
734             currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
735         elif l.type == 14:
736           #objref (CORBA::tk_objref)
737           try:
738             n.getInputPort(l.tonodeparam).edInitString(l.value)
739           except:
740             reason="Problem in initialization, not expected type (objref): %s %s" % (l.tonodeparam,l.value)
741             currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
742         elif l.type == 18:
743           #string (CORBA::tk_string)
744           try:
745             n.getInputPort(l.tonodeparam).edInitString(l.value)
746           except:
747             reason="Problem in initialization, not expected type (string): %s %s" % (l.tonodeparam,l.value)
748             currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
749         else:
750           reason="Problem in initialization, not expected type (%s): %s %s" % (l.type,l.tonodeparam,l.value)
751           currentProc.getLogger("parser").error(reason,currentProc.filename,-1)
752
753     return p
754
755
756 class SalomeProc(ComposedNode):
757     """Salome proc with all its dataflow, datastream and control links
758        The object is built by parsing an XML file.
759     """
760     def __init__(self,dataflow):
761         self.name="name"
762         self.parse(dataflow)
763         #self.links : list of dataflow links (Link objects)
764         #self.nodes : list of graph nodes
765         #self.node_dict : nodes dict ({name:node})
766         #self.datas : list of graph datas 
767         #each node has 2 lists of datastream links (inStreams, outStreams)
768
769     def parse(self,dataflow):
770         if debug:print("All XML nodes")
771         for node in dataflow:
772             if debug:print(node.tag,node)
773
774         #Parse dataflow info-list
775         self.dataflow_info=self.parseService(dataflow.find("info-list/node/service"))
776         if debug:print(self.dataflow_info)
777         if debug:print(self.dataflow_info.inParameters)
778         if debug:print(self.dataflow_info.outParameters)
779         if debug:
780             for para in self.dataflow_info.inParameters:
781                 print("inParam:",para.name,para.name.split("__",1))
782
783         self.name=dataflow.findtext("info-list/node/node-name")
784         self.coupled_node=dataflow.findtext("info-list/node/coupled-node")
785
786         if debug:print("All XML nodes dataflow/node-list")
787         nodes=[]
788         node_dict={}
789         #Parse all nodes
790         for n in dataflow.findall('node-list/node'):
791             #n is a node-list node
792             kind=n.findtext("kind")
793             comp=n.findtext("component-name")
794             name=n.findtext("node-name")
795             coupled_node=n.findtext("coupled-node")
796             interface=n.findtext("interface-name")
797             container=n.findtext("container")
798
799             #kind=1 : dataflow ?
800             #kind=2 : ?
801             #kind=9 : datastream graph ?
802             #kind=6 : ??
803             #kind=8 : ??
804
805             if kind == "0":
806               #It's a service
807               node=ComputeNode()
808               node.kind=0
809               node.sComponent = comp
810               node.interface=interface
811               node.container= getContainer(container)
812               if not node.container:
813                 node.container=addContainer(container)
814               if debug:print("\tcontainer",node.container)
815
816             elif kind == "3":
817               #It's a python function
818               node=InlineNode()
819               node.kind=3
820               codes=[]
821               fnames=[]
822               for pyfunc in n.findall("PyFunction-list/PyFunction"):
823                 fnames.append(pyfunc.findtext("FuncName"))
824                 codes.append(self.parsePyFunction(pyfunc))
825               node.fnames=fnames
826               node.codes=codes
827
828             elif kind == "4":
829               #It's a loop : make a LoopNode
830               #python functions (next, more, init) are found in codes
831               node=LoopNode()
832               node.kind=4
833               codes=[]
834               fnames=[]
835               for pyfunc in n.findall("PyFunction-list/PyFunction"):
836                 fnames.append(pyfunc.findtext("FuncName"))
837                 codes.append(self.parsePyFunction(pyfunc))
838               node.fnames=fnames
839               node.codes=codes
840
841             elif kind == "5":
842               #End of loop : make an InlineNode
843               node=InlineNode()
844               node.kind=5
845               codes=[]
846               fnames=[]
847               for pyfunc in n.findall("PyFunction-list/PyFunction"):
848                 fnames.append(pyfunc.findtext("FuncName"))
849                 codes.append(self.parsePyFunction(pyfunc))
850               node.fnames=fnames
851               node.codes=codes
852
853             elif kind == "10":
854               # It's a Macro node : make a MacroNode
855               node=MacroNode()
856               node.kind=10
857             else:
858               raise UnknownKind(kind)
859
860             node.name=name
861             node.service=None
862             node.coupled_node=coupled_node
863             #Put nodes in a dict to ease search
864             node_dict[node.name]=node
865             if debug:print("\tnode-name",node.name)
866             if debug:print("\tkind",node.kind,node.__class__.__name__)
867
868             s=n.find("service")
869             if s:
870                 node.service=self.parseService(s)
871
872
873             #Parse datastream ports
874             if debug:print("DataStream ports")
875             inStreams=[]
876             for indata in n.findall("DataStream-list/inParameter"):
877                 inStreams.append(self.parseInData(indata))
878             node.inStreams=inStreams
879             outStreams=[]
880             outStreams_dict={}
881             for outdata in n.findall("DataStream-list/outParameter"):
882                 p=self.parseOutData(outdata)
883                 outStreams.append(p)
884                 outStreams_dict[p.name]=p
885             node.outStreams=outStreams
886             node.outStreams_dict=outStreams_dict
887             if debug:print("\t++++++++++++++++++++++++++++++++++++++++++++")
888             nodes.append(node)
889
890         self.nodes=nodes
891         self.node_dict=node_dict
892         #Nodes parsing is finished.
893         #Parse dataflow and datastream links.
894         """
895         <link>
896         <fromnode-name>Node_A_1</fromnode-name>
897         <fromserviceparameter-name>a_1</fromserviceparameter-name>
898         <tonode-name>Node_B_1</tonode-name>
899         <toserviceparameter-name>b_1</toserviceparameter-name>
900         <coord-list/>
901         </link>
902         """
903         if debug:print("All XML nodes dataflow/link-list")
904         links=[]
905         if debug:print("\t++++++++++++++++++++++++++++++++++++++++++++")
906         for link in dataflow.findall('link-list/link'):
907             l=Link()
908             l.from_name=link.findtext("fromnode-name")
909             l.to_name=link.findtext("tonode-name")
910             l.from_param=link.findtext("fromserviceparameter-name")
911             l.to_param=link.findtext("toserviceparameter-name")
912             links.append(l)
913             if debug:print("\tfromnode-name",l.from_name)
914             if debug:print("\tfromserviceparameter-name",l.from_param)
915             if debug:print("\ttonode-name",l.to_name)
916             if debug:print("\ttoserviceparameter-name",l.to_param)
917             if debug:print("\t++++++++++++++++++++++++++++++++++++++++++++")
918
919         self.links=links
920         if debug:print("All XML nodes dataflow/data-list")
921         datas=[]
922         for data in dataflow.findall('data-list/data'):
923             d=self.parseData(data)
924             datas.append(d)
925             if debug:print("\ttonode-name",d.tonode)
926             if debug:print("\ttoserviceparameter-name",d.tonodeparam)
927             if debug:print("\tparameter-value",d.value)
928             if debug:print("\tparameter-type",d.type)
929             if debug:print("\t++++++++++++++++++++++++++++++++++++++++++++")
930
931         self.datas=datas
932
933     def parseService(self,s):
934         service=Service()
935         service.name=s.findtext("service-name")
936         if debug:print("\tservice-name",service.name)
937
938         inParameters=[]
939         for inParam in s.findall("inParameter-list/inParameter"):
940             p=Parameter()
941             p.name=inParam.findtext("inParameter-name")
942             p.type=typeName(inParam.findtext("inParameter-type"))
943             if debug:print("\tinParameter-name",p.name)
944             if debug:print("\tinParameter-type",p.type)
945             inParameters.append(p)
946         service.inParameters=inParameters
947         if debug:print("\t++++++++++++++++++++++++++++++++++++++++++++")
948
949         outParameters=[]
950         for outParam in s.findall("outParameter-list/outParameter"):
951             p=Parameter()
952             p.name=outParam.findtext("outParameter-name")
953             p.type=typeName(outParam.findtext("outParameter-type"))
954             if debug:print("\toutParameter-name",p.name)
955             if debug:print("\toutParameter-type",p.type)
956             outParameters.append(p)
957         service.outParameters=outParameters
958         if debug:print("\t++++++++++++++++++++++++++++++++++++++++++++")
959         return service
960
961     def parseData(self,d):
962         da=Data()
963         da.tonode=d.findtext("tonode-name")
964         da.tonodeparam=d.findtext("toserviceparameter-name")
965         da.value=d.findtext("data-value/value")
966         da.type=eval(d.findtext("data-value/value-type"))
967         if da.type < 9:
968             da.value=eval(da.value)
969         return da
970
971     def parsePyFunction(self,pyfunc):
972         if debug:print(pyfunc.tag,":",pyfunc)
973         if debug:print("\tFuncName",pyfunc.findtext("FuncName"))
974         text=""
975         for cdata in pyfunc.findall("PyFunc"):
976             if text:text=text+'\n'
977             if cdata.text != '?':
978               text=text+ cdata.text
979         return text
980
981     """<inParameter-type>1</inParameter-type>
982     <inParameter-name>istream</inParameter-name>
983     <inParameter-dependency>2</inParameter-dependency>
984     <inParameter-schema>0</inParameter-schema>
985     <inParameter-interpolation>0</inParameter-interpolation>
986     <inParameter-extrapolation>0</inParameter-extrapolation>
987     </inParameter>
988     <outParameter>
989     <outParameter-type>1</outParameter-type>
990     <outParameter-name>ostream</outParameter-name>
991     <outParameter-dependency>2</outParameter-dependency>
992     <outParameter-values>0</outParameter-values>
993     </outParameter>
994     """
995
996     def parseInData(self,d):
997         if debug:print(d.tag,":",d)
998         p=Parameter()
999         p.name=d.findtext("inParameter-name")
1000         p.type=typeName(d.findtext("inParameter-type"))
1001         p.dependency=d.findtext("inParameter-dependency")
1002         p.schema=d.findtext("inParameter-schema")
1003         p.interpolation=d.findtext("inParameter-interpolation")
1004         p.extrapolation=d.findtext("inParameter-extrapolation")
1005         if debug:print("\tinParameter-name",p.name)
1006         return p
1007
1008     def parseOutData(self,d):
1009         if debug:print(d.tag,":",d)
1010         p=Parameter()
1011         p.name=d.findtext("outParameter-name")
1012         p.type=typeName(d.findtext("outParameter-type"))
1013         p.dependency=d.findtext("outParameter-dependency")
1014         p.values=d.findtext("outParameter-values")
1015         if debug:print("\toutParameter-name",p.name)
1016         return p
1017
1018     def create_graph(self):
1019       #a graph is a dict {node:neighbours}
1020       #neighbours is a Set of neighbour nodes (of course)
1021       #for v in graph (python >= 2.3): iterate through graph nodes 
1022       #for v in graph[node] iterate through node neighbours 
1023       G={}
1024       #create all nodes without neighbours
1025       for n in self.nodes:
1026         G[n]=Set()
1027
1028       #calculate neighbours with links
1029       for link in self.links:
1030         from_node=self.node_dict[link.from_name]
1031         if link.from_param == "Gate" or link.to_param == "Gate":
1032           #control link salome : add to_name node to neighbours
1033           if debug:print("add control link",link.from_name,link.to_name)
1034           G[self.node_dict[link.from_name]].add(self.node_dict[link.to_name])
1035
1036         elif link.from_param in from_node.outStreams_dict:
1037           # datastream link : 
1038           # 1- add link in link list
1039           # 2- add in link references on from_node and to_node
1040           if debug:print("add stream link",link.from_name,link.to_name)
1041           self.node_dict[link.to_name].inStreamLinks.append(link)
1042           self.node_dict[link.from_name].outStreamLinks.append(link)
1043           link.from_node=self.node_dict[link.from_name]
1044           link.to_node=self.node_dict[link.to_name]
1045
1046         else:
1047           # other salome link
1048           # if link from Loop node to EndOfLoop node, we ignore it
1049           # all others are kept
1050           from_node=self.node_dict[link.from_name]
1051           to_node=self.node_dict[link.to_name]
1052           if isinstance(to_node,LoopNode):
1053             # If it's the link from EndOfLoop to Loop , we ignore it
1054             if to_node.coupled_node == from_node.name:
1055               if debug:print("backlink loop:",from_node,to_node)
1056               #ignored
1057               continue
1058           if debug:print("add dataflow link",link.from_name,link.to_name)
1059           G[self.node_dict[link.from_name]].add(self.node_dict[link.to_name])
1060
1061           if link.from_param != "DoLoop" and link.to_param != "DoLoop":
1062             #Links on DoLoop are used by Salome supervisor. We ignore them.
1063             #Add in the link references on nodes (from_node and to_node)
1064             #Add this link into the list of links of to_node node.
1065             self.node_dict[link.to_name].links.append(link)
1066             link.from_node=self.node_dict[link.from_name]
1067             link.to_node=self.node_dict[link.to_name]
1068
1069           #In a Salome graph with loops, head node and end node are connected 
1070           #with 2 opposite links 
1071           #Store the endloop in attribute endloop of head node.
1072           if link.from_param == "DoLoop" and link.to_param == "DoLoop" \
1073              and is_loop(self.node_dict[link.from_name]) \
1074              and isinstance(self.node_dict[link.to_name],InlineNode):
1075             #Store the end loop inline node in attribute endloop
1076             #self.node_dict[link.to_name] is the end node of the head loop node self.node_dict[link.from_name]
1077             if debug:print("add loop",link.from_name,link.to_name)
1078             self.node_dict[link.from_name].endloop=self.node_dict[link.to_name]
1079             self.node_dict[link.to_name].loop=self.node_dict[link.from_name]
1080
1081       for data in self.datas:
1082         if debug:print("datas",data)
1083         self.node_dict[data.tonode].datas.append(data)
1084
1085       self.G=G
1086
1087       #Transform the graph in place
1088       # Transform one level loops in hierarchical graph
1089       self.reduceLoop()
1090
1091       #Return the hierarchical graph that can be transform into YACS objects.
1092       return G
1093
1094     def display(self,suivi="sync"):
1095         """Display Salome proc with graphviz (dot file)"""
1096         #to display : dot -Tpng salome.dot |display
1097         f=file("salome.dot", 'w')
1098         self.write_dot(f)
1099         f.close()
1100         cmd="dot -Tpng salome.dot |display" + (suivi == "async" and "&" or "")
1101         os.system(cmd)
1102
1103     def write_dot(self,stream):
1104         """Dump Salome proc into stream with dot format"""
1105         stream.write('digraph %s {\nnode [ style="filled" ]\n' % self.name)
1106         for node in self.nodes:
1107             label = "%s:%s"% (node.name,node.__class__.__name__)
1108             color='green'
1109             stream.write('   %s [fillcolor="%s" label=< %s >];\n' % (
1110                     id(node), color, label
1111                 ))
1112         for link in self.links:
1113             from_node=self.node_dict[link.from_name]
1114             to_node=self.node_dict[link.to_name]
1115             stream.write('   %s -> %s;\n' % (id(from_node), id(to_node)))
1116         stream.write("}\n")
1117
1118
1119 def main():
1120   import traceback
1121   usage ="""Usage: %s salomeFile convertedFile
1122     where salomeFile is the name of the input schema file (old Salome syntax)
1123     and convertedFile is the name of the output schema file (new YACS syntax)
1124     """
1125   try:
1126     salomeFile=sys.argv[1]
1127     convertedFile=sys.argv[2]
1128   except :
1129     print(usage%(sys.argv[0]))
1130     sys.exit(3)
1131
1132   SALOMERuntime.RuntimeSALOME_setRuntime()
1133   loader=SalomeLoader()
1134
1135   try:
1136     p= loader.load(salomeFile)
1137     s= pilot.SchemaSave(p)
1138     s.save(convertedFile)
1139   except:
1140     traceback.print_exc(file=sys.stdout)
1141     f=open(convertedFile,'w')
1142     f.write("<proc></proc>\n")
1143     sys.exit(2)
1144
1145   logger=p.getLogger("parser")
1146   if not logger.isEmpty():
1147     print(logger.getStr())
1148     sys.exit(1)
1149
1150 if __name__ == "__main__":
1151   main()