1   
   2   
   3  __version__ = '$Revision: 4791 $'.split()[1] 
   4  __date__ = '$Date: 2006-09-24 14:01:41 -0400 (Sun, 24 Sep 2006) $'.split()[1] 
   5  __author__ = 'Kurt Schwehr' 
   6   
   7  __doc__=''' 
   8   
   9  Tools to generate python code to serialize/deserialize messages 
  10  between python and ais binary.  Trying to be as inline as possible, so 
  11  no XML on the fly like in ais-py.   
  12   
  13  serialize: python to ais binary 
  14  deserialize: ais binary to python 
  15   
  16  The generated code uses translators.py, binary.py, and aisstring.py 
  17  which should be packaged with the resulting files. 
  18   
  19  @requires: U{lxml<http://codespeak.net/lxml/>} 
  20  @requires: U{epydoc<http://epydoc.sourceforge.net/>} >= 3.0alpha3 
  21  @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} >= 1.2 
  22   
  23  @author: U{'''+__author__+'''<http://schwehr.org/>} 
  24  @version: ''' + __version__ +''' 
  25  @copyright: 2006 
  26  @var __date__: Date of last svn commit 
  27  @undocumented: __version__ __author__ __doc__ parser 
  28  @since: 2006-Sep-24 
  29  @status: under development 
  30  @organization: U{CCOM<http://ccom.unh.edu/>} 
  31  @license: GPL v2 
  32   
  33  @todo: add a link to generated doc string to bring up the html for the pretty version 
  34  @todo: write a separate validation script that distinguishes standard messages and bin messages 
  35  @todo: make sure binary is only used in AIS ITU messages and not within the binary messages! 
  36  @todo: arrays 
  37  @todo: handle entry ranges in lookup tables 
  38  @todo: arraylength of -1 for messages 12 and 14 
  39  @todo: in the table html/kml form output, if no LUT and value is the unknown case, say so in the converted column! 
  40  @todo: stop using sqlhelp.  Can have trouble with corrupted data. 
  41  @bug: NOT complete 
  42  @note: This compiler assumes that all locations are in WGS 84 (EPSG 4326) 
  43  ''' 
  44   
  45  import sys, os 
  46  from decimal import Decimal 
  47  from lxml import etree  
  48   
  50      ''' 
  51      Try to suggest a type name if one did not work. 
  52   
  53      @param printout: if true, write a suggestion to stdout. 
  54   
  55      >>> suggestType('myFieldName','unsigned int') 
  56      Recommend switching "unsigned int" to "uint" for field "myFieldName" 
  57      'uint' 
  58   
  59      >>> suggestType('JohnWarfon','yoyodyne') 
  60      Sorry!  No recommendation available for bad type "yoyodyne" for field "JohnWarfon" 
  61      ''' 
  62      newType = None 
  63      if curType.lower()=='unsigned int': 
  64          newType = 'uint' 
  65      elif curType.lower()=='unsigned decimal': 
  66          newType = 'udecimal' 
  67   
  68      if printout: 
  69          if None != newType: 
  70              print 'Recommend switching "'+curType+'" to "'+newType+'" for field "'+name+'"' 
  71          else: 
  72              print 'Sorry!  No recommendation available for bad type "'+curType+'" for field "'+name+'"' 
  73      return newType 
   74   
  75   
  77      ''' 
  78      @return: true if the tag a sub tag with name subtag 
  79      ''' 
  80      if 0<len(et.xpath(subtag)): return True 
  81      return False 
   82   
  83   
  85      ''' 
  86      Write the doc string header for the message file 
  87   
  88      @param o: Open output file to write code to. 
  89      Must be pre-expanded with the expandais.py command. 
  90      ''' 
  91      import datetime 
  92      d = datetime.datetime.utcnow() 
  93      dateStr = str(d.year)+'-'+("%02d" %d.month)+'-'+("%02d"%d.day) 
  94   
  95       
  96       
  97   
  98       
  99      o.write('''#!/usr/bin/env python 
 100   
 101  __version__ = '$Revision: 4791 $'.split()[1] 
 102  __date__ = '$Da'''+'''te: '''+dateStr+''' $'.split()[1] 
 103  __author__ = 'xmlbinmsg' 
 104   
 105  __doc__=\'\'\' 
 106   
 107  Autogenerated python functions to serialize/deserialize binary messages. 
 108   
 109  Generated by: '''+__file__+''' 
 110   
 111  Need to then wrap these functions with the outer AIS packet and then 
 112  convert the whole binary blob to a NMEA string.  Those functions are 
 113  not currently provided in this file. 
 114   
 115  serialize: python to ais binary 
 116  deserialize: ais binary to python 
 117   
 118  The generated code uses translators.py, binary.py, and aisstring.py 
 119  which should be packaged with the resulting files. 
 120   
 121  ''') 
 122   
 123      o.write(''' 
 124  @requires: U{epydoc<http://epydoc.sourceforge.net/>} > 3.0alpha3 
 125  @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} 
 126   
 127  @author: \'\'\'+__author__+\'\'\' 
 128  @version: \'\'\' + __version__ +\'\'\' 
 129  @var __date__: Date of last svn commit 
 130  @undocumented: __version__ __author__ __doc__ parser 
 131  @status: under development 
 132  @license: Generated code has no license 
 133  @todo: FIX: put in a description of the message here with fields and types. 
 134  \'\'\' 
 135   
 136  import sys 
 137  from decimal import Decimal 
 138  from BitVector import BitVector 
 139   
 140  import binary, aisstring 
 141   
 142  # FIX: check to see if these will be needed 
 143  TrueBV  = BitVector(bitstring="1") 
 144  "Why always rebuild the True bit?  This should speed things up a bunch" 
 145  FalseBV = BitVector(bitstring="0") 
 146  "Why always rebuild the False bit?  This should speed things up a bunch" 
 147   
 148   
 149  ''') 
 150      return 
  151   
 153      ''' 
 154      @param infile: xml ais binary message definition file 
 155      @param outfile: where to dump the python code 
 156      ''' 
 157   
 158      aisMsgsET = etree.parse(infile).getroot() 
 159   
 160      o = file(outfile,'w') 
 161      os.chmod(outfile,0755) 
 162   
 163      writeBeginning(o) 
 164   
 165      for msgET in aisMsgsET: 
 166          if msgET.tag != 'message': continue 
 167          print msgET.tag, msgET.attrib['name'] 
 168   
 169          if len(msgET.xpath('include-struct')) > 0: 
 170              sys.exit("ERROR: cannot handle xml that still has include-struct tags.\n  Please use expandais.py.") 
 171          buildHelpers(o,msgET,prefixName=prefixName,verbose=verbose) 
 172          buildEncode(o,msgET,prefixName=prefixName,verbose=verbose) 
 173          buildDecode(o,msgET,prefixName=prefixName) 
 174          buildDecodeParts(o,msgET,prefixName=prefixName)  
 175          buildPrint(o,msgET,prefixName=prefixName) 
 176          buildLUT(o,msgET,prefixName=prefixName) 
 177          buildSQL(o,msgET,prefixName=prefixName) 
 178          buildLaTeX(o,msgET,prefixName=prefixName,verbose=verbose) 
 179          buildTextDef(o,msgET,prefixName=prefixName) 
 180   
 181      o.write('\n\n######################################################################\n') 
 182      o.write('# UNIT TESTING\n') 
 183      o.write('######################################################################\n') 
 184      o.write('import unittest\n') 
 185   
 186      for msgET in aisMsgsET: 
 187          if msgET.tag != 'message': continue 
 188          print 'Building unit tests for message ...', msgET.attrib['name'] 
 189   
 190          buildUnitTest(o,msgET, prefixName=prefixName) 
 191           
 192      for msgET in aisMsgsET: 
 193          if msgET.tag != 'message': continue 
 194          buildMain(o,msgET, prefixName=prefixName) 
 195   
 196      return  
  197   
 198   
 199   
 200   
 201   
 203      ''' 
 204      emit the fieldList and other things??? 
 205   
 206      @param o: open file where resulting code will be written 
 207      @param msgET: Element Tree starting at a message node 
 208   
 209      @todo: for lookuptable/entry values, make it also print the decoded value. 
 210      @todo: use a different name for message and field 
 211      @todo: put in comments with the python and sql data type for each field 
 212      ''' 
 213       
 214      if verbose: 
 215          msgname = msgET.attrib['name'] 
 216          print 'Building helpers ...',msgname 
 217   
 218      if prefixName: 
 219          o.write(prefixName,'FieldList = (\n') 
 220      else: 
 221          o.write('fieldList = (\n') 
 222       
 223      for field in msgET.xpath('field'): 
 224          name = field.attrib['name'] 
 225          o.write('\t\''+name+'\',\n') 
 226   
 227      o.write(')\n\n') 
 228   
 229       
 230       
 231   
 232      if prefixName: 
 233          o.write(prefixName,'FieldListPostgres = (\n') 
 234      else: 
 235          o.write('fieldListPostgres = (\n') 
 236      finished=[]  
 237      for field in msgET.xpath('field'): 
 238          name = field.attrib['name'] 
 239          if 'postgisName' in field.attrib: 
 240              name = field.attrib['postgisName'] 
 241              if name in finished: continue  
 242              finished.append(name) 
 243              o.write('\t\''+name+'\',\t# PostGIS data type\n') 
 244          else: 
 245              o.write('\t\''+name+'\',\n') 
 246   
 247      o.write(')\n\n') 
 248   
 249      del finished 
 250   
 251   
 252   
 253      if prefixName: 
 254          o.write(prefixName,'ToPgFields = {\n') 
 255      else: 
 256          o.write('toPgFields = {\n') 
 257      for field in msgET.xpath('field'): 
 258          if 'postgisName' not in field.attrib: continue 
 259          name = field.attrib['name'] 
 260          pgName = field.attrib['postgisName'] 
 261          o.write('\t\''+name+'\':\''+pgName+'\',\n') 
 262      o.write('}\n') 
 263      o.write('''\'\'\' 
 264  Go to the Postgis field names from the straight field name 
 265  \'\'\' 
 266   
 267  ''') 
 268   
 269   
 270      if prefixName: 
 271          o.write(prefixName,'FromPgFields = {\n') 
 272      else: 
 273          o.write('fromPgFields = {\n') 
 274      finished=[]  
 275      for field in msgET.xpath('field'): 
 276          if 'postgisName' not in field.attrib: continue 
 277          name = field.attrib['name'] 
 278          pgName = field.attrib['postgisName'] 
 279   
 280          if pgName in finished: continue  
 281          finished.append(pgName) 
 282          o.write('\t\''+pgName+'\':(') 
 283          xp = 'field[@postgisName=\''+pgName+'\']' 
 284          parts = ['\''+part.attrib['name']+'\'' for part in msgET.xpath(xp)] 
 285          o.write(','.join(parts)+',),\n') 
 286      o.write('}\n') 
 287      o.write('''\'\'\' 
 288  Go from the Postgis field names to the straight field name 
 289  \'\'\' 
 290   
 291  ''') 
 292   
 293   
 294      if prefixName: 
 295          o.write(prefixName,'PgTypes = {\n') 
 296      else: 
 297          o.write('pgTypes = {\n') 
 298      finished=[]  
 299      for field in msgET.xpath('field'): 
 300          if 'postgisName' not in field.attrib: continue 
 301          pgType = field.attrib['postgisType'] 
 302          pgName = field.attrib['postgisName'] 
 303          if pgName in finished: continue  
 304          finished.append(pgName) 
 305          o.write('\t\''+pgName+'\':\''+pgType+'\',\n') 
 306      o.write('}\n') 
 307      o.write('''\'\'\' 
 308  Lookup table for each postgis field name to get its type. 
 309  \'\'\' 
 310   
 311  ''') 
  312   
 313   
 314   
 315   
 316       
 317   
 318   
 319   
 320   
 321   
 322   
 324      '''Get the maximum string length of any field name''' 
 325      maxStrLen=0 
 326      for field in msgET.xpath('field'): 
 327          fieldLen = len(field.attrib['name']) 
 328          if fieldLen>maxStrLen: maxStrLen = fieldLen 
 329      return maxStrLen 
  330   
 332      '''Pad a string out to the length requested with spaces out to the right''' 
 333      return aStr + ' '*(strlen-len(aStr)) 
  334   
 336      '''Make sure this message has both long/x and lat/y fields. 
 337      @rtype: bool 
 338      ''' 
 339       
 340   
 341      if 0==len(msgET.xpath('field[contains(@name, "longitude")]')): return False 
 342      if 0==len(msgET.xpath('field[contains(@name, "latitude")]')): return False 
 343      return True 
  344   
 346      ''' 
 347      Dig up the first field name that include longitude and return it 
 348      @todo: might want to allow a search for a special tag to mark this 
 349      ''' 
 350      return msgET.xpath('field[contains(@name, "longitude")]')[0].attrib['name'] 
  351   
 353      ''' 
 354      Dig up the first field name that include longitude and return it 
 355      @todo: might want to allow a search for a special tag to mark this 
 356      ''' 
 357      return msgET.xpath('field[contains(@name, "latitude")]')[0].attrib['name'] 
  358   
 359 -def buildPrint(o,msgET, verbose=False, prefixName=False): 
  360      ''' 
 361      Write a simple in order print for the resulting dictionary. 
 362   
 363      @param o: open file where resulting code will be written 
 364      @param msgET: Element Tree starting at a message node 
 365   
 366      @todo: for lookuptable/entry values, make it also print the decoded value. 
 367      @todo: use a different name for message and field 
 368      ''' 
 369      assert(msgET.tag=='message') 
 370      msgName = msgET.attrib['name'] 
 371       
 372   
 373      print 'Generating print ...',msgName  
 374       
 375      maxFieldLen = 1 + getMaxFieldNameLen(msgET) 
 376   
 377   
 378       
 379       
 380       
 381   
 382      printHtmlName = 'printHtml' 
 383      if prefixName: printHtmlName = name+'printHtml' 
 384   
 385   
 386   
 387   
 388       
 389       
 390       
 391       
 392      o.write('\n') 
 393      o.write('def '+printHtmlName+'(params, out=sys.stdout):\n') 
 394      o.write('\t\tout.write("<h3>'+msgName+'</h3>\\n")\n') 
 395      o.write('\t\tout.write("<table border=\\"1\\">\\n")\n') 
 396       
 397      o.write('\t\tout.write("<tr bgcolor=\\"orange\\">\\n")\n') 
 398      o.write('\t\tout.write("<th align=\\"left\\">Field Name</th>\\n")\n') 
 399      o.write('\t\tout.write("<th align=\\"left\\">Type</th>\\n")\n') 
 400      o.write('\t\tout.write("<th align=\\"left\\">Value</th>\\n")\n') 
 401      o.write('\t\tout.write("<th align=\\"left\\">Value in Lookup Table</th>\\n")\n') 
 402      o.write('\t\tout.write("<th align=\\"left\\">Units</th>\\n")\n') 
 403   
 404       
 405      for field in msgET.xpath('field'): 
 406          o.write('\t\tout.write("\\n")\n') 
 407          o.write('\t\tout.write("<tr>\\n")\n') 
 408          fieldname = field.attrib['name'] 
 409          fieldtype = field.attrib['type'] 
 410          o.write('\t\tout.write("<td>'+fieldname+'</td>\\n")\n') 
 411          o.write('\t\tout.write("<td>'+fieldtype+'</td>\\n")\n') 
 412   
 413          numbits = int(field.attrib['numberofbits']) 
 414          required = None;  
 415          if hasSubTag(field,'required'):  
 416              required = field.xpath('required')[0].text 
 417          unavailable=None;  
 418          if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 
 419          arraylen=1 
 420          if 'arraylength' in field.attrib:  arraylen=int(field.attrib['arraylength']) 
 421   
 422          if 1==arraylen or fieldtype=='aisstr6': 
 423              o.write('\t\tif \''+fieldname+'\' in params:\n\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n') 
 424              if not hasSubTag(field,'lookuptable'): 
 425                   
 426                  o.write('\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n') 
 427              else: 
 428                  lutName = fieldname+'DecodeLut' 
 429                  if prefixName: lutName = msgname+fieldname.capitalize()+'DecodeLut' 
 430   
 431                  o.write('\t\t\tif str(params[\''+fieldname+'\']) in '+lutName+':\n') 
 432                  o.write('\t\t\t\tout.write("<td>"+'+lutName+'[str(params[\''+fieldname+'\'])]+"</td>")\n') 
 433                  o.write('\t\t\telse:\n') 
 434                  o.write('\t\t\t\tout.write("<td><i>Missing LUT entry</i></td>")\n') 
 435          else: sys.exit ('FIX: handle arrays in the buildPrint func') 
 436   
 437          if hasSubTag(field,'units'): 
 438              o.write('\t\tout.write("<td>'+field.xpath('units')[0].text+'</td>\\n")\n') 
 439   
 440          o.write('\t\tout.write("</tr>\\n")\n') 
 441   
 442      o.write('\t\tout.write("</table>\\n")\n') 
 443      o.write('\n') 
 444   
 445       
 446       
 447       
 448   
 449      printKmlName = 'printKml' 
 450      if prefixName: printKmlName = msgName+'printKml' 
 451      if not haveLocatableMessage(msgET): printKmlName=None 
 452      else:  
 453           
 454          o.write('\n') 
 455          o.write('def printKml(params, out=sys.stdout):\n') 
 456          o.write('\t\'\'\'KML (Keyhole Markup Language) for Google Earth, but without the header/footer\'\'\'\n') 
 457          o.write('\tout.write("\\\t<Placemark>\\n")\n') 
 458          o.write('\tout.write("\\t\t<name>"+str(params[\''+msgET.attrib['titlefield']+'\'])+"</name>\\n")\n') 
 459          o.write('\tout.write("\\t\\t<description>\\n")\n') 
 460   
 461          o.write('\timport StringIO\n') 
 462          o.write('\tbuf = StringIO.StringIO()\n') 
 463          o.write('\tprintHtml(params,buf)\n') 
 464          o.write('\timport cgi\n') 
 465          o.write('\tout.write(cgi.escape(buf.getvalue()))\n') 
 466   
 467          o.write('\tout.write("\\t\\t</description>\\n")\n') 
 468          o.write('\tout.write("\\t\\t<styleUrl>#m_ylw-pushpin_copy0</styleUrl>\\n")\n') 
 469          o.write('\tout.write("\\t\\t<Point>\\n")\n') 
 470          o.write('\tout.write("\\t\\t\\t<coordinates>")\n') 
 471          o.write('\tout.write(str(params[\''+getLongitudeFieldName(msgET)+'\']))\n') 
 472          o.write('\tout.write(\',\')\n') 
 473          o.write('\tout.write(str(params[\''+getLatitudeFieldName(msgET)+'\']))\n') 
 474          o.write('\tout.write(",0</coordinates>\\n")\n') 
 475          o.write('\tout.write("\\t\\t</Point>\\n")\n') 
 476          o.write('\tout.write("\\t</Placemark>\\n")\n') 
 477          o.write('\n') 
 478   
 479   
 480       
 481       
 482       
 483   
 484      funcName = 'printFields' 
 485      if prefixName: funcName = msgName+'PrintFields' 
 486       
 487      o.write('def '+funcName+'(params, out=sys.stdout, format=\'std\', fieldList=None, dbType=\'postgres\'):\n') 
 488   
 489       
 490       
 491      o.write("\t'''Print a "+msgName+" message to stdout.\n\n") 
 492      o.write('\tFields in params:\n') 
 493      for field in msgET.xpath('field'): 
 494          desc = field[0].text.replace('\n',' ')  
 495          o.write('\t  - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)  
 496          if len(field.xpath("required")) == 1: 
 497              o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 
 498   
 499          o.write('\n') 
 500      o.write('\t@param params: Dictionary of field names/values.  \n') 
 501      o.write('\t@param out: File like object to write to\n') 
 502           
 503      o.write("\t@rtype: stdout\n") 
 504      o.write("\t@return: text to out\n") 
 505      o.write("\t'''\n\n") 
 506   
 507       
 508       
 509   
 510      o.write('\tif \'std\'==format:\n') 
 511      o.write('\t\tout.write("'+msgName+':\\n")\n') 
 512   
 513      if verbose: print 'number of fields = ', len(msgET.xpath('field')) 
 514   
 515       
 516      for field in msgET.xpath('field'): 
 517          fieldname = field.attrib['name'] 
 518          fieldtype = field.attrib['type'] 
 519          numbits = int(field.attrib['numberofbits']) 
 520          required = None;  
 521          if hasSubTag(field,'required'):  
 522              required = field.xpath('required')[0].text 
 523          unavailable=None;  
 524          if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 
 525          arraylen=1 
 526          if 'arraylength' in field.attrib:  
 527              arraylen=int(field.attrib['arraylength']) 
 528              if verbose: print 'Processing field ...',fieldname,'('+fieldtype+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 
 529          else: 
 530              if verbose: print 'Processing field ...',fieldname,'(',fieldtype+' ',numbits,')' 
 531   
 532          if 1==arraylen or fieldtype=='aisstr6': 
 533              o.write('\t\tif \''+fieldname+'\' in params: out.write("\t'+padStrRight(fieldname+':',maxFieldLen)+'  "+str(params[\''+fieldname+'\'])+"\\n")\n') 
 534               
 535          else: 
 536              print '\n','ERROR:',fieldname,fieldtype 
 537              sys.exit ('ERROR: FIX: handle arrays in the buildPrint func') 
 538   
 539       
 540       
 541       
 542      o.write(''' elif 'csv'==format: 
 543                  if None == options.fieldList: 
 544                          options.fieldList = fieldList 
 545                  needComma = False; 
 546                  for field in fieldList: 
 547                          if needComma: out.write(',') 
 548                          needComma = True 
 549                          if field in params: 
 550                                  out.write(str(params[field])) 
 551                          # else: leave it empty 
 552                  out.write("\\n") 
 553  ''') 
 554   
 555       
 556       
 557       
 558      o.write('\telif \'html\'==format:\n') 
 559      o.write('\t\t'+printHtmlName+'(params,out)\n') 
 560   
 561      o.write(''' elif 'sql'==format: 
 562                  sqlInsertStr(params,out,dbType=dbType) 
 563  ''') 
 564   
 565      if haveLocatableMessage(msgET): 
 566           
 567          o.write('\telif \'kml\'==format:\n') 
 568          o.write('\t\t'+printKmlName+'(params,out)\n') 
 569   
 570          o.write('\telif \'kml-full\'==format:\n') 
 571   
 572          o.write('\t\tout.write(\"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>\\n")\n') 
 573          o.write('\t\tout.write("<kml xmlns=\\"http://earth.google.com/kml/2.1\\">\\n")\n') 
 574          o.write('\t\tout.write("<Document>\\n")\n') 
 575   
 576          o.write('\t\tout.write("\t<name>'+msgName+'</name>\\n")\n') 
 577   
 578          o.write('\t\t'+printKmlName+'(params,out)\n') 
 579   
 580          o.write('\t\tout.write("</Document>\\n")\n') 
 581          o.write('\t\tout.write("</kml>\\n")\n') 
 582   
 583   
 584   
 585       
 586       
 587       
 588      o.write('\telse: \n') 
 589      o.write('\t\tprint "ERROR: unknown format:",format\n') 
 590      o.write('\t\tassert False\n') 
 591   
 592      o.write('\n\treturn # Nothing to return\n\n') 
  593   
 594   
 595   
 596   
 597   
 598   
 599   
 600   
 601   
 602   
 603   
 604 -def buildTextDef(o,msgET, verbose=False, prefixName=False): 
  605      ''' 
 606      Write functions for text definition output 
 607   
 608      @param o: open file where resulting code will be written 
 609      @param msgET: Element Tree starting at a message node 
 610      @param verbose: talk lots in the process 
 611      @param prefixName: set to a string to have the commands prefixed by that character. 
 612   
 613      @todo: should this be a style sheet thing instead? 
 614      ''' 
 615      assert(msgET.tag=='message') 
 616      msgName = msgET.attrib['name'] 
 617      print 'Generating text table definition ...', msgName  
 618   
 619      createFuncName = 'textDefinitionTable' 
 620      if prefixName: createFuncName = msgName+'textDefinitionTable' 
 621   
 622      o.write('''\n###################################################################### 
 623  # Text Definition 
 624  ###################################################################### 
 625   
 626  ''') 
 627   
 628       
 629      o.write('def '+createFuncName+'''(outfile=sys.stdout 
 630                  ,delim='\\t' 
 631                  ): 
 632          \'\'\' 
 633          Return the text definition table for this message type 
 634          @param outfile: file like object to print to. 
 635          @type outfile: file obj 
 636          @return: text table string via the outfile 
 637          @rtype: str 
 638   
 639          \'\'\' 
 640          o = outfile 
 641  ''') 
 642   
 643       
 644      o.write('\to.write(\'\'\'Parameter\'\'\'+delim+\'Number of bits\'\'\'+delim+\'\'\'Description \n') 
 645   
 646      lines = [] 
 647      totalBits = 0 
 648      for field in msgET.xpath('field'): 
 649          fieldName = field.attrib['name'] 
 650          fieldType = field.attrib['type'] 
 651          fieldBits = field.attrib['numberofbits'] 
 652          if 'arraylength' in field.attrib: 
 653              fieldBits = str(int(fieldBits) * int(field.attrib['arraylength'])) 
 654          totalBits += int(fieldBits) 
 655           
 656          fieldDescription = field.xpath('description')[0].text.replace('\n','  ') 
 657          lines.append(fieldName+'\'\'\'+delim+\'\'\''+fieldBits+'\'\'\'+delim+\'\'\''+fieldDescription) 
 658   
 659      o.write('\n'.join(lines)) 
 660       
 661      from math import ceil 
 662      numSlots = 1 + int( ceil((totalBits-168)/256.) ) 
 663      emptySlotBits = ( 168 + (numSlots-1)*256 ) - totalBits  
 664      s = '' 
 665      if numSlots>1: s = 's' 
 666      o.write(('\nTotal bits\'\'\'+delim+\'\'\''+str(totalBits)+'\'\'\'+delim+\'\'\'Appears to take '+str(numSlots)+' slot'+s))  
 667      if emptySlotBits > 0: 
 668          o.write(' with '+str(emptySlotBits)+' pad bits to fill the last slot') 
 669   
 670      o.write( ('''\'\'\') 
 671  ''')) 
  672   
 673       
 674   
 675   
 676   
 677 -def buildLaTeX(o,msgET, verbose=False, prefixName=False): 
  678      ''' 
 679      Write functions for LaTeX output 
 680   
 681      @param o: open file where resulting code will be written 
 682      @param msgET: Element Tree starting at a message node 
 683      @param verbose: talk lots in the process 
 684      @param prefixName: set to a string to have the commands prefixed by that character. 
 685   
 686      @todo: should this be a style sheet thing instead? 
 687      ''' 
 688      assert(msgET.tag=='message') 
 689      msgName = msgET.attrib['name'] 
 690      print 'Generating LaTeX definition table ...', msgName  
 691   
 692      createFuncName = 'latexDefinitionTable' 
 693      if prefixName: createFuncName = msgName+'LaTeXDefinitionTable' 
 694   
 695      o.write('''\n###################################################################### 
 696  # LATEX SUPPORT 
 697  ###################################################################### 
 698   
 699  ''') 
 700   
 701       
 702      o.write('def '+createFuncName+'''(outfile=sys.stdout 
 703                  ): 
 704          \'\'\' 
 705          Return the LaTeX definition table for this message type 
 706          @param outfile: file like object to print to. 
 707          @type outfile: file obj 
 708          @return: LaTeX table string via the outfile 
 709          @rtype: str 
 710   
 711          \'\'\' 
 712          o = outfile 
 713  ''') 
 714   
 715       
 716      o.write(''' 
 717          o.write(\'\'\' 
 718  /begin{table}%[htb] 
 719  /centering 
 720  /begin{tabular}{|l|c|l|} 
 721  /hline 
 722  Parameter & Number of bits & Description  
 723  //  /hline/hline 
 724  '''.replace('/','\\\\')) 
 725   
 726      lines = [] 
 727      totalBits = 0 
 728      for field in msgET.xpath('field'): 
 729          fieldName = field.attrib['name'].replace('_','\_')   
 730          fieldType = field.attrib['type'] 
 731          fieldBits = field.attrib['numberofbits'] 
 732          if 'arraylength' in field.attrib: 
 733              fieldBits = str(int(fieldBits) * int(field.attrib['arraylength'])) 
 734          totalBits += int(fieldBits) 
 735           
 736          fieldDescription = field.xpath('description')[0].text.replace('\n',' ') 
 737          lines.append(fieldName+' & '+fieldBits+' & '+fieldDescription) 
 738   
 739      o.write(' \\\\\\\\ \\hline \n'.join(lines)) 
 740       
 741      from math import ceil 
 742      numSlots = 1 + int( ceil((totalBits-168)/256.) ) 
 743      emptySlotBits = ( 168 + (numSlots-1)*256 ) - totalBits  
 744      s = '' 
 745      if numSlots>1: s = 's' 
 746      o.write(('// /hline /hline\nTotal bits & '+str(totalBits)+' & Appears to take '+str(numSlots)+' slot'+s).replace('/','\\\\'))  
 747      if emptySlotBits > 0: 
 748          o.write(' with '+str(emptySlotBits)+' pad bits to fill the last slot') 
 749   
 750   
 751   
 752   
 753   
 754           
 755   
 756      o.write( (''' // /hline 
 757  /end{tabular} 
 758  /caption{AIS message number '''+msgET.attrib['aismsgnum']+': '+msgET.xpath('description')[0].text.replace('\n',' ')+'''} 
 759  /label{tab:'''+msgET.attrib['name']+'''} 
 760  /end{table} 
 761  \'\'\') 
 762  ''').replace('/','\\\\')) 
  763   
 764       
 765   
 766   
 767   
 768   
 769 -def buildSQL(o,msgET, verbose=False, prefixName=False): 
  770      ''' 
 771      Write SQL code 
 772   
 773      @param o: open file where resulting code will be written 
 774      @param msgET: Element Tree starting at a message node 
 775      @param verbose: talk lots in the process 
 776      @param prefixName: set to a string to have the commands prefixed by that character. 
 777      ''' 
 778      assert(msgET.tag=='message') 
 779      msgName = msgET.attrib['name'] 
 780      print 'Generating SQL commands ...', msgName  
 781   
 782      createFuncName = 'sqlCreate' 
 783      if prefixName: createFuncName = msgName+'SqlCreate' 
 784      insertFuncName = 'sqlInsert' 
 785      if prefixName: insertFuncName = msgName+'SqlInsert' 
 786   
 787      o.write('''###################################################################### 
 788  # SQL SUPPORT 
 789  ###################################################################### 
 790   
 791  ''') 
 792   
 793      o.write('dbTableName=\''+msgName+'\'\n') 
 794      o.write('\'Database table name\'\n\n') 
 795   
 796       
 797      o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None 
 798                  ,addCoastGuardFields=True 
 799                  ,dbType='postgres' 
 800                  ): 
 801          \'\'\' 
 802          Return the SQL CREATE command for this message type 
 803          @param outfile: file like object to print to. 
 804          @param fields: which fields to put in the create.  Defaults to all. 
 805          @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 
 806          @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 
 807          @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 
 808          @type addCoastGuardFields: bool 
 809          @return: sql create string 
 810          @rtype: str 
 811   
 812          @see: sqlCreate 
 813          \'\'\' 
 814          # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded? 
 815          outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType))) 
 816   
 817  ''') 
 818   
 819      o.write('def '+createFuncName+'''(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'): 
 820          \'\'\' 
 821          Return the sqlhelp object to create the table. 
 822   
 823          @param fields: which fields to put in the create.  Defaults to all. 
 824          @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 
 825          @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 
 826          @type addCoastGuardFields: bool 
 827          @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 
 828          @return: An object that can be used to generate a return 
 829          @rtype: sqlhelp.create 
 830          \'\'\' 
 831          if None == fields: fields = fieldList 
 832          import sqlhelp 
 833  ''') 
 834   
 835      o.write('\tc = sqlhelp.create(\''+msgName+'\',dbType=dbType)\n') 
 836      o.write('\tc.addPrimaryKey()\n'); 
 837   
 838      for field in msgET.xpath('field'): 
 839          fieldName = field.attrib['name'] 
 840          fieldType = field.attrib['type'] 
 841   
 842          postgis = False 
 843          if 'postgisType' in field.attrib: 
 844              postgis=True 
 845              o.write('\tif dbType != \'postgres\':\n\t') 
 846          o.write('\tif \''+fieldName+'\' in fields: c.add') 
 847           
 848          if   fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')') 
 849          elif fieldType in ('float'     ): o.write('Real(\''+fieldName+'\')') 
 850          elif fieldType == 'bool':         o.write('Bool(\''+fieldName+'\')') 
 851          elif fieldType in ('decimal','udecimal'): 
 852              if not hasSubTag(field,'decimalplaces'): 
 853                  print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n' 
 854                  assert (False) 
 855              scaleSQL = int(field.xpath('decimalplaces')[0].text)   
 856              numBits = int(field.attrib['numberofbits']) 
 857              if not hasSubTag(field,'scale'): 
 858                  print 'ERROR: missing required <scale> for',fieldName,'of type',fieldType 
 859              scaleVal = float(field.xpath('scale')[0].text) 
 860              precision = scaleSQL + len(str(int((2**numBits)/scaleVal))) 
 861               
 862              o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')') 
 863          elif fieldType == 'binary': 
 864              numBits = int(field.attrib['numberofbits']) 
 865              if -1 == numBits: numBits=1024  
 866              o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')') 
 867          elif fieldType == 'aisstr6': 
 868              arrayLength = int(field.attrib['arraylength']) 
 869              o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')') 
 870          else: 
 871              print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n' 
 872              assert(False) 
 873          o.write('\n') 
 874   
 875      o.write(''' 
 876          if addCoastGuardFields: 
 877                  # c.addInt('cg_rssi')     # Relative signal strength indicator 
 878                  # c.addInt('cg_d')        # dBm receive strength 
 879                  # c.addInt('cg_T')        # Receive timestamp from the AIS equipment 
 880                  # c.addInt('cg_S')        # Slot received in 
 881                  # c.addVarChar('cg_x',10) # Idonno 
 882                  c.addVarChar('cg_r',15)   # Receiver station ID  -  should usually be an MMSI, but sometimes is a string 
 883                  c.addInt('cg_sec')        # UTC seconds since the epoch 
 884   
 885                  c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream 
 886  ''') 
 887   
 888   
 889       
 890       
 891       
 892      postgisFields = msgET.xpath('field[@postgisType]') 
 893      if len(postgisFields)<1: 
 894          if verbose: print 'No postgis fields' 
 895      else: 
 896          o.write('\n\tif dbType == \'postgres\':\n') 
 897          finished=[]  
 898          print 'processing postgis fields for create:' 
 899          for field in postgisFields: 
 900               
 901              pgName = field.attrib['postgisName'] 
 902              if pgName in finished: 
 903                  print 'already handled',pgName 
 904                  continue 
 905              finished.append(pgName) 
 906              pgType = field.attrib['postgisType'] 
 907               
 908              components = msgET.xpath('field[@postgisName=\''+pgName+'\']') 
 909               
 910               
 911               
 912              o.write('\t\t#--- EPSG 4326 : WGS 84\n') 
 913   
 914              o.write('''\t\t#INSERT INTO "spatial_ref_sys" ("srid","auth_name","auth_srid","srtext","proj4text") VALUES (4326,'EPSG',4326,'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ');\n''') 
 915   
 916              o.write('\t\tc.addPostGIS(\''+pgName+'\',\''+pgType+'\','+str(len(components))+',SRID=4326);\n') 
 917   
 918      o.write('\n\treturn c\n\n') 
 919   
 920   
 921       
 922      o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None, dbType='postgres'): 
 923          \'\'\' 
 924          Return the SQL INSERT command for this message type 
 925          @param params: dictionary of values keyed by field name 
 926          @param outfile: file like object to print to. 
 927          @param extraParams: A sequence of tuples containing (name,sql type) for additional fields 
 928          @return: sql create string 
 929          @rtype: str 
 930   
 931          @see: sqlCreate 
 932          \'\'\' 
 933          outfile.write(str(sqlInsert(params,extraParams,dbType=dbType))) 
 934   
 935   
 936  ''') 
 937   
 938       
 939      o.write('''def '''+insertFuncName+'''(params,extraParams=None,dbType='postgres'): 
 940          \'\'\' 
 941          Give the SQL INSERT statement 
 942          @param params: dict keyed by field name of values 
 943          @param extraParams: any extra fields that you have created beyond the normal ais message fields 
 944          @rtype: sqlhelp.insert 
 945          @return: insert class instance 
 946          @todo: allow optional type checking of params? 
 947          @warning: this will take invalid keys happily and do what??? 
 948          \'\'\' 
 949          import sqlhelp 
 950          i = sqlhelp.insert(\'''' + msgName + '''\',dbType=dbType) 
 951   
 952          if dbType=='postgres': 
 953                  finished = [] 
 954                  for key in params: 
 955                          if key in finished:  
 956                                  continue 
 957   
 958                          if key not in toPgFields and key not in fromPgFields: 
 959                                  if type(params[key])==Decimal: i.add(key,float(params[key])) 
 960                                  else: i.add(key,params[key]) 
 961                          else: 
 962                                  if key in fromPgFields: 
 963                                          val = params[key] 
 964                                          # Had better be a WKT type like POINT(-88.1 30.321) 
 965                                          i.addPostGIS(key,val) 
 966                                          finished.append(key) 
 967                                  else: 
 968                                          # Need to construct the type. 
 969                                          pgName = toPgFields[key] 
 970                                          #valStr='GeomFromText(\\''+pgTypes[pgName]+'(' 
 971                                          valStr=pgTypes[pgName]+'(' 
 972                                          vals = [] 
 973                                          for nonPgKey in fromPgFields[pgName]: 
 974                                                  vals.append(str(params[nonPgKey])) 
 975                                                  finished.append(nonPgKey) 
 976                                          valStr+=' '.join(vals)+')' 
 977                                          i.addPostGIS(pgName,valStr) 
 978          else: 
 979                  for key in params:  
 980                          if type(params[key])==Decimal: i.add(key,float(params[key])) 
 981                          else: i.add(key,params[key]) 
 982   
 983          if None != extraParams: 
 984                  for key in extraParams:  
 985                          i.add(key,extraParams[key]) 
 986   
 987          return i 
 988  ''') 
  989   
 990   
 991   
 992   
 993   
 994   
 995   
 996   
 997 -def buildLUT(o,msgET, verbose=False, prefixName=False): 
  998      ''' 
 999      Write lookup tables for enumerated types (uint or int, maybe bool too). 
1000   
1001      @todo: FIX: what to do about multiple entries with the same text?  Need to ban that kind of thing 
1002      @todo: Make doc strings for each LUT. 
1003   
1004      @param o: open file where resulting code will be written 
1005      @param msgET: Element Tree starting at a message node 
1006      ''' 
1007      assert(msgET.tag=='message') 
1008      msgname = msgET.attrib['name'] 
1009   
1010      print 'Generating lookup tables ...',msgname  
1011   
1012      for field in msgET.xpath('field'): 
1013          name = field.attrib['name'] 
1014          if not hasSubTag(field,'lookuptable'): continue 
1015          lut = field.xpath('lookuptable')[0] 
1016   
1017          lutName = name 
1018          if prefixName: lutName = msgname+name.capitalize() 
1019   
1020          o.write(lutName+'EncodeLut = {\n') 
1021          for entry in lut.xpath('entry'): 
1022              o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n') 
1023          o.write('\t} #'+lutName+'EncodeLut\n') 
1024          o.write('\n') 
1025   
1026           
1027   
1028          o.write(lutName+'DecodeLut = {\n') 
1029          for entry in lut.xpath('entry'): 
1030              o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n') 
1031          o.write('\t} # '+lutName+'EncodeLut\n') 
1032          o.write('\n') 
 1033   
1034           
1035   
1036   
1037   
1038   
1039   
1040   
1041   
1042 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False): 
 1043      ''' 
1044      Build the encoder for boolean variables 
1045      @type o: file like obj 
1046      @param o: where write the code  
1047      @type name: str 
1048      @param name: field name 
1049      @type type: str 
1050      @param type: bool, etc. 
1051      @type numbits: int = 1 
1052      @param numbits: How many bits per unit datum (must be 1 for bools) 
1053      @type required: bool or None 
1054      @param required: If not None, then the value must be set to this. 
1055      @type arraylen: int >= 1 
1056      @param arraylen: many bools will there be?  FIX: handle variable 
1057      @type unavailable: bool or None 
1058      @param unavailable: the default value to use if none given (if not None) 
1059      @return: None 
1060      ''' 
1061       
1062      if verbose: print 'bool encode',name,': unvail=',unavailable 
1063   
1064      assert type.lower()=='bool' 
1065      assert numbits==1 
1066      if arraylen != 1: assert False  
1067      if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 
1068      if None != required: 
1069          assert type(required)==bool 
1070          if required: o.write('\t\tbvList.append(TrueBV)\n') 
1071          else: o.write('\t\tbvList.append(FalseBV)\n') 
1072          if verbose: o.write('\n') 
1073          return 
1074   
1075      if None==unavailable: 
1076          o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 
1077          o.write('\telse: bvList.append(FalseBV)\n') 
1078      else:  
1079          assert type(unavailable)==bool 
1080          o.write("\tif '"+name+"' in params:\n") 
1081          o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 
1082          o.write('\t\telse: bvList.append(FalseBV)\n') 
1083          o.write('\telse:\n') 
1084          if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 
1085          else: o.write('\t\tbvList.append(FalseBV)\n') 
1086      if verbose: o.write('\n') 
 1087   
1088 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False): 
 1089      ''' 
1090      Build the encoder for unsigned integer variables 
1091   
1092      @type o: file like obj 
1093      @param o: where write the code  
1094      @type name: str 
1095      @param name: field name 
1096      @type type: str 
1097      @param type: uint, bool, etc. 
1098      @type numbits: int >= 1 
1099      @param numbits: How many bits per unit datum (must be 1..32) 
1100      @type required: bool or None 
1101      @param required: If not None, then the value must be set to this. 
1102      @type arraylen: int >= 1 
1103      @param arraylen: many unsigned ints will there be?  FIX: handle variable 
1104      @type unavailable: bool or None 
1105      @param unavailable: the default value to use if none given (if not None) 
1106      @return: None 
1107      ''' 
1108      if verbose: print '  encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 
1109   
1110      assert type=='uint' 
1111      assert numbits>=1 and numbits<=32 
1112      if arraylen != 1: assert False  
1113      if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 
1114   
1115      if None != required: 
1116          if verbose: print '  required:',required 
1117          required=int(required) 
1118          o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 
1119          if verbose: o.write('\n') 
1120          return 
1121   
1122      if None==unavailable: 
1123          o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 
1124      else:  
1125           
1126          int(unavailable)  
1127          o.write("\tif '"+name+"' in params:\n") 
1128          o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 
1129          o.write('\telse:\n') 
1130          o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 
1131   
1132      if verbose: o.write('\n') 
 1133   
1134   
1135 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False): 
 1136      ''' 
1137      Build the encoder for IEEE float variables 
1138   
1139      @type o: file like obj 
1140      @param o: where write the code  
1141      @type name: str 
1142      @param name: field name 
1143      @type fieldType: str 
1144      @param fieldType: uint, bool, etc. 
1145      @type numbits: int >= 1 
1146      @param numbits: How many bits per unit datum (must be 1..32) 
1147      @type required: bool or None 
1148      @param required: If not None, then the value must be set to this. 
1149      @type arraylen: int >= 1 
1150      @param arraylen: many unsigned ints will there be?  FIX: handle variable 
1151      @type unavailable: bool or None 
1152      @param unavailable: the default value to use if none given (if not None) 
1153      @return: None 
1154      ''' 
1155      if verbose: print '  encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 
1156   
1157      assert numbits==32  
1158      if arraylen != 1: assert False  
1159      if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 
1160   
1161      if None != required: 
1162          if verbose: print '  required:',required 
1163          required=int(required) 
1164          o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 
1165          if verbose: o.write('\n') 
1166          return 
1167   
1168      if None==unavailable: 
1169          o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 
1170      else:  
1171          int(unavailable)  
1172          o.write("\tif '"+name+"' in params:\n") 
1173          o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 
1174          o.write('\telse:\n') 
1175          o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 
1176   
1177      if verbose: o.write('\n') 
 1178   
1179   
1180 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False): 
 1181      ''' 
1182      Build the encoder for aisstr6 variables.  Generally are arrays. 
1183      @bug: do we need to optionally check for a valid string? 
1184   
1185      @type o: file like obj 
1186      @param o: where write the code  
1187      @type name: str 
1188      @param name: field name 
1189      @type fieldType: str 
1190      @param fieldType: uint, bool, etc. 
1191      @type numbits: int >= 1 
1192      @param numbits: How many bits per unit datum (must be 1..32) 
1193      @type required: bool or None 
1194      @param required: If not None, then the value must be set to this. 
1195      @type arraylen: int >= 1 
1196      @param arraylen: many unsigned ints will there be?  FIX: handle variable 
1197      @type unavailable: bool or None 
1198      @param unavailable: the default value to use if none given (if not None) 
1199      @return: None 
1200      ''' 
1201      if verbose: print '  encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 
1202      totLen = str(numbits*arraylen) 
1203      assert numbits==6  
1204      if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 
1205   
1206      if None != required: 
1207          if verbose: print '  required:',required 
1208          required=int(required) 
1209          o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n') 
1210          if verbose: o.write('\n') 
1211          return 
1212   
1213      if None==unavailable: 
1214          o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 
1215      else:  
1216          o.write("\tif '"+name+"' in params:\n") 
1217          o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 
1218          o.write('\telse:\n') 
1219          o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n') 
1220   
1221      if verbose: o.write('\n') 
 1222   
1223   
1224 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False): 
 1225      ''' 
1226      Build the encoder for signed integer variables 
1227   
1228      @type o: file like obj 
1229      @param o: where write the code  
1230      @type name: str 
1231      @param name: field name 
1232      @type type: str 
1233      @param type: uint, bool, etc. 
1234      @type numbits: int >= 1 
1235      @param numbits: How many bits per unit datum (must be 1..32) 
1236      @type required: bool or None 
1237      @param required: If not None, then the value must be set to this. 
1238      @type arraylen: int >= 1 
1239      @param arraylen: many signed ints will there be?  FIX: handle variable 
1240      @type unavailable: number or None 
1241      @param unavailable: the default value to use if none given (if not None) 
1242      @return: None 
1243      ''' 
1244      if verbose: print '  encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 
1245   
1246      assert numbits>=1 and numbits<=32 
1247      if arraylen != 1: assert False  
1248      if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 
1249   
1250      if None != required: 
1251          if verbose: print '  required:',required 
1252          required=int(required) 
1253          o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 
1254          if verbose: o.write('\n') 
1255          return 
1256   
1257   
1258      if None==unavailable: 
1259          o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 
1260      else:  
1261           
1262          int(unavailable)  
1263          o.write("\tif '"+name+"' in params:\n") 
1264          o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 
1265          o.write('\telse:\n') 
1266          o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 
1267   
1268      if verbose: o.write('\n') 
 1269   
1270   
1271   
1272   
1273 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None): 
 1274      ''' 
1275      Build the encoder for signed decimal variables 
1276   
1277      @type o: file like obj 
1278      @param o: where write the code  
1279      @type name: str 
1280      @param name: field name 
1281      @type type: str 
1282      @param type: decimal 
1283      @type numbits: int >= 1 
1284      @param numbits: How many bits per unit datum (must be 1..32) 
1285      @type required: bool or None 
1286      @param required: If not None, then the value must be set to this. 
1287      @type arraylen: int >= 1 
1288      @param arraylen: many decimals will there be?  FIX: handle variable 
1289      @type unavailable: Decimal or None 
1290      @param unavailable: the default value to use if none given (if not None) 
1291      @return: None 
1292      ''' 
1293      if verbose: print '  encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 
1294   
1295      assert numbits>=1 and numbits<=32 
1296      if arraylen != 1: assert False  
1297      if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 
1298   
1299       
1300      if None == scale:  
1301          print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 
1302          print 'Beware canadians bearing travel videos' 
1303          scale='1' 
1304   
1305      if None != required: 
1306          if verbose: print '  required:',required 
1307          required=int(required) 
1308          o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 
1309          if verbose: o.write('\n') 
1310          return 
1311   
1312      offsetStr='' 
1313      if None != offset: 
1314          offsetStr='-('+offset+')' 
1315   
1316       
1317      if None==unavailable: 
1318          o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 
1319      else:  
1320          o.write("\tif '"+name+"' in params:\n") 
1321          o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 
1322          o.write('\telse:\n') 
1323          o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 
1324   
1325      if verbose: o.write('\n') 
 1326   
1327   
1328   
1329 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None): 
 1330      ''' 
1331      Build the encoder for signed decimal variables 
1332   
1333      @type o: file like obj 
1334      @param o: where write the code  
1335      @type name: str 
1336      @param name: field name 
1337      @type type: str 
1338      @param type: decimal 
1339      @type numbits: int >= 1 
1340      @param numbits: How many bits per unit datum (must be 1..32) 
1341      @type required: bool or None 
1342      @param required: If not None, then the value must be set to this. 
1343      @type arraylen: int >= 1 
1344      @param arraylen: many decimals will there be?  FIX: handle variable 
1345      @type unavailable: Decimal or None 
1346      @param unavailable: the default value to use if none given (if not None) 
1347      @return: None 
1348      ''' 
1349      if verbose: print '  encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 
1350      assert type=='udecimal' 
1351      assert numbits>=1 and numbits<=32 
1352      if arraylen != 1: assert False  
1353      if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 
1354   
1355       
1356      if None == scale:  
1357          print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 
1358          print 'Beware canadians bearing travel videos' 
1359          scale='1' 
1360   
1361      if None != required: 
1362          if verbose: print '  required:',required 
1363          required=int(required) 
1364          assert(0<=required) 
1365          o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n') 
1366          if verbose: o.write('\n') 
1367          return 
1368   
1369      offsetStr='' 
1370      if None != offset: 
1371          offsetStr='-('+offset+')' 
1372   
1373       
1374      if None==unavailable: 
1375          o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 
1376      else:  
1377          o.write("\tif '"+name+"' in params:\n") 
1378          o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 
1379          o.write('\telse:\n') 
1380          o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n') 
1381   
1382      if verbose: o.write('\n') 
 1383   
1384   
1385   
1386 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None): 
 1387      ''' 
1388      Build the encoder for binary variables.  This is just a pass through. 
1389      This is used for the ais binary message wrapper (e.g. msg 8).  Do 
1390      not use it within binary messages. 
1391   
1392      @type o: file like obj 
1393      @param o: where write the code  
1394      @type name: str 
1395      @param name: field name 
1396      @type type: str 
1397      @param type: binary 
1398      @type numbits: int >= 1 
1399      @param numbits: How many bits per unit datum (must be 1..1024 or so) 
1400      @type required: bool or None 
1401      @param required: If not None, then the value must be set to this. 
1402      @type arraylen: int >= 1 
1403      @param arraylen: many decimals will there be?  FIX: handle variable 
1404      @type unavailable: Decimal or None 
1405      @param unavailable: the default value to use if none given (if not None) 
1406      @return: None 
1407      ''' 
1408      if verbose: print '  encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable 
1409      assert type=='binary' 
1410      assert (numbits>=1 and numbits<=1024) or numbits==-1 
1411      assert (None == required)   
1412      assert (None == unavailable)  
1413   
1414      if arraylen != 1: assert False  
1415      if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 
1416   
1417       
1418      o.write('\tbvList.append(params[\''+name+'\'])\n')   
1419   
1420      if verbose: o.write('\n') 
 1421   
1422   
1423   
1424   
1425   
1426   
1427 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1428                 bv='bv',dataDict='r',verbose=False, decodeOnly=False): 
 1429      ''' 
1430      Build the decoder for boolean variables 
1431   
1432      @type o: file like obj 
1433      @param o: where write the code  
1434      @type name: str 
1435      @param name: field name 
1436      @type type: str 
1437      @param type: uint, bool, etc. 
1438      @type startindex: int 
1439      @param startindex: bit that begins the bool(s) 
1440      @type numbits: int = 1 
1441      @param numbits: How many bits per unit datum (must be 1 for bools) 
1442      @type required: bool or None 
1443      @param required: If not None, then the value must be set to this. 
1444      @type arraylen: int >= 1 
1445      @param arraylen: many bools will there be?  FIX: handle variable 
1446      @type unavailable: bool or None 
1447      @param unavailable: the default value to use if none given (if not None) 
1448      @type bv: str 
1449      @param bv: BitVector containing the incoming data 
1450      @type dataDict: str 
1451      @param dataDict: dictionary in which to place the results 
1452      @type decodeOnly: bool 
1453      @param decodeOnly: Set to true to only get the code for decoding 
1454      @rtype: int 
1455      @return: index one past the end of where this read 
1456      ''' 
1457      assert(type=='bool') 
1458      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1459       
1460      assert numbits==1 
1461      assert arraylen == 1  
1462   
1463      if None != required: 
1464          assert type(required)==bool 
1465          if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1466          if required: o.write('True\n') 
1467          else: o.write('False\n') 
1468          if not decodeOnly: o.write('\n') 
1469          return int(startindex)+int(numbits) 
1470   
1471      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1472      o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))') 
1473      if not decodeOnly: o.write('\n') 
1474   
1475      return int(startindex)+int(numbits) 
 1476   
1477   
1478 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1479                 bv='bv',dataDict='r',verbose=False, decodeOnly=False): 
 1480      ''' 
1481      Build the decoder for unsigned integer variables 
1482   
1483      @type o: file like obj 
1484      @param o: where write the code  
1485      @type name: str 
1486      @param name: field name 
1487      @type type: str 
1488      @param type: uint, etc. 
1489      @type startindex: int 
1490      @param startindex: bit that begins the uint(s) 
1491      @type numbits: int >= 1 
1492      @param numbits: How many bits per unit datum 
1493      @type required: int or None 
1494      @param required: If not None, then the value must be set to this. 
1495      @type arraylen: int >= 1 
1496      @param arraylen: many ints will there be?  FIX: handle variable 
1497      @type unavailable: int or None 
1498      @param unavailable: the default value to use if none given (if not None) 
1499      @type bv: str 
1500      @param bv: BitVector containing the incoming data 
1501      @type dataDict: str 
1502      @param dataDict: dictionary in which to place the results 
1503      @type decodeOnly: bool 
1504      @param decodeOnly: Set to true to only get the code for decoding 
1505      @rtype: int 
1506      @return: index one past the end of where this read 
1507      ''' 
1508      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1509      if None==arraylen: arraylen=1 
1510      assert arraylen == 1  
1511      assert numbits>=1 
1512      if not decodeOnly: verbose=False 
1513   
1514      if None != required: 
1515          int(required)  
1516          if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1517          o.write(str(required)) 
1518          if not decodeOnly: o.write('\n') 
1519          return startindex+numbits 
1520   
1521      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1522      o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])') 
1523      if not decodeOnly: o.write('\n') 
1524      if verbose: o.write('\n') 
1525       
1526      return startindex+numbits 
 1527   
1528   
1529 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1530                 bv='bv',dataDict='r',verbose=False, decodeOnly=False): 
 1531      ''' 
1532      Build the decoder for unsigned integer variables 
1533   
1534      @type o: file like obj 
1535      @param o: where write the code  
1536      @type name: str 
1537      @param name: field name 
1538      @type type: str 
1539      @param type: int 
1540      @type startindex: int 
1541      @param startindex: bit that begins the int(s) 
1542      @type numbits: int >= 1 
1543      @param numbits: How many bits per unit datum 
1544      @type required: int or None 
1545      @param required: If not None, then the value must be set to this. 
1546      @type arraylen: int >= 1 
1547      @param arraylen: many ints will there be?  FIX: handle variable 
1548      @type unavailable: int or None 
1549      @param unavailable: the default value to use if none given (if not None) 
1550      @type bv: str 
1551      @param bv: BitVector containing the incoming data 
1552      @type dataDict: str 
1553      @param dataDict: dictionary in which to place the results 
1554      @type decodeOnly: bool 
1555      @param decodeOnly: Set to true to only get the code for decoding 
1556      @rtype: int 
1557      @return: index one past the end of where this read 
1558      ''' 
1559      assert type=='int' 
1560      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1561      if None==arraylen: arraylen=1 
1562      end = startindex+int(numbits)*int(arraylen) 
1563      assert arraylen == 1  
1564      assert numbits>=1 
1565   
1566      if None != required: 
1567          int(required)  
1568          if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1569          o.write(str(required)) 
1570          if not decodeOnly: o.write('\n') 
1571          return end 
1572   
1573      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1574      o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])') 
1575      if not decodeOnly: o.write('\n') 
1576      if verbose: o.write('\n') 
1577       
1578      return end 
 1579   
1580 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1581                 bv='bv',dataDict='r',verbose=False, decodeOnly=False): 
 1582      ''' 
1583      Build the decoder for IEEE float variables 
1584   
1585      @type o: file like obj 
1586      @param o: where write the code  
1587      @type name: str 
1588      @param name: field name 
1589      @type type: str 
1590      @param type: int 
1591      @type startindex: int 
1592      @param startindex: bit that begins the int(s) 
1593      @type numbits: int >= 1 
1594      @param numbits: How many bits per unit datum 
1595      @type required: float or None 
1596      @param required: If not None, then the value must be set to this. 
1597      @type arraylen: int >= 1 
1598      @param arraylen: many ints will there be?  FIX: handle variable 
1599      @type unavailable: float or None 
1600      @param unavailable: the default value to use if none given (if not None) 
1601      @type bv: str 
1602      @param bv: BitVector containing the incoming data 
1603      @type dataDict: str 
1604      @param dataDict: dictionary in which to place the results 
1605      @type decodeOnly: bool 
1606      @param decodeOnly: Set to true to only get the code for decoding 
1607      @rtype: int 
1608      @return: index one past the end of where this read 
1609      ''' 
1610      assert type=='float' 
1611      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1612      if None==arraylen: arraylen=1 
1613      end = startindex+int(numbits)*int(arraylen) 
1614      assert arraylen == 1  
1615      assert numbits>=1 
1616   
1617      if None != required: 
1618          float(required)  
1619          if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1620          o.write(str(required)) 
1621          if not decodeOnly: o.write('\n') 
1622          if verbose: o.write('\n') 
1623          return end 
1624   
1625      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1626      o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])') 
1627      if not decodeOnly: o.write('\n') 
1628       
1629      return end 
 1630   
1631   
1632 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1633                    bv='bv',dataDict='r',verbose=False, decodeOnly=False): 
 1634      ''' 
1635      Build the decoder for aisstr6 variables.  Generally arrays. 
1636      @bug: FIX: validate strings?? 
1637      @type o: file like obj 
1638      @param o: where write the code  
1639      @type name: str 
1640      @param name: field name 
1641      @type type: str 
1642      @param type: 'aisstr6' 
1643      @type startindex: int 
1644      @param startindex: bit that begins the int(s) 
1645      @type numbits: int >= 1 
1646      @param numbits: How many bits per unit datum 
1647      @type required: restricted str or None 
1648      @param required: If not None, then the value must be set to this. 
1649      @type arraylen: int >= 1 
1650      @param arraylen: many ints will there be?  FIX: handle variable 
1651      @type unavailable: restricted str or None 
1652      @param unavailable: the default value to use if none given (if not None) 
1653      @type bv: str 
1654      @param bv: BitVector containing the incoming data 
1655      @type dataDict: str 
1656      @param dataDict: dictionary in which to place the results 
1657      @type decodeOnly: bool 
1658      @param decodeOnly: Set to true to only get the code for decoding 
1659      @rtype: int 
1660      @return: index one past the end of where this read 
1661      ''' 
1662      assert type=='aisstr6' 
1663      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1664      if None==arraylen: arraylen=1 
1665      end = startindex+int(numbits)*int(arraylen) 
1666      assert arraylen >= 1  
1667      assert numbits>=1 
1668   
1669      if None != required: 
1670          float(required)  
1671          if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1672          o.write(required) 
1673          if not decodeOnly: o.write('\n') 
1674          return end 
1675   
1676      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1677      o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])') 
1678      if not decodeOnly: o.write('\n') 
1679       
1680      return end 
 1681   
1682   
1683 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1684                 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None): 
 1685      ''' 
1686      Build the decoder for signed decimal variables 
1687   
1688      @type o: file like obj 
1689      @param o: where write the code  
1690      @type name: str 
1691      @param name: field name 
1692      @type type: str 
1693      @param type: 'decimal' 
1694      @type startindex: int 
1695      @param startindex: bit that begins the int(s) 
1696      @type numbits: int >= 1 
1697      @param numbits: How many bits per unit datum 
1698      @type required: Decimal or None 
1699      @param required: If not None, then the value must be set to this. 
1700      @type arraylen: int >= 1 
1701      @param arraylen: many ints will there be?  FIX: handle variable 
1702      @type unavailable: Decimal or None 
1703      @param unavailable: the default value to use if none given (if not None) 
1704      @type bv: str 
1705      @param bv: BitVector containing the incoming data 
1706      @type dataDict: str 
1707      @param dataDict: dictionary in which to place the results 
1708      @type decodeOnly: bool 
1709      @param decodeOnly: Set to true to only get the code for decoding 
1710      @rtype: int 
1711      @return: index one past the end of where this read 
1712      ''' 
1713      assert type=='decimal' 
1714      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1715      if None==arraylen: arraylen=1 
1716      end = startindex+int(numbits)*int(arraylen) 
1717      assert arraylen == 1  
1718      assert numbits>=1 and numbits <= 32 
1719   
1720      if None == scale: scale='1'  
1721   
1722      if None != required: 
1723          Decimal(required)  
1724          if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1725          o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 
1726          if not decodeOnly: o.write('\n') 
1727          return end 
1728   
1729      offsetStr='' 
1730      if offset != None: 
1731          offsetStr += '+Decimal(\''+offset+'\')' 
1732   
1733      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1734      o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'') 
1735      if not decodeOnly: o.write('\n') 
1736       
1737      return end 
 1738   
1739   
1740   
1741   
1742 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1743                 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None): 
 1744      ''' 
1745      Build the decoder for unsigned decimal variables 
1746   
1747      @type o: file like obj 
1748      @param o: where write the code  
1749      @type name: str 
1750      @param name: field name 
1751      @type type: str 
1752      @param type: 'udecimal' 
1753      @type startindex: int 
1754      @param startindex: bit that begins the int(s) 
1755      @type numbits: int >= 1 
1756      @param numbits: How many bits per unit datum 
1757      @type required: Decimal or None 
1758      @param required: If not None, then the value must be set to this. 
1759      @type arraylen: int >= 1 
1760      @param arraylen: many ints will there be?  FIX: handle variable 
1761      @type unavailable: Decimal or None 
1762      @param unavailable: the default value to use if none given (if not None) 
1763      @type bv: str 
1764      @param bv: BitVector containing the incoming data 
1765      @type dataDict: str 
1766      @param dataDict: dictionary in which to place the results 
1767      @type decodeOnly: bool 
1768      @param decodeOnly: Set to true to only get the code for decoding 
1769      @rtype: int 
1770      @return: index one past the end of where this read 
1771      ''' 
1772      assert type=='udecimal' 
1773      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1774      if None==arraylen: arraylen=1 
1775      end = startindex+int(numbits)*int(arraylen) 
1776      assert arraylen == 1  
1777      assert numbits>=1 and numbits <= 32 
1778   
1779      if None == scale: scale='1'  
1780   
1781      if None != required: 
1782          assert (Decimal(required)>=0.)  
1783          if not decodeOnly:      o.write('\t'+dataDict+'[\''+name+'\']=') 
1784          o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 
1785          if not decodeOnly: o.write('\n') 
1786          return end 
1787   
1788      offsetStr='' 
1789      if offset != None: 
1790          offsetStr += '+Decimal(\''+offset+'\')' 
1791   
1792      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1793      o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'') 
1794      if not decodeOnly: o.write('\n') 
1795       
1796      return end 
 1797   
1798   
1799 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 
1800                 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False): 
 1801      ''' 
1802      Build the decoder for unsigned decimal variables 
1803   
1804      @type o: file like obj 
1805      @param o: where write the code  
1806      @type name: str 
1807      @param name: field name 
1808      @type type: str 
1809      @param type: 'udecimal' 
1810      @type startindex: int 
1811      @param startindex: bit that begins the int(s) 
1812      @type numbits: int >= 1 
1813      @param numbits: How many bits per unit datum.  If -1, then read to the end of the message 
1814      @type required: Decimal or None 
1815      @param required: If not None, then the value must be set to this. 
1816      @type arraylen: int >= 1 
1817      @param arraylen: many ints will there be?  FIX: handle variable 
1818      @type unavailable: Decimal or None 
1819      @param unavailable: the default value to use if none given (if not None) 
1820      @type bv: str 
1821      @param bv: BitVector containing the incoming data 
1822      @type dataDict: str 
1823      @param dataDict: dictionary in which to place the results 
1824      @type decodeOnly: bool 
1825      @param decodeOnly: Set to true to only get the code for decoding 
1826      @rtype: int 
1827      @return: index one past the end of where this read 
1828      ''' 
1829      assert type=='binary' 
1830      if verbose: print type,'decode',name,': unvail=',unavailable,'  numbits:',numbits, '  startindex=',startindex 
1831      if None==arraylen: arraylen=1 
1832      end = startindex+int(numbits)*int(arraylen) 
1833      assert arraylen == 1  
1834      assert (numbits>=1 and numbits <= 1024) or -1==numbits  
1835       
1836   
1837   
1838      if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 
1839      o.write(bv+'['+str(startindex)+':') 
1840      if int(numbits) != -1: o.write(str(end))   
1841      o.write(']') 
1842      if not decodeOnly: o.write('\n') 
1843       
1844      return end 
 1845   
1846   
1847   
1848   
1849   
1850   
1851   
1852   
1853   
1855      '''Scrape the testvalues to make a basic param 
1856   
1857      @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 
1858      ''' 
1859      name = msgET.attrib['name'] 
1860   
1861      if prefixName: o.write('def '+name+'TestParams():\n') 
1862      else: o.write('def testParams():\n') 
1863      o.write("\t'''Return a params file base on the testvalue tags.\n\t@rtype: dict\n\t@return: params based on testvalue tags\n\t'''\n") 
1864      o.write('\tparams = {}\n') 
1865      for field in msgET.xpath('field'): 
1866          name = field.attrib['name'] 
1867          type = field.attrib['type'] 
1868          if verbose: print 'buildTestParamFunc ...',name,type 
1869          val = None 
1870          if hasSubTag(field,'testvalue') and hasSubTag(field,'required'): 
1871              print 'ERROR: can not have both test value and required tags in the same field' 
1872              assert(False) 
1873          if hasSubTag(field,'testvalue'): 
1874              val = field.xpath('testvalue')[0].text 
1875          else: 
1876              if not hasSubTag(field,'required'): 
1877                  sys.exit("ERROR: missing required or testvalue for field: "+name) 
1878              val = field.xpath('required')[0].text 
1879          if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 
1880          o.write('\tparams[\''+name+'\'] = ') 
1881          if type=='bool':  
1882              if val=='1' or val.lower=='true': val = 'True' 
1883              else: val = 'False' 
1884              o.write(val) 
1885          elif type in ('uint','int','float'): 
1886              o.write(val) 
1887          elif type in ('decimal','udecimal'): 
1888              o.write('Decimal(\''+val+'\')') 
1889   
1890          elif type in ('aisstr6'): 
1891              o.write('\''+val+'\'') 
1892          elif type in ('binary'): 
1893              o.write('BitVector(bitstring=\''+val+'\')') 
1894          else: 
1895              print 'ERROR: type not handled ...',type,'  (found in the ',name,' field).  Time to buy more coffee' 
1896              suggestType(name,type) 
1897              assert(False) 
1898   
1899          o.write('\n') 
1900   
1901   
1902      o.write('\n\treturn params\n\n') 
 1903   
1905      ''' 
1906      Write the unittests for a message 
1907   
1908      @param o: open file where resulting code will be written 
1909      @param msgET: Element Tree starting at a message node 
1910      ''' 
1911      assert(msgET.tag=='message') 
1912      name = msgET.attrib['name'] 
1913   
1914      buildTestParamFunc(o,msgET, prefixName=prefixName) 
1915   
1916      o.write('class Test'+name+'(unittest.TestCase):\n') 
1917      o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n") 
1918      o.write('\tdef testEncodeDecode(self):\n\n') 
1919      if prefixName: 
1920          o.write('\t\tparams = '+name+'TestParams()\n') 
1921          o.write('\t\tbits   = '+name+'Encode(params)\n') 
1922          o.write('\t\tr      = '+name+'Decode(bits)\n\n') 
1923      else: 
1924          o.write('\t\tparams = testParams()\n') 
1925          o.write('\t\tbits   = encode(params)\n') 
1926          o.write('\t\tr      = decode(bits)\n\n') 
1927   
1928      o.write('\t\t# Check that each parameter came through ok.\n') 
1929      for field in msgET.xpath('field'): 
1930          name = field.attrib['name'] 
1931          type = field.attrib['type'] 
1932          if type in ('bool','uint','int','aisstr6','binary'): 
1933              o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 
1934          else: 
1935               
1936               
1937              places = '3' 
1938              if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 
1939              o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n') 
 1940           
1941   
1942   
1943 -def buildEncode(o,msgET, verbose=False, prefixName=False): 
 1944      ''' 
1945      Write the encoder/decoder for a message 
1946   
1947      http://jaynes.colorado.edu/PythonIdioms.html 
1948   
1949      @param o: open file where resulting code will be written 
1950      @param msgET: Element Tree starting at a message node 
1951      @todo: handle ais message 20 optional.  very troublesome 
1952      ''' 
1953      assert(msgET.tag=='message') 
1954      name = msgET.attrib['name']  
1955   
1956      print 'Generating encoder ...',name  
1957      funcName = 'encode' 
1958      if prefixName: funcName = name+'Encode' 
1959      o.write('def '+funcName+'(params, validate=False):\n') 
1960   
1961       
1962       
1963      o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n") 
1964      o.write('\tFields in params:\n') 
1965      for field in msgET.xpath('field'): 
1966          if verbose:  
1967              print field.tag,field.attrib['name'] 
1968              print field.attrib 
1969          desc = field[0].text.replace('\n',' ')  
1970          o.write('\t  - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)  
1971          if len(field.xpath("required")) == 1: 
1972              o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 
1973   
1974          o.write('\n') 
1975      o.write('\t@param params: Dictionary of field names/values.  Throws a ValueError exception if required is missing\n') 
1976      o.write('\t@param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.\n') 
1977           
1978      o.write("\t@rtype: BitVector\n") 
1979      o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n") 
1980      o.write("\t@note: The returned bits may not be 6 bit aligned.  It is up to you to pad out the bits.\n") 
1981      o.write("\t'''\n\n") 
1982   
1983       
1984       
1985   
1986      o.write('\tbvList = []\n') 
1987   
1988      if verbose: print 'number of fields = ', len(msgET.xpath('field')) 
1989   
1990      dynamicArrays = False  
1991   
1992      for field in msgET.xpath('field'): 
1993          name = field.attrib['name'] 
1994          type = field.attrib['type'] 
1995          numbits = int(field.attrib['numberofbits']) 
1996          required = None;  
1997          if hasSubTag(field,'required'):  
1998              required = field.xpath('required')[0].text 
1999          unavailable=None;  
2000          if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 
2001          arraylen=1 
2002          if 'arraylength' in field.attrib:  
2003              arraylen=int(field.attrib['arraylength']) 
2004              if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 
2005          else: 
2006              if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 
2007   
2008          if   type=='bool'   : encodeBool   (o,name,type,numbits,required,arraylen,unavailable) 
2009          elif type=='uint'   : encodeUInt   (o,name,type,numbits,required,arraylen,unavailable) 
2010          elif type=='int'    : encodeInt    (o,name,type,numbits,required,arraylen,unavailable)       
2011          elif type=='float'  : encodeFloat  (o,name,type,numbits,required,arraylen,unavailable) 
2012          elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable)       
2013          elif type=='decimal': 
2014              scale = None 
2015              if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 
2016              offset = None 
2017              if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 
2018              encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale, offset=offset) 
2019          elif type=='udecimal': 
2020              scale = None 
2021              if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 
2022              offset = None 
2023              if hasSubTag(field,'offset'):  
2024                  offset = field.xpath('offset')[0].text 
2025              encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale,offset=offset) 
2026          elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable) 
2027          else:  
2028              print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 
2029              suggestType (name,type) 
2030              assert False 
2031   
2032   
2033   
2034   
2035   
2036   
2037      o.write('\n\treturn binary.joinBV(bvList)\n\n') 
 2038   
2039   
2040   
2041   
2042   
2043   
2044   
2046   
2047      ''' 
2048      Write the decoder for a message 
2049   
2050      @param o: open file where resulting code will be written 
2051      @type msgET: elementtree 
2052      @param prefixName: if True, put the name of the message on the functions. 
2053      @param msgET: Element Tree starting at a message node 
2054      @return: None 
2055   
2056      @todo: FIX: doc strings for each decode! 
2057      @todo: FIX: check for a dac,fid, or efid.  If exists, then this is an AIS Msg 8 payload 
2058      @todo: May want to take a dictionary of already decoded fields to speed things that need prior info 
2059      for things like variable length arrays 
2060      ''' 
2061   
2062      assert(msgET.tag=='message') 
2063      name = msgET.attrib['name'] 
2064   
2065      print 'Generating partial decode functions ...',name 
2066       
2067      baseName = name+'Decode' 
2068      if not prefixName: baseName = 'decode' 
2069   
2070      startindex = 0  
2071   
2072      for field in msgET.xpath('field'): 
2073          name = field.attrib['name'] 
2074          type = field.attrib['type'] 
2075   
2076          o.write('def '+baseName+name+'(bv, validate=False):\n') 
2077           
2078           
2079          o.write('\treturn ') 
2080   
2081          numbits = int(field.attrib['numberofbits']) 
2082          required = None;  
2083          if hasSubTag(field,'required'):  
2084              required = field.xpath('required')[0].text 
2085          unavailable=None;  
2086          if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 
2087          arraylen=1 
2088          if 'arraylength' in field.attrib:  
2089              arraylen=int(field.attrib['arraylength']) 
2090              if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 
2091   
2092          assert None!=startindex 
2093          if verbose: print 'startindex',startindex 
2094          if   type=='bool'   : startindex = decodeBool   (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 
2095          elif type=='uint'   : startindex = decodeUInt   (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 
2096          elif type=='int'    : startindex = decodeInt    (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 
2097          elif type=='float'  : startindex = decodeFloat  (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 
2098          elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 
2099          elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)       
2100   
2101          elif type=='decimal': 
2102              scale = None 
2103              if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 
2104              offset = None 
2105              if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 
2106              startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset) 
2107          elif type=='udecimal': 
2108              scale = None 
2109              if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 
2110              offset = None 
2111              if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 
2112              startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset) 
2113   
2114          else:  
2115              print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 
2116              suggestType (name,type) 
2117              assert False 
2118   
2119           
2120          o.write('\n\n') 
 2121   
2122   
2123   
2124   
2125   
2126 -def buildDecode(o,msgET, verbose=False, prefixName=False): 
 2127      ''' 
2128      Write the decoder for a message 
2129   
2130      @param o: open file where resulting code will be written 
2131      @type msgET: elementtree 
2132      @param msgET: Element Tree starting at a message node 
2133      @return: None 
2134   
2135      @todo: FIX: check for a dac,fid, or efid.  If exists, then this is an AIS Msg 8 payload 
2136      ''' 
2137   
2138      assert(msgET.tag=='message') 
2139      name = msgET.attrib['name'] 
2140   
2141      print 'Generating decoder ...',name 
2142      funcName = 'decode' 
2143      if prefixName: funcName = name+'Decode' 
2144      o.write('def '+funcName+'(bv, validate=False):\n') 
2145   
2146       
2147       
2148      o.write("\t'''Unpack a "+name+" message \n\n") 
2149      o.write('\tFields in params:\n') 
2150      for field in msgET.xpath('field'): 
2151          desc = field[0].text.replace('\n',' ')  
2152          o.write('\t  - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)  
2153          if len(field.xpath("required")) == 1: 
2154              o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 
2155   
2156          o.write('\n') 
2157      o.write('\t@type bv: BitVector\n') 
2158      o.write('\t@param bv: Bits defining a message\n') 
2159      o.write('\t@param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.\n') 
2160           
2161      o.write("\t@rtype: dict\n") 
2162      o.write("\t@return: params\n") 
2163      o.write("\t'''\n\n") 
2164   
2165   
2166      o.write('\t#Would be nice to check the bit count here..\n') 
2167      o.write('\t#if validate:\n') 
2168      o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n') 
2169   
2170   
2171       
2172       
2173   
2174      o.write('\tr = {}\n') 
2175   
2176   
2177      if verbose: print 'number of fields = ', len(msgET.xpath('field')) 
2178   
2179      dynamicArrays = False  
2180   
2181      startindex = 0  
2182   
2183      for field in msgET.xpath('field'): 
2184          name = field.attrib['name'] 
2185          type = field.attrib['type'] 
2186          numbits = int(field.attrib['numberofbits']) 
2187          required = None;  
2188          if hasSubTag(field,'required'):  
2189              required = field.xpath('required')[0].text 
2190          unavailable=None;  
2191          if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 
2192          arraylen=1 
2193          if 'arraylength' in field.attrib:  
2194              arraylen=int(field.attrib['arraylength']) 
2195              if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 
2196          else: 
2197              if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 
2198   
2199          assert None!=startindex 
2200          if verbose: print 'startindex',startindex 
2201   
2202          if hasSubTag(field,'optional'): 
2203               
2204               
2205              if msgET.attrib['aismsgnum']!='20': 
2206                  sys.exit('optional is only allow on AIS msg 20.  Do NOT use it on new messages!!!') 
2207              text = field.xpath('optional')[0].text 
2208              if None==field.xpath('optional')[0].text:  
2209                   
2210                  pad = 8-(startindex%8) 
2211                  print 'not found: noreturn.  pad=',pad 
2212                  assert(pad<=6) 
2213                  o.write('\tif len(bv)<='+str(startindex+pad)+': return r; # All fields below are optional\n') 
2214              elif text != 'NoReturn': 
2215                  sys.exit ('ERROR: optional text must be NoReturn or empty') 
2216   
2217          if   type=='bool'   : startindex = decodeBool   (o,name,type,startindex,numbits,required,arraylen,unavailable) 
2218          elif type=='uint'   : startindex = decodeUInt   (o,name,type,startindex,numbits,required,arraylen,unavailable) 
2219          elif type=='int'    : startindex = decodeInt    (o,name,type,startindex,numbits,required,arraylen,unavailable) 
2220          elif type=='float'  : startindex = decodeFloat  (o,name,type,startindex,numbits,required,arraylen,unavailable) 
2221          elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 
2222          elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable)       
2223   
2224          elif type=='decimal': 
2225              scale = None 
2226              if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 
2227              offset = None 
2228              if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 
2229              startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset) 
2230   
2231          elif type=='udecimal': 
2232              scale = None 
2233              if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 
2234              offset = None 
2235              if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 
2236              startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset) 
2237   
2238          else:  
2239              print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 
2240              suggestType (name,type) 
2241              assert False 
2242   
2243   
2244          if None==startindex: print 'FIX: here. drat.  treat me right' 
2245          assert None!=startindex 
2246   
2247   
2248      o.write('\treturn r\n\n') 
 2249   
2250   
2251   
2252   
2253   
2254   
2255   
2256   
2257   
2258   
2259  aisType2pythonType={ 
2260  'bool':'Bool', 
2261  'uint':'int', 
2262  'int':'int', 
2263  'udecimal':'Decimal', 
2264  'decimal':'Decimal', 
2265  'aisstr6':'str', 
2266  'binary':'str', 
2267  'float':'Float', 
2268  } 
2269   
2270  import copy 
2271  aisType2optParseType=copy.deepcopy(aisType2pythonType) 
2272  aisType2optParseType['bool']='int'  
2273  aisType2optParseType['aisstr6']='string' 
2274  aisType2optParseType['aisstr6']='string' 
2275  aisType2optParseType['binary']='string' 
2276  aisType2optParseType['udecimal']='string'   
2277  aisType2optParseType['decimal']='string' 
2278  aisType2optParseType['float']='float' 
2279   
2280   
2282      '''Create a function that adds the options to a parse object''' 
2283   
2284      assert None != msgET 
2285      assert msgET.tag=='message' 
2286      msgName = msgET.attrib['name'] 
2287   
2288      prefix='' 
2289      if prefixName: prefix=msgName 
2290   
2291      funcName = 'addMsgOptions' 
2292      if prefixName: funcName = msgName + 'AddMsgOptions' 
2293      o.write('\ndef '+funcName+'(parser):') 
2294   
2295       
2296   
2297      o.write(''' 
2298          parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true', 
2299                  help='decode a "'''+msgName+'''" AIS message') 
2300          parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true', 
2301                  help='encode a "'''+msgName+'''" AIS message') 
2302  ''')            
2303   
2304       
2305      for field in msgET.xpath('field'): 
2306          name      = field.attrib['name'] 
2307          fieldType = field.attrib['type'] 
2308          if hasSubTag(field,'required'): 
2309              print 'skipping required field ...',name,fieldType 
2310              continue 
2311           
2312          o.write('\tparser.add_option(\'--') 
2313          if prefixName: o.write(msgName+'-') 
2314          o.write(name+'-field\', dest=\''+name+'Field\'') 
2315          if hasSubTag(field,'unavailable'): 
2316              val = field.xpath('unavailable')[0].text 
2317              o.write(',default=') 
2318              if fieldType in ('uint','int','float'):  
2319                  o.write(val) 
2320              elif fieldType in ('decimal','udecimal'): 
2321                  o.write('Decimal(\''+val+'\')') 
2322              elif fieldType in ('aisstr6','bitvector'): 
2323                  o.write('\''+val+'\'') 
2324          o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'') 
2325          o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n') 
 2326   
2327   
2328   
2329 -def buildMain(o, msgET, prefixName=False): 
 2330      assert None != msgET 
2331      assert msgET.tag=='message' 
2332      msgName = msgET.attrib['name'] 
2333   
2334      prefix='' 
2335      if prefixName: prefix=msgName 
2336   
2337      buildOptParse(o, msgET, prefixName) 
2338   
2339   
2340      o.write(''' 
2341  ############################################################ 
2342  if __name__=='__main__': 
2343   
2344          from optparse import OptionParser 
2345          parser = OptionParser(usage="%prog [options]", 
2346                  version="%prog "+__version__) 
2347   
2348          parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 
2349                  help='run the documentation tests') 
2350          parser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 
2351                  help='run the unit tests') 
2352          parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 
2353                  help='Make the test output verbose') 
2354   
2355          # FIX: remove nmea from binary messages.  No way to build the whole packet? 
2356          # FIX: or build the surrounding msg 8 for a broadcast? 
2357          typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message? 
2358          parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType' 
2359                  ,default='nmeapayload' 
2360                  ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]') 
2361   
2362   
2363          outputChoices = ('std','html','csv','sql' ''') 
2364   
2365      if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'')  
2366      o.write(''') 
2367          parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType' 
2368                  ,default='std' 
2369                  ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]') 
2370   
2371          parser.add_option('-o','--output',dest='outputFileName',default=None, 
2372                            help='Name of the python file to write [default: stdout]') 
2373   
2374          parser.add_option('-f','--fields',dest='fieldList',default=None, action='append', 
2375                            choices=fieldList, 
2376                            help='Which fields to include in the output.  Currently only for csv output [default: all]') 
2377   
2378          parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true', 
2379                            help='Print the field name for csv') 
2380   
2381          parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true', 
2382                            help='Print out an sql create command for the table.') 
2383   
2384          parser.add_option('--latex-table',dest='latexDefinitionTable',default=False,action='store_true', 
2385                            help='Print a LaTeX table of the type') 
2386   
2387          parser.add_option('--text-table',dest='textDefinitionTable',default=False,action='store_true', 
2388                            help='Print delimited table of the type (for Word table importing)') 
2389          parser.add_option('--delimt-text-table',dest='delimTextDefinitionTable',default='\\t' 
2390                            ,help='Delimiter for text table [default: \\\'%default\\\'](for Word table importing)') 
2391   
2392   
2393          dbChoices = ('sqlite','postgres') 
2394          parser.add_option('-D','--db-type',dest='dbType',default='postgres' 
2395                            ,choices=dbChoices,type='choice' 
2396                            ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]') 
2397   
2398  ''') 
2399   
2400      o.write('''\taddMsgOptions(parser)\n''') 
2401   
2402      o.write(''' 
2403          (options,args) = parser.parse_args() 
2404          success=True 
2405   
2406          if options.doctest: 
2407                  import os; print os.path.basename(sys.argv[0]), 'doctests ...', 
2408                  sys.argv= [sys.argv[0]] 
2409                  if options.verbose: sys.argv.append('-v') 
2410                  import doctest 
2411                  numfail,numtests=doctest.testmod() 
2412                  if numfail==0: print 'ok' 
2413                  else:  
2414                          print 'FAILED' 
2415                          success=False 
2416   
2417          if not success: sys.exit('Something Failed') 
2418          del success # Hide success from epydoc 
2419   
2420          if options.unittest: 
2421                  sys.argv = [sys.argv[0]] 
2422                  if options.verbose: sys.argv.append('-v') 
2423                  unittest.main() 
2424   
2425          outfile = sys.stdout 
2426          if None!=options.outputFileName: 
2427                  outfile = file(options.outputFileName,'w') 
2428   
2429  ''') 
2430   
2431   
2432   
2433       
2434       
2435       
2436       
2437      o.write('\n\tif options.doEncode:\n') 
2438      o.write('\t\t# First make sure all non required options are specified\n') 
2439      for field in msgET.xpath('field'): 
2440          name      = field.attrib['name'] 
2441          fieldType = field.attrib['type'] 
2442          varName = prefix+name+'Field' 
2443          if not hasSubTag(field,'required'): 
2444              o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n') 
2445   
2446       
2447      o.write('\t\tmsgDict={\n') 
2448      for field in msgET.xpath('field'): 
2449          name    = field.attrib['name'] 
2450          varName = prefix+name+'Field' 
2451          if hasSubTag(field,'required'): 
2452              o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n') 
2453          else: 
2454              o.write('\t\t\t\''+name+'\': options.'+varName+',\n') 
2455      o.write('\t\t}\n') 
2456   
2457      encodeFunction = 'encode' 
2458      if prefixName: encodeFunction = msgName+'Encode' 
2459      o.write(''' 
2460                  bits = '''+encodeFunction+'''(msgDict) 
2461                  if 'binary'==options.ioType: print str(bits) 
2462                  elif 'nmeapayload'==options.ioType: 
2463                          # FIX: figure out if this might be necessary at compile time 
2464                          print "bitLen",len(bits) 
2465                          bitLen=len(bits) 
2466                          if bitLen%6!=0: 
2467                              bits = bits + BitVector(size=(6 - (bitLen%6)))  # Pad out to multiple of 6 
2468                          print "result:",binary.bitvectoais6(bits)[0] 
2469   
2470   
2471                  # FIX: Do not emit this option for the binary message payloads.  Does not make sense. 
2472                  elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability") 
2473                  else: sys.exit('ERROR: unknown ioType.  Help!') 
2474  ''') 
2475   
2476   
2477       
2478       
2479       
2480      decodeFunction = 'decode' 
2481      printFields='printFields' 
2482      if prefixName:  
2483          decodeFunction = msgName+'Decode' 
2484          printFields    = msgName+'PrintFields' 
2485      o.write(''' 
2486   
2487          if options.sqlCreate: 
2488                  sqlCreateStr(outfile,options.fieldList,dbType=options.dbType) 
2489   
2490          if options.latexDefinitionTable: 
2491                  latexDefinitionTable(outfile) 
2492   
2493          # For conversion to word tables 
2494          if options.textDefinitionTable: 
2495                  textDefinitionTable(outfile,options.delimTextDefinitionTable) 
2496   
2497          if options.printCsvfieldList: 
2498                  # Make a csv separated list of fields that will be displayed for csv 
2499                  if None == options.fieldList: options.fieldList = fieldList 
2500                  import StringIO 
2501                  buf = StringIO.StringIO() 
2502                  for field in options.fieldList: 
2503                          buf.write(field+',') 
2504                  result = buf.getvalue() 
2505                  if result[-1] == ',': print result[:-1] 
2506                  else: print result 
2507   
2508          if options.doDecode: 
2509                  if len(args)==0: args = sys.stdin 
2510                  for msg in args: 
2511                          bv = None 
2512   
2513                          if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'): 
2514                                  # Found nmea 
2515                                  # FIX: do checksum 
2516                                  bv = binary.ais6tobitvec(msg.split(',')[5]) 
2517                          else: # either binary or nmeapayload... expect mostly nmeapayloads 
2518                                  # assumes that an all 0 and 1 string can not be a nmeapayload 
2519                                  binaryMsg=True 
2520                                  for c in msg: 
2521                                          if c not in ('0','1'): 
2522                                                  binaryMsg=False 
2523                                                  break 
2524                                  if binaryMsg: 
2525                                          bv = BitVector(bitstring=msg) 
2526                                  else: # nmeapayload 
2527                                          bv = binary.ais6tobitvec(msg) 
2528   
2529                          '''+printFields+'''('''+decodeFunction+'''(bv) 
2530                                      ,out=outfile 
2531                                      ,format=options.outputType 
2532                                      ,fieldList=options.fieldList 
2533                                      ,dbType=options.dbType 
2534                                      ) 
2535   
2536  ''') 
 2537   
2538   
2539   
2540  if __name__=='__main__': 
2541      from optparse import OptionParser 
2542      parser = OptionParser(usage="%prog [options]", 
2543                              version="%prog "+__version__) 
2544   
2545      parser.add_option('-o','--output',dest='outputFileName',default=None, 
2546                          help='Name of the python file to write') 
2547   
2548   
2549      parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 
2550                          help='XML definition file for the msg to use') 
2551   
2552   
2553   
2554   
2555   
2556      parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 
2557                          help='run the documentation tests') 
2558   
2559      parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true', 
2560                          help='put the field name in front of all function names.' 
2561                          +'  Allows multiple messages in one file') 
2562   
2563      parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 
2564                          help='run the tests run in verbose mode') 
2565   
2566      (options,args) = parser.parse_args() 
2567   
2568      success=True 
2569   
2570      if options.doctest: 
2571          import os; print os.path.basename(sys.argv[0]), 'doctests ...', 
2572          argvOrig = sys.argv 
2573          sys.argv= [sys.argv[0]] 
2574          if options.verbose: sys.argv.append('-v') 
2575          import doctest 
2576          numfail,numtests=doctest.testmod() 
2577          if numfail==0: print 'ok' 
2578          else:  
2579              print 'FAILED' 
2580              success=False 
2581          sys.argv = argvOrig  
2582          del argvOrig  
2583          sys.exit()  
2584   
2585       
2586       
2587   
2588       
2589       
2590   
2591       
2592   
2593      if None==options.xmlFileName: 
2594          sys.exit('ERROR: must specify an xml definition file.') 
2595      if None==options.outputFileName: 
2596          sys.exit('ERROR: must specify an python file to write to.') 
2597      generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose) 
2598   
2599      print '\nrecommend running pychecker like this:' 
2600      print '  pychecker -q',options.outputFileName 
2601