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

# Source Code for Module Gnumed.business.gmForms

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