Gnumed.business.gmForms

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