Home | Trees | Indices | Help |
|
---|
|
1 # -*- coding: utf-8 -*- 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 __author__ ="Ian Haywood <ihaywood@gnu.org>, karsten.hilbert@gmx.net" 10 11 12 import os 13 import sys 14 import time 15 import os.path 16 import logging 17 import re as regex 18 import shutil 19 import random 20 import platform 21 import subprocess 22 import io 23 import codecs 24 import socket # needed for OOo on Windows 25 #, libxml2, libxslt 26 import shlex 27 28 29 if __name__ == '__main__': 30 sys.path.insert(0, '../../') 31 from Gnumed.pycommon import gmI18N 32 gmI18N.activate_locale() 33 gmI18N.install_domain(domain = 'gnumed') 34 from Gnumed.pycommon import gmTools 35 from Gnumed.pycommon import gmDispatcher 36 from Gnumed.pycommon import gmExceptions 37 from Gnumed.pycommon import gmMatchProvider 38 from Gnumed.pycommon import gmBorg 39 from Gnumed.pycommon import gmLog2 40 from Gnumed.pycommon import gmMimeLib 41 from Gnumed.pycommon import gmShellAPI 42 from Gnumed.pycommon import gmCfg 43 from Gnumed.pycommon import gmCfg2 44 from Gnumed.pycommon import gmBusinessDBObject 45 from Gnumed.pycommon import gmPG2 46 from Gnumed.pycommon import gmDateTime 47 48 from Gnumed.business import gmPerson 49 from Gnumed.business import gmStaff 50 from Gnumed.business import gmPersonSearch 51 from Gnumed.business import gmPraxis 52 53 54 _log = logging.getLogger('gm.forms') 55 _cfg = gmCfg2.gmCfgData() 56 57 #============================================================ 58 # this order is also used in choice boxes for the engine 59 form_engine_abbrevs = ['O', 'L', 'I', 'G', 'P', 'A', 'X', 'T'] 60 61 form_engine_names = { 62 'O': 'OpenOffice', 63 'L': 'LaTeX', 64 'I': 'Image editor', 65 'G': 'Gnuplot script', 66 'P': 'PDF forms', 67 'A': 'AbiWord', 68 'X': 'Xe(La)TeX', 69 'T': 'text export' 70 } 71 72 form_engine_template_wildcards = { 73 'O': '*.o?t', 74 'L': '*.tex', 75 'G': '*.gpl', 76 'P': '*.pdf', 77 'A': '*.abw', 78 'X': '*.tex', 79 'T': '*.ini' 80 } 81 82 # is filled in further below after each engine is defined 83 form_engines = {} 84 85 #============================================================ 86 # match providers 87 #============================================================89102 #============================================================91 92 query = """ 93 SELECT 94 name_long AS data, 95 name_long AS list_label, 96 name_long AS field_label 97 FROM ref.v_paperwork_templates 98 WHERE name_long %(fragment_condition)s 99 ORDER BY list_label 100 """ 101 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])104117 #============================================================106 107 query = """ 108 SELECT 109 name_short AS data, 110 name_short AS list_label, 111 name_short AS field_label 112 FROM ref.v_paperwork_templates 113 WHERE name_short %(fragment_condition)s 114 ORDER BY name_short 115 """ 116 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])119135 136 #============================================================121 122 query = """ 123 SELECT DISTINCT ON (list_label) 124 pk AS data, 125 _(name) || ' (' || name || ')' AS list_label, 126 _(name) AS field_label 127 FROM ref.form_types 128 WHERE 129 _(name) %(fragment_condition)s 130 OR 131 name %(fragment_condition)s 132 ORDER BY list_label 133 """ 134 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])138 139 _cmd_fetch_payload = 'SELECT * FROM ref.v_paperwork_templates WHERE pk_paperwork_template = %s' 140 141 _cmds_store_payload = [ 142 """UPDATE ref.paperwork_templates SET 143 name_short = %(name_short)s, 144 name_long = %(name_long)s, 145 fk_template_type = %(pk_template_type)s, 146 instance_type = %(instance_type)s, 147 engine = %(engine)s, 148 in_use = %(in_use)s, 149 edit_after_substitution = %(edit_after_substitution)s, 150 filename = %(filename)s, 151 external_version = %(external_version)s 152 WHERE 153 pk = %(pk_paperwork_template)s 154 AND 155 xmin = %(xmin_paperwork_template)s 156 RETURNING 157 xmin AS xmin_paperwork_template 158 """ 159 ] 160 _updatable_fields = [ 161 'name_short', 162 'name_long', 163 'external_version', 164 'pk_template_type', 165 'instance_type', 166 'engine', 167 'in_use', 168 'filename', 169 'edit_after_substitution' 170 ] 171 172 _suffix4engine = { 173 'O': '.ott', 174 'L': '.tex', 175 'T': '.txt', 176 'X': '.xslt', 177 'I': '.img', 178 'P': '.pdf', 179 'G': '.gpl' 180 } 181 182 #--------------------------------------------------------257 258 #============================================================184 """The template itself better not be arbitrarily large unless you can handle that. 185 186 Note that the data type returned will be a buffer.""" 187 188 cmd = 'SELECT data FROM ref.paperwork_templates WHERE pk = %(pk)s' 189 rows, idx = gmPG2.run_ro_queries (queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False) 190 191 if len(rows) == 0: 192 raise gmExceptions.NoSuchBusinessObjectError('cannot retrieve data for template pk = %s' % self.pk_obj) 193 194 return rows[0][0]195 196 template_data = property(_get_template_data, lambda x:x) 197 198 #--------------------------------------------------------200 """Export form template from database into file.""" 201 202 if filename is None: 203 if use_sandbox: 204 sandbox_dir = gmTools.mk_sandbox_dir(prefix = 'gm2%s-' % self._payload[self._idx['engine']]) 205 else: 206 sandbox_dir = None 207 if self._payload[self._idx['filename']] is None: 208 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 209 else: 210 suffix = os.path.splitext(self._payload[self._idx['filename']].strip())[1].strip() 211 if suffix in ['', '.']: 212 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]] 213 filename = gmTools.get_unique_filename ( 214 prefix = 'gm-%s-Template-' % self._payload[self._idx['engine']], 215 suffix = suffix, 216 tmp_dir = sandbox_dir 217 ) 218 219 data_query = { 220 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM ref.paperwork_templates WHERE pk = %(pk)s', 221 'args': {'pk': self.pk_obj} 222 } 223 224 data_size_query = { 225 'cmd': 'select octet_length(data) from ref.paperwork_templates where pk = %(pk)s', 226 'args': {'pk': self.pk_obj} 227 } 228 229 result = gmPG2.bytea2file ( 230 data_query = data_query, 231 filename = filename, 232 data_size_query = data_size_query, 233 chunk_size = chunksize 234 ) 235 if result is False: 236 return None 237 238 return filename239 240 #--------------------------------------------------------242 gmPG2.file2bytea ( 243 filename = filename, 244 query = 'update ref.paperwork_templates set data = %(data)s::bytea where pk = %(pk)s and xmin = %(xmin)s', 245 args = {'pk': self.pk_obj, 'xmin': self._payload[self._idx['xmin_paperwork_template']]} 246 ) 247 # adjust for xmin change 248 self.refetch_payload()249 250 #--------------------------------------------------------252 fname = self.save_to_file(use_sandbox = use_sandbox) 253 engine = form_engines[self._payload[self._idx['engine']]] 254 form = engine(template_file = fname) 255 form.template = self 256 return form260 cmd = 'select pk from ref.paperwork_templates where name_long = %(lname)s and external_version = %(ver)s' 261 args = {'lname': name_long, 'ver': external_version} 262 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 263 264 if len(rows) == 0: 265 _log.error('cannot load form template [%s - %s]', name_long, external_version) 266 return None 267 268 return cFormTemplate(aPK_obj = rows[0]['pk'])269 270 #------------------------------------------------------------271 -def get_form_templates(engine=None, active_only=False, template_types=None, excluded_types=None, return_pks=False):272 """Load form templates.""" 273 274 args = {'eng': engine, 'in_use': active_only} 275 where_parts = ['1 = 1'] 276 277 if engine is not None: 278 where_parts.append('engine = %(eng)s') 279 280 if active_only: 281 where_parts.append('in_use IS true') 282 283 if template_types is not None: 284 args['incl_types'] = tuple(template_types) 285 where_parts.append('template_type IN %(incl_types)s') 286 287 if excluded_types is not None: 288 args['excl_types'] = tuple(excluded_types) 289 where_parts.append('template_type NOT IN %(excl_types)s') 290 291 cmd = "SELECT * FROM ref.v_paperwork_templates WHERE %s ORDER BY in_use desc, name_long" % '\nAND '.join(where_parts) 292 293 rows, idx = gmPG2.run_ro_queries ( 294 queries = [{'cmd': cmd, 'args': args}], 295 get_col_idx = True 296 ) 297 if return_pks: 298 return [ r['pk_paperwork_template'] for r in rows ] 299 templates = [ cFormTemplate(row = {'pk_field': 'pk_paperwork_template', 'data': r, 'idx': idx}) for r in rows ] 300 return templates301 302 #------------------------------------------------------------304 cmd = """ 305 INSERT INTO ref.paperwork_templates ( 306 fk_template_type, 307 name_short, 308 name_long, 309 external_version 310 ) VALUES ( 311 %(type)s, 312 %(nshort)s, 313 %(nlong)s, 314 %(ext_version)s 315 ) 316 RETURNING pk 317 """ 318 args = {'type': template_type, 'nshort': name_short, 'nlong': name_long, 'ext_version': 'new'} 319 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True) 320 template = cFormTemplate(aPK_obj = rows[0][0]) 321 return template322 323 #------------------------------------------------------------325 rows, idx = gmPG2.run_rw_queries ( 326 queries = [{ 327 'cmd': 'DELETE FROM ref.paperwork_templates WHERE pk = %(pk)s', 328 'args': {'pk': template['pk_paperwork_template']} 329 }] 330 ) 331 return True332 333 #============================================================ 334 # OpenOffice/LibreOffice API 335 #============================================================ 336 uno = None 337 cOOoDocumentCloseListener = None 338 writer_binary = None 339 340 # http://forum.openoffice.org/en/forum/viewtopic.php?t=36370 341 # http://stackoverflow.com/questions/4270962/using-pyuno-with-my-existing-python-installation 342 343 #-----------------------------------------------------------345 346 try: 347 which = subprocess.Popen ( 348 args = ('which', 'soffice'), 349 stdout = subprocess.PIPE, 350 stdin = subprocess.PIPE, 351 stderr = subprocess.PIPE, 352 universal_newlines = True 353 ) 354 except (OSError, ValueError, subprocess.CalledProcessError): 355 _log.exception('there was a problem executing [which soffice]') 356 return 357 358 soffice_path, err = which.communicate() 359 soffice_path = soffice_path.strip('\n') 360 uno_path = os.path.abspath ( os.path.join ( 361 os.path.dirname(os.path.realpath(soffice_path)), 362 '..', 363 'basis-link', 364 'program' 365 )) 366 367 _log.info('UNO should be at [%s], appending to sys.path', uno_path) 368 369 sys.path.append(uno_path)370 371 #-----------------------------------------------------------373 """FIXME: consider this: 374 375 try: 376 import uno 377 except Exception: 378 print "This Script needs to be run with the python from OpenOffice.org" 379 print "Example: /opt/OpenOffice.org/program/python %s" % ( 380 os.path.basename(sys.argv[0])) 381 print "Or you need to insert the right path at the top, where uno.py is." 382 print "Default: %s" % default_path 383 """ 384 global uno 385 if uno is not None: 386 return 387 388 try: 389 import uno 390 except ImportError: 391 __configure_path_to_UNO() 392 import uno 393 394 global unohelper, oooXCloseListener, oooNoConnectException, oooPropertyValue 395 396 import unohelper 397 from com.sun.star.util import XCloseListener as oooXCloseListener 398 from com.sun.star.connection import NoConnectException as oooNoConnectException 399 from com.sun.star.beans import PropertyValue as oooPropertyValue 400 401 #---------------------------------- 402 class _cOOoDocumentCloseListener(unohelper.Base, oooXCloseListener): 403 """Listens for events sent by OOo during the document closing 404 sequence and notifies the GNUmed client GUI so it can 405 import the closed document into the database. 406 """ 407 def __init__(self, document=None): 408 self.document = document409 410 def queryClosing(self, evt, owner): 411 # owner is True/False whether I am the owner of the doc 412 pass 413 414 def notifyClosing(self, evt): 415 pass 416 417 def disposing(self, evt): 418 self.document.on_disposed_by_ooo() 419 self.document = None 420 #---------------------------------- 421 422 global cOOoDocumentCloseListener 423 cOOoDocumentCloseListener = _cOOoDocumentCloseListener 424 425 # search for writer binary 426 global writer_binary 427 found, binary = gmShellAPI.find_first_binary(binaries = [ 428 'lowriter', 429 'oowriter', 430 'swriter' 431 ]) 432 if found: 433 _log.debug('OOo/LO writer binary found: %s', binary) 434 writer_binary = binary 435 else: 436 _log.debug('OOo/LO writer binary NOT found') 437 raise ImportError('LibreOffice/OpenOffice (lowriter/oowriter/swriter) not found') 438 439 _log.debug('python UNO bridge successfully initialized') 440 441 #------------------------------------------------------------443 """This class handles the connection to OOo. 444 445 Its Singleton instance stays around once initialized. 446 """ 447 # FIXME: need to detect closure of OOo !567 568 #------------------------------------------------------------449 450 init_ooo() 451 452 self.__setup_connection_string() 453 454 self.resolver_uri = "com.sun.star.bridge.UnoUrlResolver" 455 self.desktop_uri = "com.sun.star.frame.Desktop" 456 457 self.max_connect_attempts = 5 458 459 self.local_context = uno.getComponentContext() 460 self.uri_resolver = self.local_context.ServiceManager.createInstanceWithContext(self.resolver_uri, self.local_context) 461 462 self.__desktop = None463 #-------------------------------------------------------- 464 # external API 465 #--------------------------------------------------------467 if self.__desktop is None: 468 _log.debug('no desktop, no cleanup') 469 return 470 471 try: 472 self.__desktop.terminate() 473 except Exception: 474 _log.exception('cannot terminate OOo desktop')475 #--------------------------------------------------------477 """<filename> must be absolute""" 478 if self.desktop is None: 479 _log.error('cannot access OOo desktop') 480 return None 481 482 filename = os.path.expanduser(filename) 483 filename = os.path.abspath(filename) 484 document_uri = uno.systemPathToFileUrl(filename) 485 486 _log.debug('%s -> %s', filename, document_uri) 487 488 doc = self.desktop.loadComponentFromURL(document_uri, "_blank", 0, ()) 489 return doc490 #-------------------------------------------------------- 491 # internal helpers 492 #--------------------------------------------------------494 # later factor this out ! 495 dbcfg = gmCfg.cCfgSQL() 496 self.ooo_startup_settle_time = dbcfg.get2 ( 497 option = 'external.ooo.startup_settle_time', 498 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 499 bias = 'workplace', 500 default = 3.0 501 )502 #--------------------------------------------------------504 505 # socket: 506 # ooo_port = u'2002' 507 # #self.ooo_start_cmd = 'oowriter -invisible -norestore -nofirststartwizard -nologo -accept="socket,host=localhost,port=%s;urp;StarOffice.ServiceManager"' % ooo_port 508 # self.ooo_start_cmd = 'oowriter -invisible -norestore -accept="socket,host=localhost,port=%s;urp;"' % ooo_port 509 # self.remote_context_uri = "uno:socket,host=localhost,port=%s;urp;StarOffice.ComponentContext" % ooo_port 510 511 # pipe: 512 pipe_name = "uno-gm2lo-%s" % str(random.random())[2:] 513 _log.debug('expecting OOo/LO server on named pipe [%s]', pipe_name) 514 self.ooo_start_cmd = '%s --invisible --norestore --accept="pipe,name=%s;urp" &' % ( 515 writer_binary, 516 pipe_name 517 ) 518 _log.debug('startup command: %s', self.ooo_start_cmd) 519 520 self.remote_context_uri = "uno:pipe,name=%s;urp;StarOffice.ComponentContext" % pipe_name 521 _log.debug('remote context URI: %s', self.remote_context_uri)522 #--------------------------------------------------------524 _log.info('trying to start OOo server') 525 _log.debug('startup command: %s', self.ooo_start_cmd) 526 os.system(self.ooo_start_cmd) 527 self.__get_startup_settle_time() 528 _log.debug('waiting %s seconds for OOo to start up', self.ooo_startup_settle_time) 529 time.sleep(self.ooo_startup_settle_time)530 #-------------------------------------------------------- 531 # properties 532 #--------------------------------------------------------534 if self.__desktop is not None: 535 return self.__desktop 536 537 self.remote_context = None 538 539 attempts = self.max_connect_attempts 540 while attempts > 0: 541 542 _log.debug('attempt %s/%s', self.max_connect_attempts - attempts + 1, self.max_connect_attempts) 543 544 try: 545 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri) 546 break 547 except oooNoConnectException: 548 _log.exception('cannot connect to OOo') 549 550 # first loop ? 551 if attempts == self.max_connect_attempts: 552 self.__startup_ooo() 553 else: 554 time.sleep(1) 555 556 attempts = attempts - 1 557 558 if self.remote_context is None: 559 raise OSError(-1, 'cannot connect to OpenOffice', self.remote_context_uri) 560 561 _log.debug('connection seems established') 562 self.__desktop = self.remote_context.ServiceManager.createInstanceWithContext(self.desktop_uri, self.remote_context) 563 _log.debug('got OOo desktop handle') 564 return self.__desktop565 566 desktop = property(_get_desktop, lambda x:x)570676 #-------------------------------------------------------- 677 # internal helpers 678 #-------------------------------------------------------- 679 680 #============================================================572 573 self.template_file = template_file 574 self.instance_type = instance_type 575 self.ooo_doc = None576 #-------------------------------------------------------- 577 # external API 578 #--------------------------------------------------------580 # connect to OOo 581 ooo_srv = gmOOoConnector() 582 583 # open doc in OOo 584 self.ooo_doc = ooo_srv.open_document(filename = self.template_file) 585 if self.ooo_doc is None: 586 _log.error('cannot open document in OOo') 587 return False 588 589 # listen for close events 590 pat = gmPerson.gmCurrentPatient() 591 pat.locked = True 592 listener = cOOoDocumentCloseListener(document = self) 593 self.ooo_doc.addCloseListener(listener) 594 595 return True596 #-------------------------------------------------------- 599 #--------------------------------------------------------601 602 # new style embedded, implicit placeholders 603 searcher = self.ooo_doc.createSearchDescriptor() 604 searcher.SearchCaseSensitive = False 605 searcher.SearchRegularExpression = True 606 searcher.SearchWords = True 607 searcher.SearchString = handler.placeholder_regex 608 609 placeholder_instance = self.ooo_doc.findFirst(searcher) 610 while placeholder_instance is not None: 611 try: 612 val = handler[placeholder_instance.String] 613 except Exception: 614 val = _('error with placeholder [%s]') % placeholder_instance.String 615 _log.exception(val) 616 617 if val is None: 618 val = _('error with placeholder [%s]') % placeholder_instance.String 619 620 placeholder_instance.String = val 621 placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher) 622 623 if not old_style_too: 624 return 625 626 # old style "explicit" placeholders 627 text_fields = self.ooo_doc.getTextFields().createEnumeration() 628 while text_fields.hasMoreElements(): 629 text_field = text_fields.nextElement() 630 631 # placeholder ? 632 if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'): 633 continue 634 # placeholder of type text ? 635 if text_field.PlaceHolderType != 0: 636 continue 637 638 replacement = handler[text_field.PlaceHolder] 639 if replacement is None: 640 continue 641 642 text_field.Anchor.setString(replacement)643 #--------------------------------------------------------645 if filename is not None: 646 target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename))) 647 save_args = ( 648 oooPropertyValue('Overwrite', 0, True, 0), 649 oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0) 650 651 ) 652 # "store AS url" stores the doc, marks it unmodified and updates 653 # the internal media descriptor - as opposed to "store TO url" 654 self.ooo_doc.storeAsURL(target_url, save_args) 655 else: 656 self.ooo_doc.store()657 #--------------------------------------------------------659 self.ooo_doc.dispose() 660 pat = gmPerson.gmCurrentPatient() 661 pat.locked = False 662 self.ooo_doc = None663 #--------------------------------------------------------665 # get current file name from OOo, user may have used Save As 666 filename = uno.fileUrlToSystemPath(self.ooo_doc.URL) 667 # tell UI to import the file 668 gmDispatcher.send ( 669 signal = 'import_document_from_file', 670 filename = filename, 671 document_type = self.instance_type, 672 unlock_patient = True, 673 pk_org_unit = gmPraxis.gmCurrentPraxisBranch()['pk_org_unit'] 674 ) 675 self.ooo_doc = None682 """Ancestor for forms.""" 683700 #-------------------------------------------------------- 701 #-------------------------------------------------------- 702 # def process(self, data_source=None): 703 # """Merge values into the form template. 704 # """ 705 # pass 706 # #-------------------------------------------------------- 707 # def cleanup(self): 708 # """ 709 # A sop to TeX which can't act as a true filter: to delete temporary files 710 # """ 711 # pass 712 # #-------------------------------------------------------- 713 # def exe(self, command): 714 # """ 715 # Executes the provided command. 716 # If command cotains %F. it is substituted with the filename 717 # Otherwise, the file is fed in on stdin 718 # """ 719 # pass 720 # #-------------------------------------------------------- 721 # def store(self, params=None): 722 # """Stores the parameters in the backend. 723 # 724 # - link_obj can be a cursor, a connection or a service name 725 # - assigning a cursor to link_obj allows the calling code to 726 # group the call to store() into an enclosing transaction 727 # (for an example see gmReferral.send_referral()...) 728 # """ 729 # # some forms may not have values ... 730 # if params is None: 731 # params = {} 732 # patient_clinical = self.patient.emr 733 # encounter = patient_clinical.active_encounter['pk_encounter'] 734 # # FIXME: get_active_episode is no more 735 # #episode = patient_clinical.get_active_episode()['pk_episode'] 736 # # generate "forever unique" name 737 # cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s"; 738 # rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def) 739 # form_name = None 740 # if rows is None: 741 # _log.error('error retrieving form def for [%s]' % self.pk_def) 742 # elif len(rows) == 0: 743 # _log.error('no form def for [%s]' % self.pk_def) 744 # else: 745 # form_name = rows[0][0] 746 # # we didn't get a name but want to store the form anyhow 747 # if form_name is None: 748 # form_name=time.time() # hopefully unique enough 749 # # in one transaction 750 # queries = [] 751 # # - store form instance in form_instance 752 # cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)" 753 # queries.append((cmd, [self.pk_def, form_name, episode, encounter])) 754 # # - store params in form_data 755 # for key in params: 756 # cmd = """ 757 # insert into form_data(fk_instance, place_holder, value) 758 # values ((select currval('form_instances_pk_seq')), %s, %s::text) 759 # """ 760 # queries.append((cmd, [key, params[key]])) 761 # # - get inserted PK 762 # queries.append(("select currval ('form_instances_pk_seq')", [])) 763 # status, err = gmPG.run_commit('historica', queries, True) 764 # if status is None: 765 # _log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err)) 766 # return None 767 # return status 768 769 #================================================================ 770 # OOo template forms 771 #----------------------------------------------------------------685 self.template = None 686 self.template_filename = template_file 687 _log.debug('working on template file [%s]', self.template_filename)688 #--------------------------------------------------------690 """Parse the template into an instance and replace placeholders with values.""" 691 raise NotImplementedError692 #-------------------------------------------------------- 696 #--------------------------------------------------------773 """A forms engine wrapping OOo.""" 774782 783 #================================================================ 784 # AbiWord template forms 785 #----------------------------------------------------------------776 super(self.__class__, self).__init__(template_file = template_file) 777 778 path, ext = os.path.splitext(self.template_filename) 779 if ext in [r'', r'.']: 780 ext = r'.odt' 781 self.instance_filename = r'%s-instance%s' % (path, ext)787 """A forms engine wrapping AbiWord.""" 788 789 placeholder_regex = r'\$<.+?>\$' 790892 893 #---------------------------------------------------------------- 894 form_engines['A'] = cAbiWordForm 895 896 #================================================================ 897 # text template forms 898 #----------------------------------------------------------------792 793 super(cAbiWordForm, self).__init__(template_file = template_file) 794 795 # detect abiword 796 found, self.abiword_binary = gmShellAPI.detect_external_binary(binary = r'abiword') 797 if not found: 798 raise ImportError('<abiword(.exe)> not found')799 #--------------------------------------------------------801 # should *actually* properly parse the XML 802 803 path, ext = os.path.splitext(self.template_filename) 804 if ext in [r'', r'.']: 805 ext = r'.abw' 806 self.instance_filename = r'%s-instance%s' % (path, ext) 807 808 template_file = io.open(self.template_filename, mode = 'rt', encoding = 'utf8') 809 instance_file = io.open(self.instance_filename, mode = 'wt', encoding = 'utf8') 810 811 if self.template is not None: 812 # inject placeholder values 813 data_source.set_placeholder('form_name_long', self.template['name_long']) 814 data_source.set_placeholder('form_name_short', self.template['name_short']) 815 data_source.set_placeholder('form_version', self.template['external_version']) 816 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s')) 817 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M')) 818 819 data_source.escape_style = 'xml' 820 data_source.escape_function = None # gmTools.xml_escape_text() ? 821 822 for line in template_file: 823 824 if line.strip() in ['', '\r', '\n', '\r\n']: 825 instance_file.write(line) 826 continue 827 828 # 1) find placeholders in this line 829 placeholders_in_line = regex.findall(cAbiWordForm.placeholder_regex, line, regex.IGNORECASE) 830 # 2) and replace them 831 for placeholder in placeholders_in_line: 832 try: 833 val = data_source[placeholder.replace('<', '<').replace('>', '>')] 834 except Exception: 835 val = _('error with placeholder [%s]') % gmTools.xml_escape_string(placeholder) 836 _log.exception(val) 837 838 if val is None: 839 val = _('error with placeholder [%s]') % gmTools.xml_escape_string(placeholder) 840 841 line = line.replace(placeholder, val) 842 843 instance_file.write(line) 844 845 instance_file.close() 846 template_file.close() 847 848 if self.template is not None: 849 # remove temporary placeholders 850 data_source.unset_placeholder('form_name_long') 851 data_source.unset_placeholder('form_name_short') 852 data_source.unset_placeholder('form_version') 853 data_source.unset_placeholder('form_version_internal') 854 data_source.unset_placeholder('form_last_modified') 855 856 return857 #--------------------------------------------------------859 enc = sys.getfilesystemencoding() 860 cmd = (r'%s %s' % (self.abiword_binary, self.instance_filename.encode(enc))).encode(enc) 861 result = gmShellAPI.run_command_in_shell(command = cmd, blocking = True) 862 self.re_editable_filenames = [self.instance_filename] 863 return result864 #--------------------------------------------------------866 867 if instance_file is None: 868 instance_file = self.instance_filename 869 try: 870 open(instance_file, 'r').close() 871 except Exception: 872 _log.exception('cannot access form instance file [%s]', instance_file) 873 gmLog2.log_stack_trace() 874 return None 875 self.instance_filename = instance_file 876 877 _log.debug('ignoring <format> directive [%s], generating PDF', format) 878 879 pdf_name = os.path.splitext(self.instance_filename)[0] + '.pdf' 880 cmd = '%s --to=pdf --to-name=%s %s' % ( 881 self.abiword_binary, 882 pdf_name, 883 self.instance_filename 884 ) 885 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = True): 886 _log.error('problem running abiword, cannot generate form output') 887 gmDispatcher.send(signal = 'statustext', msg = _('Error running AbiWord. Cannot generate PDF.'), beep = True) 888 return None 889 890 self.final_output_filenames = [pdf_name] 891 return pdf_name900 """A forms engine outputting data as text for further processing.""" 9011057 #------------------------------------------------------------ 1058 form_engines['T'] = cTextForm 1059 1060 #================================================================ 1061 # LaTeX template forms 1062 #----------------------------------------------------------------903 904 super(self.__class__, self).__init__(template_file = template_file) 905 906 # create sandbox to play in (and don't assume much 907 # of anything about the template_file except that it 908 # is at our disposal for reading) 909 self.__sandbox_dir = gmTools.mk_sandbox_dir() 910 _log.debug('sandbox directory: [%s]', self.__sandbox_dir) 911 912 # parse template file which is an INI style config 913 # file containing the actual template plus metadata 914 self.form_definition_filename = self.template_filename 915 _log.debug('form definition file: [%s]', self.form_definition_filename) 916 cfg_file = io.open(self.form_definition_filename, mode = 'rt', encoding = 'utf8') 917 self.form_definition = gmCfg2.parse_INI_stream(stream = cfg_file) 918 cfg_file.close() 919 920 # extract actual template into a file 921 template_text = self.form_definition['form::template'] 922 if isinstance(template_text, type([])): 923 template_text = '\n'.join(self.form_definition['form::template']) 924 self.template_filename = gmTools.get_unique_filename ( 925 prefix = 'gm-', 926 suffix = '.txt', 927 tmp_dir = self.__sandbox_dir 928 ) 929 _log.debug('template file: [%s]', self.template_filename) 930 f = io.open(self.template_filename, mode = 'wt', encoding = 'utf8') 931 f.write(template_text) 932 f.close()933 934 #--------------------------------------------------------936 937 if self.template is not None: 938 # inject placeholder values 939 data_source.set_placeholder('form_name_long', self.template['name_long']) 940 data_source.set_placeholder('form_name_short', self.template['name_short']) 941 data_source.set_placeholder('form_version', self.template['external_version']) 942 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s')) 943 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M')) 944 945 base = os.path.join(self.__sandbox_dir, gmTools.fname_stem(self.template_filename)) 946 filenames = [ 947 self.template_filename, 948 r'%s-result-pass-1.txt' % base, 949 r'%s-result-pass-2.txt' % base, 950 r'%s-result-pass-3.txt' % base 951 ] 952 regexen = [ 953 'dummy', 954 data_source.first_pass_placeholder_regex, 955 data_source.second_pass_placeholder_regex, 956 data_source.third_pass_placeholder_regex 957 ] 958 959 current_pass = 1 960 while current_pass < 4: 961 _log.debug('placeholder substitution pass #%s', current_pass) 962 found_placeholders = self.__substitute_placeholders ( 963 input_filename = filenames[current_pass-1], 964 output_filename = filenames[current_pass], 965 data_source = data_source, 966 placeholder_regex = regexen[current_pass] 967 ) 968 current_pass += 1 969 970 # remove temporary placeholders 971 data_source.unset_placeholder('form_name_long') 972 data_source.unset_placeholder('form_name_short') 973 data_source.unset_placeholder('form_version') 974 data_source.unset_placeholder('form_version_internal') 975 data_source.unset_placeholder('form_last_modified') 976 977 self.instance_filename = self.re_editable_filenames[0] 978 979 return True980 981 #--------------------------------------------------------982 - def __substitute_placeholders(self, data_source=None, input_filename=None, output_filename=None, placeholder_regex=None):983 984 _log.debug('[%s] -> [%s]', input_filename, output_filename) 985 _log.debug('searching for placeholders with pattern: %s', placeholder_regex) 986 987 template_file = io.open(input_filename, mode = 'rt', encoding = 'utf8') 988 instance_file = io.open(output_filename, mode = 'wt', encoding = 'utf8') 989 990 for line in template_file: 991 # empty lines 992 if line.strip() in ['', '\r', '\n', '\r\n']: 993 instance_file.write(line) 994 continue 995 996 # 1) find placeholders in this line 997 placeholders_in_line = regex.findall(placeholder_regex, line, regex.IGNORECASE) 998 if len(placeholders_in_line) == 0: 999 instance_file.write(line) 1000 continue 1001 1002 # 2) replace them 1003 _log.debug('%s placeholders found in this line', len(placeholders_in_line)) 1004 for placeholder in placeholders_in_line: 1005 try: 1006 val = data_source[placeholder] 1007 except Exception: 1008 val = _('error with placeholder [%s]') % placeholder 1009 _log.exception(val) 1010 if val is None: 1011 val = _('error with placeholder [%s]') % placeholder 1012 1013 line = line.replace(placeholder, val) 1014 1015 instance_file.write(line) 1016 1017 instance_file.close() 1018 self.re_editable_filenames = [output_filename] 1019 template_file.close()1020 1021 #--------------------------------------------------------1023 1024 editor_cmd = None 1025 try: 1026 editor_cmd = self.form_definition['form::editor'] % self.instance_filename 1027 except KeyError: 1028 _log.debug('no explicit editor defined for text template') 1029 1030 if editor_cmd is None: 1031 mimetype = 'text/plain' 1032 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 1033 if editor_cmd is None: 1034 # also consider text *viewers* since pretty much any of them will be an editor as well 1035 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename) 1036 1037 if editor_cmd is not None: 1038 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1039 self.re_editable_filenames = [self.instance_filename] 1040 1041 return result1042 1043 #--------------------------------------------------------1045 try: 1046 post_processor = self.form_definition['form::post processor'] % { 1047 'input_name': self.instance_filename, 1048 'output_name': self.instance_filename + '.output' 1049 } 1050 except KeyError: 1051 _log.debug('no explicit post processor defined for text template') 1052 return True 1053 1054 self.final_output_filenames = [self.instance_filename + '.output'] 1055 1056 return gmShellAPI.run_command_in_shell(command = post_processor, blocking = True)1064 """A forms engine wrapping LaTeX (pdflatex).""" 1065 1066 _version_checked = False 10671333 1334 #------------------------------------------------------------ 1335 form_engines['L'] = cLaTeXForm 1336 1337 #================================================================ 1338 # Xe(La)TeX template forms 1339 #---------------------------------------------------------------- 1340 # Xe(La)TeX: http://www.scholarsfonts.net/xetextt.pdf1069 1070 # create sandbox for LaTeX to play in (and don't assume 1071 # much of anything about the template_file except that it 1072 # is at our disposal for reading) 1073 sandbox_dir = gmTools.mk_sandbox_dir(prefix = gmTools.fname_stem(template_file) + '_') 1074 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir) 1075 shutil.copy(template_file, sandbox_dir) 1076 template_file = os.path.join(sandbox_dir, os.path.split(template_file)[1]) 1077 1078 super().__init__(template_file = template_file) 1079 1080 self.__sandbox_dir = sandbox_dir 1081 1082 # set up PDF generator 1083 if platform.system() == 'Windows': 1084 executable = 'pdflatex.exe' 1085 else: 1086 executable = 'pdflatex' 1087 self._final_cmd_line = [ 1088 executable, 1089 '-recorder', 1090 '-interaction=nonstopmode', 1091 "-output-directory=%s" % self.__sandbox_dir 1092 ] 1093 self._draft_cmd_line = self._final_cmd_line + ['-draftmode'] 1094 1095 if not cLaTeXForm._version_checked: 1096 cLaTeXForm._version_checked = True 1097 cmd_line = [executable, '-version'] 1098 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = True) 1099 if not success: 1100 _log.error('[%s] failed, LaTeX forms not usable', cmd_line)1101 1102 #--------------------------------------------------------1104 # remove extra linefeeds which the docutils ReST2LaTeX 1105 # converter likes to add but which makes pdflatex go 1106 # crazy when ending up inside KOMAScript variables 1107 return gmTools.rst2latex_snippet(text).strip()1108 1109 #--------------------------------------------------------1111 1112 # debugging 1113 #data_source.debug = True 1114 1115 if self.template is not None: 1116 # inject placeholder values 1117 data_source.set_placeholder('form_name_long', self.template['name_long']) 1118 data_source.set_placeholder('form_name_short', self.template['name_short']) 1119 data_source.set_placeholder('form_version', self.template['external_version']) 1120 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s')) 1121 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M')) 1122 # add site-local identifying information to template for debugging 1123 f = open(self.template_filename, 'at', encoding = 'utf8') 1124 f.write('\n') 1125 f.write('%------------------------------------------------------------------\n') 1126 for line in self.template.format(): 1127 f.write('% ') 1128 f.write(line) 1129 f.write('\n') 1130 f.write('%------------------------------------------------------------------\n') 1131 f.close() 1132 1133 data_source.escape_function = gmTools.tex_escape_string 1134 data_source.escape_style = 'latex' 1135 1136 path, ext = os.path.splitext(self.template_filename) 1137 if ext in [r'', r'.']: 1138 ext = r'.tex' 1139 1140 filenames = [ 1141 self.template_filename, 1142 r'%s-result-pass-1%s' % (path, ext), 1143 r'%s-result-pass-2%s' % (path, ext), 1144 r'%s-result-pass-3%s' % (path, ext), 1145 r'%s-result-pass-4%s' % (path, ext), 1146 r'%s-result-pass-5%s' % (path, ext) 1147 ] 1148 regexen = [ 1149 'dummy', 1150 r'\$1{0,1}<[^<].+?>1{0,1}\$', 1151 r'\$2<[^<].+?>2\$', 1152 r'\$3<[^<].+?>3\$', 1153 r'\$4<[^<].+?>4\$', 1154 r'\$5<[^<].+?>5\$' 1155 ] 1156 1157 current_pass = 1 1158 while current_pass < 6: 1159 _log.debug('placeholder substitution pass #%s', current_pass) 1160 found_placeholders = self.__substitute_placeholders ( 1161 input_filename = filenames[current_pass-1], 1162 output_filename = filenames[current_pass], 1163 data_source = data_source, 1164 placeholder_regex = regexen[current_pass] 1165 ) 1166 current_pass += 1 1167 1168 # remove temporary placeholders 1169 data_source.unset_placeholder('form_name_long') 1170 data_source.unset_placeholder('form_name_short') 1171 data_source.unset_placeholder('form_version') 1172 data_source.unset_placeholder('form_version_internal') 1173 data_source.unset_placeholder('form_last_modified') 1174 1175 self.instance_filename = self.re_editable_filenames[0] 1176 1177 return1178 1179 #--------------------------------------------------------1180 - def __substitute_placeholders(self, data_source=None, input_filename=None, output_filename=None, placeholder_regex=None):1181 1182 _log.debug('[%s] -> [%s]', input_filename, output_filename) 1183 _log.debug('searching for placeholders with pattern: %s', placeholder_regex) 1184 1185 template_file = io.open(input_filename, mode = 'rt', encoding = 'utf8') 1186 instance_file = io.open(output_filename, mode = 'wt', encoding = 'utf8') 1187 1188 for line in template_file: 1189 # empty lines 1190 if line.strip() in ['', '\r', '\n', '\r\n']: 1191 instance_file.write(line) 1192 continue 1193 # TeX-comment-only lines 1194 if line.lstrip().startswith('%'): 1195 instance_file.write(line) 1196 continue 1197 1198 # 1) find placeholders in this line 1199 placeholders_in_line = regex.findall(placeholder_regex, line, regex.IGNORECASE) 1200 if len(placeholders_in_line) == 0: 1201 instance_file.write(line) 1202 continue 1203 1204 # 2) replace them 1205 _log.debug('replacing in non-empty, non-comment line: >>>%s<<<', line.rstrip(u'\n')) 1206 _log.debug('%s placeholder(s) detected', len(placeholders_in_line)) 1207 for placeholder in placeholders_in_line: 1208 if 'free_text' in placeholder: 1209 # enable reStructuredText processing 1210 data_source.escape_function = self._rst2latex_transform 1211 else: 1212 data_source.escape_function = gmTools.tex_escape_string 1213 original_ph_def = placeholder 1214 _log.debug('placeholder: >>>%s<<<', original_ph_def) 1215 # normalize start/end 1216 if placeholder.startswith('$<'): 1217 placeholder = '$1<' + placeholder[2:] 1218 if placeholder.endswith('>$'): 1219 placeholder = placeholder[:-2] + '>1$' 1220 _log.debug('normalized : >>>%s<<<', placeholder) 1221 # remove start/end 1222 placeholder = placeholder[3:-3] 1223 _log.debug('stripped : >>>%s<<<', placeholder) 1224 try: 1225 val = data_source[placeholder] 1226 except Exception: 1227 _log.exception('error with placeholder [%s]', original_ph_def) 1228 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % original_ph_def) 1229 if val is None: 1230 _log.debug('error with placeholder [%s]', original_ph_def) 1231 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % original_ph_def) 1232 _log.debug('value : >>>%s<<<', val) 1233 line = line.replace(original_ph_def, val) 1234 instance_file.write(line) 1235 1236 instance_file.close() 1237 self.re_editable_filenames = [output_filename] 1238 template_file.close() 1239 1240 return1241 1242 #--------------------------------------------------------1244 1245 mimetypes = [ 1246 'application/x-latex', 1247 'application/x-tex', 1248 'text/latex', 1249 'text/tex', 1250 'text/plain' 1251 ] 1252 1253 for mimetype in mimetypes: 1254 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 1255 if editor_cmd is not None: 1256 break 1257 1258 if editor_cmd is None: 1259 # LaTeX code is text: also consider text *viewers* 1260 # since pretty much any of them will be an editor as well 1261 for mimetype in mimetypes: 1262 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename) 1263 if editor_cmd is not None: 1264 break 1265 1266 if editor_cmd is None: 1267 return False 1268 1269 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1270 self.re_editable_filenames = [self.instance_filename] 1271 return result1272 1273 #--------------------------------------------------------1275 1276 if instance_file is None: 1277 instance_file = self.instance_filename 1278 1279 try: 1280 open(instance_file, 'r').close() 1281 except Exception: 1282 _log.exception('cannot access form instance file [%s]', instance_file) 1283 gmLog2.log_stack_trace() 1284 return None 1285 1286 self.instance_filename = instance_file 1287 1288 _log.debug('ignoring <format> directive [%s], generating PDF', format) 1289 draft_cmd = self._draft_cmd_line + [self.instance_filename] 1290 final_cmd = self._final_cmd_line + [self.instance_filename] 1291 # LaTeX can need up to three runs to get cross references et al right 1292 for run_cmd in [draft_cmd, draft_cmd, final_cmd]: 1293 success, ret_code, stdout = gmShellAPI.run_process ( 1294 cmd_line = run_cmd, 1295 acceptable_return_codes = [0], 1296 encoding = 'utf8', 1297 verbose = _cfg.get(option = 'debug') 1298 ) 1299 if not success: 1300 _log.error('problem running pdflatex, cannot generate form output, trying diagnostics') 1301 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True) 1302 found, binary = gmShellAPI.find_first_binary(binaries = ['lacheck', 'miktex-lacheck.exe']) 1303 if not found: 1304 _log.debug('lacheck not found') 1305 else: 1306 cmd_line = [binary, self.instance_filename] 1307 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = True) 1308 found, binary = gmShellAPI.find_first_binary(binaries = ['chktex', 'ChkTeX.exe']) 1309 if not found: 1310 _log.debug('chcktex not found') 1311 else: 1312 cmd_line = [binary, '--verbosity=2', '--headererr', self.instance_filename] 1313 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = True) 1314 return None 1315 1316 sandboxed_pdf_name = '%s.pdf' % os.path.splitext(self.instance_filename)[0] 1317 target_dir = os.path.normpath(os.path.join(os.path.split(sandboxed_pdf_name)[0], '..')) 1318 final_pdf_name = os.path.join ( 1319 target_dir, 1320 os.path.split(sandboxed_pdf_name)[1] 1321 ) 1322 _log.debug('copying sandboxed PDF: %s -> %s', sandboxed_pdf_name, final_pdf_name) 1323 try: 1324 shutil.copy2(sandboxed_pdf_name, target_dir) 1325 except IOError: 1326 _log.exception('cannot open/move sandboxed PDF') 1327 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 1328 return None 1329 1330 self.final_output_filenames = [final_pdf_name] 1331 1332 return final_pdf_name1342 """A forms engine wrapping Xe(La)TeX.""" 13431530 1531 #------------------------------------------------------------ 1532 form_engines['X'] = cXeTeXForm 1533 1534 #============================================================ 1535 # Gnuplot template forms 1536 #------------------------------------------------------------ 1537 _GNUPLOT_WRAPPER_SCRIPT = """# -------------------------------------------------------------- 1538 # GNUplot wrapper script used by GNUmed 1539 # 1540 # This script is used to make gnuplot 1541 # 1542 # display some debugging information 1543 # 1544 # load GNUmed specific settings such as the timestamp 1545 # format, encoding, symbol for missing values, 1546 # 1547 # load data specific values such as y(2)label or plot 1548 # title which GNUmed will have set up while exporting 1549 # test results for plotting, 1550 # 1551 # know the datafile name from the variable <gm2gpl_datafile> 1552 # which the user provided plotting script can then use 1553 # to access data like so: 1554 # 1555 # plot gm2gpl_datafile ... 1556 # 1557 # -------------------------------------------------------------- 1558 1559 # logging verbosity, depending on GNUmed client debug state 1560 gmd_log_verbose = %s 1561 1562 1563 # -- debugging ---- 1564 show version long 1565 if (gmd_log_verbose == 1) { 1566 print "-- <show all> at startup ----" 1567 show all 1568 print "-- <show variables all> at startup ----" 1569 show variables all 1570 } 1571 1572 1573 # -- data format setup ---- 1574 set encoding utf8 1575 set timefmt "%%Y-%%m-%%d_%%H:%%M" # timestamp input formatting, not for output 1576 1577 1578 # -- data file setup ---- 1579 gm2gpl_datafile = '%s' 1580 set datafile missing "<?>" 1581 set xdata time 1582 set x2data time 1583 1584 1585 # -- process additional definitions from GNUmed ---- 1586 gm2gpl_datafile_conf = gm2gpl_datafile.'.conf' 1587 load gm2gpl_datafile_conf 1588 1589 1590 # -- actually run the user provided plotting script ---- 1591 call '%s' 1592 1593 1594 # -- debugging ---- 1595 if (gmd_log_verbose == 1) { 1596 print "-- <show all> after running user provided plotting script ----" 1597 show all 1598 print "-- <show variables all> after running user provided plotting script ----" 1599 show variables all 1600 1601 # PNG output: 1602 #set terminal png enhanced transparent nointerlace truecolor #medium #crop 1603 ##set output 'test_terminal.png' 1604 ##test 1605 #set output gm2gpl_datafile.'.dbg.png' 1606 #replot 1607 1608 # ASCII art output: 1609 #set terminal dumb size 120,45 feed enhanced ansirgb 1610 ##set output 'test_terminal.txt' 1611 ##test 1612 #set output gm2gpl_datafile.'.dbg.txt' 1613 #replot 1614 #set terminal dumb size 120,45 feed enhanced mono 1615 ##set output 'test_terminal.ascii.txt' 1616 ##test 1617 #set output gm2gpl_datafile.'.dbg.ascii.txt' 1618 #replot 1619 } 1620 """ 16211345 1346 # create sandbox for LaTeX to play in (and don't assume 1347 # much of anything about the template_file except that it 1348 # is at our disposal) 1349 sandbox_dir = gmTools.mk_sandbox_dir(prefix = gmTools.fname_stem(template_file) + '_') 1350 _log.debug('Xe(La)TeX sandbox directory: [%s]', sandbox_dir) 1351 shutil.copy(template_file, sandbox_dir) 1352 template_file = os.path.join(sandbox_dir, os.path.split(template_file)[1]) 1353 1354 super(self.__class__, self).__init__(template_file = template_file) 1355 1356 self.__sandbox_dir = sandbox_dir1357 #--------------------------------------------------------1359 1360 if self.template is not None: 1361 # inject placeholder values 1362 data_source.set_placeholder('form_name_long', self.template['name_long']) 1363 data_source.set_placeholder('form_name_short', self.template['name_short']) 1364 data_source.set_placeholder('form_version', self.template['external_version']) 1365 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s')) 1366 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M')) 1367 1368 data_source.escape_function = gmTools.xetex_escape_string 1369 data_source.escape_style = 'xetex' 1370 1371 path, ext = os.path.splitext(self.template_filename) 1372 if ext in [r'', r'.']: 1373 ext = r'.tex' 1374 1375 filenames = [ 1376 self.template_filename, 1377 r'%s-result_run1%s' % (path, ext), 1378 r'%s-result_run2%s' % (path, ext), 1379 r'%s-result_run3%s' % (path, ext) 1380 ] 1381 1382 found_placeholders = True 1383 current_run = 1 1384 while found_placeholders and (current_run < 4): 1385 _log.debug('placeholder substitution run #%s', current_run) 1386 found_placeholders = self.__substitute_placeholders ( 1387 input_filename = filenames[current_run-1], 1388 output_filename = filenames[current_run], 1389 data_source = data_source 1390 ) 1391 current_run += 1 1392 1393 if self.template is not None: 1394 # remove temporary placeholders 1395 data_source.unset_placeholder('form_name_long') 1396 data_source.unset_placeholder('form_name_short') 1397 data_source.unset_placeholder('form_version') 1398 data_source.unset_placeholder('form_version_internal') 1399 data_source.unset_placeholder('form_last_modified') 1400 1401 self.instance_filename = self.re_editable_filenames[0] 1402 1403 return1404 #--------------------------------------------------------1405 - def __substitute_placeholders(self, data_source=None, input_filename=None, output_filename=None):1406 _log.debug('[%s] -> [%s]', input_filename, output_filename) 1407 1408 found_placeholders = False 1409 1410 template_file = io.open(input_filename, mode = 'rt', encoding = 'utf8') 1411 instance_file = io.open(output_filename, mode = 'wt', encoding = 'utf8') 1412 1413 for line in template_file: 1414 1415 if line.strip() in ['', '\r', '\n', '\r\n']: # empty lines 1416 instance_file.write(line) 1417 continue 1418 if line.startswith('%'): # TeX comment 1419 instance_file.write(line) 1420 continue 1421 1422 for placeholder_regex in [data_source.first_pass_placeholder_regex, data_source.second_pass_placeholder_regex, data_source.third_pass_placeholder_regex]: 1423 # 1) find placeholders in this line 1424 placeholders_in_line = regex.findall(placeholder_regex, line, regex.IGNORECASE) 1425 if len(placeholders_in_line) == 0: 1426 continue 1427 _log.debug('%s placeholders found with pattern: %s', len(placeholders_in_line), placeholder_regex) 1428 found_placeholders = True 1429 # 2) replace them 1430 for placeholder in placeholders_in_line: 1431 try: 1432 val = data_source[placeholder] 1433 except Exception: 1434 _log.exception('error with placeholder [%s]', placeholder) 1435 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % placeholder) 1436 1437 if val is None: 1438 _log.debug('error with placeholder [%s]', placeholder) 1439 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder) 1440 1441 line = line.replace(placeholder, val) 1442 1443 instance_file.write(line) 1444 1445 instance_file.close() 1446 self.re_editable_filenames = [output_filename] 1447 template_file.close() 1448 1449 return found_placeholders1450 #--------------------------------------------------------1452 1453 mimetypes = [ 1454 'application/x-xetex', 1455 'application/x-latex', 1456 'application/x-tex', 1457 'text/plain' 1458 ] 1459 1460 for mimetype in mimetypes: 1461 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 1462 if editor_cmd is not None: 1463 break 1464 1465 if editor_cmd is None: 1466 # Xe(La)TeX code is utf8: also consider text *viewers* 1467 # since pretty much any of them will be an editor as well 1468 for mimetype in mimetypes: 1469 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename) 1470 if editor_cmd is not None: 1471 break 1472 1473 if editor_cmd is None: 1474 return False 1475 1476 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1477 self.re_editable_filenames = [self.instance_filename] 1478 return result1479 #--------------------------------------------------------1481 1482 if instance_file is None: 1483 instance_file = self.instance_filename 1484 1485 try: 1486 open(instance_file, 'r').close() 1487 except Exception: 1488 _log.exception('cannot access form instance file [%s]', instance_file) 1489 gmLog2.log_stack_trace() 1490 return None 1491 1492 self.instance_filename = instance_file 1493 1494 _log.debug('ignoring <format> directive [%s], generating PDF', format) 1495 1496 # Xe(La)TeX can need up to three runs to get cross references et al right 1497 if platform.system() == 'Windows': 1498 # not yet supported: -draftmode 1499 # does not support: -shell-escape 1500 draft_cmd = r'xelatex.exe -interaction=nonstopmode -output-directory=%s %s' % (self.__sandbox_dir, self.instance_filename) 1501 final_cmd = r'xelatex.exe -interaction=nonstopmode -output-directory=%s %s' % (self.__sandbox_dir, self.instance_filename) 1502 else: 1503 # not yet supported: -draftmode 1504 draft_cmd = r'xelatex -interaction=nonstopmode -output-directory=%s -shell-escape %s' % (self.__sandbox_dir, self.instance_filename) 1505 final_cmd = r'xelatex -interaction=nonstopmode -output-directory=%s -shell-escape %s' % (self.__sandbox_dir, self.instance_filename) 1506 1507 for run_cmd in [draft_cmd, draft_cmd, final_cmd]: 1508 if not gmShellAPI.run_command_in_shell(command = run_cmd, blocking = True, acceptable_return_codes = [0, 1]): 1509 _log.error('problem running xelatex, cannot generate form output') 1510 gmDispatcher.send(signal = 'statustext', msg = _('Error running xelatex. Cannot turn Xe(La)TeX template into PDF.'), beep = True) 1511 return None 1512 1513 sandboxed_pdf_name = '%s.pdf' % os.path.splitext(self.instance_filename)[0] 1514 target_dir = os.path.normpath(os.path.join(os.path.split(sandboxed_pdf_name)[0], '..')) 1515 final_pdf_name = os.path.join ( 1516 target_dir, 1517 os.path.split(sandboxed_pdf_name)[1] 1518 ) 1519 _log.debug('copying sandboxed PDF: %s -> %s', sandboxed_pdf_name, final_pdf_name) 1520 try: 1521 shutil.copy2(sandboxed_pdf_name, target_dir) 1522 except IOError: 1523 _log.exception('cannot open/move sandboxed PDF') 1524 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 1525 return None 1526 1527 self.final_output_filenames = [final_pdf_name] 1528 1529 return final_pdf_name1623 """A forms engine wrapping Gnuplot.""" 1624 1625 #-------------------------------------------------------- 1629 #--------------------------------------------------------1674 1675 #------------------------------------------------------------ 1676 form_engines['G'] = cGnuplotForm 1677 1678 #============================================================ 1679 # fPDF form engine 1680 #------------------------------------------------------------1631 """Allow editing the instance of the template.""" 1632 self.re_editable_filenames = [] 1633 return True1634 1635 #--------------------------------------------------------1637 """Generate output suitable for further processing outside this class, e.g. printing. 1638 1639 Expects .data_filename to be set. 1640 """ 1641 wrapper_filename = gmTools.get_unique_filename ( 1642 prefix = 'gm2gpl-wrapper-', 1643 suffix = '.gpl', 1644 tmp_dir = gmTools.fname_dir(self.data_filename) 1645 ) 1646 wrapper_script = io.open(wrapper_filename, mode = 'wt', encoding = 'utf8') 1647 wrapper_script.write(_GNUPLOT_WRAPPER_SCRIPT % ( 1648 gmTools.bool2subst(_cfg.get(option = 'debug'), '1', '0', '0'), 1649 self.data_filename, 1650 self.template_filename 1651 )) 1652 wrapper_script.close() 1653 # FIXME: cater for configurable path 1654 if platform.system() == 'Windows': 1655 exec_name = 'gnuplot.exe' 1656 else: 1657 exec_name = 'gnuplot' 1658 cmd_line = [ 1659 exec_name, 1660 '-p', # persist plot window after gnuplot exits (in case the wxt terminal is used) 1661 wrapper_filename 1662 ] 1663 success, exit_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = _cfg.get(option = 'debug')) 1664 if not success: 1665 gmDispatcher.send(signal = 'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True) 1666 return 1667 1668 self.final_output_filenames = [ 1669 self.data_filename, 1670 self.template_filename, 1671 wrapper_filename 1672 ] 1673 return1682 """A forms engine wrapping PDF forms. 1683 1684 Johann Felix Soden <johfel@gmx.de> helped with this. 1685 1686 http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf 1687 1688 http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/fdf_data_exchange.pdf 1689 """ 16901887 #------------------------------------------------------------ 1888 form_engines['P'] = cPDFForm 1889 1890 #============================================================ 1891 # older code 1892 #------------------------------------------------------------1692 1693 super(cPDFForm, self).__init__(template_file = template_file) 1694 1695 # detect pdftk 1696 found, self.pdftk_binary = gmShellAPI.detect_external_binary(binary = r'pdftk') 1697 if not found: 1698 raise ImportError('<pdftk(.exe)> not found') 1699 return # should be superfluous, actually 1700 1701 enc = sys.getfilesystemencoding() 1702 self.pdftk_binary = self.pdftk_binary.encode(enc) 1703 1704 base_name, ext = os.path.splitext(self.template_filename) 1705 self.fdf_dumped_filename = ('%s.fdf' % base_name).encode(enc) 1706 self.fdf_replaced_filename = ('%s-replaced.fdf' % base_name).encode(enc) 1707 self.pdf_filled_filename = ('%s-filled.pdf' % base_name).encode(enc) 1708 self.pdf_flattened_filename = ('%s-filled-flattened.pdf' % base_name).encode(enc)1709 #--------------------------------------------------------1711 1712 # dump form fields from template 1713 cmd_line = [ 1714 self.pdftk_binary, 1715 self.template_filename, 1716 r'generate_fdf', 1717 r'output', 1718 self.fdf_dumped_filename 1719 ] 1720 _log.debug(' '.join(cmd_line)) 1721 try: 1722 pdftk = subprocess.Popen(cmd_line) 1723 except OSError: 1724 _log.exception('cannot run <pdftk> (dump data from form)') 1725 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot extract fields from PDF form template.'), beep = True) 1726 return False 1727 1728 pdftk.communicate() 1729 if pdftk.returncode != 0: 1730 _log.error('<pdftk> returned [%s], failed to dump data from PDF form into FDF', pdftk.returncode) 1731 return False 1732 1733 # parse dumped FDF file for "/V (...)" records 1734 # and replace placeholders therein 1735 fdf_dumped_file = io.open(self.fdf_dumped_filename, mode = 'rt', encoding = 'utf8') 1736 fdf_replaced_file = io.open(self.fdf_replaced_filename, mode = 'wt', encoding = 'utf8') 1737 1738 string_value_regex = r'\s*/V\s*\(.+\)\s*$' 1739 for line in fdf_dumped_file: 1740 if not regex.match(string_value_regex, line): 1741 fdf_replaced_file.write(line) 1742 continue 1743 1744 # strip cruft around the string value 1745 raw_str_val = line.strip() # remove framing whitespace 1746 raw_str_val = raw_str_val[2:] # remove leading "/V" 1747 raw_str_val = raw_str_val.lstrip() # remove whitespace between "/V" and "(" 1748 raw_str_val = raw_str_val[1:] # remove opening "(" 1749 raw_str_val = raw_str_val[2:] # remove BOM-16-BE 1750 raw_str_val = raw_str_val.rstrip() # remove trailing whitespace 1751 raw_str_val = raw_str_val[:-1] # remove closing ")" 1752 1753 # work on FDF escapes 1754 raw_str_val = raw_str_val.replace('\(', '(') # remove escaping of "(" 1755 raw_str_val = raw_str_val.replace('\)', ')') # remove escaping of ")" 1756 1757 # by now raw_str_val should contain the actual 1758 # string value, albeit encoded as UTF-16, so 1759 # decode it into a unicode object, 1760 # split multi-line fields on "\n" literal 1761 raw_str_lines = raw_str_val.split('\x00\\n') 1762 value_template_lines = [] 1763 for raw_str_line in raw_str_lines: 1764 value_template_lines.append(raw_str_line.decode('utf_16_be')) 1765 1766 replaced_lines = [] 1767 for value_template in value_template_lines: 1768 # find any placeholders within 1769 placeholders_in_value = regex.findall(data_source.placeholder_regex, value_template, regex.IGNORECASE) 1770 for placeholder in placeholders_in_value: 1771 try: 1772 replacement = data_source[placeholder] 1773 except Exception: 1774 _log.exception(replacement) 1775 replacement = _('error with placeholder [%s]') % placeholder 1776 if replacement is None: 1777 replacement = _('error with placeholder [%s]') % placeholder 1778 value_template = value_template.replace(placeholder, replacement) 1779 1780 value_template = value_template.encode('utf_16_be') 1781 1782 if len(placeholders_in_value) > 0: 1783 value_template = value_template.replace(r'(', r'\(') 1784 value_template = value_template.replace(r')', r'\)') 1785 1786 replaced_lines.append(value_template) 1787 1788 replaced_line = '\x00\\n'.join(replaced_lines) 1789 1790 fdf_replaced_file.write('/V (') 1791 fdf_replaced_file.write(codecs.BOM_UTF16_BE) 1792 fdf_replaced_file.write(replaced_line) 1793 fdf_replaced_file.write(')\n') 1794 1795 fdf_replaced_file.close() 1796 fdf_dumped_file.close() 1797 1798 # merge replaced data back into form 1799 cmd_line = [ 1800 self.pdftk_binary, 1801 self.template_filename, 1802 r'fill_form', 1803 self.fdf_replaced_filename, 1804 r'output', 1805 self.pdf_filled_filename 1806 ] 1807 _log.debug(' '.join(cmd_line)) 1808 try: 1809 pdftk = subprocess.Popen(cmd_line) 1810 except OSError: 1811 _log.exception('cannot run <pdftk> (merge data into form)') 1812 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot fill in PDF form template.'), beep = True) 1813 return False 1814 1815 pdftk.communicate() 1816 if pdftk.returncode != 0: 1817 _log.error('<pdftk> returned [%s], failed to merge FDF data into PDF form', pdftk.returncode) 1818 return False 1819 1820 return True1821 #--------------------------------------------------------1823 mimetypes = [ 1824 'application/pdf', 1825 'application/x-pdf' 1826 ] 1827 1828 for mimetype in mimetypes: 1829 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.pdf_filled_filename) 1830 if editor_cmd is not None: 1831 break 1832 1833 if editor_cmd is None: 1834 _log.debug('editor cmd not found, trying viewer cmd') 1835 for mimetype in mimetypes: 1836 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.pdf_filled_filename) 1837 if editor_cmd is not None: 1838 break 1839 1840 if editor_cmd is None: 1841 return False 1842 1843 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1844 1845 path, fname = os.path.split(self.pdf_filled_filename) 1846 candidate = os.path.join(gmTools.gmPaths().home_dir, fname) 1847 1848 if os.access(candidate, os.R_OK): 1849 _log.debug('filled-in PDF found: %s', candidate) 1850 os.rename(self.pdf_filled_filename, self.pdf_filled_filename + '.bak') 1851 shutil.move(candidate, path) 1852 else: 1853 _log.debug('filled-in PDF not found: %s', candidate) 1854 1855 self.re_editable_filenames = [self.pdf_filled_filename] 1856 1857 return result1858 #--------------------------------------------------------1860 """Generate output suitable for further processing outside this class, e.g. printing.""" 1861 1862 # eventually flatten the filled in form so we 1863 # can keep both a flattened and an editable copy: 1864 cmd_line = [ 1865 self.pdftk_binary, 1866 self.pdf_filled_filename, 1867 r'output', 1868 self.pdf_flattened_filename, 1869 r'flatten' 1870 ] 1871 _log.debug(' '.join(cmd_line)) 1872 try: 1873 pdftk = subprocess.Popen(cmd_line) 1874 except OSError: 1875 _log.exception('cannot run <pdftk> (flatten filled in form)') 1876 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot flatten filled in PDF form.'), beep = True) 1877 return None 1878 1879 pdftk.communicate() 1880 if pdftk.returncode != 0: 1881 _log.error('<pdftk> returned [%s], failed to flatten filled in PDF form', pdftk.returncode) 1882 return None 1883 1884 self.final_output_filenames = [self.pdf_flattened_filename] 1885 1886 return self.pdf_flattened_filename1894 """A forms engine wrapping LaTeX. 1895 """ 18991952 1953 1954 1955 1956 #================================================================ 1957 # define a class for HTML forms (for printing) 1958 #================================================================1901 try: 1902 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params]) 1903 # create a 'sandbox' directory for LaTeX to play in 1904 self.tmp = tempfile.mktemp () 1905 os.makedirs (self.tmp) 1906 self.oldcwd = os.getcwd() 1907 os.chdir (self.tmp) 1908 stdin = os.popen ("latex", "w", 2048) 1909 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout 1910 # FIXME: send LaTeX output to the logger 1911 stdin.close () 1912 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True): 1913 raise FormError ('DVIPS returned error') 1914 except EnvironmentError as e: 1915 _log.error(e.strerror) 1916 raise FormError (e.strerror) 1917 return open("texput.ps")19181920 """ 1921 For testing purposes, runs Xdvi on the intermediate TeX output 1922 WARNING: don't try this on Windows 1923 """ 1924 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)19251927 if "%F" in command: 1928 command.replace ("%F", "texput.ps") 1929 else: 1930 command = "%s < texput.ps" % command 1931 try: 1932 if not gmShellAPI.run_command_in_shell(command, blocking=True): 1933 _log.error("external command %s returned non-zero" % command) 1934 raise FormError ('external command %s returned error' % command) 1935 except EnvironmentError as e: 1936 _log.error(e.strerror) 1937 raise FormError (e.strerror) 1938 return True19391941 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print') 1942 self.exe (command)19431945 """ 1946 Delete all the LaTeX output iles 1947 """ 1948 for i in os.listdir ('.'): 1949 os.unlink (i) 1950 os.chdir (self.oldcwd) 1951 os.rmdir (self.tmp)1960 """This class can create XML document from requested data, 1961 then process it with XSLT template and display results 1962 """ 1963 1964 # FIXME: make the path configurable ? 1965 _preview_program = 'oowriter ' #this program must be in the system PATH 19662043 2044 2045 #===================================================== 2046 #class LaTeXFilter(Cheetah.Filters.Filter):1968 1969 if template is None: 1970 raise ValueError('%s: cannot create form instance without a template' % __name__) 1971 1972 cFormEngine.__init__(self, template = template) 1973 1974 self._FormData = None 1975 1976 # here we know/can assume that the template was stored as a utf-8 1977 # encoded string so use that conversion to create unicode: 1978 #self._XSLTData = str(str(template.template_data), 'UTF-8') 1979 # but in fact, str() knows how to handle buffers, so simply: 1980 self._XSLTData = str(self.template.template_data, 'UTF-8', 'strict') 1981 1982 # we must still devise a method of extracting the SQL query: 1983 # - either by retrieving it from a particular tag in the XSLT or 1984 # - by making the stored template actually be a dict which, unpickled, 1985 # has the keys "xslt" and "sql" 1986 self._SQL_query = 'select 1' #this sql query must output valid xml1987 #-------------------------------------------------------- 1988 # external API 1989 #--------------------------------------------------------1991 """get data from backend and process it with XSLT template to produce readable output""" 1992 1993 # extract SQL (this is wrong but displays what is intended) 1994 xslt = libxml2.parseDoc(self._XSLTData) 1995 root = xslt.children 1996 for child in root: 1997 if child.type == 'element': 1998 self._SQL_query = child.content 1999 break 2000 2001 # retrieve data from backend 2002 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False) 2003 2004 __header = '<?xml version="1.0" encoding="UTF-8"?>\n' 2005 __body = rows[0][0] 2006 2007 # process XML data according to supplied XSLT, producing HTML 2008 self._XMLData =__header + __body 2009 style = libxslt.parseStylesheetDoc(xslt) 2010 xml = libxml2.parseDoc(self._XMLData) 2011 html = style.applyStylesheet(xml, None) 2012 self._FormData = html.serialize() 2013 2014 style.freeStylesheet() 2015 xml.freeDoc() 2016 html.freeDoc()2017 #--------------------------------------------------------2019 if self._FormData is None: 2020 raise ValueError('Preview request for empty form. Make sure the form is properly initialized and process() was performed') 2021 2022 fname = gmTools.get_unique_filename(prefix = 'gm_XSLT_form-', suffix = '.html') 2023 #html_file = os.open(fname, 'wb') 2024 #html_file.write(self._FormData.encode('UTF-8')) 2025 html_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'strict') # or 'replace' ? 2026 html_file.write(self._FormData) 2027 html_file.close() 2028 2029 cmd = '%s %s' % (self.__class__._preview_program, fname) 2030 2031 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False): 2032 _log.error('%s: cannot launch report preview program' % __name__) 2033 return False 2034 2035 #os.unlink(self.filename) #delete file 2036 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK) 2037 2038 return True2039 #--------------------------------------------------------2086 2087 2088 #=========================================================== 2091 2092 #============================================================ 2093 # convenience functions 2094 #------------------------------------------------------------2049 """ 2050 Convience function to escape ISO-Latin-1 strings for TeX output 2051 WARNING: not all ISO-Latin-1 characters are expressible in TeX 2052 FIXME: nevertheless, there are a few more we could support 2053 2054 Also intelligently convert lists and tuples into TeX-style table lines 2055 """ 2056 if type(item) is str: 2057 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX? 2058 item = item.replace ("&", "\\&") 2059 item = item.replace ("$", "\\$") 2060 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now 2061 item = item.replace ("\n", "\\\\ ") 2062 if len (item.strip ()) == 0: 2063 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it 2064 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX 2065 item = item.encode ('latin-1', 'replace') 2066 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}', 2067 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions 2068 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`', 2069 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}', 2070 '\xc7':'\\c{C}', '\xc8':'\\`{E}', 2071 '\xa1': '!`', 2072 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent' 2073 } 2074 for k, i in trans.items (): 2075 item = item.replace (k, i) 2076 elif type(item) is list or type(item) is tuple: 2077 item = string.join ([self.conv_enc(i, ' & ') for i in item], table_sep) 2078 elif item is None: 2079 item = '\\relax % Python None\n' 2080 elif type(item) is int or type(item) is float: 2081 item = str(item) 2082 else: 2083 item = str(item) 2084 _log.warning("unknown type %s, string %s" % (type(item), item)) 2085 return item2096 """ 2097 Instantiates a FormEngine based on the form ID or name from the backend 2098 """ 2099 try: 2100 # it's a number: match to form ID 2101 id = int (id) 2102 cmd = 'select template, engine, pk from paperwork_templates where pk = %s' 2103 except ValueError: 2104 # it's a string, match to the form's name 2105 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ? 2106 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s' 2107 result = gmPG.run_ro_query ('reference', cmd, None, id) 2108 if result is None: 2109 _log.error('error getting form [%s]' % id) 2110 raise gmExceptions.FormError ('error getting form [%s]' % id) 2111 if len(result) == 0: 2112 _log.error('no form [%s] found' % id) 2113 raise gmExceptions.FormError ('no such form found [%s]' % id) 2114 if result[0][1] == 'L': 2115 return LaTeXForm (result[0][2], result[0][0]) 2116 elif result[0][1] == 'T': 2117 return TextForm (result[0][2], result[0][0]) 2118 else: 2119 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id)) 2120 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))2121 #------------------------------------------------------------- 2128 #------------------------------------------------------------- 2129 2130 test_letter = """ 2131 \\documentclass{letter} 2132 \\address{ $DOCTOR \\\\ 2133 $DOCTORADDRESS} 2134 \\signature{$DOCTOR} 2135 2136 \\begin{document} 2137 \\begin{letter}{$RECIPIENTNAME \\\\ 2138 $RECIPIENTADDRESS} 2139 2140 \\opening{Dear $RECIPIENTNAME} 2141 2142 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\ 2143 2144 $TEXT 2145 2146 \\ifnum$INCLUDEMEDS>0 2147 \\textbf{Medications List} 2148 2149 \\begin{tabular}{lll} 2150 $MEDSLIST 2151 \\end{tabular} 2152 \\fi 2153 2154 \\ifnum$INCLUDEDISEASES>0 2155 \\textbf{Disease List} 2156 2157 \\begin{tabular}{l} 2158 $DISEASELIST 2159 \\end{tabular} 2160 \\fi 2161 2162 \\closing{$CLOSING} 2163 2164 \\end{letter} 2165 \\end{document} 2166 """ 2167 21682170 f = io.open('../../test-area/ian/terry-form.tex') 2171 params = { 2172 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle", 2173 'DOCTORSNAME': 'Ian Haywood', 2174 'DOCTORSADDRESS': '1 Smith St\nMelbourne', 2175 'PATIENTNAME':'Joe Bloggs', 2176 'PATIENTADDRESS':'18 Fred St\nMelbourne', 2177 'REQUEST':'echocardiogram', 2178 'THERAPY':'on warfarin', 2179 'CLINICALNOTES':"""heard new murmur 2180 Here's some 2181 crap to demonstrate how it can cover multiple lines.""", 2182 'COPYADDRESS':'Jack Jones\nHannover, Germany', 2183 'ROUTINE':1, 2184 'URGENT':0, 2185 'FAX':1, 2186 'PHONE':1, 2187 'PENSIONER':1, 2188 'VETERAN':0, 2189 'PADS':0, 2190 'INSTRUCTIONS':'Take the blue pill, Neo' 2191 } 2192 form = LaTeXForm (1, f.read()) 2193 form.process (params) 2194 form.xdvi () 2195 form.cleanup ()21962198 form = LaTeXForm (2, test_letter) 2199 params = {'RECIPIENTNAME':'Dr. Richard Terry', 2200 'RECIPIENTADDRESS':'1 Main St\nNewcastle', 2201 'DOCTOR':'Dr. Ian Haywood', 2202 'DOCTORADDRESS':'1 Smith St\nMelbourne', 2203 'PATIENTNAME':'Joe Bloggs', 2204 'PATIENTADDRESS':'18 Fred St, Melbourne', 2205 'TEXT':"""This is the main text of the referral letter""", 2206 'DOB':'12/3/65', 2207 'INCLUDEMEDS':1, 2208 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]], 2209 'INCLUDEDISEASES':0, 'DISEASELIST':'', 2210 'CLOSING':'Yours sincerely,' 2211 } 2212 form.process (params) 2213 print(os.getcwd()) 2214 form.xdvi() 2215 form.cleanup()2216 2217 #------------------------------------------------------------2219 template = io.open('../../test-area/ian/Formularkopf-DE.tex') 2220 form = LaTeXForm(template=template.read()) 2221 params = { 2222 'PATIENT LASTNAME': 'Kirk', 2223 'PATIENT FIRSTNAME': 'James T.', 2224 'PATIENT STREET': 'Hauptstrasse', 2225 'PATIENT ZIP': '02999', 2226 'PATIENT TOWN': 'Gross Saerchen', 2227 'PATIENT DOB': '22.03.1931' 2228 } 2229 form.process(params) 2230 form.xdvi() 2231 form.cleanup()2232 2233 #============================================================ 2234 # main 2235 #------------------------------------------------------------ 2236 if __name__ == '__main__': 2237 2238 if len(sys.argv) < 2: 2239 sys.exit() 2240 2241 if sys.argv[1] != 'test': 2242 sys.exit() 2243 2244 gmDateTime.init() 2245 2246 #-------------------------------------------------------- 2247 # OOo 2248 #--------------------------------------------------------2250 init_ooo()2251 #-------------------------------------------------------- 2256 #--------------------------------------------------------2258 srv = gmOOoConnector() 2259 doc = srv.open_document(filename = sys.argv[2]) 2260 print("document:", doc)2261 #--------------------------------------------------------2263 doc = cOOoLetter(template_file = sys.argv[2]) 2264 doc.open_in_ooo() 2265 print("document:", doc) 2266 input('press <ENTER> to continue') 2267 doc.show() 2268 #doc.replace_placeholders() 2269 #doc.save_in_ooo('~/test_cOOoLetter.odt') 2270 # doc = None 2271 # doc.close_in_ooo() 2272 input('press <ENTER> to continue')2273 #--------------------------------------------------------2275 doc = open_uri_in_ooo(filename=sys.argv[1]) 2276 2277 class myCloseListener(unohelper.Base, oooXCloseListener): 2278 def disposing(self, evt): 2279 print("disposing:")2280 def notifyClosing(self, evt): 2281 print("notifyClosing:") 2282 def queryClosing(self, evt, owner): 2283 # owner is True/False whether I am the owner of the doc 2284 print("queryClosing:") 2285 2286 l = myCloseListener() 2287 doc.addCloseListener(l) 2288 2289 tfs = doc.getTextFields().createEnumeration() 2290 print(tfs) 2291 print(dir(tfs)) 2292 while tfs.hasMoreElements(): 2293 tf = tfs.nextElement() 2294 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'): 2295 print(tf.getPropertyValue('PlaceHolder')) 2296 print(" ", tf.getPropertyValue('Hint')) 2297 2298 # doc.close(True) # closes but leaves open the dedicated OOo window 2299 doc.dispose() # closes and disposes of the OOo window 2300 #--------------------------------------------------------2302 pat = gmPersonSearch.ask_for_patient() 2303 if pat is None: 2304 return 2305 gmPerson.set_active_patient(patient = pat) 2306 2307 doc = cOOoLetter(template_file = sys.argv[2]) 2308 doc.open_in_ooo() 2309 print(doc) 2310 doc.show() 2311 #doc.replace_placeholders() 2312 #doc.save_in_ooo('~/test_cOOoLetter.odt') 2313 doc = None 2314 # doc.close_in_ooo() 2315 input('press <ENTER> to continue')2316 #-------------------------------------------------------- 2317 # other 2318 #--------------------------------------------------------2320 template = cFormTemplate(aPK_obj = sys.argv[2]) 2321 print(template) 2322 print(template.save_to_file())2323 #--------------------------------------------------------2325 template = cFormTemplate(aPK_obj = sys.argv[2]) 2326 template.update_template_from_file(filename = sys.argv[3])2327 #--------------------------------------------------------2329 pat = gmPersonSearch.ask_for_patient() 2330 if pat is None: 2331 return 2332 gmPerson.set_active_patient(patient = pat) 2333 2334 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2335 2336 path = os.path.abspath(sys.argv[2]) 2337 form = cLaTeXForm(template_file = path) 2338 2339 from Gnumed.wxpython import gmMacro 2340 ph = gmMacro.gmPlaceholderHandler() 2341 ph.debug = True 2342 instance_file = form.substitute_placeholders(data_source = ph) 2343 pdf_name = form.generate_output(instance_file = instance_file) 2344 print("final PDF file is:", pdf_name)2345 #--------------------------------------------------------2347 pat = gmPersonSearch.ask_for_patient() 2348 if pat is None: 2349 return 2350 gmPerson.set_active_patient(patient = pat) 2351 2352 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2353 2354 path = os.path.abspath(sys.argv[2]) 2355 form = cPDFForm(template_file = path) 2356 2357 from Gnumed.wxpython import gmMacro 2358 ph = gmMacro.gmPlaceholderHandler() 2359 ph.debug = True 2360 instance_file = form.substitute_placeholders(data_source = ph) 2361 pdf_name = form.generate_output(instance_file = instance_file) 2362 print("final PDF file is:", pdf_name)2363 #--------------------------------------------------------2365 pat = gmPersonSearch.ask_for_patient() 2366 if pat is None: 2367 return 2368 gmPerson.set_active_patient(patient = pat) 2369 2370 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2371 2372 path = os.path.abspath(sys.argv[2]) 2373 form = cAbiWordForm(template_file = path) 2374 2375 from Gnumed.wxpython import gmMacro 2376 ph = gmMacro.gmPlaceholderHandler() 2377 ph.debug = True 2378 instance_file = form.substitute_placeholders(data_source = ph) 2379 form.edit() 2380 final_name = form.generate_output(instance_file = instance_file) 2381 print("final file is:", final_name)2382 #--------------------------------------------------------2384 2385 from Gnumed.business import gmPraxis 2386 2387 branches = gmPraxis.get_praxis_branches() 2388 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0]) 2389 print(praxis) 2390 2391 pat = gmPersonSearch.ask_for_patient() 2392 if pat is None: 2393 return 2394 gmPerson.set_active_patient(patient = pat) 2395 2396 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2397 2398 path = os.path.abspath(sys.argv[2]) 2399 form = cTextForm(template_file = path) 2400 2401 from Gnumed.wxpython import gmMacro 2402 ph = gmMacro.gmPlaceholderHandler() 2403 ph.debug = True 2404 print("placeholder substitution worked:", form.substitute_placeholders(data_source = ph)) 2405 print(form.re_editable_filenames) 2406 form.edit() 2407 form.generate_output()2408 #-------------------------------------------------------- 2409 #-------------------------------------------------------- 2410 #-------------------------------------------------------- 2411 # now run the tests 2412 #test_au() 2413 #test_de() 2414 2415 # OOo 2416 #test_init_ooo() 2417 #test_ooo_connect() 2418 #test_open_ooo_doc_from_srv() 2419 #test_open_ooo_doc_from_letter() 2420 #play_with_ooo() 2421 #test_cOOoLetter() 2422 2423 #test_cFormTemplate() 2424 #set_template_from_file() 2425 2426 gmPG2.request_login_params(setup_pool = True) 2427 if not gmPraxis.activate_first_praxis_branch(): 2428 print('no praxis') 2429 test_latex_form() 2430 #test_pdf_form() 2431 #test_abiword_form() 2432 #test_text_form() 2433 2434 #============================================================ 2435
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Thu Jul 23 01:55:31 2020 | http://epydoc.sourceforge.net |