Package Gnumed :: Package business :: Module gmForms
[frames] | no frames]

Source Code for Module Gnumed.business.gmForms

   1  # -*- coding: latin-1 -*- 
   2  """GNUmed forms classes 
   3   
   4  Business layer for printing all manners of forms, letters, scripts etc. 
   5    
   6  license: GPL v2 or later 
   7  """ 
   8  #============================================================ 
   9  __version__ = "$Revision: 1.79 $" 
  10  __author__ ="Ian Haywood <ihaywood@gnu.org>, karsten.hilbert@gmx.net" 
  11   
  12   
  13  import os, sys, time, os.path, logging 
  14  import codecs 
  15  import re as regex 
  16  import shutil 
  17  import random, platform, subprocess 
  18  import socket                                                                           # needed for OOo on Windows 
  19  #, libxml2, libxslt 
  20  import shlex 
  21   
  22   
  23  if __name__ == '__main__': 
  24          sys.path.insert(0, '../../') 
  25  from Gnumed.pycommon import gmTools 
  26  from Gnumed.pycommon import gmDispatcher 
  27  from Gnumed.pycommon import gmExceptions 
  28  from Gnumed.pycommon import gmMatchProvider 
  29  from Gnumed.pycommon import gmBorg 
  30  from Gnumed.pycommon import gmLog2 
  31  from Gnumed.pycommon import gmMimeLib 
  32  from Gnumed.pycommon import gmShellAPI 
  33  from Gnumed.pycommon import gmCfg 
  34  from Gnumed.pycommon import gmBusinessDBObject 
  35  from Gnumed.pycommon import gmPG2 
  36   
  37  from Gnumed.business import gmPerson 
  38  from Gnumed.business import gmStaff 
  39  from Gnumed.business import gmPersonSearch 
  40  from Gnumed.business import gmSurgery 
  41   
  42   
  43  _log = logging.getLogger('gm.forms') 
  44  _log.info(__version__) 
  45   
  46  #============================================================ 
  47  # this order is also used in choice boxes for the engine 
  48  form_engine_abbrevs = [u'O', u'L', u'I', u'G', u'P'] 
  49   
  50  form_engine_names = { 
  51          u'O': 'OpenOffice', 
  52          u'L': 'LaTeX', 
  53          u'I': 'Image editor', 
  54          u'G': 'Gnuplot script', 
  55          u'P': 'PDF forms' 
  56  } 
  57   
  58  form_engine_template_wildcards = { 
  59          u'O': u'*.o?t', 
  60          u'L': u'*.tex', 
  61          u'G': u'*.gpl', 
  62          u'P': u'*.pdf' 
  63  } 
  64   
  65  # is filled in further below after each engine is defined 
  66  form_engines = {} 
  67   
  68  #============================================================ 
  69  # match providers 
  70  #============================================================ 
71 -class cFormTemplateNameLong_MatchProvider(gmMatchProvider.cMatchProvider_SQL2):
72
73 - def __init__(self):
74 75 query = u""" 76 SELECT 77 name_long AS data, 78 name_long AS list_label, 79 name_long AS field_label 80 FROM ref.v_paperwork_templates 81 WHERE name_long %(fragment_condition)s 82 ORDER BY list_label 83 """ 84 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
85 #============================================================
86 -class cFormTemplateNameShort_MatchProvider(gmMatchProvider.cMatchProvider_SQL2):
87
88 - def __init__(self):
89 90 query = u""" 91 SELECT 92 name_short AS data, 93 name_short AS list_label, 94 name_short AS field_label 95 FROM ref.v_paperwork_templates 96 WHERE name_short %(fragment_condition)s 97 ORDER BY name_short 98 """ 99 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
100 #============================================================
101 -class cFormTemplateType_MatchProvider(gmMatchProvider.cMatchProvider_SQL2):
102
103 - def __init__(self):
104 105 query = u""" 106 SELECT DISTINCT ON (list_label) 107 pk AS data, 108 _(name) || ' (' || name || ')' AS list_label, 109 _(name) AS field_label 110 FROM ref.form_types 111 WHERE 112 _(name) %(fragment_condition)s 113 OR 114 name %(fragment_condition)s 115 ORDER BY list_label 116 """ 117 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
118 #============================================================
119 -class cFormTemplate(gmBusinessDBObject.cBusinessDBObject):
120 121 _cmd_fetch_payload = u'select * from ref.v_paperwork_templates where pk_paperwork_template = %s' 122 123 _cmds_store_payload = [ 124 u"""update ref.paperwork_templates set 125 name_short = %(name_short)s, 126 name_long = %(name_long)s, 127 fk_template_type = %(pk_template_type)s, 128 instance_type = %(instance_type)s, 129 engine = %(engine)s, 130 in_use = %(in_use)s, 131 filename = %(filename)s, 132 external_version = %(external_version)s 133 where 134 pk = %(pk_paperwork_template)s and 135 xmin = %(xmin_paperwork_template)s 136 """, 137 u"""select xmin_paperwork_template from ref.v_paperwork_templates where pk_paperwork_template = %(pk_paperwork_template)s""" 138 ] 139 140 _updatable_fields = [ 141 u'name_short', 142 u'name_long', 143 u'external_version', 144 u'pk_template_type', 145 u'instance_type', 146 u'engine', 147 u'in_use', 148 u'filename' 149 ] 150 151 _suffix4engine = { 152 u'O': u'.ott', 153 u'L': u'.tex', 154 u'T': u'.txt', 155 u'X': u'.xslt', 156 u'I': u'.img', 157 u'P': u'.pdf' 158 } 159 160 #--------------------------------------------------------
161 - def _get_template_data(self):
162 """The template itself better not be arbitrarily large unless you can handle that. 163 164 Note that the data type returned will be a buffer.""" 165 166 cmd = u'SELECT data FROM ref.paperwork_templates WHERE pk = %(pk)s' 167 rows, idx = gmPG2.run_ro_queries (queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False) 168 169 if len(rows) == 0: 170 raise gmExceptions.NoSuchBusinessObjectError('cannot retrieve data for template pk = %s' % self.pk_obj) 171 172 return rows[0][0]
173 174 template_data = property(_get_template_data, lambda x:x) 175 #--------------------------------------------------------
176 - def export_to_file(self, filename=None, chunksize=0):
177 """Export form template from database into file.""" 178 179 if filename is None: 180 if self._payload[self._idx['filename']] is None: 181 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 182 else: 183 suffix = os.path.splitext(self._payload[self._idx['filename']].strip())[1].strip() 184 if suffix in [u'', u'.']: 185 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 186 187 filename = gmTools.get_unique_filename ( 188 prefix = 'gm-%s-Template-' % self._payload[self._idx['engine']], 189 suffix = suffix 190 ) 191 192 data_query = { 193 'cmd': u'SELECT substring(data from %(start)s for %(size)s) FROM ref.paperwork_templates WHERE pk = %(pk)s', 194 'args': {'pk': self.pk_obj} 195 } 196 197 data_size_query = { 198 'cmd': u'select octet_length(data) from ref.paperwork_templates where pk = %(pk)s', 199 'args': {'pk': self.pk_obj} 200 } 201 202 result = gmPG2.bytea2file ( 203 data_query = data_query, 204 filename = filename, 205 data_size_query = data_size_query, 206 chunk_size = chunksize 207 ) 208 if result is False: 209 return None 210 211 return filename
212 #--------------------------------------------------------
213 - def update_template_from_file(self, filename=None):
214 gmPG2.file2bytea ( 215 filename = filename, 216 query = u'update ref.paperwork_templates set data = %(data)s::bytea where pk = %(pk)s and xmin = %(xmin)s', 217 args = {'pk': self.pk_obj, 'xmin': self._payload[self._idx['xmin_paperwork_template']]} 218 ) 219 # adjust for xmin change 220 self.refetch_payload()
221 #--------------------------------------------------------
222 - def instantiate(self):
223 fname = self.export_to_file() 224 engine = form_engines[self._payload[self._idx['engine']]] 225 return engine(template_file = fname)
226 #============================================================
227 -def get_form_template(name_long=None, external_version=None):
228 cmd = u'select pk from ref.paperwork_templates where name_long = %(lname)s and external_version = %(ver)s' 229 args = {'lname': name_long, 'ver': external_version} 230 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 231 232 if len(rows) == 0: 233 _log.error('cannot load form template [%s - %s]', name_long, external_version) 234 return None 235 236 return cFormTemplate(aPK_obj = rows[0]['pk'])
237 #------------------------------------------------------------
238 -def get_form_templates(engine=None, active_only=False, template_types=None, excluded_types=None):
239 """Load form templates.""" 240 241 args = {'eng': engine, 'in_use': active_only} 242 where_parts = [u'1 = 1'] 243 244 if engine is not None: 245 where_parts.append(u'engine = %(eng)s') 246 247 if active_only: 248 where_parts.append(u'in_use IS true') 249 250 if template_types is not None: 251 args['incl_types'] = tuple(template_types) 252 where_parts.append(u'template_type IN %(incl_types)s') 253 254 if excluded_types is not None: 255 args['excl_types'] = tuple(excluded_types) 256 where_parts.append(u'template_type NOT IN %(excl_types)s') 257 258 cmd = u"SELECT * FROM ref.v_paperwork_templates WHERE %s ORDER BY in_use desc, name_long" % u'\nAND '.join(where_parts) 259 260 rows, idx = gmPG2.run_ro_queries ( 261 queries = [{'cmd': cmd, 'args': args}], 262 get_col_idx = True 263 ) 264 templates = [ cFormTemplate(row = {'pk_field': 'pk_paperwork_template', 'data': r, 'idx': idx}) for r in rows ] 265 266 return templates
267 #------------------------------------------------------------
268 -def create_form_template(template_type=None, name_short=None, name_long=None):
269 270 cmd = u'insert into ref.paperwork_templates (fk_template_type, name_short, name_long, external_version) values (%(type)s, %(nshort)s, %(nlong)s, %(ext_version)s)' 271 rows, idx = gmPG2.run_rw_queries ( 272 queries = [ 273 {'cmd': cmd, 'args': {'type': template_type, 'nshort': name_short, 'nlong': name_long, 'ext_version': 'new'}}, 274 {'cmd': u"select currval(pg_get_serial_sequence('ref.paperwork_templates', 'pk'))"} 275 ], 276 return_data = True 277 ) 278 template = cFormTemplate(aPK_obj = rows[0][0]) 279 return template
280 #------------------------------------------------------------
281 -def delete_form_template(template=None):
282 rows, idx = gmPG2.run_rw_queries ( 283 queries = [ 284 {'cmd': u'delete from ref.paperwork_templates where pk=%(pk)s', 'args': {'pk': template['pk_paperwork_template']}} 285 ] 286 ) 287 return True
288 #============================================================ 289 # OpenOffice/LibreOffice API 290 #============================================================ 291 uno = None 292 cOOoDocumentCloseListener = None 293 writer_binary = None 294 295 #-----------------------------------------------------------
296 -def __configure_path_to_UNO():
297 298 try: 299 which = subprocess.Popen ( 300 args = ('which', 'soffice'), 301 stdout = subprocess.PIPE, 302 stdin = subprocess.PIPE, 303 stderr = subprocess.PIPE, 304 universal_newlines = True 305 ) 306 except (OSError, ValueError, subprocess.CalledProcessError): 307 _log.exception('there was a problem executing [which soffice]') 308 return 309 310 soffice_path, err = which.communicate() 311 soffice_path = soffice_path.strip('\n') 312 uno_path = os.path.abspath ( os.path.join ( 313 os.path.dirname(os.path.realpath(soffice_path)), 314 '..', 315 'basis-link', 316 'program' 317 )) 318 319 _log.info('UNO should be at [%s], appending to sys.path', uno_path) 320 321 sys.path.append(uno_path)
322 #-----------------------------------------------------------
323 -def init_ooo():
324 """FIXME: consider this: 325 326 try: 327 import uno 328 except: 329 print "This Script needs to be run with the python from OpenOffice.org" 330 print "Example: /opt/OpenOffice.org/program/python %s" % ( 331 os.path.basename(sys.argv[0])) 332 print "Or you need to insert the right path at the top, where uno.py is." 333 print "Default: %s" % default_path 334 """ 335 global uno 336 if uno is not None: 337 return 338 339 try: 340 import uno 341 except ImportError: 342 __configure_path_to_UNO() 343 import uno 344 345 global unohelper, oooXCloseListener, oooNoConnectException, oooPropertyValue 346 347 import unohelper 348 from com.sun.star.util import XCloseListener as oooXCloseListener 349 from com.sun.star.connection import NoConnectException as oooNoConnectException 350 from com.sun.star.beans import PropertyValue as oooPropertyValue 351 352 #---------------------------------- 353 class _cOOoDocumentCloseListener(unohelper.Base, oooXCloseListener): 354 """Listens for events sent by OOo during the document closing 355 sequence and notifies the GNUmed client GUI so it can 356 import the closed document into the database. 357 """ 358 def __init__(self, document=None): 359 self.document = document
360 361 def queryClosing(self, evt, owner): 362 # owner is True/False whether I am the owner of the doc 363 pass 364 365 def notifyClosing(self, evt): 366 pass 367 368 def disposing(self, evt): 369 self.document.on_disposed_by_ooo() 370 self.document = None 371 #---------------------------------- 372 373 global cOOoDocumentCloseListener 374 cOOoDocumentCloseListener = _cOOoDocumentCloseListener 375 376 # search for writer binary 377 global writer_binary 378 found, binary = gmShellAPI.find_first_binary(binaries = [ 379 'lowriter', 380 'oowriter' 381 ]) 382 if found: 383 _log.debug('OOo/LO writer binary found: %s', binary) 384 writer_binary = binary 385 else: 386 _log.debug('OOo/LO writer binary NOT found') 387 raise ImportError('LibreOffice/OpenOffice (lowriter/oowriter) not found') 388 389 _log.debug('python UNO bridge successfully initialized') 390 391 #------------------------------------------------------------
392 -class gmOOoConnector(gmBorg.cBorg):
393 """This class handles the connection to OOo. 394 395 Its Singleton instance stays around once initialized. 396 """ 397 # FIXME: need to detect closure of OOo !
398 - def __init__(self):
399 400 init_ooo() 401 402 #self.ooo_start_cmd = 'oowriter -invisible -accept="socket,host=localhost,port=2002;urp;"' 403 #self.remote_context_uri = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" 404 405 pipe_name = "uno-gm2lo-%s" % str(random.random())[2:] 406 _log.debug('pipe name: %s', pipe_name) 407 408 #self.ooo_start_cmd = '%s -invisible -norestore -accept="pipe,name=%s;urp"' % ( 409 self.ooo_start_cmd = '%s --norestore --accept="pipe,name=%s;urp" &' % ( 410 writer_binary, 411 pipe_name 412 ) 413 _log.debug('startup command: %s', self.ooo_start_cmd) 414 415 self.remote_context_uri = "uno:pipe,name=%s;urp;StarOffice.ComponentContext" % pipe_name 416 _log.debug('remote context URI: %s', self.remote_context_uri) 417 418 self.resolver_uri = "com.sun.star.bridge.UnoUrlResolver" 419 self.desktop_uri = "com.sun.star.frame.Desktop" 420 421 self.local_context = uno.getComponentContext() 422 self.uri_resolver = self.local_context.ServiceManager.createInstanceWithContext(self.resolver_uri, self.local_context) 423 424 self.__desktop = None
425 #--------------------------------------------------------
426 - def cleanup(self, force=True):
427 if self.__desktop is None: 428 _log.debug('no desktop, no cleanup') 429 return 430 431 try: 432 self.__desktop.terminate() 433 except: 434 _log.exception('cannot terminate OOo desktop')
435 #--------------------------------------------------------
436 - def open_document(self, filename=None):
437 """<filename> must be absolute""" 438 439 if self.desktop is None: 440 _log.error('cannot access OOo desktop') 441 return None 442 443 filename = os.path.expanduser(filename) 444 filename = os.path.abspath(filename) 445 document_uri = uno.systemPathToFileUrl(filename) 446 447 _log.debug('%s -> %s', filename, document_uri) 448 449 doc = self.desktop.loadComponentFromURL(document_uri, "_blank", 0, ()) 450 return doc
451 #-------------------------------------------------------- 452 # internal helpers 453 #--------------------------------------------------------
454 - def __get_startup_settle_time(self):
455 # later factor this out ! 456 dbcfg = gmCfg.cCfgSQL() 457 self.ooo_startup_settle_time = dbcfg.get2 ( 458 option = u'external.ooo.startup_settle_time', 459 workplace = gmSurgery.gmCurrentPractice().active_workplace, 460 bias = u'workplace', 461 default = 3.0 462 )
463 #-------------------------------------------------------- 464 # properties 465 #--------------------------------------------------------
466 - def _get_desktop(self):
467 if self.__desktop is not None: 468 return self.__desktop 469 470 try: 471 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri) 472 except oooNoConnectException: 473 _log.exception('cannot connect to OOo server') 474 _log.info('trying to start OOo server') 475 os.system(self.ooo_start_cmd) 476 self.__get_startup_settle_time() 477 _log.debug('waiting %s seconds for OOo to start up', self.ooo_startup_settle_time) 478 time.sleep(self.ooo_startup_settle_time) # OOo sometimes needs a bit 479 try: 480 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri) 481 except oooNoConnectException: 482 _log.exception('cannot start (or connect to started) OOo server') 483 return None 484 485 self.__desktop = self.remote_context.ServiceManager.createInstanceWithContext(self.desktop_uri, self.remote_context) 486 _log.debug('connection seems established') 487 return self.__desktop
488 489 desktop = property(_get_desktop, lambda x:x)
490 #------------------------------------------------------------
491 -class cOOoLetter(object):
492
493 - def __init__(self, template_file=None, instance_type=None):
494 495 self.template_file = template_file 496 self.instance_type = instance_type 497 self.ooo_doc = None
498 #-------------------------------------------------------- 499 # external API 500 #--------------------------------------------------------
501 - def open_in_ooo(self):
502 # connect to OOo 503 ooo_srv = gmOOoConnector() 504 505 # open doc in OOo 506 self.ooo_doc = ooo_srv.open_document(filename = self.template_file) 507 if self.ooo_doc is None: 508 _log.error('cannot open document in OOo') 509 return False 510 511 # listen for close events 512 pat = gmPerson.gmCurrentPatient() 513 pat.locked = True 514 listener = cOOoDocumentCloseListener(document = self) 515 self.ooo_doc.addCloseListener(listener) 516 517 return True
518 #--------------------------------------------------------
519 - def show(self, visible=True):
520 self.ooo_doc.CurrentController.Frame.ContainerWindow.setVisible(visible)
521 #--------------------------------------------------------
522 - def replace_placeholders(self, handler=None, old_style_too = True):
523 524 # new style embedded, implicit placeholders 525 searcher = self.ooo_doc.createSearchDescriptor() 526 searcher.SearchCaseSensitive = False 527 searcher.SearchRegularExpression = True 528 searcher.SearchWords = True 529 searcher.SearchString = handler.placeholder_regex 530 531 placeholder_instance = self.ooo_doc.findFirst(searcher) 532 while placeholder_instance is not None: 533 try: 534 val = handler[placeholder_instance.String] 535 except: 536 _log.exception(val) 537 val = _('error with placeholder [%s]') % placeholder_instance.String 538 539 if val is None: 540 val = _('error with placeholder [%s]') % placeholder_instance.String 541 542 placeholder_instance.String = val 543 placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher) 544 545 if not old_style_too: 546 return 547 548 # old style "explicit" placeholders 549 text_fields = self.ooo_doc.getTextFields().createEnumeration() 550 while text_fields.hasMoreElements(): 551 text_field = text_fields.nextElement() 552 553 # placeholder ? 554 if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'): 555 continue 556 # placeholder of type text ? 557 if text_field.PlaceHolderType != 0: 558 continue 559 560 replacement = handler[text_field.PlaceHolder] 561 if replacement is None: 562 continue 563 564 text_field.Anchor.setString(replacement)
565 #--------------------------------------------------------
566 - def save_in_ooo(self, filename=None):
567 if filename is not None: 568 target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename))) 569 save_args = ( 570 oooPropertyValue('Overwrite', 0, True, 0), 571 oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0) 572 573 ) 574 # "store AS url" stores the doc, marks it unmodified and updates 575 # the internal media descriptor - as opposed to "store TO url" 576 self.ooo_doc.storeAsURL(target_url, save_args) 577 else: 578 self.ooo_doc.store()
579 #--------------------------------------------------------
580 - def close_in_ooo(self):
581 self.ooo_doc.dispose() 582 pat = gmPerson.gmCurrentPatient() 583 pat.locked = False 584 self.ooo_doc = None
585 #--------------------------------------------------------
586 - def on_disposed_by_ooo(self):
587 # get current file name from OOo, user may have used Save As 588 filename = uno.fileUrlToSystemPath(self.ooo_doc.URL) 589 # tell UI to import the file 590 gmDispatcher.send ( 591 signal = u'import_document_from_file', 592 filename = filename, 593 document_type = self.instance_type, 594 unlock_patient = True 595 ) 596 self.ooo_doc = None
597 #-------------------------------------------------------- 598 # internal helpers 599 #-------------------------------------------------------- 600 601 #============================================================
602 -class cFormEngine(object):
603 """Ancestor for forms.""" 604
605 - def __init__(self, template_file=None):
606 self.template_filename = template_file
607 #--------------------------------------------------------
608 - def substitute_placeholders(self, data_source=None):
609 """Parse the template into an instance and replace placeholders with values.""" 610 raise NotImplementedError
611 #--------------------------------------------------------
612 - def edit(self):
613 """Allow editing the instance of the template.""" 614 raise NotImplementedError
615 #--------------------------------------------------------
616 - def generate_output(self, format=None):
617 """Generate output suitable for further processing outside this class, e.g. printing.""" 618 raise NotImplementedError
619 #--------------------------------------------------------
620 - def process(self, data_source=None):
621 """Merge values into the form template. 622 """ 623 pass
624 #--------------------------------------------------------
625 - def cleanup(self):
626 """ 627 A sop to TeX which can't act as a true filter: to delete temporary files 628 """ 629 pass
630 #--------------------------------------------------------
631 - def exe(self, command):
632 """ 633 Executes the provided command. 634 If command cotains %F. it is substituted with the filename 635 Otherwise, the file is fed in on stdin 636 """ 637 pass
638 #--------------------------------------------------------
639 - def store(self, params=None):
640 """Stores the parameters in the backend. 641 642 - link_obj can be a cursor, a connection or a service name 643 - assigning a cursor to link_obj allows the calling code to 644 group the call to store() into an enclosing transaction 645 (for an example see gmReferral.send_referral()...) 646 """ 647 # some forms may not have values ... 648 if params is None: 649 params = {} 650 patient_clinical = self.patient.get_emr() 651 encounter = patient_clinical.active_encounter['pk_encounter'] 652 # FIXME: get_active_episode is no more 653 #episode = patient_clinical.get_active_episode()['pk_episode'] 654 # generate "forever unique" name 655 cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s"; 656 rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def) 657 form_name = None 658 if rows is None: 659 _log.error('error retrieving form def for [%s]' % self.pk_def) 660 elif len(rows) == 0: 661 _log.error('no form def for [%s]' % self.pk_def) 662 else: 663 form_name = rows[0][0] 664 # we didn't get a name but want to store the form anyhow 665 if form_name is None: 666 form_name=time.time() # hopefully unique enough 667 # in one transaction 668 queries = [] 669 # - store form instance in form_instance 670 cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)" 671 queries.append((cmd, [self.pk_def, form_name, episode, encounter])) 672 # - store params in form_data 673 for key in params.keys(): 674 cmd = """ 675 insert into form_data(fk_instance, place_holder, value) 676 values ((select currval('form_instances_pk_seq')), %s, %s::text) 677 """ 678 queries.append((cmd, [key, params[key]])) 679 # - get inserted PK 680 queries.append(("select currval ('form_instances_pk_seq')", [])) 681 status, err = gmPG.run_commit('historica', queries, True) 682 if status is None: 683 _log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err)) 684 return None 685 return status
686 687 #================================================================ 688 # OOo template forms 689 #----------------------------------------------------------------
690 -class cOOoForm(cFormEngine):
691 """A forms engine wrapping OOo.""" 692
693 - def __init__(self, template_file=None):
694 super(self.__class__, self).__init__(template_file = template_file) 695 696 697 path, ext = os.path.splitext(self.template_filename) 698 if ext in [r'', r'.']: 699 ext = r'.odt' 700 self.instance_filename = r'%s-instance%s' % (path, ext)
701 702 #================================================================ 703 # LaTeX template forms 704 #----------------------------------------------------------------
705 -class cLaTeXForm(cFormEngine):
706 """A forms engine wrapping LaTeX.""" 707
708 - def __init__(self, template_file=None):
709 super(self.__class__, self).__init__(template_file = template_file) 710 path, ext = os.path.splitext(self.template_filename) 711 if ext in [r'', r'.']: 712 ext = r'.tex' 713 self.instance_filename = r'%s-instance%s' % (path, ext)
714 #--------------------------------------------------------
715 - def substitute_placeholders(self, data_source=None):
716 717 template_file = codecs.open(self.template_filename, 'rU', 'utf8') 718 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8') 719 720 for line in template_file: 721 722 if line.strip() in [u'', u'\r', u'\n', u'\r\n']: 723 instance_file.write(line) 724 continue 725 726 # 1) find placeholders in this line 727 placeholders_in_line = regex.findall(data_source.placeholder_regex, line, regex.IGNORECASE) 728 # 2) and replace them 729 for placeholder in placeholders_in_line: 730 try: 731 val = data_source[placeholder] 732 except: 733 _log.exception(val) 734 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder) 735 736 if val is None: 737 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder) 738 739 line = line.replace(placeholder, val) 740 741 instance_file.write(line) 742 743 instance_file.close() 744 template_file.close() 745 746 return
747 #--------------------------------------------------------
748 - def edit(self):
749 750 mimetypes = [ 751 u'application/x-latex', 752 u'application/x-tex', 753 u'text/plain' 754 ] 755 756 for mimetype in mimetypes: 757 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 758 if editor_cmd is not None: 759 break 760 761 if editor_cmd is None: 762 editor_cmd = u'sensible-editor %s' % self.instance_filename 763 764 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 765 self.re_editable_filenames = [self.instance_filename] 766 767 return result
768 #--------------------------------------------------------
769 - def generate_output(self, instance_file = None, format=None):
770 771 if instance_file is None: 772 instance_file = self.instance_filename 773 774 try: 775 open(instance_file, 'r').close() 776 except: 777 _log.exception('cannot access form instance file [%s]', instance_file) 778 gmLog2.log_stack_trace() 779 return None 780 781 self.instance_filename = instance_file 782 783 _log.debug('ignoring <format> directive [%s], generating PDF', format) 784 785 # create sandbox for LaTeX to play in 786 sandbox_dir = os.path.splitext(self.template_filename)[0] 787 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir) 788 789 old_cwd = os.getcwd() 790 _log.debug('CWD: [%s]', old_cwd) 791 792 gmTools.mkdir(sandbox_dir) 793 794 os.chdir(sandbox_dir) 795 try: 796 sandboxed_instance_filename = os.path.join(sandbox_dir, os.path.split(self.instance_filename)[1]) 797 shutil.move(self.instance_filename, sandboxed_instance_filename) 798 799 # LaTeX can need up to three runs to get cross references et al right 800 if platform.system() == 'Windows': 801 draft_cmd = r'pdflatex.exe -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename 802 final_cmd = r'pdflatex.exe -interaction nonstopmode %s' % sandboxed_instance_filename 803 else: 804 draft_cmd = r'pdflatex -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename 805 final_cmd = r'pdflatex -interaction nonstopmode %s' % sandboxed_instance_filename 806 for run_cmd in [draft_cmd, draft_cmd, final_cmd]: 807 if not gmShellAPI.run_command_in_shell(command = run_cmd, blocking = True, acceptable_return_codes = [0, 1]): 808 _log.error('problem running pdflatex, cannot generate form output') 809 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True) 810 os.chdir(old_cwd) 811 return None 812 finally: 813 os.chdir(old_cwd) 814 815 sandboxed_pdf_name = u'%s.pdf' % os.path.splitext(sandboxed_instance_filename)[0] 816 target_dir = os.path.split(self.instance_filename)[0] 817 try: 818 shutil.move(sandboxed_pdf_name, target_dir) 819 except IOError: 820 _log.exception('cannot move sandboxed PDF: %s -> %s', sandboxed_pdf_name, target_dir) 821 gmDispatcher.send(signal = 'statustext', msg = _('Sandboxed PDF output file cannot be moved.'), beep = True) 822 return None 823 824 final_pdf_name = u'%s.pdf' % os.path.splitext(self.instance_filename)[0] 825 826 try: 827 open(final_pdf_name, 'r').close() 828 except IOError: 829 _log.exception('cannot open target PDF: %s', final_pdf_name) 830 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 831 return None 832 833 self.final_output_filenames = [final_pdf_name] 834 835 return final_pdf_name
836 #------------------------------------------------------------ 837 form_engines[u'L'] = cLaTeXForm 838 #============================================================ 839 # Gnuplot template forms 840 #------------------------------------------------------------
841 -class cGnuplotForm(cFormEngine):
842 """A forms engine wrapping Gnuplot.""" 843 844 #--------------------------------------------------------
845 - def substitute_placeholders(self, data_source=None):
846 """Parse the template into an instance and replace placeholders with values.""" 847 pass
848 #--------------------------------------------------------
849 - def edit(self):
850 """Allow editing the instance of the template.""" 851 self.re_editable_filenames = [] 852 return True
853 #--------------------------------------------------------
854 - def generate_output(self, format=None):
855 """Generate output suitable for further processing outside this class, e.g. printing. 856 857 Expects .data_filename to be set. 858 """ 859 self.conf_filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.conf') 860 fname_file = codecs.open(self.conf_filename, 'wb', 'utf8') 861 fname_file.write('# setting the gnuplot data file\n') 862 fname_file.write("gm2gpl_datafile = '%s'\n" % self.data_filename) 863 fname_file.close() 864 865 # FIXME: cater for configurable path 866 if platform.system() == 'Windows': 867 exec_name = 'gnuplot.exe' 868 else: 869 exec_name = 'gnuplot' 870 871 args = [exec_name, '-p', self.conf_filename, self.template_filename] 872 _log.debug('plotting args: %s' % str(args)) 873 874 try: 875 gp = subprocess.Popen ( 876 args = args, 877 close_fds = True 878 ) 879 except (OSError, ValueError, subprocess.CalledProcessError): 880 _log.exception('there was a problem executing gnuplot') 881 gmDispatcher.send(signal = u'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True) 882 return 883 884 gp.communicate() 885 886 self.final_output_filenames = [ 887 self.conf_filename, 888 self.data_filename, 889 self.template_filename 890 ] 891 892 return
893 #------------------------------------------------------------ 894 form_engines[u'G'] = cGnuplotForm 895 896 #============================================================ 897 # fPDF form engine 898 #------------------------------------------------------------
899 -class cPDFForm(cFormEngine):
900 """A forms engine wrapping PDF forms. 901 902 Johann Felix Soden <johfel@gmx.de> helped with this. 903 904 http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf 905 906 http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/fdf_data_exchange.pdf 907 """ 908
909 - def __init__(self, template_file=None):
910 911 super(cPDFForm, self).__init__(template_file = template_file) 912 913 # detect pdftk 914 found, self.pdftk_binary = gmShellAPI.detect_external_binary(binary = r'pdftk') 915 if not found: 916 raise ImportError('<pdftk(.exe)> not found') 917 return # should be superfluous, actually 918 919 enc = sys.getfilesystemencoding() 920 self.pdftk_binary = self.pdftk_binary.encode(enc) 921 922 base_name, ext = os.path.splitext(self.template_filename) 923 self.fdf_dumped_filename = (u'%s.fdf' % base_name).encode(enc) 924 self.fdf_replaced_filename = (u'%s-replaced.fdf' % base_name).encode(enc) 925 self.pdf_filled_filename = (u'%s-filled.pdf' % base_name).encode(enc) 926 self.pdf_flattened_filename = (u'%s-filled-flattened.pdf' % base_name).encode(enc)
927 #--------------------------------------------------------
928 - def substitute_placeholders(self, data_source=None):
929 930 # dump form fields from template 931 cmd_line = [ 932 self.pdftk_binary, 933 self.template_filename, 934 r'generate_fdf', 935 r'output', 936 self.fdf_dumped_filename 937 ] 938 _log.debug(u' '.join(cmd_line)) 939 try: 940 pdftk = subprocess.Popen(cmd_line) 941 except OSError: 942 _log.exception('cannot run <pdftk> (dump data from form)') 943 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot extract fields from PDF form template.'), beep = True) 944 return False 945 946 pdftk.communicate() 947 if pdftk.returncode != 0: 948 _log.error('<pdftk> returned [%s], failed to dump data from PDF form into FDF', pdftk.returncode) 949 return False 950 951 # parse dumped FDF file for "/V (...)" records 952 # and replace placeholders therein 953 fdf_dumped_file = open(self.fdf_dumped_filename, 'rbU') 954 fdf_replaced_file = codecs.open(self.fdf_replaced_filename, 'wb') 955 956 string_value_regex = r'\s*/V\s*\(.+\)\s*$' 957 for line in fdf_dumped_file: 958 if not regex.match(string_value_regex, line): 959 fdf_replaced_file.write(line) 960 continue 961 962 # strip cruft around the string value 963 raw_str_val = line.strip() # remove framing whitespace 964 raw_str_val = raw_str_val[2:] # remove leading "/V" 965 raw_str_val = raw_str_val.lstrip() # remove whitespace between "/V" and "(" 966 raw_str_val = raw_str_val[1:] # remove opening "(" 967 raw_str_val = raw_str_val[2:] # remove BOM-16-BE 968 raw_str_val = raw_str_val.rstrip() # remove trailing whitespace 969 raw_str_val = raw_str_val[:-1] # remove closing ")" 970 971 # work on FDF escapes 972 raw_str_val = raw_str_val.replace('\(', '(') # remove escaping of "(" 973 raw_str_val = raw_str_val.replace('\)', ')') # remove escaping of ")" 974 975 # by now raw_str_val should contain the actual 976 # string value, albeit encoded as UTF-16, so 977 # decode it into a unicode object, 978 # split multi-line fields on "\n" literal 979 raw_str_lines = raw_str_val.split('\x00\\n') 980 value_template_lines = [] 981 for raw_str_line in raw_str_lines: 982 value_template_lines.append(raw_str_line.decode('utf_16_be')) 983 984 replaced_lines = [] 985 for value_template in value_template_lines: 986 # find any placeholders within 987 placeholders_in_value = regex.findall(data_source.placeholder_regex, value_template, regex.IGNORECASE) 988 for placeholder in placeholders_in_value: 989 try: 990 replacement = data_source[placeholder] 991 except: 992 _log.exception(replacement) 993 replacement = _('error with placeholder [%s]') % placeholder 994 if replacement is None: 995 replacement = _('error with placeholder [%s]') % placeholder 996 value_template = value_template.replace(placeholder, replacement) 997 998 value_template = value_template.encode('utf_16_be') 999 1000 if len(placeholders_in_value) > 0: 1001 value_template = value_template.replace(r'(', r'\(') 1002 value_template = value_template.replace(r')', r'\)') 1003 1004 replaced_lines.append(value_template) 1005 1006 replaced_line = '\x00\\n'.join(replaced_lines) 1007 1008 fdf_replaced_file.write('/V (') 1009 fdf_replaced_file.write(codecs.BOM_UTF16_BE) 1010 fdf_replaced_file.write(replaced_line) 1011 fdf_replaced_file.write(')\n') 1012 1013 fdf_replaced_file.close() 1014 fdf_dumped_file.close() 1015 1016 # merge replaced data back into form 1017 cmd_line = [ 1018 self.pdftk_binary, 1019 self.template_filename, 1020 r'fill_form', 1021 self.fdf_replaced_filename, 1022 r'output', 1023 self.pdf_filled_filename 1024 ] 1025 _log.debug(u' '.join(cmd_line)) 1026 try: 1027 pdftk = subprocess.Popen(cmd_line) 1028 except OSError: 1029 _log.exception('cannot run <pdftk> (merge data into form)') 1030 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot fill in PDF form template.'), beep = True) 1031 return False 1032 1033 pdftk.communicate() 1034 if pdftk.returncode != 0: 1035 _log.error('<pdftk> returned [%s], failed to merge FDF data into PDF form', pdftk.returncode) 1036 return False 1037 1038 return True
1039 #--------------------------------------------------------
1040 - def edit(self):
1041 mimetypes = [ 1042 u'application/pdf', 1043 u'application/x-pdf' 1044 ] 1045 1046 for mimetype in mimetypes: 1047 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.pdf_filled_filename) 1048 if editor_cmd is not None: 1049 break 1050 1051 if editor_cmd is None: 1052 _log.debug('editor cmd not found, trying viewer cmd') 1053 for mimetype in mimetypes: 1054 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.pdf_filled_filename) 1055 if editor_cmd is not None: 1056 break 1057 1058 if editor_cmd is None: 1059 return False 1060 1061 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1062 1063 path, fname = os.path.split(self.pdf_filled_filename) 1064 candidate = os.path.join(gmTools.gmPaths().home_dir, fname) 1065 1066 if os.access(candidate, os.R_OK): 1067 _log.debug('filled-in PDF found: %s', candidate) 1068 os.rename(self.pdf_filled_filename, self.pdf_filled_filename + '.bak') 1069 shutil.move(candidate, path) 1070 else: 1071 _log.debug('filled-in PDF not found: %s', candidate) 1072 1073 self.re_editable_filenames = [self.pdf_filled_filename] 1074 1075 return result
1076 #--------------------------------------------------------
1077 - def generate_output(self, format=None):
1078 """Generate output suitable for further processing outside this class, e.g. printing.""" 1079 1080 # eventually flatten the filled in form so we 1081 # can keep both a flattened and an editable copy: 1082 cmd_line = [ 1083 self.pdftk_binary, 1084 self.pdf_filled_filename, 1085 r'output', 1086 self.pdf_flattened_filename, 1087 r'flatten' 1088 ] 1089 _log.debug(u' '.join(cmd_line)) 1090 try: 1091 pdftk = subprocess.Popen(cmd_line) 1092 except OSError: 1093 _log.exception('cannot run <pdftk> (flatten filled in form)') 1094 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot flatten filled in PDF form.'), beep = True) 1095 return None 1096 1097 pdftk.communicate() 1098 if pdftk.returncode != 0: 1099 _log.error('<pdftk> returned [%s], failed to flatten filled in PDF form', pdftk.returncode) 1100 return None 1101 1102 self.final_output_filenames = [self.pdf_flattened_filename] 1103 1104 return self.pdf_flattened_filename
1105 #------------------------------------------------------------ 1106 form_engines[u'P'] = cPDFForm 1107 1108 #============================================================ 1109 # older code 1110 #------------------------------------------------------------
1111 -class cIanLaTeXForm(cFormEngine):
1112 """A forms engine wrapping LaTeX. 1113 """
1114 - def __init__(self, id, template):
1115 self.id = id 1116 self.template = template
1117
1118 - def process (self,params={}):
1119 try: 1120 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params]) 1121 # create a 'sandbox' directory for LaTeX to play in 1122 self.tmp = tempfile.mktemp () 1123 os.makedirs (self.tmp) 1124 self.oldcwd = os.getcwd () 1125 os.chdir (self.tmp) 1126 stdin = os.popen ("latex", "w", 2048) 1127 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout 1128 # FIXME: send LaTeX output to the logger 1129 stdin.close () 1130 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True): 1131 raise FormError ('DVIPS returned error') 1132 except EnvironmentError, e: 1133 _log.error(e.strerror) 1134 raise FormError (e.strerror) 1135 return file ("texput.ps")
1136
1137 - def xdvi (self):
1138 """ 1139 For testing purposes, runs Xdvi on the intermediate TeX output 1140 WARNING: don't try this on Windows 1141 """ 1142 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)
1143
1144 - def exe (self, command):
1145 if "%F" in command: 1146 command.replace ("%F", "texput.ps") 1147 else: 1148 command = "%s < texput.ps" % command 1149 try: 1150 if not gmShellAPI.run_command_in_shell(command, blocking=True): 1151 _log.error("external command %s returned non-zero" % command) 1152 raise FormError ('external command %s returned error' % command) 1153 except EnvironmentError, e: 1154 _log.error(e.strerror) 1155 raise FormError (e.strerror) 1156 return True
1157
1158 - def printout (self):
1159 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print') 1160 self.exe (command)
1161
1162 - def cleanup (self):
1163 """ 1164 Delete all the LaTeX output iles 1165 """ 1166 for i in os.listdir ('.'): 1167 os.unlink (i) 1168 os.chdir (self.oldcwd) 1169 os.rmdir (self.tmp)
1170 1171 1172 1173 1174 #================================================================ 1175 # define a class for HTML forms (for printing) 1176 #================================================================
1177 -class cXSLTFormEngine(cFormEngine):
1178 """This class can create XML document from requested data, 1179 then process it with XSLT template and display results 1180 """ 1181 1182 # FIXME: make the path configurable ? 1183 _preview_program = u'oowriter ' #this program must be in the system PATH 1184
1185 - def __init__(self, template=None):
1186 1187 if template is None: 1188 raise ValueError(u'%s: cannot create form instance without a template' % __name__) 1189 1190 cFormEngine.__init__(self, template = template) 1191 1192 self._FormData = None 1193 1194 # here we know/can assume that the template was stored as a utf-8 1195 # encoded string so use that conversion to create unicode: 1196 #self._XSLTData = unicode(str(template.template_data), 'UTF-8') 1197 # but in fact, unicode() knows how to handle buffers, so simply: 1198 self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict') 1199 1200 # we must still devise a method of extracting the SQL query: 1201 # - either by retrieving it from a particular tag in the XSLT or 1202 # - by making the stored template actually be a dict which, unpickled, 1203 # has the keys "xslt" and "sql" 1204 self._SQL_query = u'select 1' #this sql query must output valid xml
1205 #-------------------------------------------------------- 1206 # external API 1207 #--------------------------------------------------------
1208 - def process(self, sql_parameters):
1209 """get data from backend and process it with XSLT template to produce readable output""" 1210 1211 # extract SQL (this is wrong but displays what is intended) 1212 xslt = libxml2.parseDoc(self._XSLTData) 1213 root = xslt.children 1214 for child in root: 1215 if child.type == 'element': 1216 self._SQL_query = child.content 1217 break 1218 1219 # retrieve data from backend 1220 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False) 1221 1222 __header = '<?xml version="1.0" encoding="UTF-8"?>\n' 1223 __body = rows[0][0] 1224 1225 # process XML data according to supplied XSLT, producing HTML 1226 self._XMLData =__header + __body 1227 style = libxslt.parseStylesheetDoc(xslt) 1228 xml = libxml2.parseDoc(self._XMLData) 1229 html = style.applyStylesheet(xml, None) 1230 self._FormData = html.serialize() 1231 1232 style.freeStylesheet() 1233 xml.freeDoc() 1234 html.freeDoc()
1235 #--------------------------------------------------------
1236 - def preview(self):
1237 if self._FormData is None: 1238 raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed' 1239 1240 fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html') 1241 #html_file = os.open(fname, 'wb') 1242 #html_file.write(self._FormData.encode('UTF-8')) 1243 html_file = codecs.open(fname, 'wb', 'utf8', 'strict') # or 'replace' ? 1244 html_file.write(self._FormData) 1245 html_file.close() 1246 1247 cmd = u'%s %s' % (self.__class__._preview_program, fname) 1248 1249 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False): 1250 _log.error('%s: cannot launch report preview program' % __name__) 1251 return False 1252 1253 #os.unlink(self.filename) #delete file 1254 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK) 1255 1256 return True
1257 #--------------------------------------------------------
1258 - def print_directly(self):
1259 #not so fast, look at it first 1260 self.preview()
1261 1262 1263 #===================================================== 1264 #class LaTeXFilter(Cheetah.Filters.Filter):
1265 -class LaTeXFilter:
1266 - def filter (self, item, table_sep= " \\\\\n", **kwds):
1267 """ 1268 Convience function to escape ISO-Latin-1 strings for TeX output 1269 WARNING: not all ISO-Latin-1 characters are expressible in TeX 1270 FIXME: nevertheless, there are a few more we could support 1271 1272 Also intelligently convert lists and tuples into TeX-style table lines 1273 """ 1274 if type (item) is types.UnicodeType or type (item) is types.StringType: 1275 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX? 1276 item = item.replace ("&", "\\&") 1277 item = item.replace ("$", "\\$") 1278 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now 1279 item = item.replace ("\n", "\\\\ ") 1280 if len (item.strip ()) == 0: 1281 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it 1282 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX 1283 if type (item) is types.UnicodeType: 1284 item = item.encode ('latin-1', 'replace') 1285 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}', 1286 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions 1287 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`', 1288 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}', 1289 '\xc7':'\\c{C}', '\xc8':'\\`{E}', 1290 '\xa1': '!`', 1291 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'} 1292 for k, i in trans.items (): 1293 item = item.replace (k, i) 1294 elif type (item) is types.ListType or type (item) is types.TupleType: 1295 item = string.join ([self.filter (i, ' & ') for i in item], table_sep) 1296 elif item is None: 1297 item = '\\relax % Python None\n' 1298 elif type (item) is types.IntType or type (item) is types.FloatType: 1299 item = str (item) 1300 else: 1301 item = str (item) 1302 _log.warning("unknown type %s, string %s" % (type (item), item)) 1303 return item
1304 1305 1306 #===========================================================
1307 -class cHL7Form (cFormEngine):
1308 pass
1309 1310 #============================================================ 1311 # convenience functions 1312 #------------------------------------------------------------
1313 -def get_form(id):
1314 """ 1315 Instantiates a FormEngine based on the form ID or name from the backend 1316 """ 1317 try: 1318 # it's a number: match to form ID 1319 id = int (id) 1320 cmd = 'select template, engine, pk from paperwork_templates where pk = %s' 1321 except ValueError: 1322 # it's a string, match to the form's name 1323 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ? 1324 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s' 1325 result = gmPG.run_ro_query ('reference', cmd, None, id) 1326 if result is None: 1327 _log.error('error getting form [%s]' % id) 1328 raise gmExceptions.FormError ('error getting form [%s]' % id) 1329 if len(result) == 0: 1330 _log.error('no form [%s] found' % id) 1331 raise gmExceptions.FormError ('no such form found [%s]' % id) 1332 if result[0][1] == 'L': 1333 return LaTeXForm (result[0][2], result[0][0]) 1334 elif result[0][1] == 'T': 1335 return TextForm (result[0][2], result[0][0]) 1336 else: 1337 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id)) 1338 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))
1339 #-------------------------------------------------------------
1340 -class FormError (Exception):
1341 - def __init__ (self, value):
1342 self.value = value
1343
1344 - def __str__ (self):
1345 return repr (self.value)
1346 #------------------------------------------------------------- 1347 1348 test_letter = """ 1349 \\documentclass{letter} 1350 \\address{ $DOCTOR \\\\ 1351 $DOCTORADDRESS} 1352 \\signature{$DOCTOR} 1353 1354 \\begin{document} 1355 \\begin{letter}{$RECIPIENTNAME \\\\ 1356 $RECIPIENTADDRESS} 1357 1358 \\opening{Dear $RECIPIENTNAME} 1359 1360 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\ 1361 1362 $TEXT 1363 1364 \\ifnum$INCLUDEMEDS>0 1365 \\textbf{Medications List} 1366 1367 \\begin{tabular}{lll} 1368 $MEDSLIST 1369 \\end{tabular} 1370 \\fi 1371 1372 \\ifnum$INCLUDEDISEASES>0 1373 \\textbf{Disease List} 1374 1375 \\begin{tabular}{l} 1376 $DISEASELIST 1377 \\end{tabular} 1378 \\fi 1379 1380 \\closing{$CLOSING} 1381 1382 \\end{letter} 1383 \\end{document} 1384 """ 1385 1386
1387 -def test_au():
1388 f = open('../../test-area/ian/terry-form.tex') 1389 params = { 1390 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle", 1391 'DOCTORSNAME': 'Ian Haywood', 1392 'DOCTORSADDRESS': '1 Smith St\nMelbourne', 1393 'PATIENTNAME':'Joe Bloggs', 1394 'PATIENTADDRESS':'18 Fred St\nMelbourne', 1395 'REQUEST':'echocardiogram', 1396 'THERAPY':'on warfarin', 1397 'CLINICALNOTES':"""heard new murmur 1398 Here's some 1399 crap to demonstrate how it can cover multiple lines.""", 1400 'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany', 1401 'ROUTINE':1, 1402 'URGENT':0, 1403 'FAX':1, 1404 'PHONE':1, 1405 'PENSIONER':1, 1406 'VETERAN':0, 1407 'PADS':0, 1408 'INSTRUCTIONS':u'Take the blue pill, Neo' 1409 } 1410 form = LaTeXForm (1, f.read()) 1411 form.process (params) 1412 form.xdvi () 1413 form.cleanup ()
1414
1415 -def test_au2 ():
1416 form = LaTeXForm (2, test_letter) 1417 params = {'RECIPIENTNAME':'Dr. Richard Terry', 1418 'RECIPIENTADDRESS':'1 Main St\nNewcastle', 1419 'DOCTOR':'Dr. Ian Haywood', 1420 'DOCTORADDRESS':'1 Smith St\nMelbourne', 1421 'PATIENTNAME':'Joe Bloggs', 1422 'PATIENTADDRESS':'18 Fred St, Melbourne', 1423 'TEXT':"""This is the main text of the referral letter""", 1424 'DOB':'12/3/65', 1425 'INCLUDEMEDS':1, 1426 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]], 1427 'INCLUDEDISEASES':0, 'DISEASELIST':'', 1428 'CLOSING':'Yours sincerely,' 1429 } 1430 form.process (params) 1431 print os.getcwd () 1432 form.xdvi () 1433 form.cleanup ()
1434 #------------------------------------------------------------
1435 -def test_de():
1436 template = open('../../test-area/ian/Formularkopf-DE.tex') 1437 form = LaTeXForm(template=template.read()) 1438 params = { 1439 'PATIENT LASTNAME': 'Kirk', 1440 'PATIENT FIRSTNAME': 'James T.', 1441 'PATIENT STREET': 'Hauptstrasse', 1442 'PATIENT ZIP': '02999', 1443 'PATIENT TOWN': 'Gross Saerchen', 1444 'PATIENT DOB': '22.03.1931' 1445 } 1446 form.process(params) 1447 form.xdvi() 1448 form.cleanup()
1449 1450 #============================================================ 1451 # main 1452 #------------------------------------------------------------ 1453 if __name__ == '__main__': 1454 1455 if len(sys.argv) < 2: 1456 sys.exit() 1457 1458 if sys.argv[1] != 'test': 1459 sys.exit() 1460 1461 from Gnumed.pycommon import gmI18N, gmDateTime 1462 gmI18N.activate_locale() 1463 gmI18N.install_domain(domain='gnumed') 1464 gmDateTime.init() 1465 1466 #-------------------------------------------------------- 1467 # OOo 1468 #--------------------------------------------------------
1469 - def test_init_ooo():
1470 init_ooo()
1471 #--------------------------------------------------------
1472 - def test_ooo_connect():
1473 srv = gmOOoConnector() 1474 print srv 1475 print srv.desktop
1476 #--------------------------------------------------------
1477 - def test_open_ooo_doc_from_srv():
1478 srv = gmOOoConnector() 1479 doc = srv.open_document(filename = sys.argv[2]) 1480 print "document:", doc
1481 #--------------------------------------------------------
1482 - def test_open_ooo_doc_from_letter():
1483 doc = cOOoLetter(template_file = sys.argv[2]) 1484 doc.open_in_ooo() 1485 print "document:", doc 1486 raw_input('press <ENTER> to continue') 1487 doc.show() 1488 #doc.replace_placeholders() 1489 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1490 # doc = None 1491 # doc.close_in_ooo() 1492 raw_input('press <ENTER> to continue')
1493 #--------------------------------------------------------
1494 - def play_with_ooo():
1495 try: 1496 doc = open_uri_in_ooo(filename=sys.argv[1]) 1497 except: 1498 _log.exception('cannot open [%s] in OOo' % sys.argv[1]) 1499 raise 1500 1501 class myCloseListener(unohelper.Base, oooXCloseListener): 1502 def disposing(self, evt): 1503 print "disposing:"
1504 def notifyClosing(self, evt): 1505 print "notifyClosing:" 1506 def queryClosing(self, evt, owner): 1507 # owner is True/False whether I am the owner of the doc 1508 print "queryClosing:" 1509 1510 l = myCloseListener() 1511 doc.addCloseListener(l) 1512 1513 tfs = doc.getTextFields().createEnumeration() 1514 print tfs 1515 print dir(tfs) 1516 while tfs.hasMoreElements(): 1517 tf = tfs.nextElement() 1518 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'): 1519 print tf.getPropertyValue('PlaceHolder') 1520 print " ", tf.getPropertyValue('Hint') 1521 1522 # doc.close(True) # closes but leaves open the dedicated OOo window 1523 doc.dispose() # closes and disposes of the OOo window 1524 #--------------------------------------------------------
1525 - def test_cOOoLetter():
1526 pat = gmPersonSearch.ask_for_patient() 1527 if pat is None: 1528 return 1529 gmPerson.set_active_patient(patient = pat) 1530 1531 doc = cOOoLetter(template_file = sys.argv[2]) 1532 doc.open_in_ooo() 1533 print doc 1534 doc.show() 1535 #doc.replace_placeholders() 1536 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1537 doc = None 1538 # doc.close_in_ooo() 1539 raw_input('press <ENTER> to continue')
1540 #-------------------------------------------------------- 1541 # other 1542 #--------------------------------------------------------
1543 - def test_cFormTemplate():
1544 template = cFormTemplate(aPK_obj = sys.argv[2]) 1545 print template 1546 print template.export_to_file()
1547 #--------------------------------------------------------
1548 - def set_template_from_file():
1549 template = cFormTemplate(aPK_obj = sys.argv[2]) 1550 template.update_template_from_file(filename = sys.argv[3])
1551 #--------------------------------------------------------
1552 - def test_latex_form():
1553 pat = gmPersonSearch.ask_for_patient() 1554 if pat is None: 1555 return 1556 gmPerson.set_active_patient(patient = pat) 1557 1558 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 1559 1560 path = os.path.abspath(sys.argv[2]) 1561 form = cLaTeXForm(template_file = path) 1562 1563 from Gnumed.wxpython import gmMacro 1564 ph = gmMacro.gmPlaceholderHandler() 1565 ph.debug = True 1566 instance_file = form.substitute_placeholders(data_source = ph) 1567 pdf_name = form.generate_output(instance_file = instance_file) 1568 print "final PDF file is:", pdf_name
1569 #--------------------------------------------------------
1570 - def test_pdf_form():
1571 pat = gmPersonSearch.ask_for_patient() 1572 if pat is None: 1573 return 1574 gmPerson.set_active_patient(patient = pat) 1575 1576 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 1577 1578 path = os.path.abspath(sys.argv[2]) 1579 form = cLaPDFForm(template_file = path) 1580 1581 from Gnumed.wxpython import gmMacro 1582 ph = gmMacro.gmPlaceholderHandler() 1583 ph.debug = True 1584 instance_file = form.substitute_placeholders(data_source = ph) 1585 pdf_name = form.generate_output(instance_file = instance_file) 1586 print "final PDF file is:", pdf_name
1587 #-------------------------------------------------------- 1588 #-------------------------------------------------------- 1589 # now run the tests 1590 #test_au() 1591 #test_de() 1592 1593 # OOo 1594 #test_init_ooo() 1595 #test_ooo_connect() 1596 #test_open_ooo_doc_from_srv() 1597 #test_open_ooo_doc_from_letter() 1598 #play_with_ooo() 1599 #test_cOOoLetter() 1600 1601 #test_cFormTemplate() 1602 #set_template_from_file() 1603 #test_latex_form() 1604 test_pdf_form() 1605 1606 #============================================================ 1607