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

# Source Code for Module Gnumed.business.gmForms

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