CMS 3D CMS Logo

/data/refman/pasoursint/CMSSW_5_2_9/src/FWCore/GuiBrowsers/python/DOTExport.py

Go to the documentation of this file.
00001 import sys
00002 import os
00003 import logging
00004 import random
00005 import subprocess
00006 import re
00007 import struct
00008 
00009 import FWCore.ParameterSet.SequenceTypes as sqt
00010 import FWCore.ParameterSet.Config as cms
00011 import FWCore.ParameterSet.Modules as mod
00012 import FWCore.ParameterSet.Types as typ
00013 import FWCore.ParameterSet.Mixins as mix
00014 
00015 from Vispa.Plugins.ConfigEditor.ConfigDataAccessor import ConfigDataAccessor
00016 from FWCore.GuiBrowsers.FileExportPlugin import FileExportPlugin
00017 
00018 class DotProducer(object):
00019   def __init__(self,data,options,shapes):
00020     self.data = data
00021     self.options = options
00022     self.shapes = shapes
00023     self.nodes={}
00024     #lists of starts, ends of paths for path-endpath and source-path connections
00025     self.pathstarts=[]
00026     self.pathends=[]
00027     self.endstarts=[]
00028     self.toplevel = self.getTopLevel()
00029     
00030   def getTopLevel(self):
00031       
00032     #build a dictionary of available top-level objects
00033     all_toplevel={}
00034     if self.data.process():
00035       for tlo in self.data.children(self.data.topLevelObjects()[0]):
00036         children = self.data.children(tlo)
00037         if children:
00038           all_toplevel[tlo._label]=children
00039     else:
00040       #case if we have only an anonymous (non-process) file
00041       #pick up (modules, sequences, paths)
00042       for tlo in self.data.topLevelObjects():
00043         if self.data.type(tlo)=='Sequence':
00044           if 'sequences' in all_toplevel:
00045             all_toplevel['sequences']+=[tlo]
00046           else:
00047             all_toplevel['sequences']=[tlo]
00048         if self.data.type(tlo)=='Path':
00049           if 'paths' in all_toplevel:
00050             all_toplevel['paths']+=[tlo]
00051           else:
00052             all_toplevel['paths']=[tlo]
00053         if self.data.type(tlo) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule'):
00054           self.nodes[self.data.label(tlo)]={'obj':tlo,'n_label':self.nodeLabel(tlo),'n_shape':self.shapes.get(self.data.type(tlo),'plaintext'),'inpath':True} 
00055         if self.options['services'] and self.data.type(tlo)=='Service':
00056           self.nodes[self.data.label(tlo)]={'obj':tlo,'n_label':self.nodeLabel(tlo),'n_shape':self.shapes.get(self.data.type(tlo),'plaintext'),'inpath':False}
00057         if self.options['es'] and self.data.type(tlo) in ('ESSource','ESProducer'):
00058           self.nodes[self.data.label(tlo)]={'obj':tlo,'n_label':self.nodeLabel(tlo),'n_shape':self.shapes.get(self.data.type(tlo),'plaintext'),'inpath':False}
00059     return all_toplevel      
00060 
00061   def seqRecurseChildren(self,obj):
00062     children = self.data.children(obj)
00063     if children:
00064       seqlabel = self.data.label(obj)
00065       if self.options['file']:
00066         seqlabel += '\\n%s:%s' % (self.data.pypackage(obj),self.data.lineNumber(obj))
00067       result='subgraph clusterSeq%s {\nlabel="Sequence %s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(obj),seqlabel,self.options['color_sequence'],self.options['color_sequence'],self.options['font_name'],self.options['font_size'])
00068       for c in children:
00069         result += self.seqRecurseChildren(c)
00070       result+='}\n'
00071       return result
00072     else:
00073       self.nodes[self.data.label(obj)]={'obj':obj,'n_label':self.nodeLabel(obj),'n_shape':self.shapes.get(self.data.type(obj),'plaintext'),'inpath':True}
00074       return '%s\n'%self.data.label(obj)   
00075       
00076   def recurseChildren(self,obj):
00077     result=[]
00078     children=self.data.children(obj)
00079     if children:
00080       for c in children:
00081         result += self.recurseChildren(c)
00082     else:
00083       result.append(obj)
00084     return result
00085     
00086   #write out an appropriate node label
00087   def nodeLabel(self,obj):
00088     result = self.data.label(obj)
00089     if self.options['class']:
00090       result += '\\n%s'%self.data.classname(obj)
00091     if self.options['file']:
00092       result += '\\n%s:%s'%(self.data.pypackage(obj),self.data.lineNumber(obj))
00093     return result
00094     
00095     #generate an appropriate URL by replacing placeholders in baseurl
00096   def nodeURL(self,obj):
00097     classname = self.data.classname(obj)
00098     pypath = self.data.pypath(obj)
00099     pyline = self.data.lineNumber(obj)
00100     url = self.options['urlbase'].replace('$classname',classname).replace('$pypath',pypath).replace('$pyline',pyline)
00101     return url
00102     
00103   def makePath(self,path,endpath=False):
00104     children = self.recurseChildren(path)
00105     pathlabel = self.data.label(path)
00106     if self.options['file']:
00107       pathlabel += '\\n%s:%s'%(self.data.pypackage(path),self.data.lineNumber(path))
00108     if endpath:
00109       pathresult = 'subgraph cluster%s {\nlabel="%s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(path),pathlabel,self.options['color_endpath'],self.options['color_endpath'],self.options['font_name'],self.options['font_size'])
00110     else:
00111       pathresult = 'subgraph cluster%s {\nlabel="%s"\ncolor="%s"\nfontcolor="%s"\nfontname="%s"\nfontsize=%s\n' % (self.data.label(path),pathlabel,self.options['color_path'],self.options['color_path'],self.options['font_name'],self.options['font_size'])
00112     if self.options['seqconnect']:
00113       if endpath:
00114         self.endstarts.append('endstart_%s'%self.data.label(path))
00115         self.nodes['endstart_%s'%self.data.label(path)]={'obj':path,'n_label':'Start %s'%self.data.label(path),'n_color':'grey','n_shape':'plaintext','inpath':False}
00116       else:
00117         self.pathstarts.append('start_%s'%self.data.label(path))
00118         self.pathends.append('end_%s'%self.data.label(path))
00119         self.nodes['start_%s'%self.data.label(path)]={'obj':path,'n_label':'Start %s'%self.data.label(path),'n_color':'grey','n_shape':'plaintext','inpath':False}
00120         self.nodes['end_%s'%self.data.label(path)]={'obj':path,'n_label':'End %s'%self.data.label(path),'n_color':'grey','n_shape':'plaintext','inpath':False}
00121     labels=[]
00122     for c in children:
00123       #this is also done in seqRecurseChildren, so will be duplicated
00124       #unncessary, but relatively cheap and saves more cff/cfg conditionals
00125       self.nodes[self.data.label(c)]={'obj':c,'n_label':self.nodeLabel(c),'n_shape':self.shapes.get(self.data.type(c),'plaintext'),'inpath':True}
00126       labels.append(self.data.label(c))
00127     if self.options['seqconnect']:
00128       pathresult += '->'.join(labels)+'\n'
00129     else:
00130       if not self.options['seq']:
00131         pathresult += '\n'.join(labels)+'\n'
00132     if self.options['seq']:
00133       if self.data.children(path):
00134         for path_child in self.data.children(path):
00135           pathresult += self.seqRecurseChildren(path_child)
00136     pathresult += '}\n'
00137     if len(labels)>0 and self.options['seqconnect']:
00138       if endpath:
00139         pathresult += 'endstart_%s->%s\n' % (self.data.label(path),labels[0])
00140       else:
00141         pathresult += 'start_%s->%s\n%s->end_%s\n' % (self.data.label(path),labels[0],labels[-1],self.data.label(path))
00142     
00143     return pathresult
00144 
00145   def producePaths(self):
00146     result=''
00147     if 'paths' in self.toplevel:
00148       for path in self.toplevel['paths']:
00149         result += self.makePath(path)
00150     if self.options['endpath']:
00151       if 'endpaths' in self.toplevel:
00152         for path in self.toplevel['endpaths']:
00153           result += self.makePath(path,True)
00154     if 'sequences' in self.toplevel:
00155       for seq in self.toplevel['sequences']:
00156         result += self.seqRecurseChildren(seq)
00157     return result
00158     
00159   def connectPaths(self):
00160     result=''
00161     for p in self.pathends:
00162       for p2 in self.endstarts:
00163         result+="%s->%s\n" % (p,p2)
00164     return result
00165   
00166   def connectTags(self):
00167     #if we are connecting by tag, add labelled tag joining lines
00168     #this doesn't have to be exclusive with sequence connection, by stylistically probably should be
00169     result=''
00170     allobjects = [self.nodes[n]['obj'] for n in self.nodes if self.nodes[n]['inpath']]
00171     self.data.readConnections(allobjects)
00172     connections = self.data.connections()
00173     for objects,names in connections.items():
00174       if self.options['taglabel']:
00175         result += '%s->%s[label="%s",color="%s",fontcolor="%s",fontsize=%s,fontname="%s"]\n' % (objects[0],objects[1],names[1],self.options['color_inputtag'],self.options['color_inputtag'],self.options['font_size'],self.options['font_name'])
00176       else:
00177         result += '%s->%s[color="%s"]\n' % (objects[0],objects[1],self.options['color_inputtag'])
00178     return result
00179   
00180   
00181   def produceSource(self):
00182     #add the source
00183     #if we are connecting sequences, connect it to all the path starts
00184     #if we are connecting sequences and have a schedule, connect it to path #0
00185     result=''
00186     if 'source' in self.toplevel:
00187       for s in self.toplevel['source']:
00188         self.nodes['source']={'obj':s,'n_label':self.data.classname(s),'n_shape':self.shapes['Source']}
00189         if self.options['seqconnect']:
00190             for p in self.pathstarts:
00191               result += 'source->%s\n' % (p)   
00192     return result
00193     
00194   def produceServices(self):
00195     # add service, eventsetup nodes
00196     # this will usually result in thousands and isn't that interesting
00197     servicenodes=[]
00198     result=''
00199     if self.options['es']:
00200       if 'essources' in self.toplevel:
00201         for e in self.toplevel['essources']:
00202           servicenodes.append(self.data.label(e))
00203           self.nodes[self.data.label(e)]={'obj':e,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['ESSource'],'inpath':False}
00204       if 'esproducers' in self.toplevel:
00205         for e in self.toplevel['esproducers']:
00206           servicenodes.append(self.data.label(e))
00207           self.nodes[self.data.label(e)]={'obj':e,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['ESProducer'],'inpath':False}
00208     if self.options['services']:
00209       if 'services' in self.toplevel:
00210         for s in self.toplevel['services']:
00211           self.servicenodes.append(self.data.label(s))
00212           self.nodes[self.data.label(s)]={'obj':s,'n_label':self.nodeLabel(e), 'n_shape':self.shapes['Service'],'inpath':False}
00213     #find the maximum path and endpath lengths for servicenode layout
00214     maxpath=max([len(recurseChildren(path) for path in self.toplevel.get('paths',(0,)))])
00215     maxendpath=max([len(recurseChildren(path) for path in self.toplevel.get('endpaths',(0,)))])
00216     
00217     #add invisible links between service nodes where necessary to ensure they only fill to the same height as the longest path+endpath
00218     #this constraint should only apply for link view
00219     for i,s in enumerate(servicenodes[:-1]):
00220       if not i%(maxpath+maxendpath)==(maxpath+maxendpath)-1:
00221         result+='%s->%s[style=invis]\n' % (s,servicenodes[i+1])
00222     return result
00223     
00224   def produceNodes(self):
00225     result=''
00226     for n in self.nodes:
00227       self.nodes[n]['n_fontname']=self.options['font_name']
00228       self.nodes[n]['n_fontsize']=self.options['font_size']
00229       if self.options['url']:
00230         self.nodes[n]['n_URL']=self.nodeURL(self.nodes[n]['obj'])
00231       result += "%s[%s]\n" % (n,','.join(['%s="%s"' % (k[2:],v) for k,v in self.nodes[n].items() if k[0:2]=='n_']))
00232     return result
00233     
00234   def produceLegend(self):
00235     """
00236     Return a legend subgraph using current shape and colour preferences.
00237     """
00238     return 'subgraph clusterLegend {\nlabel="legend"\ncolor=red\nSource->Producer->Filter->Analyzer\nService->ESSource[style=invis]\nESSource->ESProducer[style=invis]\nProducer->Filter[color="%s",label="InputTag",fontcolor="%s"]\nProducer[shape=%s]\nFilter[shape=%s]\nAnalyzer[shape=%s]\nESSource[shape=%s]\nESProducer[shape=%s]\nSource[shape=%s]\nService[shape=%s]\nsubgraph clusterLegendSequence {\nlabel="Sequence"\ncolor="%s"\nfontcolor="%s"\nProducer\nFilter\n}\n}\n' % (self.options['color_inputtag'],self.options['color_inputtag'],self.shapes['EDProducer'],self.shapes['EDFilter'],self.shapes['EDAnalyzer'],self.shapes['ESSource'],self.shapes['ESProducer'],self.shapes['Source'],self.shapes['Service'],self.options['color_sequence'],self.options['color_sequence'])
00239     
00240   def __call__(self):
00241     blocks=[]
00242     if self.options['legend']:
00243       blocks += [self.produceLegend()]
00244     blocks += [self.producePaths()]
00245     if self.options['seqconnect']:
00246       blocks += [self.connectPaths()]
00247     if self.options['tagconnect']:
00248       blocks += [self.connectTags()]
00249     if self.options['source']:
00250       blocks += [self.produceSource()]
00251     if self.options['es'] or self.options['services']:
00252       blocks += [self.produceServices()]
00253     blocks += [self.produceNodes()]
00254     if self.data.process():
00255       return 'digraph configbrowse {\nsubgraph clusterProcess {\nlabel="%s\\n%s"\nfontsize=%s\nfontname="%s"\n%s\n}\n}\n' % (self.data.process().name_(),self.data._filename,self.options['font_size'],self.options['font_name'],'\n'.join(blocks))
00256     else:
00257       return 'digraph configbrowse {\nsubgraph clusterCFF {\nlabel="%s"\nfontsize=%s\nfontname="%s"\n%s\n}\n}\n' % (self.data._filename,self.options['font_size'],self.options['font_name'],'\n'.join(blocks))
00258   
00259   
00260 
00261 class DotExport(FileExportPlugin):
00262   """
00263   Export a CMSSW config file to DOT (https://www.graphviz.org) markup, either as raw markup or by invoking the dot program, as an image.
00264   """
00265   option_types={
00266     'legend':('Show Legend','boolean',True),
00267     'source':('Show Source','boolean',True),
00268     'es':('Show Event Setup','boolean',False),
00269     'tagconnect':('Connect InputTags','boolean',True),
00270     'seqconnect':('Connect Module Sequence','boolean',False),
00271     'services':('Show Services','boolean',False),
00272     'endpath':('Show EndPaths','boolean',True),
00273     'seq':('Group Sequences','boolean',True),
00274     'class':('Show Class','boolean',True),
00275     'file':('Show File','boolean',True),
00276     'schedule':('Show Schedule','boolean',False),
00277     'taglabel':('Show Tag Labels','boolean',True),
00278     'color_path':('Path Color','color','#ff00ff'),
00279     'color_endpath':('EndPath Color','color','#ff0000'),
00280     'color_sequence':('Sequence Color','color','#00ff00'),
00281     'color_inputtag':('InputTag Color','color','#0000ff'),
00282     'color_schedule':('Schedule Color','color','#00ffff'),
00283     'url':('Include URLs','boolean',False), #this is only purposeful for png+map mode
00284     'urlprocess':('Postprocess URL (for client-side imagemaps)','boolean',False), #see processMap documentation; determines whether to treat 'urlbase' as a dictionary for building a more complex imagemap or a simple URL
00285     'urlbase':('URL to generate','string',"{'split_x':1,'split_y':2,'scale_x':1.,'scale_y':1.,'cells':[{'top':0,'left':0,'width':1,'height':1,'html_href':'https://cmslxr.fnal.gov/lxr/ident/?i=$classname','html_alt':'LXR','html_class':'LXR'},{'top':1,'left':0,'width':1,'height':1,'html_href':'https://cmssw.cvs.cern.ch/cgi-bin/cmssw.cgi/CMSSW/$pypath?view=markup#$pyline','html_alt':'CVS','html_class':'CVS'}]}"), #CVS markup view doesn't allow line number links, only annotate view (which doesn't then highlight the code...)
00286     'node_graphs':('Produce individual graphs focussing on each node','boolean',False),
00287     'node_graphs_restrict':('Select which nodes to make node graphs for','string',''),
00288     'node_depth':('Search depth for individual node graphs','int',1),
00289     'font_name':('Font name','string','Times-Roman'),
00290     'font_size':('Font size','int',8),
00291     'png_max_size':('Maximum edge for png image','int',16768)
00292   }
00293   plugin_name='DOT Export'
00294   file_types=('bmp','dot','eps','gif','jpg','pdf','png','ps','svg','tif','png+map','stdout')
00295   def __init__(self):
00296     FileExportPlugin.__init__(self)
00297     
00298     #could make these customizeable in the general options dict
00299     self.shapes={}
00300     self.shapes['EDProducer']='box'
00301     self.shapes['EDFilter']='invhouse'
00302     self.shapes['EDAnalyzer']='house'
00303     self.shapes['OutputModule']='invtrapezium'
00304     self.shapes['ESSource']='Mdiamond'     
00305     self.shapes['ESProducer']='Msquare'
00306     self.shapes['Source']='ellipse'
00307     self.shapes['Service']='diamond'
00308     
00309   def dotIndenter(self,dot):
00310     """
00311     Simple indenter for dot output, mainly to prettify it for human reading.
00312     """
00313     spaces = lambda d: ''.join([space]*d)
00314     newdot = ""
00315     depth = 0
00316     space = '  '
00317     for line in dot.splitlines():
00318       if '{' in line:
00319         newdot += spaces(depth)+line+'\n'
00320         depth += 1
00321       elif '}' in line:
00322         depth -= 1
00323         newdot += spaces(depth)+line+'\n'
00324       else:
00325         newdot += spaces(depth)+line+'\n'
00326     return newdot
00327   
00328   def selectNode(self,dotdata,node,depth_s):
00329     depth = int(depth_s)
00330     backtrace=False
00331     if depth<0:
00332       depth = abs(depth)
00333       backtrace=True
00334     re_link = re.compile(r'^\s*?(\w*?)->(\w*?)(?:\[.*?\])?$',re.MULTILINE)
00335     re_nodedef = re.compile(r'^\s*?(\w*?)(?:\[.*?\])?$',re.MULTILINE)
00336     re_title = re.compile(r'^label=\"(.*?)\"$',re.MULTILINE)
00337     re_nodeprops = re.compile(r'^\s*?('+node+r')\[(.*?)\]$',re.MULTILINE)
00338     
00339     nodes = re_nodedef.findall(dotdata)
00340     if not node in nodes:
00341       raise Exception, "Selected node (%s) not found" % (node)
00342     links_l = re_link.findall(dotdata)
00343     links = {}
00344     for link in links_l:
00345       if not backtrace:
00346         if link[0] in links:
00347           links[link[0]] += [link[1]]
00348         else:
00349           links[link[0]] = [link[1]]
00350       if link[1] in links:
00351           links[link[1]] += [link[0]]
00352       else:
00353         links[link[1]] = [link[0]]
00354       
00355     def node_recursor(links,depthleft,start):
00356       if start in links:
00357         if depthleft==0:
00358           return links[start]+[start]
00359         else:
00360           result = [start]
00361           for l in links[start]:
00362             result.extend(node_recursor(links,depthleft-1,l))
00363           return result
00364       else:
00365         return [start]
00366     
00367     
00368     include_nodes = set(node_recursor(links,depth-1,node))
00369     include_nodes.add(node)
00370     
00371     class link_replacer:
00372       def __init__(self,include_nodes):
00373         self.include_nodes=include_nodes
00374       def __call__(self,match):
00375         if match.group(1) in self.include_nodes and match.group(2) in self.include_nodes:
00376           return match.group(0)
00377         return ''
00378     class node_replacer:
00379       def __init__(self,include_nodes):
00380         self.include_nodes=include_nodes
00381       def __call__(self,match):
00382         if match.group(1) in self.include_nodes:
00383           return match.group(0)
00384         return ''
00385     
00386     dotdata = re_link.sub(link_replacer(include_nodes),dotdata)
00387     dotdata = re_nodedef.sub(node_replacer(include_nodes),dotdata)
00388     dotdata = re_title.sub(r'label="\g<1>\\nDepth '+str(depth_s)+r' from node ' +node+r'"',dotdata,1)
00389     dotdata = re_nodeprops.sub('\\g<1>[\\g<2>,color="red"]',dotdata,1)
00390     
00391     return dotdata
00392    
00393   def processMap(self,mapdata):
00394     """
00395     Re-process the client-side image-map produces when png+map is selected.
00396     DOT will only ever put a single URL in the imagemap corresponding to a node, with the 'url' parameter (after html encoding) as the url, and the 'title' parameter as the title. This isn't useful behaviour for our purposes. We want probably several link areas, or a javascript link to make a menu appear, or other more complex behaviour.
00397     
00398     If the option 'urlprocess' is turned on, this function is called, and it expects to find a dictionary it can eval in the url parameter. I can't think of a less messy way of passing data to this function without having inner access to DOT at the moment.
00399     
00400     This function iterates through all the areas in the mapfile, replacing each one with one or more areas according to the rules in the dictionary stored in the URL parameter.
00401     
00402     The dictionary should have structure:
00403     {
00404       split_x:#,
00405       split_y:#,
00406       scale_x:#,
00407       scale_y:#,
00408       cells:[
00409               {
00410                 top:#,
00411                 left:#,
00412                 width:#,
00413                 height:#,
00414                 html_attribute1:"...",
00415                 html_attribute2:"..."
00416             ]
00417     }
00418     The imagemap is first scaled in size by scale_x and scale_y.
00419     It is then split into split_x*split_y rectangular cells.
00420     New areas are created for each defined cell with the defined top,left location and width,height. This will not check you aren't making new areas that overlap if you define them as such.
00421     The areas then get attributes defined by html_attribute fields - eg, 'html_href':'mypage.htm' becomes 'href'='mypage.htm' in the area. Probably you want as a minimum to define html_href and html_alt. It would also be useful to set html_class to allow highlighting of different link types, or html_onclick/onmouseover for more exotic behaviour.
00422     
00423     This will probably be quite sensitive to the presence of special characters, complex splitting schemes, etc. Use with caution.
00424     
00425     This may be somewhat replaceable with the <html_label> and cut-down table format that graphviz provides, but I haven't had much of an experiment with that.
00426     """
00427     new_areas=[]
00428     area_default = {'split_x':1,'scale_x':1.,'split_y':1,'scale_y':1.,'cells':[]}
00429     cell_default = {'top':0,'left':0,'width':1,'height':1,'html_href':'#'}
00430     re_area = re.compile('<area.*?/>',re.DOTALL)
00431     #sometimes DOT comes up with negative coordinates, so we need to deal with them here (so all the other links will work at least)
00432     re_content = re.compile('href="(.*?)" title=".*?" alt="" coords="(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6}),(-?[0-9]{1,6})"',re.DOTALL)
00433     re_htmlunquote = re.compile('&#([0-9]{1,3});')
00434     mapdata = re_htmlunquote.sub(lambda x: chr(int(x.group(1))),mapdata)
00435     areas = re_area.findall(mapdata)
00436     for area in areas:
00437       #print area
00438       data = re_content.search(area)
00439       baseurl = data.group(1)
00440       x1,y1,x2,y2 = map(int,(data.group(2),data.group(3),data.group(4),data.group(5)))
00441       rad_x,rad_y = int((x2-x1)*0.5),int((y2-y1)*0.5)
00442       centre_x,centre_y = x1+rad_x,y1+rad_y
00443       basedict = eval(baseurl)
00444       for ad in area_default:
00445         if not ad in basedict:
00446           basedict[ad]=area_default[ad]
00447       rad_x = int(rad_x*basedict['scale_x'])
00448       rad_y = int(rad_y*basedict['scale_y'])
00449       top_x,top_y = centre_x-rad_x,centre_y-rad_y
00450       split_x,split_y = int((2*rad_x)/basedict['split_x']),int((2*rad_y)/basedict['split_y'])
00451       
00452       for cell in basedict['cells']:
00453         for cd in cell_default:
00454           if not cd in cell:
00455             cell[cd]=cell_default[cd]
00456         x1,y1 = top_x+split_x*cell['left'],top_y+split_y*cell['top']
00457         x2,y2 = x1+split_x*cell['width'],y1+split_y*cell['height']
00458         area_html = '<area shape="rect" coords="%s,%s,%s,%s" %s />' % (x1,y1,x2,y2,' '.join(['%s="%s"'%(key.split('_',1)[1],value) for key, value in cell.items() if key.startswith('html_')]))
00459         new_areas.append(area_html)
00460     return '<map id="configbrowse" name="configbrowse">\n%s\n</map>'%('\n'.join(new_areas))  
00461     
00462     
00463   def export(self,data,filename,filetype):
00464     #if not data.process():
00465     #  raise "DOTExport requires a cms.Process object"  
00466     
00467     #dot = self.produceDOT(data)
00468     dot_producer = DotProducer(data,self.options,self.shapes)
00469     dot = dot_producer()
00470     
00471     if len(dot_producer.nodes)>0:
00472     
00473       if self.options['node_graphs']:
00474         nodes = [n for n in dot_producer.nodes if data.type(dot_producer.nodes[n]['obj']) in ('EDAnalyzer','EDFilter','EDProducer','OutputModule')]
00475         for n in nodes:
00476           if self.options['node_graphs_restrict'] in n:
00477             try:
00478               node_dot = self.selectNode(dot,n,self.options['node_depth'])
00479               self.write_output(node_dot,filename.replace('.','_%s.'%n),filetype)
00480             except:
00481               pass
00482       else:
00483         dot = self.dotIndenter(dot)
00484         self.write_output(dot,filename,filetype)
00485     else:
00486       print "WARNING: Empty image. Not saved."
00487     
00488   
00489   def get_png_size(self,filename):
00490     png_header = '\x89PNG\x0d\x0a\x1a\x0a'
00491     ihdr = 'IHDR'
00492     filedata = open(filename,'r').read(24)
00493     png_data = struct.unpack('>8s4s4sII',filedata)
00494     if not (png_data[0]==png_header and png_data[2]==ihdr):
00495       raise 'PNG header or IHDR not found'
00496     return png_data[3],png_data[4]
00497     
00498   def write_output(self,dot,filename,filetype):
00499     #don't use try-except-finally here, we want any errors passed on so the enclosing program can decide how to handle them
00500     if filetype=='dot':
00501       dotfile = open(filename,'w')
00502       dotfile.write(dot)
00503       dotfile.close()
00504     elif filetype=='stdout':
00505       print result
00506     elif filetype=='pdf':
00507       dot_p = subprocess.Popen(['dot','-Tps2'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
00508       ps2 = dot_p.communicate(dot)[0]
00509       if not dot_p.returncode==0:
00510         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00511       pdf_p = subprocess.Popen(['ps2pdf','-',filename],stdin=subprocess.PIPE)
00512       pdf_p.communicate(ps2)
00513       if not pdf_p.returncode==0:
00514         raise "ps2pdf returned non-zero exit code: %s"%pdf_p.returncode
00515     elif filetype=='png+map':
00516       if '.' in filename:
00517         filename = filename.split('.')[0]
00518       dot_p = subprocess.Popen(['dot','-Tpng','-o','%s.png'%filename,'-Tcmapx_np'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
00519       mapdata = dot_p.communicate(dot)[0]
00520       if not dot_p.returncode==0:
00521         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00522       if self.options['urlprocess']:
00523         mapdata = self.processMap(mapdata)
00524       mapfile = open('%s.map'%filename,'w')
00525       mapfile.write(mapdata)
00526       mapfile.close()
00527       filesize = self.get_png_size('%s.png'%filename)
00528       if max(filesize) > self.options['png_max_size']:
00529         print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
00530         os.remove('%s.png'%filename)
00531         os.remove('%s.map'%filename)
00532     elif filetype=='png':
00533       dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
00534       dot_p.communicate(dot)
00535       if not dot_p.returncode==0:
00536         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00537       filesize = self.get_png_size(filename)
00538       if max(filesize) > self.options['png_max_size']:
00539         print "png image is too large (%s pixels/%s max pixels), deleting" % (filesize,self.options['png_max_size'])
00540         os.remove(filename)
00541     else:
00542       dot_p = subprocess.Popen(['dot','-T%s'%(filetype),'-o',filename],stdin=subprocess.PIPE)
00543       dot_p.communicate(dot)
00544       if not dot_p.returncode==0:
00545         raise "dot returned non-zero exit code: %s"%dot_p.returncode
00546     
00547     
00548