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