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

Source Code for Module Gnumed.business.gmForms

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