Home | Trees | Indices | Help |
|
---|
|
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 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, codecs, re as regex, shutil, random, platform, subprocess 14 #, libxml2, libxslt 15 16 17 if __name__ == '__main__': 18 sys.path.insert(0, '../../') 19 from Gnumed.pycommon import gmTools, gmBorg, gmMatchProvider, gmExceptions, gmDispatcher 20 from Gnumed.pycommon import gmPG2, gmBusinessDBObject, gmCfg, gmShellAPI, gmMimeLib, gmLog2 21 from Gnumed.business import gmPerson, gmSurgery 22 23 24 _log = logging.getLogger('gm.forms') 25 _log.info(__version__) 26 27 #============================================================ 28 # this order is also used in choice boxes for the engine 29 form_engine_abbrevs = [u'O', u'L', u'I', u'G'] 30 31 form_engine_names = { 32 u'O': 'OpenOffice', 33 u'L': 'LaTeX', 34 u'I': 'Image editor', 35 u'G': 'Gnuplot script' 36 } 37 38 form_engine_template_wildcards = { 39 u'O': u'*.o?t', 40 u'L': u'*.tex', 41 u'G': u'*.gpl' 42 } 43 44 # is filled in further below after each engine is defined 45 form_engines = {} 46 47 #============================================================ 48 # match providers 49 #============================================================5161 #============================================================53 54 query = u""" 55 select name_long, name_long 56 from ref.v_paperwork_templates 57 where name_long %(fragment_condition)s 58 order by name_long 59 """ 60 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])6373 #============================================================65 66 query = u""" 67 select name_short, name_short 68 from ref.v_paperwork_templates 69 where name_short %(fragment_condition)s 70 order by name_short 71 """ 72 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])7591 #============================================================77 78 query = u""" 79 select * from ( 80 select pk, _(name) as l10n_name from ref.form_types 81 where _(name) %(fragment_condition)s 82 83 union 84 85 select pk, _(name) as l10n_name from ref.form_types 86 where name %(fragment_condition)s 87 ) as union_result 88 order by l10n_name 89 """ 90 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])93 94 _cmd_fetch_payload = u'select * from ref.v_paperwork_templates where pk_paperwork_template = %s' 95 96 _cmds_store_payload = [ 97 u"""update ref.paperwork_templates set 98 name_short = %(name_short)s, 99 name_long = %(name_long)s, 100 fk_template_type = %(pk_template_type)s, 101 instance_type = %(instance_type)s, 102 engine = %(engine)s, 103 in_use = %(in_use)s, 104 filename = %(filename)s, 105 external_version = %(external_version)s 106 where 107 pk = %(pk_paperwork_template)s and 108 xmin = %(xmin_paperwork_template)s 109 """, 110 u"""select xmin_paperwork_template from ref.v_paperwork_templates where pk_paperwork_template = %(pk_paperwork_template)s""" 111 ] 112 113 _updatable_fields = [ 114 u'name_short', 115 u'name_long', 116 u'external_version', 117 u'pk_template_type', 118 u'instance_type', 119 u'engine', 120 u'in_use', 121 u'filename' 122 ] 123 124 _suffix4engine = { 125 u'O': u'.ott', 126 u'L': u'.tex', 127 u'T': u'.txt', 128 u'X': u'.xslt', 129 u'I': u'.img' 130 } 131 132 #--------------------------------------------------------198 #============================================================134 """The template itself better not be arbitrarily large unless you can handle that. 135 136 Note that the data type returned will be a buffer.""" 137 138 cmd = u'SELECT data FROM ref.paperwork_templates WHERE pk = %(pk)s' 139 rows, idx = gmPG2.run_ro_queries (queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False) 140 141 if len(rows) == 0: 142 raise gmExceptions.NoSuchBusinessObjectError('cannot retrieve data for template pk = %s' % self.pk_obj) 143 144 return rows[0][0]145 146 template_data = property(_get_template_data, lambda x:x) 147 #--------------------------------------------------------149 """Export form template from database into file.""" 150 151 if filename is None: 152 if self._payload[self._idx['filename']] is None: 153 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 154 else: 155 suffix = os.path.splitext(self._payload[self._idx['filename']].strip())[1].strip() 156 if suffix in [u'', u'.']: 157 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 158 159 filename = gmTools.get_unique_filename ( 160 prefix = 'gm-%s-Template-' % self._payload[self._idx['engine']], 161 suffix = suffix 162 ) 163 164 data_query = { 165 'cmd': u'SELECT substring(data from %(start)s for %(size)s) FROM ref.paperwork_templates WHERE pk = %(pk)s', 166 'args': {'pk': self.pk_obj} 167 } 168 169 data_size_query = { 170 'cmd': u'select octet_length(data) from ref.paperwork_templates where pk = %(pk)s', 171 'args': {'pk': self.pk_obj} 172 } 173 174 result = gmPG2.bytea2file ( 175 data_query = data_query, 176 filename = filename, 177 data_size_query = data_size_query, 178 chunk_size = chunksize 179 ) 180 if result is False: 181 return None 182 183 return filename184 #--------------------------------------------------------186 gmPG2.file2bytea ( 187 filename = filename, 188 query = u'update ref.paperwork_templates set data = %(data)s::bytea where pk = %(pk)s and xmin = %(xmin)s', 189 args = {'pk': self.pk_obj, 'xmin': self._payload[self._idx['xmin_paperwork_template']]} 190 ) 191 # adjust for xmin change 192 self.refetch_payload()193 #--------------------------------------------------------195 fname = self.export_to_file() 196 engine = form_engines[self._payload[self._idx['engine']]] 197 return engine(template_file = fname)200 cmd = u'select pk from ref.paperwork_templates where name_long = %(lname)s and external_version = %(ver)s' 201 args = {'lname': name_long, 'ver': external_version} 202 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 203 204 if len(rows) == 0: 205 _log.error('cannot load form template [%s - %s]', name_long, external_version) 206 return None 207 208 return cFormTemplate(aPK_obj = rows[0]['pk'])209 #------------------------------------------------------------211 """Load form templates.""" 212 213 args = {'eng': engine, 'in_use': active_only} 214 215 where_parts = [] 216 if engine is not None: 217 where_parts.append(u'engine = %(eng)s') 218 219 if active_only: 220 where_parts.append(u'in_use is True') 221 222 if len(where_parts) == 0: 223 cmd = u"select * from ref.v_paperwork_templates order by in_use desc, name_long" 224 else: 225 cmd = u"select * from ref.v_paperwork_templates where %s order by in_use desc, name_long" % u'and'.join(where_parts) 226 227 rows, idx = gmPG2.run_ro_queries ( 228 queries = [{'cmd': cmd, 'args': args}], 229 get_col_idx = True 230 ) 231 templates = [ cFormTemplate(row = {'pk_field': 'pk_paperwork_template', 'data': r, 'idx': idx}) for r in rows ] 232 233 return templates234 #------------------------------------------------------------236 237 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)' 238 rows, idx = gmPG2.run_rw_queries ( 239 queries = [ 240 {'cmd': cmd, 'args': {'type': template_type, 'nshort': name_short, 'nlong': name_long, 'ext_version': 'new'}}, 241 {'cmd': u"select currval(pg_get_serial_sequence('ref.paperwork_templates', 'pk'))"} 242 ], 243 return_data = True 244 ) 245 template = cFormTemplate(aPK_obj = rows[0][0]) 246 return template247 #------------------------------------------------------------249 rows, idx = gmPG2.run_rw_queries ( 250 queries = [ 251 {'cmd': u'delete from ref.paperwork_templates where pk=%(pk)s', 'args': {'pk': template['pk_paperwork_template']}} 252 ] 253 ) 254 return True255 #============================================================ 256 # OpenOffice API 257 #============================================================ 258 uno = None 259 cOOoDocumentCloseListener = None 260 261 #-----------------------------------------------------------263 264 try: 265 which = subprocess.Popen ( 266 args = ('which', 'soffice'), 267 stdout = subprocess.PIPE, 268 stdin = subprocess.PIPE, 269 stderr = subprocess.PIPE, 270 universal_newlines = True 271 ) 272 except (OSError, ValueError, subprocess.CalledProcessError): 273 _log.exception('there was a problem executing [%s]', cmd) 274 return 275 276 soffice_path, err = which.communicate() 277 soffice_path = soffice_path.strip('\n') 278 uno_path = os.path.abspath ( os.path.join ( 279 os.path.dirname(os.path.realpath(soffice_path)), 280 '..', 281 'basis-link', 282 'program' 283 )) 284 285 _log.info('UNO should be at [%s], appending to sys.path', uno_path) 286 287 sys.path.append(uno_path)288 #-----------------------------------------------------------290 """FIXME: consider this: 291 292 try: 293 import uno 294 except: 295 print "This Script needs to be run with the python from OpenOffice.org" 296 print "Example: /opt/OpenOffice.org/program/python %s" % ( 297 os.path.basename(sys.argv[0])) 298 print "Or you need to insert the right path at the top, where uno.py is." 299 print "Default: %s" % default_path 300 """ 301 global uno 302 if uno is not None: 303 return 304 305 try: 306 import uno 307 except ImportError: 308 __configure_path_to_UNO() 309 import uno 310 311 global unohelper, oooXCloseListener, oooNoConnectException, oooPropertyValue 312 313 import unohelper 314 from com.sun.star.util import XCloseListener as oooXCloseListener 315 from com.sun.star.connection import NoConnectException as oooNoConnectException 316 from com.sun.star.beans import PropertyValue as oooPropertyValue 317 318 #---------------------------------- 319 class _cOOoDocumentCloseListener(unohelper.Base, oooXCloseListener): 320 """Listens for events sent by OOo during the document closing 321 sequence and notifies the GNUmed client GUI so it can 322 import the closed document into the database. 323 """ 324 def __init__(self, document=None): 325 self.document = document326 327 def queryClosing(self, evt, owner): 328 # owner is True/False whether I am the owner of the doc 329 pass 330 331 def notifyClosing(self, evt): 332 pass 333 334 def disposing(self, evt): 335 self.document.on_disposed_by_ooo() 336 self.document = None 337 #---------------------------------- 338 339 global cOOoDocumentCloseListener 340 cOOoDocumentCloseListener = _cOOoDocumentCloseListener 341 342 _log.debug('python UNO bridge successfully initialized') 343 344 #------------------------------------------------------------346 """This class handles the connection to OOo. 347 348 Its Singleton instance stays around once initialized. 349 """ 350 # FIXME: need to detect closure of OOo !438 #------------------------------------------------------------352 353 init_ooo() 354 355 #self.ooo_start_cmd = 'oowriter -invisible -accept="socket,host=localhost,port=2002;urp;"' 356 #self.remote_context_uri = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" 357 358 pipe_name = "uno-gm2ooo-%s" % str(random.random())[2:] 359 self.ooo_start_cmd = 'oowriter -invisible -norestore -accept="pipe,name=%s;urp"' % pipe_name 360 self.remote_context_uri = "uno:pipe,name=%s;urp;StarOffice.ComponentContext" % pipe_name 361 362 _log.debug('pipe name: %s', pipe_name) 363 _log.debug('startup command: %s', self.ooo_start_cmd) 364 _log.debug('remote context URI: %s', self.remote_context_uri) 365 366 self.resolver_uri = "com.sun.star.bridge.UnoUrlResolver" 367 self.desktop_uri = "com.sun.star.frame.Desktop" 368 369 self.local_context = uno.getComponentContext() 370 self.uri_resolver = self.local_context.ServiceManager.createInstanceWithContext(self.resolver_uri, self.local_context) 371 372 self.__desktop = None373 #--------------------------------------------------------375 if self.__desktop is None: 376 _log.debug('no desktop, no cleanup') 377 return 378 379 try: 380 self.__desktop.terminate() 381 except: 382 _log.exception('cannot terminate OOo desktop')383 #--------------------------------------------------------385 """<filename> must be absolute""" 386 387 if self.desktop is None: 388 _log.error('cannot access OOo desktop') 389 return None 390 391 filename = os.path.expanduser(filename) 392 filename = os.path.abspath(filename) 393 document_uri = uno.systemPathToFileUrl(filename) 394 395 _log.debug('%s -> %s', filename, document_uri) 396 397 doc = self.desktop.loadComponentFromURL(document_uri, "_blank", 0, ()) 398 return doc399 #-------------------------------------------------------- 400 # internal helpers 401 #--------------------------------------------------------403 # later factor this out ! 404 dbcfg = gmCfg.cCfgSQL() 405 self.ooo_startup_settle_time = dbcfg.get2 ( 406 option = u'external.ooo.startup_settle_time', 407 workplace = gmSurgery.gmCurrentPractice().active_workplace, 408 bias = u'workplace', 409 default = 3.0 410 )411 #-------------------------------------------------------- 412 # properties 413 #--------------------------------------------------------415 if self.__desktop is not None: 416 return self.__desktop 417 418 try: 419 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri) 420 except oooNoConnectException: 421 _log.exception('cannot connect to OOo server') 422 _log.info('trying to start OOo server') 423 os.system(self.ooo_start_cmd) 424 self.__get_startup_settle_time() 425 _log.debug('waiting %s seconds for OOo to start up', self.ooo_startup_settle_time) 426 time.sleep(self.ooo_startup_settle_time) # OOo sometimes needs a bit 427 try: 428 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri) 429 except oooNoConnectException: 430 _log.exception('cannot start (or connect to started) OOo server') 431 return None 432 433 self.__desktop = self.remote_context.ServiceManager.createInstanceWithContext(self.desktop_uri, self.remote_context) 434 _log.debug('connection seems established') 435 return self.__desktop436 437 desktop = property(_get_desktop, lambda x:x)440545 #-------------------------------------------------------- 546 # internal helpers 547 #-------------------------------------------------------- 548 549 #============================================================442 443 self.template_file = template_file 444 self.instance_type = instance_type 445 self.ooo_doc = None446 #-------------------------------------------------------- 447 # external API 448 #--------------------------------------------------------450 # connect to OOo 451 ooo_srv = gmOOoConnector() 452 453 # open doc in OOo 454 self.ooo_doc = ooo_srv.open_document(filename = self.template_file) 455 if self.ooo_doc is None: 456 _log.error('cannot open document in OOo') 457 return False 458 459 # listen for close events 460 pat = gmPerson.gmCurrentPatient() 461 pat.locked = True 462 listener = cOOoDocumentCloseListener(document = self) 463 self.ooo_doc.addCloseListener(listener) 464 465 return True466 #-------------------------------------------------------- 469 #--------------------------------------------------------471 472 # new style embedded, implicit placeholders 473 searcher = self.ooo_doc.createSearchDescriptor() 474 searcher.SearchCaseSensitive = False 475 searcher.SearchRegularExpression = True 476 searcher.SearchWords = True 477 searcher.SearchString = handler.placeholder_regex 478 479 placeholder_instance = self.ooo_doc.findFirst(searcher) 480 while placeholder_instance is not None: 481 try: 482 val = handler[placeholder_instance.String] 483 except: 484 _log.exception(val) 485 val = _('error with placeholder [%s]') % placeholder_instance.String 486 487 if val is None: 488 val = _('error with placeholder [%s]') % placeholder_instance.String 489 490 placeholder_instance.String = val 491 placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher) 492 493 if not old_style_too: 494 return 495 496 # old style "explicit" placeholders 497 text_fields = self.ooo_doc.getTextFields().createEnumeration() 498 while text_fields.hasMoreElements(): 499 text_field = text_fields.nextElement() 500 501 # placeholder ? 502 if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'): 503 continue 504 # placeholder of type text ? 505 if text_field.PlaceHolderType != 0: 506 continue 507 508 replacement = handler[text_field.PlaceHolder] 509 if replacement is None: 510 continue 511 512 text_field.Anchor.setString(replacement)513 #--------------------------------------------------------515 if filename is not None: 516 target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename))) 517 save_args = ( 518 oooPropertyValue('Overwrite', 0, True, 0), 519 oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0) 520 521 ) 522 # "store AS url" stores the doc, marks it unmodified and updates 523 # the internal media descriptor - as opposed to "store TO url" 524 self.ooo_doc.storeAsURL(target_url, save_args) 525 else: 526 self.ooo_doc.store()527 #--------------------------------------------------------529 self.ooo_doc.dispose() 530 pat = gmPerson.gmCurrentPatient() 531 pat.locked = False 532 self.ooo_doc = None533 #--------------------------------------------------------535 # get current file name from OOo, user may have used Save As 536 filename = uno.fileUrlToSystemPath(self.ooo_doc.URL) 537 # tell UI to import the file 538 gmDispatcher.send ( 539 signal = u'import_document_from_file', 540 filename = filename, 541 document_type = self.instance_type, 542 unlock_patient = True 543 ) 544 self.ooo_doc = None551 """Ancestor for forms.""" 552 555 #--------------------------------------------------------634 635 #================================================================ 636 # OOo template forms 637 #----------------------------------------------------------------557 """Parse the template into an instance and replace placeholders with values.""" 558 raise NotImplementedError559 #-------------------------------------------------------- 563 #--------------------------------------------------------565 """Generate output suitable for further processing outside this class, e.g. printing.""" 566 raise NotImplementedError567 #-------------------------------------------------------- 572 #--------------------------------------------------------574 """ 575 A sop to TeX which can't act as a true filter: to delete temporary files 576 """ 577 pass578 #--------------------------------------------------------580 """ 581 Executes the provided command. 582 If command cotains %F. it is substituted with the filename 583 Otherwise, the file is fed in on stdin 584 """ 585 pass586 #--------------------------------------------------------588 """Stores the parameters in the backend. 589 590 - link_obj can be a cursor, a connection or a service name 591 - assigning a cursor to link_obj allows the calling code to 592 group the call to store() into an enclosing transaction 593 (for an example see gmReferral.send_referral()...) 594 """ 595 # some forms may not have values ... 596 if params is None: 597 params = {} 598 patient_clinical = self.patient.get_emr() 599 encounter = patient_clinical.active_encounter['pk_encounter'] 600 # FIXME: get_active_episode is no more 601 #episode = patient_clinical.get_active_episode()['pk_episode'] 602 # generate "forever unique" name 603 cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s"; 604 rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def) 605 form_name = None 606 if rows is None: 607 _log.error('error retrieving form def for [%s]' % self.pk_def) 608 elif len(rows) == 0: 609 _log.error('no form def for [%s]' % self.pk_def) 610 else: 611 form_name = rows[0][0] 612 # we didn't get a name but want to store the form anyhow 613 if form_name is None: 614 form_name=time.time() # hopefully unique enough 615 # in one transaction 616 queries = [] 617 # - store form instance in form_instance 618 cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)" 619 queries.append((cmd, [self.pk_def, form_name, episode, encounter])) 620 # - store params in form_data 621 for key in params.keys(): 622 cmd = """ 623 insert into form_data(fk_instance, place_holder, value) 624 values ((select currval('form_instances_pk_seq')), %s, %s::text) 625 """ 626 queries.append((cmd, [key, params[key]])) 627 # - get inserted PK 628 queries.append(("select currval ('form_instances_pk_seq')", [])) 629 status, err = gmPG.run_commit('historica', queries, True) 630 if status is None: 631 _log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err)) 632 return None 633 return status639 """A forms engine wrapping OOo.""" 640649 650 #================================================================ 651 # LaTeX template forms 652 #----------------------------------------------------------------642 super(self.__class__, self).__init__(template_file = template_file) 643 644 645 path, ext = os.path.splitext(self.template_filename) 646 if ext in [r'', r'.']: 647 ext = r'.odt' 648 self.instance_filename = r'%s-instance%s' % (path, ext)654 """A forms engine wrapping LaTeX.""" 655778 #------------------------------------------------------------ 779 form_engines[u'L'] = cLaTeXForm 780 #============================================================ 781 # Gnuplot template forms 782 #------------------------------------------------------------657 super(self.__class__, self).__init__(template_file = template_file) 658 path, ext = os.path.splitext(self.template_filename) 659 if ext in [r'', r'.']: 660 ext = r'.tex' 661 self.instance_filename = r'%s-instance%s' % (path, ext)662 #--------------------------------------------------------664 665 template_file = codecs.open(self.template_filename, 'rU', 'utf8') 666 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8') 667 668 for line in template_file: 669 670 if line.strip() in [u'', u'\r', u'\n', u'\r\n']: 671 instance_file.write(line) 672 continue 673 674 # 1) find placeholders in this line 675 placeholders_in_line = regex.findall(data_source.placeholder_regex, line, regex.IGNORECASE) 676 # 2) and replace them 677 for placeholder in placeholders_in_line: 678 #line = line.replace(placeholder, self._texify_string(data_source[placeholder])) 679 try: 680 val = data_source[placeholder] 681 except: 682 _log.exception(val) 683 val = _('error with placeholder [%s]') % placeholder 684 685 if val is None: 686 val = _('error with placeholder [%s]') % placeholder 687 688 line = line.replace(placeholder, val) 689 690 instance_file.write(line) 691 692 instance_file.close() 693 template_file.close() 694 695 return696 #--------------------------------------------------------698 699 mimetypes = [ 700 u'application/x-latex', 701 u'application/x-tex', 702 u'text/plain' 703 ] 704 705 for mimetype in mimetypes: 706 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 707 708 if editor_cmd is None: 709 editor_cmd = u'sensible-editor %s' % self.instance_filename 710 711 return gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)712 #--------------------------------------------------------714 715 if instance_file is None: 716 instance_file = self.instance_filename 717 718 try: 719 open(instance_file, 'r').close() 720 except: 721 _log.exception('cannot access form instance file [%s]', instance_file) 722 gmLog2.log_stack_trace() 723 return None 724 725 self.instance_filename = instance_file 726 727 _log.debug('ignoring <format> directive [%s], generating PDF', format) 728 729 # create sandbox for LaTeX to play in 730 sandbox_dir = os.path.splitext(self.template_filename)[0] 731 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir) 732 733 old_cwd = os.getcwd() 734 _log.debug('CWD: [%s]', old_cwd) 735 736 gmTools.mkdir(sandbox_dir) 737 os.chdir(sandbox_dir) 738 739 sandboxed_instance_filename = os.path.join(sandbox_dir, os.path.split(self.instance_filename)[1]) 740 shutil.move(self.instance_filename, sandboxed_instance_filename) 741 742 # LaTeX can need up to three runs to get cross-references et al right 743 if platform.system() == 'Windows': 744 cmd = r'pdflatex.exe -interaction nonstopmode %s' % sandboxed_instance_filename 745 else: 746 cmd = r'pdflatex -interaction nonstopmode %s' % sandboxed_instance_filename 747 for run in [1, 2, 3]: 748 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True): 749 _log.error('problem running pdflatex, cannot generate form output') 750 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True) 751 return None 752 753 os.chdir(old_cwd) 754 pdf_name = u'%s.pdf' % os.path.splitext(sandboxed_instance_filename)[0] 755 shutil.move(pdf_name, os.path.split(self.instance_filename)[0]) 756 pdf_name = u'%s.pdf' % os.path.splitext(self.instance_filename)[0] 757 758 # cleanup LaTeX sandbox ? 759 if cleanup: 760 for fname in os.listdir(sandbox_dir): 761 os.remove(os.path.join(sandbox_dir, fname)) 762 os.rmdir(sandbox_dir) 763 764 try: 765 open(pdf_name, 'r').close() 766 return pdf_name 767 except IOError: 768 _log.exception('cannot open target PDF: %s', pdf_name) 769 770 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 771 return None772 #--------------------------------------------------------774 try: 775 os.remove(self.template_filename) 776 except: 777 _log.debug(u'cannot remove template file [%s]', self.template_filename)784 """A forms engine wrapping Gnuplot.""" 785 786 #-------------------------------------------------------- 790 #-------------------------------------------------------- 794 #--------------------------------------------------------836 #------------------------------------------------------------ 837 form_engines[u'G'] = cGnuplotForm 838 #------------------------------------------------------------ 839 #------------------------------------------------------------796 """Generate output suitable for further processing outside this class, e.g. printing. 797 798 Expects .data_filename to be set. 799 """ 800 self.conf_filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.conf') 801 fname_file = codecs.open(self.conf_filename, 'wb', 'utf8') 802 fname_file.write('# setting the gnuplot data file\n') 803 fname_file.write("gm2gpl_datafile = '%s'\n" % self.data_filename) 804 fname_file.close() 805 806 # FIXME: cater for configurable path 807 if platform.system() == 'Windows': 808 exec_name = 'gnuplot.exe' 809 else: 810 exec_name = 'gnuplot' 811 812 args = [exec_name, '-p', self.conf_filename, self.template_filename] 813 _log.debug('plotting args: %s' % str(args)) 814 815 try: 816 gp = subprocess.Popen ( 817 args = args, 818 close_fds = True 819 ) 820 except (OSError, ValueError, subprocess.CalledProcessError): 821 _log.exception('there was a problem executing gnuplot') 822 gmDispatcher.send(signal = u'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True) 823 return 824 825 gp.communicate() 826 827 return828 #--------------------------------------------------------830 try: 831 os.remove(self.template_filename) 832 os.remove(self.conf_filename) 833 os.remove(self.data_filename) 834 except StandardError: 835 _log.exception(u'cannot remove either of script/conf/data file')841 """A forms engine wrapping LaTeX. 842 """ 846899 900 901 902 903 #================================================================ 904 # define a class for HTML forms (for printing) 905 #================================================================848 try: 849 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params]) 850 # create a 'sandbox' directory for LaTeX to play in 851 self.tmp = tempfile.mktemp () 852 os.makedirs (self.tmp) 853 self.oldcwd = os.getcwd () 854 os.chdir (self.tmp) 855 stdin = os.popen ("latex", "w", 2048) 856 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout 857 # FIXME: send LaTeX output to the logger 858 stdin.close () 859 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True): 860 raise FormError ('DVIPS returned error') 861 except EnvironmentError, e: 862 _log.error(e.strerror) 863 raise FormError (e.strerror) 864 return file ("texput.ps")865867 """ 868 For testing purposes, runs Xdvi on the intermediate TeX output 869 WARNING: don't try this on Windows 870 """ 871 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)872874 if "%F" in command: 875 command.replace ("%F", "texput.ps") 876 else: 877 command = "%s < texput.ps" % command 878 try: 879 if not gmShellAPI.run_command_in_shell(command, blocking=True): 880 _log.error("external command %s returned non-zero" % command) 881 raise FormError ('external command %s returned error' % command) 882 except EnvironmentError, e: 883 _log.error(e.strerror) 884 raise FormError (e.strerror) 885 return True886888 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print') 889 self.exe (command)890907 """This class can create XML document from requested data, 908 then process it with XSLT template and display results 909 """ 910 911 # FIXME: make the path configurable ? 912 _preview_program = u'oowriter ' #this program must be in the system PATH 913990 991 992 #===================================================== 993 #class LaTeXFilter(Cheetah.Filters.Filter):915 916 if template is None: 917 raise ValueError(u'%s: cannot create form instance without a template' % __name__) 918 919 cFormEngine.__init__(self, template = template) 920 921 self._FormData = None 922 923 # here we know/can assume that the template was stored as a utf-8 924 # encoded string so use that conversion to create unicode: 925 #self._XSLTData = unicode(str(template.template_data), 'UTF-8') 926 # but in fact, unicode() knows how to handle buffers, so simply: 927 self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict') 928 929 # we must still devise a method of extracting the SQL query: 930 # - either by retrieving it from a particular tag in the XSLT or 931 # - by making the stored template actually be a dict which, unpickled, 932 # has the keys "xslt" and "sql" 933 self._SQL_query = u'select 1' #this sql query must output valid xml934 #-------------------------------------------------------- 935 # external API 936 #--------------------------------------------------------938 """get data from backend and process it with XSLT template to produce readable output""" 939 940 # extract SQL (this is wrong but displays what is intended) 941 xslt = libxml2.parseDoc(self._XSLTData) 942 root = xslt.children 943 for child in root: 944 if child.type == 'element': 945 self._SQL_query = child.content 946 break 947 948 # retrieve data from backend 949 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False) 950 951 __header = '<?xml version="1.0" encoding="UTF-8"?>\n' 952 __body = rows[0][0] 953 954 # process XML data according to supplied XSLT, producing HTML 955 self._XMLData =__header + __body 956 style = libxslt.parseStylesheetDoc(xslt) 957 xml = libxml2.parseDoc(self._XMLData) 958 html = style.applyStylesheet(xml, None) 959 self._FormData = html.serialize() 960 961 style.freeStylesheet() 962 xml.freeDoc() 963 html.freeDoc()964 #--------------------------------------------------------966 if self._FormData is None: 967 raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed' 968 969 fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html') 970 #html_file = os.open(fname, 'wb') 971 #html_file.write(self._FormData.encode('UTF-8')) 972 html_file = codecs.open(fname, 'wb', 'utf8', 'strict') # or 'replace' ? 973 html_file.write(self._FormData) 974 html_file.close() 975 976 cmd = u'%s %s' % (self.__class__._preview_program, fname) 977 978 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False): 979 _log.error('%s: cannot launch report preview program' % __name__) 980 return False 981 982 #os.unlink(self.filename) #delete file 983 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK) 984 985 return True986 #--------------------------------------------------------1033 1034 1035 #=========================================================== 1038 1039 #============================================================ 1040 # convenience functions 1041 #------------------------------------------------------------996 """ 997 Convience function to escape ISO-Latin-1 strings for TeX output 998 WARNING: not all ISO-Latin-1 characters are expressible in TeX 999 FIXME: nevertheless, there are a few more we could support 1000 1001 Also intelligently convert lists and tuples into TeX-style table lines 1002 """ 1003 if type (item) is types.UnicodeType or type (item) is types.StringType: 1004 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX? 1005 item = item.replace ("&", "\\&") 1006 item = item.replace ("$", "\\$") 1007 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now 1008 item = item.replace ("\n", "\\\\ ") 1009 if len (item.strip ()) == 0: 1010 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it 1011 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX 1012 if type (item) is types.UnicodeType: 1013 item = item.encode ('latin-1', 'replace') 1014 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}', 1015 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions 1016 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`', 1017 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}', 1018 '\xc7':'\\c{C}', '\xc8':'\\`{E}', 1019 '\xa1': '!`', 1020 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'} 1021 for k, i in trans.items (): 1022 item = item.replace (k, i) 1023 elif type (item) is types.ListType or type (item) is types.TupleType: 1024 item = string.join ([self.filter (i, ' & ') for i in item], table_sep) 1025 elif item is None: 1026 item = '\\relax % Python None\n' 1027 elif type (item) is types.IntType or type (item) is types.FloatType: 1028 item = str (item) 1029 else: 1030 item = str (item) 1031 _log.warning("unknown type %s, string %s" % (type (item), item)) 1032 return item1043 """ 1044 Instantiates a FormEngine based on the form ID or name from the backend 1045 """ 1046 try: 1047 # it's a number: match to form ID 1048 id = int (id) 1049 cmd = 'select template, engine, pk from paperwork_templates where pk = %s' 1050 except ValueError: 1051 # it's a string, match to the form's name 1052 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ? 1053 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s' 1054 result = gmPG.run_ro_query ('reference', cmd, None, id) 1055 if result is None: 1056 _log.error('error getting form [%s]' % id) 1057 raise gmExceptions.FormError ('error getting form [%s]' % id) 1058 if len(result) == 0: 1059 _log.error('no form [%s] found' % id) 1060 raise gmExceptions.FormError ('no such form found [%s]' % id) 1061 if result[0][1] == 'L': 1062 return LaTeXForm (result[0][2], result[0][0]) 1063 elif result[0][1] == 'T': 1064 return TextForm (result[0][2], result[0][0]) 1065 else: 1066 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id)) 1067 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))1068 #------------------------------------------------------------- 1075 #------------------------------------------------------------- 1076 1077 test_letter = """ 1078 \\documentclass{letter} 1079 \\address{ $DOCTOR \\\\ 1080 $DOCTORADDRESS} 1081 \\signature{$DOCTOR} 1082 1083 \\begin{document} 1084 \\begin{letter}{$RECIPIENTNAME \\\\ 1085 $RECIPIENTADDRESS} 1086 1087 \\opening{Dear $RECIPIENTNAME} 1088 1089 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\ 1090 1091 $TEXT 1092 1093 \\ifnum$INCLUDEMEDS>0 1094 \\textbf{Medications List} 1095 1096 \\begin{tabular}{lll} 1097 $MEDSLIST 1098 \\end{tabular} 1099 \\fi 1100 1101 \\ifnum$INCLUDEDISEASES>0 1102 \\textbf{Disease List} 1103 1104 \\begin{tabular}{l} 1105 $DISEASELIST 1106 \\end{tabular} 1107 \\fi 1108 1109 \\closing{$CLOSING} 1110 1111 \\end{letter} 1112 \\end{document} 1113 """ 1114 11151117 f = open('../../test-area/ian/terry-form.tex') 1118 params = { 1119 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle", 1120 'DOCTORSNAME': 'Ian Haywood', 1121 'DOCTORSADDRESS': '1 Smith St\nMelbourne', 1122 'PATIENTNAME':'Joe Bloggs', 1123 'PATIENTADDRESS':'18 Fred St\nMelbourne', 1124 'REQUEST':'echocardiogram', 1125 'THERAPY':'on warfarin', 1126 'CLINICALNOTES':"""heard new murmur 1127 Here's some 1128 crap to demonstrate how it can cover multiple lines.""", 1129 'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany', 1130 'ROUTINE':1, 1131 'URGENT':0, 1132 'FAX':1, 1133 'PHONE':1, 1134 'PENSIONER':1, 1135 'VETERAN':0, 1136 'PADS':0, 1137 'INSTRUCTIONS':u'Take the blue pill, Neo' 1138 } 1139 form = LaTeXForm (1, f.read()) 1140 form.process (params) 1141 form.xdvi () 1142 form.cleanup ()11431145 form = LaTeXForm (2, test_letter) 1146 params = {'RECIPIENTNAME':'Dr. Richard Terry', 1147 'RECIPIENTADDRESS':'1 Main St\nNewcastle', 1148 'DOCTOR':'Dr. Ian Haywood', 1149 'DOCTORADDRESS':'1 Smith St\nMelbourne', 1150 'PATIENTNAME':'Joe Bloggs', 1151 'PATIENTADDRESS':'18 Fred St, Melbourne', 1152 'TEXT':"""This is the main text of the referral letter""", 1153 'DOB':'12/3/65', 1154 'INCLUDEMEDS':1, 1155 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]], 1156 'INCLUDEDISEASES':0, 'DISEASELIST':'', 1157 'CLOSING':'Yours sincerely,' 1158 } 1159 form.process (params) 1160 print os.getcwd () 1161 form.xdvi () 1162 form.cleanup ()1163 #------------------------------------------------------------1165 template = open('../../test-area/ian/Formularkopf-DE.tex') 1166 form = LaTeXForm(template=template.read()) 1167 params = { 1168 'PATIENT LASTNAME': 'Kirk', 1169 'PATIENT FIRSTNAME': 'James T.', 1170 'PATIENT STREET': 'Hauptstrasse', 1171 'PATIENT ZIP': '02999', 1172 'PATIENT TOWN': 'Gross Saerchen', 1173 'PATIENT DOB': '22.03.1931' 1174 } 1175 form.process(params) 1176 form.xdvi() 1177 form.cleanup()1178 1179 #============================================================ 1180 # main 1181 #------------------------------------------------------------ 1182 if __name__ == '__main__': 1183 1184 if len(sys.argv) < 2: 1185 sys.exit() 1186 1187 if sys.argv[1] != 'test': 1188 sys.exit() 1189 1190 from Gnumed.pycommon import gmI18N, gmDateTime 1191 gmI18N.activate_locale() 1192 gmI18N.install_domain(domain='gnumed') 1193 gmDateTime.init() 1194 1195 #-------------------------------------------------------- 1196 # OOo 1197 #--------------------------------------------------------1199 init_ooo()1200 #-------------------------------------------------------- 1205 #--------------------------------------------------------1207 srv = gmOOoConnector() 1208 doc = srv.open_document(filename = sys.argv[2]) 1209 print "document:", doc1210 #--------------------------------------------------------1212 doc = cOOoLetter(template_file = sys.argv[2]) 1213 doc.open_in_ooo() 1214 print "document:", doc 1215 raw_input('press <ENTER> to continue') 1216 doc.show() 1217 #doc.replace_placeholders() 1218 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1219 # doc = None 1220 # doc.close_in_ooo() 1221 raw_input('press <ENTER> to continue')1222 #--------------------------------------------------------1224 try: 1225 doc = open_uri_in_ooo(filename=sys.argv[1]) 1226 except: 1227 _log.exception('cannot open [%s] in OOo' % sys.argv[1]) 1228 raise 1229 1230 class myCloseListener(unohelper.Base, oooXCloseListener): 1231 def disposing(self, evt): 1232 print "disposing:"1233 def notifyClosing(self, evt): 1234 print "notifyClosing:" 1235 def queryClosing(self, evt, owner): 1236 # owner is True/False whether I am the owner of the doc 1237 print "queryClosing:" 1238 1239 l = myCloseListener() 1240 doc.addCloseListener(l) 1241 1242 tfs = doc.getTextFields().createEnumeration() 1243 print tfs 1244 print dir(tfs) 1245 while tfs.hasMoreElements(): 1246 tf = tfs.nextElement() 1247 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'): 1248 print tf.getPropertyValue('PlaceHolder') 1249 print " ", tf.getPropertyValue('Hint') 1250 1251 # doc.close(True) # closes but leaves open the dedicated OOo window 1252 doc.dispose() # closes and disposes of the OOo window 1253 #--------------------------------------------------------1255 pat = gmPerson.ask_for_patient() 1256 if pat is None: 1257 return 1258 gmPerson.set_active_patient(patient = pat) 1259 1260 doc = cOOoLetter(template_file = sys.argv[2]) 1261 doc.open_in_ooo() 1262 print doc 1263 doc.show() 1264 #doc.replace_placeholders() 1265 #doc.save_in_ooo('~/test_cOOoLetter.odt') 1266 doc = None 1267 # doc.close_in_ooo() 1268 raw_input('press <ENTER> to continue')1269 #-------------------------------------------------------- 1270 # other 1271 #--------------------------------------------------------1273 template = cFormTemplate(aPK_obj = sys.argv[2]) 1274 print template 1275 print template.export_to_file()1276 #--------------------------------------------------------1278 template = cFormTemplate(aPK_obj = sys.argv[2]) 1279 template.update_template_from_file(filename = sys.argv[3])1280 #--------------------------------------------------------1282 pat = gmPerson.ask_for_patient() 1283 if pat is None: 1284 return 1285 gmPerson.set_active_patient(patient = pat) 1286 1287 gmPerson.gmCurrentProvider(provider = gmPerson.cStaff()) 1288 1289 path = os.path.abspath(sys.argv[2]) 1290 form = cLaTeXForm(template_file = path) 1291 1292 from Gnumed.wxpython import gmMacro 1293 ph = gmMacro.gmPlaceholderHandler() 1294 ph.debug = True 1295 instance_file = form.substitute_placeholders(data_source = ph) 1296 pdf_name = form.generate_output(instance_file = instance_file, cleanup = False) 1297 print "final PDF file is:", pdf_name1298 1299 #-------------------------------------------------------- 1300 #-------------------------------------------------------- 1301 # now run the tests 1302 #test_au() 1303 #test_de() 1304 1305 # OOo 1306 #test_init_ooo() 1307 #test_ooo_connect() 1308 #test_open_ooo_doc_from_srv() 1309 #test_open_ooo_doc_from_letter() 1310 #play_with_ooo() 1311 #test_cOOoLetter() 1312 1313 #test_cFormTemplate() 1314 #set_template_from_file() 1315 test_latex_form() 1316 1317 #============================================================ 1318
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sat May 22 04:13:59 2010 | http://epydoc.sourceforge.net |