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