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