Gnumed.business.gmForms

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

# Source Code for Module Gnumed.business.gmForms

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