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