Package Gnumed :: Package wxpython :: Module gmMacro |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmMacro

       1  #  coding: utf8
       2  """GNUmed macro primitives.
       3
       4  This module implements functions a macro can legally use.
       5  """
       6  #=====================================================================
       7  __version__ = "$Revision: 1.51 $"
       8  __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
       9
      10  import sys, time, random, types, logging
      11
      12
      13  import wx
      14
      15
      16  if __name__ == '__main__':
      17          sys.path.insert(0, '../../')
      18  from Gnumed.pycommon import gmI18N
      19  if __name__ == '__main__':
      20          gmI18N.activate_locale()
      21          gmI18N.install_domain()
      22  from Gnumed.pycommon import gmGuiBroker
      23  from Gnumed.pycommon import gmTools
      24  from Gnumed.pycommon import gmBorg
      25  from Gnumed.pycommon import gmExceptions
      26  from Gnumed.pycommon import gmCfg2
      27  from Gnumed.pycommon import gmDateTime
      28
      29  from Gnumed.business import gmPerson
      30  from Gnumed.business import gmStaff
      31  from Gnumed.business import gmDemographicRecord
      32  from Gnumed.business import gmMedication
      33  from Gnumed.business import gmPathLab
      34  from Gnumed.business import gmPersonSearch
      35  from Gnumed.business import gmVaccination
      36  from Gnumed.business import gmPersonSearch
      37
      38  from Gnumed.wxpython import gmGuiHelpers
      39  from Gnumed.wxpython import gmNarrativeWidgets
      40  from Gnumed.wxpython import gmPatSearchWidgets
      41  from Gnumed.wxpython import gmPlugin
      42  from Gnumed.wxpython import gmEMRStructWidgets
      43
      44
      45  _log = logging.getLogger('gm.scripting')
      46  _cfg = gmCfg2.gmCfgData()
      47
      48  #=====================================================================
      49  known_placeholders = [
      50          'lastname',
      51          'firstname',
      52          'title',
      53          'date_of_birth',
      54          'progress_notes',
      55          'soap',
      56          'soap_s',
      57          'soap_o',
      58          'soap_a',
      59          'soap_p',
      60          'soap_u',
      61          u'client_version',
      62          u'current_provider',
      63          u'primary_praxis_provider',                     # primary provider for current patient in this praxis
      64          u'allergy_state'
      65  ]
      66
      67
      68  # values for those placeholders must be injected from the outside before using them,
      69  # in use they must conform to the "placeholder::::length" syntax,
      70  # as long as they resolve to None they return themselves
      71  _injectable_placeholders = {
      72          u'form_name_long': None,
      73          u'form_name_short': None,
      74          u'form_version': None
      75  }
      76
      77
      78  # those must satisfy the pattern "$<name::args::(optional) max string length>$" when used
      79  known_variant_placeholders = [
      80          u'soap',
      81          u'progress_notes',                      # "args" holds: categories//template
      82                                                                  #       categories: string with 'soapu '; ' ' == None == admin
      83                                                                  #       template:       u'something %s something'               (do not include // in template !)
      84          u'emr_journal',                         # "args" format:   <categories>//<template>//<line length>//<time range>//<target format>
      85                                                                  #       categories:        string with any of "s", "o", "a", "p", "u", " ";
      86                                                                  #                                  (" " == None == admin category)
      87                                                                  #       template:          something %s something else
      88                                                                  #                                  (Do not include // in the template !)
      89                                                                  #       line length:   the length of individual lines, not the total placeholder length
      90                                                                  #       time range:        the number of weeks going back in time
      91                                                                  #       target format: "tex" or anything else, if "tex", data will be tex-escaped
      92          u'date_of_birth',
      93
      94          u'patient_address',                     # "args": <type of address>//<optional formatting template>
      95          u'adr_street',                          # "args" holds: type of address
      96          u'adr_number',
      97          u'adr_location',
      98          u'adr_postcode',
      99          u'adr_region',
     100          u'adr_country',
     101
     102          u'patient_comm',                                                # args: comm channel type as per database
     103          u'external_id',                                                 # args: <type of ID>//<issuer of ID>
     104          u'gender_mapper',                                               # "args" holds: <value when person is male> // <is female> // <is other>
     105                                                                                          #                               eg. "male//female//other"
     106                                                                                          #                               or: "Lieber Patient//Liebe Patientin"
     107          u'current_meds',                                                # "args" holds: line template
     108          u'current_meds_table',                                  # "args" holds: format, options
     109                                                                                          #                               currently only "latex"
     110          u'current_meds_notes',                                  # "args" holds: format, options
     111          u'lab_table',                                                   # "args" holds: format (currently "latex" only)
     112          u'latest_vaccs_table',                                  # "args" holds: format, options
     113          u'today',                                                               # "args" holds: strftime format
     114          u'tex_escape',                                                  # "args" holds: string to escape
     115          u'allergies',                                                   # "args" holds: line template, one allergy per line
     116          u'allergy_list',                                                # "args" holds: template per allergy, allergies on one line
     117          u'problems',                                                    # "args" holds: line template, one problem per line
     118          u'name',                                                                # "args" holds: template for name parts arrangement
     119          u'free_text',                                                   # show a dialog for entering some free text
     120          u'soap_for_encounters',                                 # "args" holds: soap cats // strftime date format
     121          u'encounter_list',                                              # "args" holds: per-encounter template, each ends up on one line
     122          u'current_provider_external_id',                # args: <type of ID>//<issuer of ID>
     123          u'primary_praxis_provider_external_id'  # args: <type of ID>//<issuer of ID>
     124  ]
     125
     126  default_placeholder_regex = r'\$<.+?>\$'                                # this one works (except that OOo cannot be non-greedy |-( )
     127
     128  #_regex_parts = [
     129  #       r'\$<\w+::.*(?::)\d+>\$',
     130  #       r'\$<\w+::.+(?!>\$)>\$',
     131  #       r'\$<\w+?>\$'
     132  #]
     133  #default_placeholder_regex = r'|'.join(_regex_parts)
     134
     135  default_placeholder_start = u'$<'
     136  default_placeholder_end = u'>$'
     137  #=====================================================================
     138 -class gmPlaceholderHandler(gmBorg.cBorg):
     139          """Returns values for placeholders.
     140
     141          - patient related placeholders operate on the currently active patient
     142          - is passed to the forms handling code, for example
     143
     144          Return values when .debug is False:
     145          - errors with placeholders return None
     146          - placeholders failing to resolve to a value return an empty string
     147
     148          Return values when .debug is True:
     149          - errors with placeholders return an error string
     150          - placeholders failing to resolve to a value return a warning string
     151
     152          There are several types of placeholders:
     153
     154          simple static placeholders
     155                  - those are listed in known_placeholders
     156                  - they are used as-is
     157
     158          extended static placeholders
     159                  - those are, effectively, static placeholders
     160                    with a maximum length attached (after "::::")
     161
     162          injectable placeholders
     163                  - they must be set up before use by set_placeholder()
     164                  - they should be removed after use by unset_placeholder()
     165                  - the syntax is like extended static placeholders
     166                  - they are listed in _injectable_placeholders
     167
     168          variant placeholders
     169                  - those are listed in known_variant_placeholders
     170                  - they are parsed into placeholder, data, and maximum length
     171                  - the length is optional
     172                  - data is passed to the handler
     173
     174          Note that this cannot be called from a non-gui thread unless
     175          wrapped in wx.CallAfter().
     176          """
     177 -        def __init__(self, *args, **kwargs):
     178
     179                  self.pat = gmPerson.gmCurrentPatient()
     180                  self.debug = False
     181
     182                  self.invalid_placeholder_template = _('invalid placeholder [%s]')
     183          #--------------------------------------------------------
     184          # external API
     185          #--------------------------------------------------------
     186 -        def set_placeholder(self, key=None, value=None):
     187                  _injectable_placeholders[key]
     188                  _injectable_placeholders[key] = value
     189          #--------------------------------------------------------
     190 -        def unset_placeholder(self, key=None):
     191                  _injectable_placeholders[key]
     192                  _injectable_placeholders[key] = None
     193          #--------------------------------------------------------
     194          # __getitem__ API
     195          #--------------------------------------------------------
     196 -        def __getitem__(self, placeholder):
     197                  """Map self['placeholder'] to self.placeholder.
     198
     199                  This is useful for replacing placeholders parsed out
     200                  of documents as strings.
     201
     202                  Unknown/invalid placeholders still deliver a result but
     203                  it will be glaringly obvious if debugging is enabled.
     204                  """
     205                  _log.debug('replacing [%s]', placeholder)
     206
     207                  original_placeholder = placeholder
     208
     209                  if placeholder.startswith(default_placeholder_start):
     210                          placeholder = placeholder[len(default_placeholder_start):]
     211                          if placeholder.endswith(default_placeholder_end):
     212                                  placeholder = placeholder[:-len(default_placeholder_end)]
     213                          else:
     214                                  _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
     215                                  if self.debug:
     216                                          return self.invalid_placeholder_template % original_placeholder
     217                                  return None
     218
     219                  # simple static placeholder ?
     220                  if placeholder in known_placeholders:
     221                          return getattr(self, placeholder)
     222
     223                  # injectable placeholder ?
     224                  parts = placeholder.split('::::', 1)
     225                  if len(parts) == 2:
     226                          name, lng = parts
     227                          unknown_injectable = False
     228                          try:
     229                                  val = _injectable_placeholders[name]
     230                          except KeyError:
     231                                  unknown_injectable = True
     232                          except:
     233                                  _log.exception('placeholder handling error: %s', original_placeholder)
     234                                  if self.debug:
     235                                          return self.invalid_placeholder_template % original_placeholder
     236                                  return None
     237                          if not unknown_injectable:
     238                                  if val is None:
     239                                          if self.debug:
     240                                                  return u'injectable placeholder [%s]: no value available' % name
     241                                          return placeholder
     242                                  return val[:int(lng)]
     243
     244                  # extended static placeholder ?
     245                  parts = placeholder.split('::::', 1)
     246                  if len(parts) == 2:
     247                          name, lng = parts
     248                          try:
     249                                  return getattr(self, name)[:int(lng)]
     250                          except:
     251                                  _log.exception('placeholder handling error: %s', original_placeholder)
     252                                  if self.debug:
     253                                          return self.invalid_placeholder_template % original_placeholder
     254                                  return None
     255
     256                  # variable placeholders
     257                  parts = placeholder.split('::')
     258                  if len(parts) == 2:
     259                          name, data = parts
     260                          lng = None
     261                  if len(parts) == 3:
     262                          name, data, lng = parts
     263                          try:
     264                                  lng = int(lng)
     265                          except (TypeError, ValueError):
     266                                  _log.error('placeholder length definition error: %s, discarding length: >%s<', original_placeholder, lng)
     267                                  lng = None
     268                  if len(parts) > 3:
     269                          _log.warning('invalid placeholder layout: %s', original_placeholder)
     270                          if self.debug:
     271                                  return self.invalid_placeholder_template % original_placeholder
     272                          return None
     273
     274                  handler = getattr(self, '_get_variant_%s' % name, None)
     275                  if handler is None:
     276                          _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
     277                          if self.debug:
     278                                  return self.invalid_placeholder_template % original_placeholder
     279                          return None
     280
     281                  try:
     282                          if lng is None:
     283                                  return handler(data = data)
     284                          return handler(data = data)[:lng]
     285                  except:
     286                          _log.exception('placeholder handling error: %s', original_placeholder)
     287                          if self.debug:
     288                                  return self.invalid_placeholder_template % original_placeholder
     289                          return None
     290
     291                  _log.error('something went wrong, should never get here')
     292                  return None
     293          #--------------------------------------------------------
     294          # properties actually handling placeholders
     295          #--------------------------------------------------------
     296          # property helpers
     297          #--------------------------------------------------------
     298 -        def _setter_noop(self, val):
     299                  """This does nothing, used as a NOOP properties setter."""
     300                  pass
     301          #--------------------------------------------------------
     302 -        def _get_lastname(self):
     303                  return self.pat.get_active_name()['lastnames']
     304          #--------------------------------------------------------
     305 -        def _get_firstname(self):
     306                  return self.pat.get_active_name()['firstnames']
     307          #--------------------------------------------------------
     308 -        def _get_title(self):
     309                  return gmTools.coalesce(self.pat.get_active_name()['title'], u'')
     310          #--------------------------------------------------------
     311 -        def _get_dob(self):
     312                  return self._get_variant_date_of_birth(data='%x')
     313          #--------------------------------------------------------
     314 -        def _get_progress_notes(self):
     315                  return self._get_variant_soap()
     316          #--------------------------------------------------------
     317 -        def _get_soap_s(self):
     318                  return self._get_variant_soap(data = u's')
     319          #--------------------------------------------------------
     320 -        def _get_soap_o(self):
     321                  return self._get_variant_soap(data = u'o')
     322          #--------------------------------------------------------
     323 -        def _get_soap_a(self):
     324                  return self._get_variant_soap(data = u'a')
     325          #--------------------------------------------------------
     326 -        def _get_soap_p(self):
     327                  return self._get_variant_soap(data = u'p')
     328          #--------------------------------------------------------
     329 -        def _get_soap_u(self):
     330                  return self._get_variant_soap(data = u'u')
     331          #--------------------------------------------------------
     332 -        def _get_soap_admin(self):
     333                  return self._get_variant_soap(soap_cats = None)
     334          #--------------------------------------------------------
     335 -        def _get_client_version(self):
     336                  return gmTools.coalesce (
     337                          _cfg.get(option = u'client_version'),
     338                          u'%s' % self.__class__.__name__
     339                  )
     340          #--------------------------------------------------------
     341 -        def _get_primary_praxis_provider(self):
     342                  prov = self.pat.primary_provider
     343                  if prov is None:
     344                          return self._get_current_provider()
     345
     346                  title = gmTools.coalesce (
     347                          prov['title'],
     348                          gmPerson.map_gender2salutation(prov['gender'])
     349                  )
     350
     351                  tmp = u'%s %s. %s' % (
     352                          title,
     353                          prov['firstnames'][:1],
     354                          prov['lastnames']
     355                  )
     356
     357                  return tmp
     358          #--------------------------------------------------------
     359 -        def _get_current_provider(self):
     360                  prov = gmStaff.gmCurrentProvider()
     361
     362                  title = gmTools.coalesce (
     363                          prov['title'],
     364                          gmPerson.map_gender2salutation(prov['gender'])
     365                  )
     366
     367                  tmp = u'%s %s. %s' % (
     368                          title,
     369                          prov['firstnames'][:1],
     370                          prov['lastnames']
     371                  )
     372
     373                  return tmp
     374          #--------------------------------------------------------
     375 -        def _get_allergy_state(self):
     376                  allg_state = self.pat.get_emr().allergy_state
     377
     378                  if allg_state['last_confirmed'] is None:
     379                          date_confirmed = u''
     380                  else:
     381                          date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
     382
     383                  tmp = u'%s%s' % (
     384                          allg_state.state_string,
     385                          date_confirmed
     386                  )
     387                  return tmp
     388          #--------------------------------------------------------
     389          # property definitions for static placeholders
     390          #--------------------------------------------------------
     391          placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
     392
     393          #--------------------------------------------------------
     394
     395          # placeholders
     396          lastname = property(_get_lastname, _setter_noop)
     397          firstname = property(_get_firstname, _setter_noop)
     398          title = property(_get_title, _setter_noop)
     399          date_of_birth = property(_get_dob, _setter_noop)
     400
     401          progress_notes = property(_get_progress_notes, _setter_noop)
     402          soap = property(_get_progress_notes, _setter_noop)
     403          soap_s = property(_get_soap_s, _setter_noop)
     404          soap_o = property(_get_soap_o, _setter_noop)
     405          soap_a = property(_get_soap_a, _setter_noop)
     406          soap_p = property(_get_soap_p, _setter_noop)
     407          soap_u = property(_get_soap_u, _setter_noop)
     408          soap_admin = property(_get_soap_admin, _setter_noop)
     409
     410          allergy_state = property(_get_allergy_state, _setter_noop)
     411
     412          client_version = property(_get_client_version, _setter_noop)
     413
     414          current_provider = property(_get_current_provider, _setter_noop)
     415          primary_praxis_provider = property(_get_primary_praxis_provider, _setter_noop)
     416          #--------------------------------------------------------
     417          # variant handlers
     418          #--------------------------------------------------------
     419 -        def _get_variant_encounter_list(self, data=None):
     420
     421                  encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
     422                  if not encounters:
     423                          return u''
     424
     425                  template = data
     426
     427                  lines = []
     428                  for enc in encounters:
     429                          try:
     430                                  lines.append(template % enc)
     431                          except:
     432                                  lines.append(u'error formatting encounter')
     433                                  _log.exception('problem formatting encounter list')
     434                                  _log.error('template: %s', template)
     435                                  _log.error('encounter: %s', encounter)
     436
     437                  return u'\n'.join(lines)
     438          #--------------------------------------------------------
     439 -        def _get_variant_soap_for_encounters(self, data=None):
     440                  """Select encounters from list and format SOAP thereof.
     441
     442                  data: soap_cats (' ' -> None -> admin) // date format
     443                  """
     444                  # defaults
     445                  cats = None
     446                  date_format = None
     447
     448                  if data is not None:
     449                          data_parts = data.split('//')
     450
     451                          # part[0]: categories
     452                          if len(data_parts[0]) > 0:
     453                                  cats = []
     454                                  if u' ' in data_parts[0]:
     455                                          cats.append(None)
     456                                          data_parts[0] = data_parts[0].replace(u' ', u'')
     457                                  cats.extend(list(data_parts[0]))
     458
     459                          # part[1]: date format
     460                          if len(data_parts) > 1:
     461                                  if len(data_parts[1]) > 0:
     462                                          date_format = data_parts[1]
     463
     464                  encounters = gmEMRStructWidgets.select_encounters(single_selection = False)
     465                  if not encounters:
     466                          return u''
     467
     468                  chunks = []
     469                  for enc in encounters:
     470                          chunks.append(enc.format_latex (
     471                                  date_format = date_format,
     472                                  soap_cats = cats,
     473                                  soap_order = u'soap_rank, date'
     474                          ))
     475
     476                  return u''.join(chunks)
     477          #--------------------------------------------------------
     478 -        def _get_variant_emr_journal(self, data=None):
     479                  # default: all categories, neutral template
     480                  cats = list(u'soapu')
     481                  cats.append(None)
     482                  template = u'%s'
     483                  interactive = True
     484                  line_length = 9999
     485                  target_format = None
     486                  time_range = None
     487
     488                  if data is not None:
     489                          data_parts = data.split('//')
     490
     491                          # part[0]: categories
     492                          cats = []
     493                          # ' ' -> None == admin
     494                          for c in list(data_parts[0]):
     495                                  if c == u' ':
     496                                          c = None
     497                                  cats.append(c)
     498                          # '' -> SOAP + None
     499                          if cats == u'':
     500                                  cats = list(u'soapu').append(None)
     501
     502                          # part[1]: template
     503                          if len(data_parts) > 1:
     504                                  template = data_parts[1]
     505
     506                          # part[2]: line length
     507                          if len(data_parts) > 2:
     508                                  try:
     509                                          line_length = int(data_parts[2])
     510                                  except:
     511                                          line_length = 9999
     512
     513                          # part[3]: weeks going back in time
     514                          if len(data_parts) > 3:
     515                                  try:
     516                                          time_range = 7 * int(data_parts[3])
     517                                  except:
     518                                          time_range = None
     519
     520                          # part[4]: output format
     521                          if len(data_parts) > 4:
     522                                  target_format = data_parts[4]
     523
     524                  # FIXME: will need to be a generator later on
     525                  narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
     526
     527                  if len(narr) == 0:
     528                          return u''
     529
     530                  if target_format == u'tex':
     531                          keys = narr[0].keys()
     532                          lines = []
     533                          line_dict = {}
     534                          for n in narr:
     535                                  for key in keys:
     536                                          if isinstance(n[key], basestring):
     537                                                  line_dict[key] = gmTools.tex_escape_string(text = n[key])
     538                                                  continue
     539                                          line_dict[key] = n[key]
     540                                  try:
     541                                          lines.append((template % line_dict)[:line_length])
     542                                  except KeyError:
     543                                          return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
     544                  else:
     545                          try:
     546                                  lines = [ (template % n)[:line_length] for n in narr ]
     547                          except KeyError:
     548                                  return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
     549
     550                  return u'\n'.join(lines)
     551          #--------------------------------------------------------
     552 -        def _get_variant_progress_notes(self, data=None):
     553                  return self._get_variant_soap(data=data)
     554          #--------------------------------------------------------
     555 -        def _get_variant_soap(self, data=None):
     556
     557                  # default: all categories, neutral template
     558                  cats = list(u'soapu')
     559                  cats.append(None)
     560                  template = u'%s'
     561
     562                  if data is not None:
     563                          data_parts = data.split('//')
     564
     565                          # part[0]: categories
     566                          cats = []
     567                          # ' ' -> None == admin
     568                          for cat in list(data_parts[0]):
     569                                  if cat == u' ':
     570                                          cat = None
     571                                  cats.append(cat)
     572                          # '' -> SOAP + None
     573                          if cats == u'':
     574                                  cats = list(u'soapu')
     575                                  cats.append(None)
     576
     577                          # part[1]: template
     578                          if len(data_parts) > 1:
     579                                  template = data_parts[1]
     580
     581                  #narr = gmNarrativeWidgets.select_narrative_from_episodes_new(soap_cats = cats)
     582                  narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
     583
     584                  if narr is None:
     585                          return u''
     586
     587                  if len(narr) == 0:
     588                          return u''
     589
     590                  try:
     591                          narr = [ template % n['narrative'] for n in narr ]
     592                  except KeyError:
     593                          return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
     594
     595                  return u'\n'.join(narr)
     596          #--------------------------------------------------------
     597 -        def _get_variant_name(self, data=None):
     598                  if data is None:
     599                          return [_('template is missing')]
     600
     601                  name = self.pat.get_active_name()
     602
     603                  parts = {
     604                          'title': gmTools.coalesce(name['title'], u''),
     605                          'firstnames': name['firstnames'],
     606                          'lastnames': name['lastnames'],
     607                          'preferred': gmTools.coalesce (
     608                                  initial = name['preferred'],
     609                                  instead = u' ',
     610                                  template_initial = u' "%s" '
     611                          )
     612                  }
     613
     614                  return data % parts
     615          #--------------------------------------------------------
     616 -        def _get_variant_date_of_birth(self, data='%x'):
     617                  return self.pat.get_formatted_dob(format = str(data), encoding = gmI18N.get_encoding())
     618          #--------------------------------------------------------
     619          # FIXME: extend to all supported genders
     620 -        def _get_variant_gender_mapper(self, data='male//female//other'):
     621                  values = data.split('//', 2)
     622
     623                  if len(values) == 2:
     624                          male_value, female_value = values
     625                          other_value = u'<unkown gender>'
     626                  elif len(values) == 3:
     627                          male_value, female_value, other_value = values
     628                  else:
     629                          return _('invalid gender mapping layout: [%s]') % data
     630
     631                  if self.pat['gender'] == u'm':
     632                          return male_value
     633
     634                  if self.pat['gender'] == u'f':
     635                          return female_value
     636
     637                  return other_value
     638          #--------------------------------------------------------
     639          # address related placeholders
     640          #--------------------------------------------------------
     641 -        def _get_variant_patient_address(self, data=u''):
     642
     643                  data_parts = data.split(u'//')
     644
     645                  if data_parts[0].strip() == u'':
     646                          adr_type = u'home'
     647                  else:
     648                          adr_type = data_parts[0]
     649
     650                  template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_state)s, %(l10n_country)s')
     651                  if len(data_parts) > 1:
     652                          if data_parts[1].strip() != u'':
     653                                  template = data_parts[1]
     654
     655                  adrs = self.pat.get_addresses(address_type = adr_type)
     656                  if len(adrs) == 0:
     657                          if self.debug:
     658                                  return _('no address for type [%s]') % adr_type
     659                          return u''
     660
     661                  adr = adrs[0]
     662                  data = {
     663                          'street': adr['street'],
     664                          'notes_street': gmTools.coalesce(adr['notes_street'], u''),
     665                          'postcode': adr['postcode'],
     666                          'number': adr['number'],
     667                          'subunit': gmTools.coalesce(adr['subunit'], u''),
     668                          'notes_subunit': gmTools.coalesce(adr['notes_subunit'], u''),
     669                          'urb': adr['urb'],
     670                          'suburb': gmTools.coalesce(adr['suburb'], u''),
     671                          'l10n_state': adr['l10n_state'],
     672                          'l10n_country': adr['l10n_country'],
     673                          'code_state': adr['code_state'],
     674                          'code_country': adr['code_country']
     675                  }
     676
     677                  try:
     678                          return template % data
     679                  except StandardError:
     680                          _log.exception('error formatting address')
     681                          _log.error('template: %s', template)
     682
     683                  return None
     684          #--------------------------------------------------------
     685 -        def _get_variant_adr_street(self, data=u'?'):
     686                  adrs = self.pat.get_addresses(address_type=data)
     687                  if len(adrs) == 0:
     688                          if self.debug:
     689                                  return _('no street for address type [%s]') % data
     690                          return u''
     691                  return adrs[0]['street']
     692          #--------------------------------------------------------
     693 -        def _get_variant_adr_number(self, data=u'?'):
     694                  adrs = self.pat.get_addresses(address_type=data)
     695                  if len(adrs) == 0:
     696                          if self.debug:
     697                                  return _('no number for address type [%s]') % data
     698                          return u''
     699                  return adrs[0]['number']
     700          #--------------------------------------------------------
     701 -        def _get_variant_adr_location(self, data=u'?'):
     702                  adrs = self.pat.get_addresses(address_type=data)
     703                  if len(adrs) == 0:
     704                          if self.debug:
     705                                  return _('no location for address type [%s]') % data
     706                          return u''
     707                  return adrs[0]['urb']
     708          #--------------------------------------------------------
     709 -        def _get_variant_adr_postcode(self, data=u'?'):
     710                  adrs = self.pat.get_addresses(address_type = data)
     711                  if len(adrs) == 0:
     712                          if self.debug:
     713                                  return _('no postcode for address type [%s]') % data
     714                          return u''
     715                  return adrs[0]['postcode']
     716          #--------------------------------------------------------
     717 -        def _get_variant_adr_region(self, data=u'?'):
     718                  adrs = self.pat.get_addresses(address_type = data)
     719                  if len(adrs) == 0:
     720                          if self.debug:
     721                                  return _('no region for address type [%s]') % data
     722                          return u''
     723                  return adrs[0]['l10n_state']
     724          #--------------------------------------------------------
     725 -        def _get_variant_adr_country(self, data=u'?'):
     726                  adrs = self.pat.get_addresses(address_type = data)
     727                  if len(adrs) == 0:
     728                          if self.debug:
     729                                  return _('no country for address type [%s]') % data
     730                          return u''
     731                  return adrs[0]['l10n_country']
     732          #--------------------------------------------------------
     733 -        def _get_variant_patient_comm(self, data=u'?'):
     734                  comms = self.pat.get_comm_channels(comm_medium = data)
     735                  if len(comms) == 0:
     736                          if self.debug:
     737                                  return _('no URL for comm channel [%s]') % data
     738                          return u''
     739                  return comms[0]['url']
     740          #--------------------------------------------------------
     741 -        def _get_variant_current_provider_external_id(self, data=u''):
     742                  data_parts = data.split(u'//')
     743                  if len(data_parts) < 2:
     744                          return u'current provider external ID: template is missing'
     745
     746                  id_type = data_parts[0].strip()
     747                  if id_type == u'':
     748                          return u'current provider external ID: type is missing'
     749
     750                  issuer = data_parts[1].strip()
     751                  if issuer == u'':
     752                          return u'current provider external ID: issuer is missing'
     753
     754                  prov = gmStaff.gmCurrentProvider()
     755                  ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
     756
     757                  if len(ids) == 0:
     758                          if self.debug:
     759                                  return _('no external ID [%s] by [%s]') % (id_type, issuer)
     760                          return u''
     761
     762                  return ids[0]['value']
     763          #--------------------------------------------------------
     764 -        def _get_variant_primary_praxis_provider_external_id(self, data=u''):
     765                  data_parts = data.split(u'//')
     766                  if len(data_parts) < 2:
     767                          return u'primary in-praxis provider external ID: template is missing'
     768
     769                  id_type = data_parts[0].strip()
     770                  if id_type == u'':
     771                          return u'primary in-praxis provider external ID: type is missing'
     772
     773                  issuer = data_parts[1].strip()
     774                  if issuer == u'':
     775                          return u'primary in-praxis provider external ID: issuer is missing'
     776
     777                  prov = self.pat.primary_provider
     778                  if prov is None:
     779                          if self.debug:
     780                                  return _('no primary in-praxis provider')
     781                          return u''
     782
     783                  ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
     784
     785                  if len(ids) == 0:
     786                          if self.debug:
     787                                  return _('no external ID [%s] by [%s]') % (id_type, issuer)
     788                          return u''
     789
     790                  return ids[0]['value']
     791          #--------------------------------------------------------
     792 -        def _get_variant_external_id(self, data=u''):
     793                  data_parts = data.split(u'//')
     794                  if len(data_parts) < 2:
     795                          return u'patient external ID: template is missing'
     796
     797                  id_type = data_parts[0].strip()
     798                  if id_type == u'':
     799                          return u'patient external ID: type is missing'
     800
     801                  issuer = data_parts[1].strip()
     802                  if issuer == u'':
     803                          return u'patient external ID: issuer is missing'
     804
     805                  ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
     806
     807                  if len(ids) == 0:
     808                          if self.debug:
     809                                  return _('no external ID [%s] by [%s]') % (id_type, issuer)
     810                          return u''
     811
     812                  return ids[0]['value']
     813          #--------------------------------------------------------
     814 -        def _get_variant_allergy_list(self, data=None):
     815                  if data is None:
     816                          return [_('template is missing')]
     817
     818                  template, separator = data.split('//', 2)
     819
     820                  emr = self.pat.get_emr()
     821                  return separator.join([ template % a for a in emr.get_allergies() ])
     822          #--------------------------------------------------------
     823 -        def _get_variant_allergies(self, data=None):
     824
     825                  if data is None:
     826                          return [_('template is missing')]
     827
     828                  emr = self.pat.get_emr()
     829                  return u'\n'.join([ data % a for a in emr.get_allergies() ])
     830          #--------------------------------------------------------
     831 -        def _get_variant_current_meds(self, data=None):
     832
     833                  if data is None:
     834                          return [_('template is missing')]
     835
     836                  emr = self.pat.get_emr()
     837                  current_meds = emr.get_current_substance_intake (
     838                          include_inactive = False,
     839                          include_unapproved = False,
     840                          order_by = u'brand, substance'
     841                  )
     842
     843                  return u'\n'.join([ data % m.fields_as_dict(date_format = '%Y %B %d') for m in current_meds ])
     844          #--------------------------------------------------------
     845 -        def _get_variant_current_meds_table(self, data=None):
     846
     847                  options = data.split('//')
     848
     849                  if u'latex' in options:
     850                          return gmMedication.format_substance_intake (
     851                                  emr = self.pat.get_emr(),
     852                                  output_format = u'latex',
     853                                  table_type = u'by-brand'
     854                          )
     855
     856                  _log.error('no known current medications table formatting style in [%s]', data)
     857                  return _('unknown current medication table formatting style')
     858          #--------------------------------------------------------
     859 -        def _get_variant_current_meds_notes(self, data=None):
     860
     861                  options = data.split('//')
     862
     863                  if u'latex' in options:
     864                          return gmMedication.format_substance_intake_notes (
     865                                  emr = self.pat.get_emr(),
     866                                  output_format = u'latex',
     867                                  table_type = u'by-brand'
     868                          )
     869
     870                  _log.error('no known current medications notes formatting style in [%s]', data)
     871                  return _('unknown current medication notes formatting style')
     872          #--------------------------------------------------------
     873 -        def _get_variant_lab_table(self, data=None):
     874
     875                  options = data.split('//')
     876
     877                  emr = self.pat.get_emr()
     878
     879                  if u'latex' in options:
     880                          return gmPathLab.format_test_results (
     881                                  results = emr.get_test_results_by_date(),
     882                                  output_format = u'latex'
     883                          )
     884
     885                  _log.error('no known test results table formatting style in [%s]', data)
     886                  return _('unknown test results table formatting style [%s]') % data
     887          #--------------------------------------------------------
     888 -        def _get_variant_latest_vaccs_table(self, data=None):
     889
     890                  options = data.split('//')
     891
     892                  emr = self.pat.get_emr()
     893
     894                  if u'latex' in options:
     895                          return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr)
     896
     897                  _log.error('no known vaccinations table formatting style in [%s]', data)
     898                  return _('unknown vaccinations table formatting style [%s]') % data
     899          #--------------------------------------------------------
     900 -        def _get_variant_problems(self, data=None):
     901
     902                  if data is None:
     903                          return [_('template is missing')]
     904
     905                  probs = self.pat.get_emr().get_problems()
     906
     907                  return u'\n'.join([ data % p for p in probs ])
     908          #--------------------------------------------------------
     909 -        def _get_variant_today(self, data='%x'):
     910                  return gmDateTime.pydt_now_here().strftime(str(data)).decode(gmI18N.get_encoding())
     911          #--------------------------------------------------------
     912 -        def _get_variant_tex_escape(self, data=None):
     913                  return gmTools.tex_escape_string(text = data)
     914          #--------------------------------------------------------
     915 -        def _get_variant_free_text(self, data=u'tex//'):
     916                  # <data>:
     917                  #       format: tex (only, currently)
     918                  #       message: shown in input dialog, must not contain "//" or "::"
     919
     920                  data_parts = data.split('//')
     921                  format = data_parts[0]
     922                  if len(data_parts) > 1:
     923                          msg = data_parts[1]
     924                  else:
     925                          msg = _('generic text')
     926
     927                  dlg = gmGuiHelpers.cMultilineTextEntryDlg (
     928                          None,
     929                          -1,
     930                          title = _('Replacing <free_text> placeholder'),
     931                          msg = _('Below you can enter free text.\n\n [%s]') % msg
     932                  )
     933                  dlg.enable_user_formatting = True
     934                  decision = dlg.ShowModal()
     935
     936                  if decision != wx.ID_SAVE:
     937                          dlg.Destroy()
     938                          if self.debug:
     939                                  return _('Text input cancelled by user.')
     940                          return u''
     941
     942                  text = dlg.value.strip()
     943                  if dlg.is_user_formatted:
     944                          dlg.Destroy()
     945                          return text
     946
     947                  dlg.Destroy()
     948
     949                  if format == u'tex':
     950                          return gmTools.tex_escape_string(text = text)
     951
     952                  return text
     953          #--------------------------------------------------------
     954          # internal helpers
     955          #--------------------------------------------------------
     956
     957  #=====================================================================
     958 -class cMacroPrimitives:
     959          """Functions a macro can legally use.
     960
     961          An instance of this class is passed to the GNUmed scripting
     962          listener. Hence, all actions a macro can legally take must
     963          be defined in this class. Thus we achieve some screening for
     964          security and also thread safety handling.
     965          """
     966          #-----------------------------------------------------------------
     967 -        def __init__(self, personality = None):
     968                  if personality is None:
     969                          raise gmExceptions.ConstructorError, 'must specify personality'
     970                  self.__personality = personality
     971                  self.__attached = 0
     972                  self._get_source_personality = None
     973                  self.__user_done = False
     974                  self.__user_answer = 'no answer yet'
     975                  self.__pat = gmPerson.gmCurrentPatient()
     976
     977                  self.__auth_cookie = str(random.random())
     978                  self.__pat_lock_cookie = str(random.random())
     979                  self.__lock_after_load_cookie = str(random.random())
     980
     981                  _log.info('slave mode personality is [%s]', personality)
     982          #-----------------------------------------------------------------
     983          # public API
     984          #-----------------------------------------------------------------
     985 -        def attach(self, personality = None):
     986                  if self.__attached:
     987                          _log.error('attach with [%s] rejected, already serving a client', personality)
     988                          return (0, _('attach rejected, already serving a client'))
     989                  if personality != self.__personality:
     990                          _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
     991                          return (0, _('attach to personality [%s] rejected') % personality)
     992                  self.__attached = 1
     993                  self.__auth_cookie = str(random.random())
     994                  return (1, self.__auth_cookie)
     995          #-----------------------------------------------------------------
     996 -        def detach(self, auth_cookie=None):
     997                  if not self.__attached:
     998                          return 1
     999                  if auth_cookie != self.__auth_cookie:
    1000                          _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
    1001                          return 0
    1002                  self.__attached = 0
    1003                  return 1
    1004          #-----------------------------------------------------------------
    1005 -        def force_detach(self):
    1006                  if not self.__attached:
    1007                          return 1
    1008                  self.__user_done = False
    1009                  # FIXME: use self.__sync_cookie for syncing with user interaction
    1010                  wx.CallAfter(self._force_detach)
    1011                  return 1
    1012          #-----------------------------------------------------------------
    1013 -        def version(self):
    1014                  ver = _cfg.get(option = u'client_version')
    1015                  return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
    1016          #-----------------------------------------------------------------
    1017 -        def shutdown_gnumed(self, auth_cookie=None, forced=False):
    1018                  """Shuts down this client instance."""
    1019                  if not self.__attached:
    1020                          return 0
    1021                  if auth_cookie != self.__auth_cookie:
    1022                          _log.error('non-authenticated shutdown_gnumed()')
    1023                          return 0
    1024                  wx.CallAfter(self._shutdown_gnumed, forced)
    1025                  return 1
    1026          #-----------------------------------------------------------------
    1027 -        def raise_gnumed(self, auth_cookie = None):
    1028                  """Raise ourselves to the top of the desktop."""
    1029                  if not self.__attached:
    1030                          return 0
    1031                  if auth_cookie != self.__auth_cookie:
    1032                          _log.error('non-authenticated raise_gnumed()')
    1033                          return 0
    1034                  return "cMacroPrimitives.raise_gnumed() not implemented"
    1035          #-----------------------------------------------------------------
    1036 -        def get_loaded_plugins(self, auth_cookie = None):
    1037                  if not self.__attached:
    1038                          return 0
    1039                  if auth_cookie != self.__auth_cookie:
    1040                          _log.error('non-authenticated get_loaded_plugins()')
    1041                          return 0
    1042                  gb = gmGuiBroker.GuiBroker()
    1043                  return gb['horstspace.notebook.gui'].keys()
    1044          #-----------------------------------------------------------------
    1045 -        def raise_notebook_plugin(self, auth_cookie = None, a_plugin = None):
    1046                  """Raise a notebook plugin within GNUmed."""
    1047                  if not self.__attached:
    1048                          return 0
    1049                  if auth_cookie != self.__auth_cookie:
    1050                          _log.error('non-authenticated raise_notebook_plugin()')
    1051                          return 0
    1052                  # FIXME: use semaphore
    1053                  wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
    1054                  return 1
    1055          #-----------------------------------------------------------------
    1056 -        def load_patient_from_external_source(self, auth_cookie = None):
    1057                  """Load external patient, perhaps create it.
    1058
    1059                  Callers must use get_user_answer() to get status information.
    1060                  It is unsafe to proceed without knowing the completion state as
    1061                  the controlled client may be waiting for user input from a
    1062                  patient selection list.
    1063                  """
    1064                  if not self.__attached:
    1065                          return (0, _('request rejected, you are not attach()ed'))
    1066                  if auth_cookie != self.__auth_cookie:
    1067                          _log.error('non-authenticated load_patient_from_external_source()')
    1068                          return (0, _('rejected load_patient_from_external_source(), not authenticated'))
    1069                  if self.__pat.locked:
    1070                          _log.error('patient is locked, cannot load from external source')
    1071                          return (0, _('current patient is locked'))
    1072                  self.__user_done = False
    1073                  wx.CallAfter(self._load_patient_from_external_source)
    1074                  self.__lock_after_load_cookie = str(random.random())
    1075                  return (1, self.__lock_after_load_cookie)
    1076          #-----------------------------------------------------------------
    1077 -        def lock_loaded_patient(self, auth_cookie = None, lock_after_load_cookie = None):
    1078                  if not self.__attached:
    1079                          return (0, _('request rejected, you are not attach()ed'))
    1080                  if auth_cookie != self.__auth_cookie:
    1081                          _log.error('non-authenticated lock_load_patient()')
    1082                          return (0, _('rejected lock_load_patient(), not authenticated'))
    1083                  # FIXME: ask user what to do about wrong cookie
    1084                  if lock_after_load_cookie != self.__lock_after_load_cookie:
    1085                          _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
    1086                          return (0, 'patient lock-after-load request rejected, wrong cookie provided')
    1087                  self.__pat.locked = True
    1088                  self.__pat_lock_cookie = str(random.random())
    1089                  return (1, self.__pat_lock_cookie)
    1090          #-----------------------------------------------------------------
    1091 -        def lock_into_patient(self, auth_cookie = None, search_params = None):
    1092                  if not self.__attached:
    1093                          return (0, _('request rejected, you are not attach()ed'))
    1094                  if auth_cookie != self.__auth_cookie:
    1095                          _log.error('non-authenticated lock_into_patient()')
    1096                          return (0, _('rejected lock_into_patient(), not authenticated'))
    1097                  if self.__pat.locked:
    1098                          _log.error('patient is already locked')
    1099                          return (0, _('already locked into a patient'))
    1100                  searcher = gmPersonSearch.cPatientSearcher_SQL()
    1101                  if type(search_params) == types.DictType:
    1102                          idents = searcher.get_identities(search_dict=search_params)
    1103                          raise StandardError("must use dto, not search_dict")
    1104                  else:
    1105                          idents = searcher.get_identities(search_term=search_params)
    1106                  if idents is None:
    1107                          return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
    1108                  if len(idents) == 0:
    1109                          return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
    1110                  # FIXME: let user select patient
    1111                  if len(idents) > 1:
    1112                          return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
    1113                  if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
    1114                          return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
    1115                  self.__pat.locked = True
    1116                  self.__pat_lock_cookie = str(random.random())
    1117                  return (1, self.__pat_lock_cookie)
    1118          #-----------------------------------------------------------------
    1119 -        def unlock_patient(self, auth_cookie = None, unlock_cookie = None):
    1120                  if not self.__attached:
    1121                          return (0, _('request rejected, you are not attach()ed'))
    1122                  if auth_cookie != self.__auth_cookie:
    1123                          _log.error('non-authenticated unlock_patient()')
    1124                          return (0, _('rejected unlock_patient, not authenticated'))
    1125                  # we ain't locked anyways, so succeed
    1126                  if not self.__pat.locked:
    1127                          return (1, '')
    1128                  # FIXME: ask user what to do about wrong cookie
    1129                  if unlock_cookie != self.__pat_lock_cookie:
    1130                          _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
    1131                          return (0, 'patient unlock request rejected, wrong cookie provided')
    1132                  self.__pat.locked = False
    1133                  return (1, '')
    1134          #-----------------------------------------------------------------
    1135 -        def assume_staff_identity(self, auth_cookie = None, staff_name = "Dr.Jekyll", staff_creds = None):
    1136                  if not self.__attached:
    1137                          return 0
    1138                  if auth_cookie != self.__auth_cookie:
    1139                          _log.error('non-authenticated select_identity()')
    1140                          return 0
    1141                  return "cMacroPrimitives.assume_staff_identity() not implemented"
    1142          #-----------------------------------------------------------------
    1143 -        def get_user_answer(self):
    1144                  if not self.__user_done:
    1145                          return (0, 'still waiting')
    1146                  self.__user_done = False
    1147                  return (1, self.__user_answer)
    1148          #-----------------------------------------------------------------
    1149          # internal API
    1150          #-----------------------------------------------------------------
    1151 -        def _force_detach(self):
    1152                  msg = _(
    1153                          'Someone tries to forcibly break the existing\n'
    1154                          'controlling connection. This may or may not\n'
    1155                          'have legitimate reasons.\n\n'
    1156                          'Do you want to allow breaking the connection ?'
    1157                  )
    1158                  can_break_conn = gmGuiHelpers.gm_show_question (
    1159                          aMessage = msg,
    1160                          aTitle = _('forced detach attempt')
    1161                  )
    1162                  if can_break_conn:
    1163                          self.__user_answer = 1
    1164                  else:
    1165                          self.__user_answer = 0
    1166                  self.__user_done = True
    1167                  if can_break_conn:
    1168                          self.__pat.locked = False
    1169                          self.__attached = 0
    1170                  return 1
    1171          #-----------------------------------------------------------------
    1172 -        def _shutdown_gnumed(self, forced=False):
    1173                  top_win = wx.GetApp().GetTopWindow()
    1174                  if forced:
    1175                          top_win.Destroy()
    1176                  else:
    1177                          top_win.Close()
    1178          #-----------------------------------------------------------------
    1179 -        def _load_patient_from_external_source(self):
    1180                  patient = gmPatSearchWidgets.get_person_from_external_sources(search_immediately = True, activate_immediately = True)
    1181                  if patient is not None:
    1182                          self.__user_answer = 1
    1183                  else:
    1184                          self.__user_answer = 0
    1185                  self.__user_done = True
    1186                  return 1
    1187  #=====================================================================
    1188  # main
    1189  #=====================================================================
    1190  if __name__ == '__main__':
    1191
    1192          if len(sys.argv) < 2:
    1193                  sys.exit()
    1194
    1195          if sys.argv[1] != 'test':
    1196                  sys.exit()
    1197
    1198          gmI18N.activate_locale()
    1199          gmI18N.install_domain()
    1200
    1201          #--------------------------------------------------------
    1202 -        def test_placeholders():
    1203                  handler = gmPlaceholderHandler()
    1204                  handler.debug = True
    1205
    1206                  for placeholder in ['a', 'b']:
    1207                          print handler[placeholder]
    1208
    1209                  pat = gmPersonSearch.ask_for_patient()
    1210                  if pat is None:
    1211                          return
    1212
    1213                  gmPatSearchWidgets.set_active_patient(patient = pat)
    1214
    1215                  print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
    1216
    1217                  app = wx.PyWidgetTester(size = (200, 50))
    1218                  for placeholder in known_placeholders:
    1219                          print placeholder, "=", handler[placeholder]
    1220
    1221                  ph = 'progress_notes::ap'
    1222                  print '%s: %s' % (ph, handler[ph])
    1223          #--------------------------------------------------------
    1224 -        def test_new_variant_placeholders():
    1225
    1226                  tests = [
    1227                          # should work:
    1228                          '$<lastname>$',
    1229                          '$<lastname::::3>$',
    1230                          '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
    1231
    1232                          # should fail:
    1233                          'lastname',
    1234                          '$<lastname',
    1235                          '$<lastname::',
    1236                          '$<lastname::>$',
    1237                          '$<lastname::abc>$',
    1238                          '$<lastname::abc::>$',
    1239                          '$<lastname::abc::3>$',
    1240                          '$<lastname::abc::xyz>$',
    1241                          '$<lastname::::>$',
    1242                          '$<lastname::::xyz>$',
    1243
    1244                          '$<date_of_birth::%Y-%m-%d>$',
    1245                          '$<date_of_birth::%Y-%m-%d::3>$',
    1246                          '$<date_of_birth::%Y-%m-%d::>$',
    1247
    1248                          # should work:
    1249                          '$<adr_location::home::35>$',
    1250                          '$<gender_mapper::male//female//other::5>$',
    1251                          '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
    1252                          '$<allergy_list::%(descriptor)s, >$',
    1253                          '$<current_meds_table::latex//by-brand>$'
    1254
    1255  #                       'firstname',
    1256  #                       'title',
    1257  #                       'date_of_birth',
    1258  #                       'progress_notes',
    1259  #                       'soap',
    1260  #                       'soap_s',
    1261  #                       'soap_o',
    1262  #                       'soap_a',
    1263  #                       'soap_p',
    1264
    1265  #                       'soap',
    1266  #                       'progress_notes',
    1267  #                       'date_of_birth'
    1268                  ]
    1269
    1270                  tests = [
    1271                          '$<latest_vaccs_table::latex>$'
    1272                  ]
    1273
    1274                  pat = gmPersonSearch.ask_for_patient()
    1275                  if pat is None:
    1276                          return
    1277
    1278                  gmPatSearchWidgets.set_active_patient(patient = pat)
    1279
    1280                  handler = gmPlaceholderHandler()
    1281                  handler.debug = True
    1282
    1283                  for placeholder in tests:
    1284                          print placeholder, "=>", handler[placeholder]
    1285                          print "--------------"
    1286                          raw_input()
    1287
    1288  #               print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
    1289
    1290  #               app = wx.PyWidgetTester(size = (200, 50))
    1291  #               for placeholder in known_placeholders:
    1292  #                       print placeholder, "=", handler[placeholder]
    1293
    1294  #               ph = 'progress_notes::ap'
    1295  #               print '%s: %s' % (ph, handler[ph])
    1296
    1297          #--------------------------------------------------------
    1298 -        def test_scripting():
    1299                  from Gnumed.pycommon import gmScriptingListener
    1300                  import xmlrpclib
    1301                  listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
    1302
    1303                  s = xmlrpclib.ServerProxy('http://localhost:9999')
    1304                  print "should fail:", s.attach()
    1305                  print "should fail:", s.attach('wrong cookie')
    1306                  print "should work:", s.version()
    1307                  print "should fail:", s.raise_gnumed()
    1308                  print "should fail:", s.raise_notebook_plugin('test plugin')
    1309                  print "should fail:", s.lock_into_patient('kirk, james')
    1310                  print "should fail:", s.unlock_patient()
    1311                  status, conn_auth = s.attach('unit test')
    1312                  print "should work:", status, conn_auth
    1313                  print "should work:", s.version()
    1314                  print "should work:", s.raise_gnumed(conn_auth)
    1315                  status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
    1316                  print "should work:", status, pat_auth
    1317                  print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
    1318                  print "should work", s.unlock_patient(conn_auth, pat_auth)
    1319                  data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
    1320                  status, pat_auth = s.lock_into_patient(conn_auth, data)
    1321                  print "should work:", status, pat_auth
    1322                  print "should work", s.unlock_patient(conn_auth, pat_auth)
    1323                  print s.detach('bogus detach cookie')
    1324                  print s.detach(conn_auth)
    1325                  del s
    1326
    1327                  listener.shutdown()
    1328          #--------------------------------------------------------
    1329 -        def test_placeholder_regex():
    1330
    1331                  import re as regex
    1332
    1333                  tests = [
    1334                          ' $<lastname>$ ',
    1335                          ' $<lastname::::3>$ ',
    1336
    1337                          # should fail:
    1338                          '$<date_of_birth::%Y-%m-%d>$',
    1339                          '$<date_of_birth::%Y-%m-%d::3>$',
    1340                          '$<date_of_birth::%Y-%m-%d::>$',
    1341
    1342                          '$<adr_location::home::35>$',
    1343                          '$<gender_mapper::male//female//other::5>$',
    1344                          '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
    1345                          '$<allergy_list::%(descriptor)s, >$',
    1346
    1347                          '\\noindent Patient: $<lastname>$, $<firstname>$',
    1348                          '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
    1349                          '$<current_meds::               \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
    1350                  ]
    1351
    1352                  tests = [
    1353
    1354                          'junk   $<lastname::::3>$                       junk',
    1355                          'junk   $<lastname::abc::3>$            junk',
    1356                          'junk   $<lastname::abc>$                       junk',
    1357                          'junk   $<lastname>$                            junk',
    1358
    1359                          'junk   $<lastname>$   junk   $<firstname>$                                             junk',
    1360                          'junk   $<lastname::abc>$   junk   $<fiststname::abc>$                  junk',
    1361                          'junk   $<lastname::abc::3>$   junk   $<firstname::abc::3>$             junk',
    1362                          'junk   $<lastname::::3>$   junk   $<firstname::::3>$                   junk'
    1363
    1364                  ]
    1365
    1366                  print "testing placeholder regex:", default_placeholder_regex
    1367                  print ""
    1368
    1369                  for t in tests:
    1370                          print 'line: "%s"' % t
    1371                          print "placeholders:"
    1372                          for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
    1373                                  print ' => "%s"' % p
    1374                          print " "
    1375          #--------------------------------------------------------
    1376 -        def test_placeholder():
    1377
    1378                  phs = [
    1379                          #u'emr_journal::soapu //%(date)s  %(modified_by)s  %(soap_cat)s  %(narrative)s//30::',
    1380                          #u'free_text::tex//placeholder test::9999',
    1381                          #u'soap_for_encounters:://::9999',
    1382                          #u'soap_a',,
    1383                          #u'encounter_list::%(started)s: %(assessment_of_encounter)s::30',
    1384                          #u'patient_comm::homephone::1234',
    1385                          #u'patient_address::home//::1234',
    1386                          #u'adr_region::home::1234',
    1387                          #u'adr_country::home::1234',
    1388                          #u'external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234',
    1389                          #u'primary_praxis_provider',
    1390                          #u'current_provider',
    1391                          #u'current_provider_external_id::Starfleet Serial Number//Star Fleet Central Staff Office::1234',
    1392                          #u'current_provider_external_id::LANR//LÄK::1234'
    1393                          #u'primary_praxis_provider_external_id::LANR//LÄK::1234'
    1394                          #u'form_name_long::::1234',
    1395                          #u'form_name_long::::5',
    1396                          #u'form_version::::5',
    1397                          u'$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) from %(started)s for %(duration)s as %(schedule)s until %(discontinued)s <==\\n::250>$'
    1398                  ]
    1399
    1400                  handler = gmPlaceholderHandler()
    1401                  handler.debug = True
    1402
    1403                  gmStaff.set_current_provider_to_logged_on_user()
    1404                  pat = gmPersonSearch.ask_for_patient()
    1405                  if pat is None:
    1406                          return
    1407
    1408                  gmPatSearchWidgets.set_active_patient(patient = pat)
    1409
    1410                  app = wx.PyWidgetTester(size = (200, 50))
    1411                  #handler.set_placeholder('form_name_long', 'ein Testformular')
    1412                  for ph in phs:
    1413                          print ph
    1414                          print ' => %s' % handler[ph]
    1415                  #handler.unset_placeholder('form_name_long')
    1416          #--------------------------------------------------------
    1417
    1418          #test_placeholders()
    1419          #test_new_variant_placeholders()
    1420          #test_scripting()
    1421          #test_placeholder_regex()
    1422          test_placeholder()
    1423
    1424  #=====================================================================
    1425