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