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: 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: 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: 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.keys(): 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: 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: 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: 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).""" 10651320 1321 #------------------------------------------------------------ 1322 form_engines['L'] = cLaTeXForm 1323 1324 #================================================================ 1325 # Xe(La)TeX template forms 1326 #---------------------------------------------------------------- 1327 # Xe(La)TeX: http://www.scholarsfonts.net/xetextt.pdf1067 1068 # create sandbox for LaTeX to play in (and don't assume 1069 # much of anything about the template_file except that it 1070 # is at our disposal for reading) 1071 sandbox_dir = gmTools.mk_sandbox_dir(prefix = gmTools.fname_stem(template_file) + '_') 1072 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir) 1073 shutil.copy(template_file, sandbox_dir) 1074 template_file = os.path.join(sandbox_dir, os.path.split(template_file)[1]) 1075 1076 super(self.__class__, self).__init__(template_file = template_file) 1077 1078 self.__sandbox_dir = sandbox_dir 1079 1080 # set up PDF generator 1081 if platform.system() == 'Windows': 1082 executable = 'pdflatex.exe' 1083 else: 1084 executable = 'pdflatex' 1085 self._final_cmd_line = [ 1086 executable, 1087 '-recorder', 1088 '-interaction=nonstopmode', 1089 "-output-directory=%s" % self.__sandbox_dir 1090 ] 1091 self._draft_cmd_line = self._final_cmd_line + ['-draftmode']1092 1093 #--------------------------------------------------------1095 # remove extra linefeeds which the docutils ReST2LaTeX 1096 # converter likes to add but which makes pdflatex go 1097 # crazy when ending up inside KOMAScript variables 1098 return gmTools.rst2latex_snippet(text).strip()1099 1100 #--------------------------------------------------------1102 1103 # debugging 1104 #data_source.debug = True 1105 1106 if self.template is not None: 1107 # inject placeholder values 1108 data_source.set_placeholder('form_name_long', self.template['name_long']) 1109 data_source.set_placeholder('form_name_short', self.template['name_short']) 1110 data_source.set_placeholder('form_version', self.template['external_version']) 1111 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s')) 1112 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M')) 1113 # add site-local identifying information to template for debugging 1114 f = open(self.template_filename, 'at', encoding = 'utf8') 1115 f.write('\n') 1116 f.write('%------------------------------------------------------------------\n') 1117 for line in self.template.format(): 1118 f.write('% ') 1119 f.write(line) 1120 f.write('\n') 1121 f.write('%------------------------------------------------------------------\n') 1122 f.close() 1123 1124 data_source.escape_function = gmTools.tex_escape_string 1125 data_source.escape_style = 'latex' 1126 1127 path, ext = os.path.splitext(self.template_filename) 1128 if ext in [r'', r'.']: 1129 ext = r'.tex' 1130 1131 filenames = [ 1132 self.template_filename, 1133 r'%s-result-pass-1%s' % (path, ext), 1134 r'%s-result-pass-2%s' % (path, ext), 1135 r'%s-result-pass-3%s' % (path, ext) 1136 ] 1137 regexen = [ 1138 'dummy', 1139 r'\$1{0,1}<[^<].+?>1{0,1}\$', 1140 r'\$2<[^<].+?>2\$', 1141 r'\$3<[^<].+?>3\$' 1142 ] 1143 1144 current_pass = 1 1145 while current_pass < 4: 1146 _log.debug('placeholder substitution pass #%s', current_pass) 1147 found_placeholders = self.__substitute_placeholders ( 1148 input_filename = filenames[current_pass-1], 1149 output_filename = filenames[current_pass], 1150 data_source = data_source, 1151 placeholder_regex = regexen[current_pass] 1152 ) 1153 current_pass += 1 1154 1155 # remove temporary placeholders 1156 data_source.unset_placeholder('form_name_long') 1157 data_source.unset_placeholder('form_name_short') 1158 data_source.unset_placeholder('form_version') 1159 data_source.unset_placeholder('form_version_internal') 1160 data_source.unset_placeholder('form_last_modified') 1161 1162 self.instance_filename = self.re_editable_filenames[0] 1163 1164 return1165 1166 #--------------------------------------------------------1167 - def __substitute_placeholders(self, data_source=None, input_filename=None, output_filename=None, placeholder_regex=None):1168 1169 _log.debug('[%s] -> [%s]', input_filename, output_filename) 1170 _log.debug('searching for placeholders with pattern: %s', placeholder_regex) 1171 1172 template_file = io.open(input_filename, mode = 'rt', encoding = 'utf8') 1173 instance_file = io.open(output_filename, mode = 'wt', encoding = 'utf8') 1174 1175 for line in template_file: 1176 # empty lines 1177 if line.strip() in ['', '\r', '\n', '\r\n']: 1178 instance_file.write(line) 1179 continue 1180 # TeX-comment-only lines 1181 if line.lstrip().startswith('%'): 1182 instance_file.write(line) 1183 continue 1184 1185 # 1) find placeholders in this line 1186 placeholders_in_line = regex.findall(placeholder_regex, line, regex.IGNORECASE) 1187 if len(placeholders_in_line) == 0: 1188 instance_file.write(line) 1189 continue 1190 1191 # 2) replace them 1192 _log.debug('replacing in non-empty, non-comment line: >>>%s<<<', line.rstrip(u'\n')) 1193 _log.debug('%s placeholder(s) detected', len(placeholders_in_line)) 1194 for placeholder in placeholders_in_line: 1195 if 'free_text' in placeholder: 1196 # enable reStructuredText processing 1197 data_source.escape_function = self._rst2latex_transform 1198 else: 1199 data_source.escape_function = gmTools.tex_escape_string 1200 original_ph_def = placeholder 1201 _log.debug('placeholder: >>>%s<<<', original_ph_def) 1202 # normalize start/end 1203 if placeholder.startswith('$<'): 1204 placeholder = '$1<' + placeholder[2:] 1205 if placeholder.endswith('>$'): 1206 placeholder = placeholder[:-2] + '>1$' 1207 _log.debug('normalized : >>>%s<<<', placeholder) 1208 # remove start/end 1209 placeholder = placeholder[3:-3] 1210 _log.debug('stripped : >>>%s<<<', placeholder) 1211 try: 1212 val = data_source[placeholder] 1213 except: 1214 _log.exception('error with placeholder [%s]', original_ph_def) 1215 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % original_ph_def) 1216 if val is None: 1217 _log.debug('error with placeholder [%s]', original_ph_def) 1218 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % original_ph_def) 1219 _log.debug('value : >>>%s<<<', val) 1220 line = line.replace(original_ph_def, val) 1221 instance_file.write(line) 1222 1223 instance_file.close() 1224 self.re_editable_filenames = [output_filename] 1225 template_file.close() 1226 1227 return1228 1229 #--------------------------------------------------------1231 1232 mimetypes = [ 1233 'application/x-latex', 1234 'application/x-tex', 1235 'text/latex', 1236 'text/tex', 1237 'text/plain' 1238 ] 1239 1240 for mimetype in mimetypes: 1241 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 1242 if editor_cmd is not None: 1243 break 1244 1245 if editor_cmd is None: 1246 # LaTeX code is text: also consider text *viewers* 1247 # since pretty much any of them will be an editor as well 1248 for mimetype in mimetypes: 1249 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename) 1250 if editor_cmd is not None: 1251 break 1252 1253 if editor_cmd is None: 1254 return False 1255 1256 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1257 self.re_editable_filenames = [self.instance_filename] 1258 return result1259 1260 #--------------------------------------------------------1262 1263 if instance_file is None: 1264 instance_file = self.instance_filename 1265 1266 try: 1267 open(instance_file, 'r').close() 1268 except: 1269 _log.exception('cannot access form instance file [%s]', instance_file) 1270 gmLog2.log_stack_trace() 1271 return None 1272 1273 self.instance_filename = instance_file 1274 1275 _log.debug('ignoring <format> directive [%s], generating PDF', format) 1276 draft_cmd = self._draft_cmd_line + [self.instance_filename] 1277 final_cmd = self._final_cmd_line + [self.instance_filename] 1278 # LaTeX can need up to three runs to get cross references et al right 1279 for run_cmd in [draft_cmd, draft_cmd, final_cmd]: 1280 success, ret_code, stdout = gmShellAPI.run_process ( 1281 cmd_line = run_cmd, 1282 acceptable_return_codes = [0], 1283 encoding = 'utf8', 1284 verbose = _cfg.get(option = 'debug') 1285 ) 1286 if not success: 1287 _log.error('problem running pdflatex, cannot generate form output, trying diagnostics') 1288 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True) 1289 found, binary = gmShellAPI.find_first_binary(binaries = ['lacheck', 'miktex-lacheck.exe']) 1290 if not found: 1291 _log.debug('lacheck not found') 1292 else: 1293 cmd_line = [binary, self.instance_filename] 1294 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = True) 1295 found, binary = gmShellAPI.find_first_binary(binaries = ['chktex', 'ChkTeX.exe']) 1296 if not found: 1297 _log.debug('chcktex not found') 1298 else: 1299 cmd_line = [binary, '--verbosity=2', '--headererr', self.instance_filename] 1300 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = True) 1301 return None 1302 1303 sandboxed_pdf_name = '%s.pdf' % os.path.splitext(self.instance_filename)[0] 1304 target_dir = os.path.normpath(os.path.join(os.path.split(sandboxed_pdf_name)[0], '..')) 1305 final_pdf_name = os.path.join ( 1306 target_dir, 1307 os.path.split(sandboxed_pdf_name)[1] 1308 ) 1309 _log.debug('copying sandboxed PDF: %s -> %s', sandboxed_pdf_name, final_pdf_name) 1310 try: 1311 shutil.copy2(sandboxed_pdf_name, target_dir) 1312 except IOError: 1313 _log.exception('cannot open/move sandboxed PDF') 1314 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 1315 return None 1316 1317 self.final_output_filenames = [final_pdf_name] 1318 1319 return final_pdf_name1329 """A forms engine wrapping Xe(La)TeX.""" 13301517 1518 #------------------------------------------------------------ 1519 form_engines['X'] = cXeTeXForm 1520 1521 #============================================================ 1522 # Gnuplot template forms 1523 #------------------------------------------------------------ 1524 _GNUPLOT_WRAPPER_SCRIPT = """# -------------------------------------------------------------- 1525 # GNUplot wrapper script used by GNUmed 1526 # 1527 # This script is used to make gnuplot 1528 # 1529 # display some debugging information 1530 # 1531 # load GNUmed specific settings such as the timestamp 1532 # format, encoding, symbol for missing values, 1533 # 1534 # load data specific values such as y(2)label or plot 1535 # title which GNUmed will have set up while exporting 1536 # test results for plotting, 1537 # 1538 # know the datafile name from the variable <gm2gpl_datafile> 1539 # which the user provided plotting script can then use 1540 # to access data like so: 1541 # 1542 # plot gm2gpl_datafile ... 1543 # 1544 # -------------------------------------------------------------- 1545 1546 # logging verbosity, depending on GNUmed client debug state 1547 gmd_log_verbose = %s 1548 1549 1550 # -- debugging ---- 1551 show version long 1552 if (gmd_log_verbose == 1) { 1553 print "-- <show all> at startup ----" 1554 show all 1555 print "-- <show variables all> at startup ----" 1556 show variables all 1557 } 1558 1559 1560 # -- data format setup ---- 1561 set encoding utf8 1562 set timefmt "%%Y-%%m-%%d_%%H:%%M" # timestamp input formatting, not for output 1563 1564 1565 # -- data file setup ---- 1566 gm2gpl_datafile = '%s' 1567 set datafile missing "<?>" 1568 set xdata time 1569 set x2data time 1570 1571 1572 # -- process additional definitions from GNUmed ---- 1573 gm2gpl_datafile_conf = gm2gpl_datafile.'.conf' 1574 load gm2gpl_datafile_conf 1575 1576 1577 # -- actually run the user provided plotting script ---- 1578 call '%s' 1579 1580 1581 # -- debugging ---- 1582 if (gmd_log_verbose == 1) { 1583 print "-- <show all> after running user provided plotting script ----" 1584 show all 1585 print "-- <show variables all> after running user provided plotting script ----" 1586 show variables all 1587 1588 # PNG output: 1589 #set terminal png enhanced transparent nointerlace truecolor #medium #crop 1590 ##set output 'test_terminal.png' 1591 ##test 1592 #set output gm2gpl_datafile.'.dbg.png' 1593 #replot 1594 1595 # ASCII art output: 1596 #set terminal dumb size 120,45 feed enhanced ansirgb 1597 ##set output 'test_terminal.txt' 1598 ##test 1599 #set output gm2gpl_datafile.'.dbg.txt' 1600 #replot 1601 #set terminal dumb size 120,45 feed enhanced mono 1602 ##set output 'test_terminal.ascii.txt' 1603 ##test 1604 #set output gm2gpl_datafile.'.dbg.ascii.txt' 1605 #replot 1606 } 1607 """ 16081332 1333 # create sandbox for LaTeX to play in (and don't assume 1334 # much of anything about the template_file except that it 1335 # is at our disposal) 1336 sandbox_dir = gmTools.mk_sandbox_dir(prefix = gmTools.fname_stem(template_file) + '_') 1337 _log.debug('Xe(La)TeX sandbox directory: [%s]', sandbox_dir) 1338 shutil.copy(template_file, sandbox_dir) 1339 template_file = os.path.join(sandbox_dir, os.path.split(template_file)[1]) 1340 1341 super(self.__class__, self).__init__(template_file = template_file) 1342 1343 self.__sandbox_dir = sandbox_dir1344 #--------------------------------------------------------1346 1347 if self.template is not None: 1348 # inject placeholder values 1349 data_source.set_placeholder('form_name_long', self.template['name_long']) 1350 data_source.set_placeholder('form_name_short', self.template['name_short']) 1351 data_source.set_placeholder('form_version', self.template['external_version']) 1352 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s')) 1353 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M')) 1354 1355 data_source.escape_function = gmTools.xetex_escape_string 1356 data_source.escape_style = 'xetex' 1357 1358 path, ext = os.path.splitext(self.template_filename) 1359 if ext in [r'', r'.']: 1360 ext = r'.tex' 1361 1362 filenames = [ 1363 self.template_filename, 1364 r'%s-result_run1%s' % (path, ext), 1365 r'%s-result_run2%s' % (path, ext), 1366 r'%s-result_run3%s' % (path, ext) 1367 ] 1368 1369 found_placeholders = True 1370 current_run = 1 1371 while found_placeholders and (current_run < 4): 1372 _log.debug('placeholder substitution run #%s', current_run) 1373 found_placeholders = self.__substitute_placeholders ( 1374 input_filename = filenames[current_run-1], 1375 output_filename = filenames[current_run], 1376 data_source = data_source 1377 ) 1378 current_run += 1 1379 1380 if self.template is not None: 1381 # remove temporary placeholders 1382 data_source.unset_placeholder('form_name_long') 1383 data_source.unset_placeholder('form_name_short') 1384 data_source.unset_placeholder('form_version') 1385 data_source.unset_placeholder('form_version_internal') 1386 data_source.unset_placeholder('form_last_modified') 1387 1388 self.instance_filename = self.re_editable_filenames[0] 1389 1390 return1391 #--------------------------------------------------------1392 - def __substitute_placeholders(self, data_source=None, input_filename=None, output_filename=None):1393 _log.debug('[%s] -> [%s]', input_filename, output_filename) 1394 1395 found_placeholders = False 1396 1397 template_file = io.open(input_filename, mode = 'rt', encoding = 'utf8') 1398 instance_file = io.open(output_filename, mode = 'wt', encoding = 'utf8') 1399 1400 for line in template_file: 1401 1402 if line.strip() in ['', '\r', '\n', '\r\n']: # empty lines 1403 instance_file.write(line) 1404 continue 1405 if line.startswith('%'): # TeX comment 1406 instance_file.write(line) 1407 continue 1408 1409 for placeholder_regex in [data_source.first_pass_placeholder_regex, data_source.second_pass_placeholder_regex, data_source.third_pass_placeholder_regex]: 1410 # 1) find placeholders in this line 1411 placeholders_in_line = regex.findall(placeholder_regex, line, regex.IGNORECASE) 1412 if len(placeholders_in_line) == 0: 1413 continue 1414 _log.debug('%s placeholders found with pattern: %s', len(placeholders_in_line), placeholder_regex) 1415 found_placeholders = True 1416 # 2) replace them 1417 for placeholder in placeholders_in_line: 1418 try: 1419 val = data_source[placeholder] 1420 except: 1421 _log.exception('error with placeholder [%s]', placeholder) 1422 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % placeholder) 1423 1424 if val is None: 1425 _log.debug('error with placeholder [%s]', placeholder) 1426 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder) 1427 1428 line = line.replace(placeholder, val) 1429 1430 instance_file.write(line) 1431 1432 instance_file.close() 1433 self.re_editable_filenames = [output_filename] 1434 template_file.close() 1435 1436 return found_placeholders1437 #--------------------------------------------------------1439 1440 mimetypes = [ 1441 'application/x-xetex', 1442 'application/x-latex', 1443 'application/x-tex', 1444 'text/plain' 1445 ] 1446 1447 for mimetype in mimetypes: 1448 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename) 1449 if editor_cmd is not None: 1450 break 1451 1452 if editor_cmd is None: 1453 # Xe(La)TeX code is utf8: also consider text *viewers* 1454 # since pretty much any of them will be an editor as well 1455 for mimetype in mimetypes: 1456 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename) 1457 if editor_cmd is not None: 1458 break 1459 1460 if editor_cmd is None: 1461 return False 1462 1463 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1464 self.re_editable_filenames = [self.instance_filename] 1465 return result1466 #--------------------------------------------------------1468 1469 if instance_file is None: 1470 instance_file = self.instance_filename 1471 1472 try: 1473 open(instance_file, 'r').close() 1474 except: 1475 _log.exception('cannot access form instance file [%s]', instance_file) 1476 gmLog2.log_stack_trace() 1477 return None 1478 1479 self.instance_filename = instance_file 1480 1481 _log.debug('ignoring <format> directive [%s], generating PDF', format) 1482 1483 # Xe(La)TeX can need up to three runs to get cross references et al right 1484 if platform.system() == 'Windows': 1485 # not yet supported: -draftmode 1486 # does not support: -shell-escape 1487 draft_cmd = r'xelatex.exe -interaction=nonstopmode -output-directory=%s %s' % (self.__sandbox_dir, self.instance_filename) 1488 final_cmd = r'xelatex.exe -interaction=nonstopmode -output-directory=%s %s' % (self.__sandbox_dir, self.instance_filename) 1489 else: 1490 # not yet supported: -draftmode 1491 draft_cmd = r'xelatex -interaction=nonstopmode -output-directory=%s -shell-escape %s' % (self.__sandbox_dir, self.instance_filename) 1492 final_cmd = r'xelatex -interaction=nonstopmode -output-directory=%s -shell-escape %s' % (self.__sandbox_dir, self.instance_filename) 1493 1494 for run_cmd in [draft_cmd, draft_cmd, final_cmd]: 1495 if not gmShellAPI.run_command_in_shell(command = run_cmd, blocking = True, acceptable_return_codes = [0, 1]): 1496 _log.error('problem running xelatex, cannot generate form output') 1497 gmDispatcher.send(signal = 'statustext', msg = _('Error running xelatex. Cannot turn Xe(La)TeX template into PDF.'), beep = True) 1498 return None 1499 1500 sandboxed_pdf_name = '%s.pdf' % os.path.splitext(self.instance_filename)[0] 1501 target_dir = os.path.normpath(os.path.join(os.path.split(sandboxed_pdf_name)[0], '..')) 1502 final_pdf_name = os.path.join ( 1503 target_dir, 1504 os.path.split(sandboxed_pdf_name)[1] 1505 ) 1506 _log.debug('copying sandboxed PDF: %s -> %s', sandboxed_pdf_name, final_pdf_name) 1507 try: 1508 shutil.copy2(sandboxed_pdf_name, target_dir) 1509 except IOError: 1510 _log.exception('cannot open/move sandboxed PDF') 1511 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True) 1512 return None 1513 1514 self.final_output_filenames = [final_pdf_name] 1515 1516 return final_pdf_name1610 """A forms engine wrapping Gnuplot.""" 1611 1612 #-------------------------------------------------------- 1616 #--------------------------------------------------------1661 1662 #------------------------------------------------------------ 1663 form_engines['G'] = cGnuplotForm 1664 1665 #============================================================ 1666 # fPDF form engine 1667 #------------------------------------------------------------1618 """Allow editing the instance of the template.""" 1619 self.re_editable_filenames = [] 1620 return True1621 1622 #--------------------------------------------------------1624 """Generate output suitable for further processing outside this class, e.g. printing. 1625 1626 Expects .data_filename to be set. 1627 """ 1628 wrapper_filename = gmTools.get_unique_filename ( 1629 prefix = 'gm2gpl-wrapper-', 1630 suffix = '.gpl', 1631 tmp_dir = gmTools.fname_dir(self.data_filename) 1632 ) 1633 wrapper_script = io.open(wrapper_filename, mode = 'wt', encoding = 'utf8') 1634 wrapper_script.write(_GNUPLOT_WRAPPER_SCRIPT % ( 1635 gmTools.bool2subst(_cfg.get(option = 'debug'), '1', '0', '0'), 1636 self.data_filename, 1637 self.template_filename 1638 )) 1639 wrapper_script.close() 1640 # FIXME: cater for configurable path 1641 if platform.system() == 'Windows': 1642 exec_name = 'gnuplot.exe' 1643 else: 1644 exec_name = 'gnuplot' 1645 cmd_line = [ 1646 exec_name, 1647 '-p', # persist plot window after gnuplot exits (in case the wxt terminal is used) 1648 wrapper_filename 1649 ] 1650 success, exit_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = _cfg.get(option = 'debug')) 1651 if not success: 1652 gmDispatcher.send(signal = 'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True) 1653 return 1654 1655 self.final_output_filenames = [ 1656 self.data_filename, 1657 self.template_filename, 1658 wrapper_filename 1659 ] 1660 return1669 """A forms engine wrapping PDF forms. 1670 1671 Johann Felix Soden <johfel@gmx.de> helped with this. 1672 1673 http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf 1674 1675 http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/fdf_data_exchange.pdf 1676 """ 16771874 #------------------------------------------------------------ 1875 form_engines['P'] = cPDFForm 1876 1877 #============================================================ 1878 # older code 1879 #------------------------------------------------------------1679 1680 super(cPDFForm, self).__init__(template_file = template_file) 1681 1682 # detect pdftk 1683 found, self.pdftk_binary = gmShellAPI.detect_external_binary(binary = r'pdftk') 1684 if not found: 1685 raise ImportError('<pdftk(.exe)> not found') 1686 return # should be superfluous, actually 1687 1688 enc = sys.getfilesystemencoding() 1689 self.pdftk_binary = self.pdftk_binary.encode(enc) 1690 1691 base_name, ext = os.path.splitext(self.template_filename) 1692 self.fdf_dumped_filename = ('%s.fdf' % base_name).encode(enc) 1693 self.fdf_replaced_filename = ('%s-replaced.fdf' % base_name).encode(enc) 1694 self.pdf_filled_filename = ('%s-filled.pdf' % base_name).encode(enc) 1695 self.pdf_flattened_filename = ('%s-filled-flattened.pdf' % base_name).encode(enc)1696 #--------------------------------------------------------1698 1699 # dump form fields from template 1700 cmd_line = [ 1701 self.pdftk_binary, 1702 self.template_filename, 1703 r'generate_fdf', 1704 r'output', 1705 self.fdf_dumped_filename 1706 ] 1707 _log.debug(' '.join(cmd_line)) 1708 try: 1709 pdftk = subprocess.Popen(cmd_line) 1710 except OSError: 1711 _log.exception('cannot run <pdftk> (dump data from form)') 1712 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot extract fields from PDF form template.'), beep = True) 1713 return False 1714 1715 pdftk.communicate() 1716 if pdftk.returncode != 0: 1717 _log.error('<pdftk> returned [%s], failed to dump data from PDF form into FDF', pdftk.returncode) 1718 return False 1719 1720 # parse dumped FDF file for "/V (...)" records 1721 # and replace placeholders therein 1722 fdf_dumped_file = io.open(self.fdf_dumped_filename, mode = 'rt', encoding = 'utf8') 1723 fdf_replaced_file = io.open(self.fdf_replaced_filename, mode = 'wt', encoding = 'utf8') 1724 1725 string_value_regex = r'\s*/V\s*\(.+\)\s*$' 1726 for line in fdf_dumped_file: 1727 if not regex.match(string_value_regex, line): 1728 fdf_replaced_file.write(line) 1729 continue 1730 1731 # strip cruft around the string value 1732 raw_str_val = line.strip() # remove framing whitespace 1733 raw_str_val = raw_str_val[2:] # remove leading "/V" 1734 raw_str_val = raw_str_val.lstrip() # remove whitespace between "/V" and "(" 1735 raw_str_val = raw_str_val[1:] # remove opening "(" 1736 raw_str_val = raw_str_val[2:] # remove BOM-16-BE 1737 raw_str_val = raw_str_val.rstrip() # remove trailing whitespace 1738 raw_str_val = raw_str_val[:-1] # remove closing ")" 1739 1740 # work on FDF escapes 1741 raw_str_val = raw_str_val.replace('\(', '(') # remove escaping of "(" 1742 raw_str_val = raw_str_val.replace('\)', ')') # remove escaping of ")" 1743 1744 # by now raw_str_val should contain the actual 1745 # string value, albeit encoded as UTF-16, so 1746 # decode it into a unicode object, 1747 # split multi-line fields on "\n" literal 1748 raw_str_lines = raw_str_val.split('\x00\\n') 1749 value_template_lines = [] 1750 for raw_str_line in raw_str_lines: 1751 value_template_lines.append(raw_str_line.decode('utf_16_be')) 1752 1753 replaced_lines = [] 1754 for value_template in value_template_lines: 1755 # find any placeholders within 1756 placeholders_in_value = regex.findall(data_source.placeholder_regex, value_template, regex.IGNORECASE) 1757 for placeholder in placeholders_in_value: 1758 try: 1759 replacement = data_source[placeholder] 1760 except: 1761 _log.exception(replacement) 1762 replacement = _('error with placeholder [%s]') % placeholder 1763 if replacement is None: 1764 replacement = _('error with placeholder [%s]') % placeholder 1765 value_template = value_template.replace(placeholder, replacement) 1766 1767 value_template = value_template.encode('utf_16_be') 1768 1769 if len(placeholders_in_value) > 0: 1770 value_template = value_template.replace(r'(', r'\(') 1771 value_template = value_template.replace(r')', r'\)') 1772 1773 replaced_lines.append(value_template) 1774 1775 replaced_line = '\x00\\n'.join(replaced_lines) 1776 1777 fdf_replaced_file.write('/V (') 1778 fdf_replaced_file.write(codecs.BOM_UTF16_BE) 1779 fdf_replaced_file.write(replaced_line) 1780 fdf_replaced_file.write(')\n') 1781 1782 fdf_replaced_file.close() 1783 fdf_dumped_file.close() 1784 1785 # merge replaced data back into form 1786 cmd_line = [ 1787 self.pdftk_binary, 1788 self.template_filename, 1789 r'fill_form', 1790 self.fdf_replaced_filename, 1791 r'output', 1792 self.pdf_filled_filename 1793 ] 1794 _log.debug(' '.join(cmd_line)) 1795 try: 1796 pdftk = subprocess.Popen(cmd_line) 1797 except OSError: 1798 _log.exception('cannot run <pdftk> (merge data into form)') 1799 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot fill in PDF form template.'), beep = True) 1800 return False 1801 1802 pdftk.communicate() 1803 if pdftk.returncode != 0: 1804 _log.error('<pdftk> returned [%s], failed to merge FDF data into PDF form', pdftk.returncode) 1805 return False 1806 1807 return True1808 #--------------------------------------------------------1810 mimetypes = [ 1811 'application/pdf', 1812 'application/x-pdf' 1813 ] 1814 1815 for mimetype in mimetypes: 1816 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.pdf_filled_filename) 1817 if editor_cmd is not None: 1818 break 1819 1820 if editor_cmd is None: 1821 _log.debug('editor cmd not found, trying viewer cmd') 1822 for mimetype in mimetypes: 1823 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.pdf_filled_filename) 1824 if editor_cmd is not None: 1825 break 1826 1827 if editor_cmd is None: 1828 return False 1829 1830 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True) 1831 1832 path, fname = os.path.split(self.pdf_filled_filename) 1833 candidate = os.path.join(gmTools.gmPaths().home_dir, fname) 1834 1835 if os.access(candidate, os.R_OK): 1836 _log.debug('filled-in PDF found: %s', candidate) 1837 os.rename(self.pdf_filled_filename, self.pdf_filled_filename + '.bak') 1838 shutil.move(candidate, path) 1839 else: 1840 _log.debug('filled-in PDF not found: %s', candidate) 1841 1842 self.re_editable_filenames = [self.pdf_filled_filename] 1843 1844 return result1845 #--------------------------------------------------------1847 """Generate output suitable for further processing outside this class, e.g. printing.""" 1848 1849 # eventually flatten the filled in form so we 1850 # can keep both a flattened and an editable copy: 1851 cmd_line = [ 1852 self.pdftk_binary, 1853 self.pdf_filled_filename, 1854 r'output', 1855 self.pdf_flattened_filename, 1856 r'flatten' 1857 ] 1858 _log.debug(' '.join(cmd_line)) 1859 try: 1860 pdftk = subprocess.Popen(cmd_line) 1861 except OSError: 1862 _log.exception('cannot run <pdftk> (flatten filled in form)') 1863 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot flatten filled in PDF form.'), beep = True) 1864 return None 1865 1866 pdftk.communicate() 1867 if pdftk.returncode != 0: 1868 _log.error('<pdftk> returned [%s], failed to flatten filled in PDF form', pdftk.returncode) 1869 return None 1870 1871 self.final_output_filenames = [self.pdf_flattened_filename] 1872 1873 return self.pdf_flattened_filename1881 """A forms engine wrapping LaTeX. 1882 """ 18861939 1940 1941 1942 1943 #================================================================ 1944 # define a class for HTML forms (for printing) 1945 #================================================================1888 try: 1889 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params]) 1890 # create a 'sandbox' directory for LaTeX to play in 1891 self.tmp = tempfile.mktemp () 1892 os.makedirs (self.tmp) 1893 self.oldcwd = os.getcwd() 1894 os.chdir (self.tmp) 1895 stdin = os.popen ("latex", "w", 2048) 1896 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout 1897 # FIXME: send LaTeX output to the logger 1898 stdin.close () 1899 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True): 1900 raise FormError ('DVIPS returned error') 1901 except EnvironmentError as e: 1902 _log.error(e.strerror) 1903 raise FormError (e.strerror) 1904 return open("texput.ps")19051907 """ 1908 For testing purposes, runs Xdvi on the intermediate TeX output 1909 WARNING: don't try this on Windows 1910 """ 1911 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)19121914 if "%F" in command: 1915 command.replace ("%F", "texput.ps") 1916 else: 1917 command = "%s < texput.ps" % command 1918 try: 1919 if not gmShellAPI.run_command_in_shell(command, blocking=True): 1920 _log.error("external command %s returned non-zero" % command) 1921 raise FormError ('external command %s returned error' % command) 1922 except EnvironmentError as e: 1923 _log.error(e.strerror) 1924 raise FormError (e.strerror) 1925 return True19261928 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print') 1929 self.exe (command)19301932 """ 1933 Delete all the LaTeX output iles 1934 """ 1935 for i in os.listdir ('.'): 1936 os.unlink (i) 1937 os.chdir (self.oldcwd) 1938 os.rmdir (self.tmp)1947 """This class can create XML document from requested data, 1948 then process it with XSLT template and display results 1949 """ 1950 1951 # FIXME: make the path configurable ? 1952 _preview_program = 'oowriter ' #this program must be in the system PATH 19532030 2031 2032 #===================================================== 2033 #class LaTeXFilter(Cheetah.Filters.Filter):1955 1956 if template is None: 1957 raise ValueError('%s: cannot create form instance without a template' % __name__) 1958 1959 cFormEngine.__init__(self, template = template) 1960 1961 self._FormData = None 1962 1963 # here we know/can assume that the template was stored as a utf-8 1964 # encoded string so use that conversion to create unicode: 1965 #self._XSLTData = str(str(template.template_data), 'UTF-8') 1966 # but in fact, str() knows how to handle buffers, so simply: 1967 self._XSLTData = str(self.template.template_data, 'UTF-8', 'strict') 1968 1969 # we must still devise a method of extracting the SQL query: 1970 # - either by retrieving it from a particular tag in the XSLT or 1971 # - by making the stored template actually be a dict which, unpickled, 1972 # has the keys "xslt" and "sql" 1973 self._SQL_query = 'select 1' #this sql query must output valid xml1974 #-------------------------------------------------------- 1975 # external API 1976 #--------------------------------------------------------1978 """get data from backend and process it with XSLT template to produce readable output""" 1979 1980 # extract SQL (this is wrong but displays what is intended) 1981 xslt = libxml2.parseDoc(self._XSLTData) 1982 root = xslt.children 1983 for child in root: 1984 if child.type == 'element': 1985 self._SQL_query = child.content 1986 break 1987 1988 # retrieve data from backend 1989 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False) 1990 1991 __header = '<?xml version="1.0" encoding="UTF-8"?>\n' 1992 __body = rows[0][0] 1993 1994 # process XML data according to supplied XSLT, producing HTML 1995 self._XMLData =__header + __body 1996 style = libxslt.parseStylesheetDoc(xslt) 1997 xml = libxml2.parseDoc(self._XMLData) 1998 html = style.applyStylesheet(xml, None) 1999 self._FormData = html.serialize() 2000 2001 style.freeStylesheet() 2002 xml.freeDoc() 2003 html.freeDoc()2004 #--------------------------------------------------------2006 if self._FormData is None: 2007 raise ValueError('Preview request for empty form. Make sure the form is properly initialized and process() was performed') 2008 2009 fname = gmTools.get_unique_filename(prefix = 'gm_XSLT_form-', suffix = '.html') 2010 #html_file = os.open(fname, 'wb') 2011 #html_file.write(self._FormData.encode('UTF-8')) 2012 html_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'strict') # or 'replace' ? 2013 html_file.write(self._FormData) 2014 html_file.close() 2015 2016 cmd = '%s %s' % (self.__class__._preview_program, fname) 2017 2018 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False): 2019 _log.error('%s: cannot launch report preview program' % __name__) 2020 return False 2021 2022 #os.unlink(self.filename) #delete file 2023 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK) 2024 2025 return True2026 #--------------------------------------------------------2073 2074 2075 #=========================================================== 2078 2079 #============================================================ 2080 # convenience functions 2081 #------------------------------------------------------------2036 """ 2037 Convience function to escape ISO-Latin-1 strings for TeX output 2038 WARNING: not all ISO-Latin-1 characters are expressible in TeX 2039 FIXME: nevertheless, there are a few more we could support 2040 2041 Also intelligently convert lists and tuples into TeX-style table lines 2042 """ 2043 if type(item) is str: 2044 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX? 2045 item = item.replace ("&", "\\&") 2046 item = item.replace ("$", "\\$") 2047 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now 2048 item = item.replace ("\n", "\\\\ ") 2049 if len (item.strip ()) == 0: 2050 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it 2051 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX 2052 item = item.encode ('latin-1', 'replace') 2053 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}', 2054 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions 2055 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`', 2056 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}', 2057 '\xc7':'\\c{C}', '\xc8':'\\`{E}', 2058 '\xa1': '!`', 2059 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent' 2060 } 2061 for k, i in trans.items (): 2062 item = item.replace (k, i) 2063 elif type(item) is list or type(item) is tuple: 2064 item = string.join ([self.conv_enc(i, ' & ') for i in item], table_sep) 2065 elif item is None: 2066 item = '\\relax % Python None\n' 2067 elif type(item) is int or type(item) is float: 2068 item = str(item) 2069 else: 2070 item = str(item) 2071 _log.warning("unknown type %s, string %s" % (type(item), item)) 2072 return item2083 """ 2084 Instantiates a FormEngine based on the form ID or name from the backend 2085 """ 2086 try: 2087 # it's a number: match to form ID 2088 id = int (id) 2089 cmd = 'select template, engine, pk from paperwork_templates where pk = %s' 2090 except ValueError: 2091 # it's a string, match to the form's name 2092 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ? 2093 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s' 2094 result = gmPG.run_ro_query ('reference', cmd, None, id) 2095 if result is None: 2096 _log.error('error getting form [%s]' % id) 2097 raise gmExceptions.FormError ('error getting form [%s]' % id) 2098 if len(result) == 0: 2099 _log.error('no form [%s] found' % id) 2100 raise gmExceptions.FormError ('no such form found [%s]' % id) 2101 if result[0][1] == 'L': 2102 return LaTeXForm (result[0][2], result[0][0]) 2103 elif result[0][1] == 'T': 2104 return TextForm (result[0][2], result[0][0]) 2105 else: 2106 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id)) 2107 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))2108 #------------------------------------------------------------- 2115 #------------------------------------------------------------- 2116 2117 test_letter = """ 2118 \\documentclass{letter} 2119 \\address{ $DOCTOR \\\\ 2120 $DOCTORADDRESS} 2121 \\signature{$DOCTOR} 2122 2123 \\begin{document} 2124 \\begin{letter}{$RECIPIENTNAME \\\\ 2125 $RECIPIENTADDRESS} 2126 2127 \\opening{Dear $RECIPIENTNAME} 2128 2129 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\ 2130 2131 $TEXT 2132 2133 \\ifnum$INCLUDEMEDS>0 2134 \\textbf{Medications List} 2135 2136 \\begin{tabular}{lll} 2137 $MEDSLIST 2138 \\end{tabular} 2139 \\fi 2140 2141 \\ifnum$INCLUDEDISEASES>0 2142 \\textbf{Disease List} 2143 2144 \\begin{tabular}{l} 2145 $DISEASELIST 2146 \\end{tabular} 2147 \\fi 2148 2149 \\closing{$CLOSING} 2150 2151 \\end{letter} 2152 \\end{document} 2153 """ 2154 21552157 f = io.open('../../test-area/ian/terry-form.tex') 2158 params = { 2159 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle", 2160 'DOCTORSNAME': 'Ian Haywood', 2161 'DOCTORSADDRESS': '1 Smith St\nMelbourne', 2162 'PATIENTNAME':'Joe Bloggs', 2163 'PATIENTADDRESS':'18 Fred St\nMelbourne', 2164 'REQUEST':'echocardiogram', 2165 'THERAPY':'on warfarin', 2166 'CLINICALNOTES':"""heard new murmur 2167 Here's some 2168 crap to demonstrate how it can cover multiple lines.""", 2169 'COPYADDRESS':'Jack Jones\nHannover, Germany', 2170 'ROUTINE':1, 2171 'URGENT':0, 2172 'FAX':1, 2173 'PHONE':1, 2174 'PENSIONER':1, 2175 'VETERAN':0, 2176 'PADS':0, 2177 'INSTRUCTIONS':'Take the blue pill, Neo' 2178 } 2179 form = LaTeXForm (1, f.read()) 2180 form.process (params) 2181 form.xdvi () 2182 form.cleanup ()21832185 form = LaTeXForm (2, test_letter) 2186 params = {'RECIPIENTNAME':'Dr. Richard Terry', 2187 'RECIPIENTADDRESS':'1 Main St\nNewcastle', 2188 'DOCTOR':'Dr. Ian Haywood', 2189 'DOCTORADDRESS':'1 Smith St\nMelbourne', 2190 'PATIENTNAME':'Joe Bloggs', 2191 'PATIENTADDRESS':'18 Fred St, Melbourne', 2192 'TEXT':"""This is the main text of the referral letter""", 2193 'DOB':'12/3/65', 2194 'INCLUDEMEDS':1, 2195 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]], 2196 'INCLUDEDISEASES':0, 'DISEASELIST':'', 2197 'CLOSING':'Yours sincerely,' 2198 } 2199 form.process (params) 2200 print(os.getcwd()) 2201 form.xdvi() 2202 form.cleanup()2203 2204 #------------------------------------------------------------2206 template = io.open('../../test-area/ian/Formularkopf-DE.tex') 2207 form = LaTeXForm(template=template.read()) 2208 params = { 2209 'PATIENT LASTNAME': 'Kirk', 2210 'PATIENT FIRSTNAME': 'James T.', 2211 'PATIENT STREET': 'Hauptstrasse', 2212 'PATIENT ZIP': '02999', 2213 'PATIENT TOWN': 'Gross Saerchen', 2214 'PATIENT DOB': '22.03.1931' 2215 } 2216 form.process(params) 2217 form.xdvi() 2218 form.cleanup()2219 2220 #============================================================ 2221 # main 2222 #------------------------------------------------------------ 2223 if __name__ == '__main__': 2224 2225 if len(sys.argv) < 2: 2226 sys.exit() 2227 2228 if sys.argv[1] != 'test': 2229 sys.exit() 2230 2231 gmDateTime.init() 2232 2233 #-------------------------------------------------------- 2234 # OOo 2235 #--------------------------------------------------------2237 init_ooo()2238 #-------------------------------------------------------- 2243 #--------------------------------------------------------2245 srv = gmOOoConnector() 2246 doc = srv.open_document(filename = sys.argv[2]) 2247 print("document:", doc)2248 #--------------------------------------------------------2250 doc = cOOoLetter(template_file = sys.argv[2]) 2251 doc.open_in_ooo() 2252 print("document:", doc) 2253 input('press <ENTER> to continue') 2254 doc.show() 2255 #doc.replace_placeholders() 2256 #doc.save_in_ooo('~/test_cOOoLetter.odt') 2257 # doc = None 2258 # doc.close_in_ooo() 2259 input('press <ENTER> to continue')2260 #--------------------------------------------------------2262 try: 2263 doc = open_uri_in_ooo(filename=sys.argv[1]) 2264 except: 2265 _log.exception('cannot open [%s] in OOo' % sys.argv[1]) 2266 raise 2267 2268 class myCloseListener(unohelper.Base, oooXCloseListener): 2269 def disposing(self, evt): 2270 print("disposing:")2271 def notifyClosing(self, evt): 2272 print("notifyClosing:") 2273 def queryClosing(self, evt, owner): 2274 # owner is True/False whether I am the owner of the doc 2275 print("queryClosing:") 2276 2277 l = myCloseListener() 2278 doc.addCloseListener(l) 2279 2280 tfs = doc.getTextFields().createEnumeration() 2281 print(tfs) 2282 print(dir(tfs)) 2283 while tfs.hasMoreElements(): 2284 tf = tfs.nextElement() 2285 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'): 2286 print(tf.getPropertyValue('PlaceHolder')) 2287 print(" ", tf.getPropertyValue('Hint')) 2288 2289 # doc.close(True) # closes but leaves open the dedicated OOo window 2290 doc.dispose() # closes and disposes of the OOo window 2291 #--------------------------------------------------------2293 pat = gmPersonSearch.ask_for_patient() 2294 if pat is None: 2295 return 2296 gmPerson.set_active_patient(patient = pat) 2297 2298 doc = cOOoLetter(template_file = sys.argv[2]) 2299 doc.open_in_ooo() 2300 print(doc) 2301 doc.show() 2302 #doc.replace_placeholders() 2303 #doc.save_in_ooo('~/test_cOOoLetter.odt') 2304 doc = None 2305 # doc.close_in_ooo() 2306 input('press <ENTER> to continue')2307 #-------------------------------------------------------- 2308 # other 2309 #--------------------------------------------------------2311 template = cFormTemplate(aPK_obj = sys.argv[2]) 2312 print(template) 2313 print(template.save_to_file())2314 #--------------------------------------------------------2316 template = cFormTemplate(aPK_obj = sys.argv[2]) 2317 template.update_template_from_file(filename = sys.argv[3])2318 #--------------------------------------------------------2320 pat = gmPersonSearch.ask_for_patient() 2321 if pat is None: 2322 return 2323 gmPerson.set_active_patient(patient = pat) 2324 2325 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2326 2327 path = os.path.abspath(sys.argv[2]) 2328 form = cLaTeXForm(template_file = path) 2329 2330 from Gnumed.wxpython import gmMacro 2331 ph = gmMacro.gmPlaceholderHandler() 2332 ph.debug = True 2333 instance_file = form.substitute_placeholders(data_source = ph) 2334 pdf_name = form.generate_output(instance_file = instance_file) 2335 print("final PDF file is:", pdf_name)2336 #--------------------------------------------------------2338 pat = gmPersonSearch.ask_for_patient() 2339 if pat is None: 2340 return 2341 gmPerson.set_active_patient(patient = pat) 2342 2343 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2344 2345 path = os.path.abspath(sys.argv[2]) 2346 form = cPDFForm(template_file = path) 2347 2348 from Gnumed.wxpython import gmMacro 2349 ph = gmMacro.gmPlaceholderHandler() 2350 ph.debug = True 2351 instance_file = form.substitute_placeholders(data_source = ph) 2352 pdf_name = form.generate_output(instance_file = instance_file) 2353 print("final PDF file is:", pdf_name)2354 #--------------------------------------------------------2356 pat = gmPersonSearch.ask_for_patient() 2357 if pat is None: 2358 return 2359 gmPerson.set_active_patient(patient = pat) 2360 2361 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2362 2363 path = os.path.abspath(sys.argv[2]) 2364 form = cAbiWordForm(template_file = path) 2365 2366 from Gnumed.wxpython import gmMacro 2367 ph = gmMacro.gmPlaceholderHandler() 2368 ph.debug = True 2369 instance_file = form.substitute_placeholders(data_source = ph) 2370 form.edit() 2371 final_name = form.generate_output(instance_file = instance_file) 2372 print("final file is:", final_name)2373 #--------------------------------------------------------2375 2376 from Gnumed.business import gmPraxis 2377 2378 branches = gmPraxis.get_praxis_branches() 2379 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0]) 2380 print(praxis) 2381 2382 pat = gmPersonSearch.ask_for_patient() 2383 if pat is None: 2384 return 2385 gmPerson.set_active_patient(patient = pat) 2386 2387 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff()) 2388 2389 path = os.path.abspath(sys.argv[2]) 2390 form = cTextForm(template_file = path) 2391 2392 from Gnumed.wxpython import gmMacro 2393 ph = gmMacro.gmPlaceholderHandler() 2394 ph.debug = True 2395 print("placeholder substitution worked:", form.substitute_placeholders(data_source = ph)) 2396 print(form.re_editable_filenames) 2397 form.edit() 2398 form.generate_output()2399 #-------------------------------------------------------- 2400 #-------------------------------------------------------- 2401 #-------------------------------------------------------- 2402 # now run the tests 2403 #test_au() 2404 #test_de() 2405 2406 # OOo 2407 #test_init_ooo() 2408 #test_ooo_connect() 2409 #test_open_ooo_doc_from_srv() 2410 #test_open_ooo_doc_from_letter() 2411 #play_with_ooo() 2412 #test_cOOoLetter() 2413 2414 #test_cFormTemplate() 2415 #set_template_from_file() 2416 test_latex_form() 2417 #test_pdf_form() 2418 #test_abiword_form() 2419 #test_text_form() 2420 2421 #============================================================ 2422
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Fri Sep 13 01:55:28 2019 | http://epydoc.sourceforge.net |