Modules | Files | Inheritance Tree | Inheritance Graph | Name Index | Config
File: Synopsis/Formatter/Dia.py
    1| # $Id: Dia.py,v 1.12 2001/07/19 04:03:05 chalky Exp $
    2| #
    3| # This file is a part of Synopsis.
    4| # Copyright (C) 2000, 2001 Stephen Davies
    5| #
    6| # Synopsis is free software; you can redistribute it and/or modify it
    7| # under the terms of the GNU General Public License as published by
    8| # the Free Software Foundation; either version 2 of the License, or
    9| # (at your option) any later version.
   10| #
   11| # This program is distributed in the hope that it will be useful,
   12| # but WITHOUT ANY WARRANTY; without even the implied warranty of
   13| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   14| # General Public License for more details.
   15| #
   16| # You should have received a copy of the GNU General Public License
   17| # along with this program; if not, write to the Free Software
   18| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   19| # 02111-1307, USA.
   20| #
   21| # $Log: Dia.py,v $
   22| # Revision 1.12  2001/07/19 04:03:05  chalky
   23| # New .syn file format.
   24| #
   25| # Revision 1.11  2001/07/17 22:27:28  chalky
   26| # Fixed regression in import line
   27| #
   28| # Revision 1.10  2001/05/25 13:45:49  stefan
   29| # fix problem with getopt error reporting
   30| #
   31| # Revision 1.9  2001/02/13 06:55:23  chalky
   32| # Made synopsis -l work again
   33| #
   34| # Revision 1.8  2001/01/31 06:51:24  stefan
   35| # add support for '-v' to all modules; modified toc lookup to use additional url as prefix
   36| #
   37| # Revision 1.7  2001/01/27 06:26:41  chalky
   38| # Added parameter support
   39| #
   40| # Revision 1.6  2001/01/24 01:38:36  chalky
   41| # Added docstrings to all modules
   42| #
   43| # Revision 1.5  2001/01/22 19:54:41  stefan
   44| # better support for help message
   45| #
   46| # Revision 1.4  2001/01/22 17:06:15  stefan
   47| # added copyright notice, and switched on logging
   48| #
   49| 
   50| """Generates a .dia file of unpositioned classes and generalizations."""
   51| # THIS-IS-A-FORMATTER
   52| 
   53| import sys, getopt, os, os.path, string, re
   54| from Synopsis.Core import Type, AST, Util
   55| 
   56| verbose = 0
   57| 
   58| def k2a(keys):
   59|     "Convert a keys dict to a string of attributes"
   60|     return string.join(map(lambda item:' %s="%s"'%item, keys.items()), '')
   61| 
   62| def quote(str):
   63|     "Remove HTML chars from str"
   64|     str = re.sub('&', '&', str)
   65|     str = re.sub('<','&lt;', str)
   66|     str = re.sub('>','&gt;', str)
   67|     #str = re.sub('<','lt', str)
   68|     #str = re.sub('>','gt', str)
   69|     str = re.sub("'", '&#39;', str)
   70|     str = re.sub('"', '&quot;', str)
   71|     return str
   72| 
   73| def usage():
   74|     print """\
   75| Dia Formatter Usage:
   76|   -m    hide methods/operations
   77|   -a    hide attributes/variables
   78| """
   79| 
   80| class DiaFormatter(AST.Visitor, Type.Visitor):
   81|     """Outputs a Dia file
   82|     """
   83|     def __init__(self, filename):
   84|         self.__indent = 0
   85|         self.__istring = "  "
   86|         self.__filename = filename
   87|         self.__os = None
   88|         self.__oidcount = 0
   89|         self.__inherits = [] # list of from,to tuples
   90|         self.__objects = {} #maps tuple names to object ids
   91|     def indent(self): self.__os.write(self.__istring * self.__indent)
   92|     def incr(self): self.__indent = self.__indent + 1
   93|     def decr(self): self.__indent = self.__indent - 1
   94|     def write(self, text): self.__os.write(text)
   95|     def scope(self): return ()
   96| 
   97|     def startTag(self, tagname, **keys):
   98|         "Starts a tag and indents, attributes using keyword arguments"
   99|         self.indent()
  100|         self.write("<%s%s>\n"%(tagname,k2a(keys)))
  101|         self.incr()
  102|     def startTagDict(self, tagname, attrs):
  103|         "Starts a tag and indents, attributes using dictionary argument"
  104|         self.indent()
  105|         self.write("<%s%s>\n"%(tagname,k2a(attrs)))
  106|         self.incr()
  107|     def endTag(self, tagname):
  108|         "Un-indents and closes tag"
  109|         self.decr()
  110|         self.indent()
  111|         self.write("</%s>\n"%tagname)
  112|     def soloTag(self, tagname, **keys):
  113|         "Writes a solo tag with attributes from keyword arguments"
  114|         self.indent()
  115|         self.write("<%s%s/>\n"%(tagname,k2a(keys)))
  116|     def attribute(self, name, type, value, allow_solo=1):
  117|         "Writes an attribute with given name, type and value"
  118|         self.startTag('attribute', name=name)
  119|         if not value and allow_solo:
  120|             self.soloTag(type)
  121|         elif type == 'string':
  122|             self.indent()
  123|             self.write('<string>#%s#</string>\n'%value)
  124|         else:
  125|             self.soloTag(type, val=value)
  126|         self.endTag('attribute')
  127| 
  128|     def output(self, declarations):
  129|         """Output declarations to file"""
  130|         self.__os = open(filename, 'w')        
  131| 
  132|         self.write('<?xml version="1.0"?>\n')
  133|         self.startTagDict('diagram', {'xmlns:dia':"http://www.lysator.liu.se/~alla/dia/"})
  134| 
  135|         self.doDiagramData()
  136|         self.startTag('layer', name='Background', visible='true')
  137|         for decl in declarations:
  138|             decl.accept(self)
  139|         for inheritance in self.__inherits:
  140|             self.doInheritance(inheritance)
  141|         self.endTag('layer')
  142|         self.endTag('diagram')
  143| 
  144|         self.__os.close()
  145| 
  146|     def doDiagramData(self):
  147|         "Write the stock diagramdata stuff"
  148|         self.startTag('diagramdata')
  149|         self.attribute('background', 'color', '#ffffff')
  150|         self.startTag('attribute', name='paper')
  151|         self.startTag('composite', type='paper')
  152|         self.attribute('name','string','A4')
  153|         self.attribute('tmargin','real','2.82')
  154|         self.attribute('bmargin','real','2.82')
  155|         self.attribute('lmargin','real','2.82')
  156|         self.attribute('rmargin','real','2.82')
  157|         self.attribute('is_portrait','boolean','true')
  158|         self.attribute('scaling','real','1')
  159|         self.attribute('fitto','boolean','false')
  160|         self.endTag('composite')
  161|         self.endTag('attribute')
  162|         self.startTag('attribute', name='grid')
  163|         self.startTag('composite', type='grid')
  164|         self.attribute('width_x', 'real', '1')
  165|         self.attribute('width_y', 'real', '1')
  166|         self.attribute('visible_x', 'int', '1')
  167|         self.attribute('visible_y', 'int', '1')
  168|         self.endTag('composite')
  169|         self.endTag('attribute')
  170|         self.startTag('attribute', name='guides')
  171|         self.startTag('composite', type='guides')
  172|         self.soloTag('attribute', name='hguides')
  173|         self.soloTag('attribute', name='vguides')
  174|         self.endTag('composite')
  175|         self.endTag('attribute')
  176|         self.endTag('diagramdata')
  177| 
  178|     def doInheritance(self, inherit):
  179|         "Create a generalization object for one inheritance"
  180|         from_id, to_id = map(self.getObjectID, inherit)
  181|         id = self.createObjectID(None)
  182|         self.startTag('object', type='UML - Generalization', version='0', id=id)
  183|         self.attribute('obj_pos', 'point', '1,0')
  184|         self.attribute('obj_bb', 'rectangle', '0,0;2,2')
  185|         self.startTag('attribute', name='orth_points')
  186|         self.soloTag('point', val='1,0')
  187|         self.soloTag('point', val='1,1')
  188|         self.soloTag('point', val='0,1')
  189|         self.soloTag('point', val='0,2')
  190|         self.endTag('attribute')
  191|         self.startTag('attribute', name='orth_orient')
  192|         self.soloTag('enum', val='1')
  193|         self.soloTag('enum', val='0')
  194|         self.soloTag('enum', val='1')
  195|         self.endTag('attribute')
  196|         self.attribute('name', 'string', None)
  197|         self.attribute('stereotype', 'string', None)
  198|         self.startTag('connections')
  199|         self.soloTag('connection', handle='0', to=from_id, connection='6')
  200|         self.soloTag('connection', handle='1', to=to_id, connection='1')
  201|         self.endTag('connections')
  202|         self.endTag('object')
  203|         
  204| 
  205|     def createObjectID(self, decl):
  206|         "Creates a new object identifier, and remembers it with the given declaration"
  207|         idstr = 'O'+str(self.__oidcount)
  208|         if decl: self.__objects[decl.name()] = idstr
  209|         self.__oidcount = self.__oidcount+1
  210|         return idstr
  211| 
  212|     def getObjectID(self, decl):
  213|         "Returns the stored identifier for the given object"
  214|         try:
  215|             return self.__objects[decl.name()]
  216|         except KeyError:
  217|             print "Warning: no ID for",decl.name()
  218|             return 0
  219|     
  220|     def formatType(self, type):
  221|         "Returns a string representation for the given type"
  222|         if type is None: return '(unknown)'
  223|         type.accept(self)
  224|         return self.__type
  225|     
  226|     #################### Type Visitor ###########################################
  227| 
  228|     def visitBaseType(self, type):
  229|         self.__type = Util.ccolonName(type.name())
  230|         
  231|     def visitUnknown(self, type):
  232|         self.__type = Util.ccolonName(type.name(), self.scope())
  233|         
  234|     def visitDeclared(self, type):
  235|         self.__type = Util.ccolonName(type.name(), self.scope())
  236|         
  237|     def visitModifier(self, type):
  238|         aliasStr = self.formatType(type.alias())
  239|         premod = map(lambda x:x+" ", type.premod())
  240|         self.__type = "%s%s%s"%(string.join(premod,''), aliasStr, string.join(type.postmod(),''))
  241|             
  242|     def visitParametrized(self, type):
  243|         temp = self.formatType(type.template())
  244|         params = map(self.formatType, type.parameters())
  245|         self.__type = "%s<%s>"%(temp,string.join(params, ", "))
  246| 
  247|     def visitFunctionType(self, type):
  248|         ret = self.formatType(type.returnType())
  249|         params = map(self.formatType, type.parameters())
  250|         self.__type = "%s(%s)(%s)"%(ret,string.join(type.premod(),''),string.join(params,", "))
  251| 
  252|     ################# AST visitor #################
  253| 
  254|     def visitDeclaration(self, decl):
  255|         "Default is to not do anything with it"
  256|         #print "Not writing",decl.type(), decl.name()
  257|         pass
  258| 
  259|     def visitModule(self, decl):
  260|         "Just traverse child declarations"
  261|         # TODO: make a Package UML object and maybe link classes to it?
  262|         for d in decl.declarations():
  263|             d.accept(self)
  264| 
  265|     def visitClass(self, decl):
  266|         "Creates a Class object for one class, with attributes and operations"
  267|         id = self.createObjectID(decl)
  268|         self.startTag('object', type='UML - Class', version='0', id=id)
  269|         self.attribute('objpos', 'point', '1,1')
  270|         self.attribute('obj_bb', 'rectangle', '1,1;2,2')
  271|         self.attribute('elem_corner', 'point', '1.05,1.05')
  272|         self.attribute('elem_width', 'real', '1')
  273|         self.attribute('elem_height', 'real', '5')
  274|         self.attribute('name', 'string', Util.ccolonName(decl.name()))
  275|         self.attribute('stereotype', 'string', None)
  276|         self.attribute('abstract', 'boolean', 'false')
  277|         self.attribute('suppress_attributes', 'boolean', 'false')
  278|         self.attribute('suppress_operations', 'boolean', 'false')
  279|         self.attribute('visible_attributes', 'boolean', hide_attributes and 'false' or 'true')
  280|         self.attribute('visible_operations', 'boolean', hide_operations and 'false' or 'true')
  281|         # Do attributes
  282|         afilt = lambda d: d.type() == 'attribute' or d.type() == 'variable'
  283|         attributes = filter(afilt, decl.declarations())
  284|         if not len(attributes) or hide_attributes:
  285|             self.soloTag('attribute', name='attributes')
  286|         else:
  287|             self.startTag('attribute', name='attributes')
  288|             for attr in attributes:
  289|                self.startTag('composite', type='umlattribute')
  290|                self.attribute('name', 'string', attr.name()[-1])
  291|                if attr.type() == 'attribute':
  292|                    self.attribute('type', 'string', self.formatType(attr.returnType()))
  293|         else:
  294|                    self.attribute('type', 'string', self.formatType(attr.vtype()))
  295|                self.attribute('value', 'string', None)
  296|                self.attribute('visibility', 'enum', '0')
  297|                self.attribute('abstract', 'boolean', 'false')
  298|                self.attribute('class_scope', 'boolean', 'false')
  299|                self.endTag('composite')
  300|             self.endTag('attribute')
  301|         # Do operations
  302|         operations = filter(lambda d: d.type() == 'operation', decl.declarations())
  303|         if not len(operations) or hide_operations:
  304|             self.soloTag('attribute', name='operations')
  305|         else:
  306|             self.startTag('attribute', name='operations')
  307|             for oper in operations:
  308|                self.startTag('composite', type='umloperation')
  309|                self.attribute('name', 'string', oper.name()[-1])
  310|                self.attribute('type', 'string', self.formatType(oper.returnType()))
  311|                self.attribute('visibility', 'enum', '0')
  312|                self.attribute('abstract', 'boolean', 'false')
  313|                self.attribute('class_scope', 'boolean', 'false')
  314|                if len(oper.parameters()) and not hide_params:
  315|                    self.startTag('attribute', name='parameters')
  316|                    for param in oper.parameters():
  317|                       self.startTag('composite', name='umlparameter')
  318|                       self.attribute('name', 'string', param.identifier())
  319|                       self.attribute('type', 'string', '', 0)
  320|                       self.attribute('value', 'string', quote(param.value()))
  321|                       self.attribute('kind', 'enum', '0')
  322|                       self.endTag('composite')
  323|                    self.endTag('attribute')
  324|         else:
  325|                    self.soloTag('attribute', name='parameters')
  326|  
  327|                self.endTag('composite')
  328|             self.endTag('attribute')
  329|         # Finish class object
  330|         self.attribute('template', 'boolean', 'false')
  331|         self.soloTag('attribute', name='templates')
  332|         self.endTag('object')
  333| 
  334|         for inherit in decl.parents():
  335|             self.__inherits.append( (inherit.parent(), decl) )
  336| 
  337| def usage():
  338|     """Print usage to stdout"""
  339|     print \
  340| """
  341|   -o <file>                            Output file
  342|   -m                                   hide operations
  343|   -a                                   hide attributes
  344|   -p                                   hide parameters"""
  345| 
  346| def __parseArgs(args):
  347|     global filename, hide_operations, hide_attributes, hide_params, verbose
  348|     filename = None
  349|     hide_operations = hide_attributes = hide_params = 0
  350|     try:
  351|         opts,remainder = getopt.getopt(args, "o:mapv")
  352|     except getopt.error, e:
  353|         sys.stderr.write("Error in arguments: " + str(e) + "\n")
  354|         sys.exit(1)
  355| 
  356|     for opt in opts:
  357|         o,a = opt
  358| 
  359|         if o == "-o":
  360|             filename = a
  361|         elif o == "-m": hide_operations = 1
  362|         elif o == "-a": hide_attributes = 1
  363|         elif o == "-p": hide_params = 1
  364|         elif o == "-v": verbose = 1
  365|     
  366|     if filename is None:
  367|         sys.stderr.write("Error: No output specified.\n")
  368|         sys.exit(1)
  369| 
  370| def format(args, ast, config_obj):
  371|     __parseArgs(args)
  372| 
  373|     formatter = DiaFormatter(filename)
  374| 
  375|     formatter.output(ast.declarations())
  376|