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