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