1 """GNUmed macro primitives.
2
3 This module implements functions a macro can legally use.
4 """
5
6 __version__ = "$Revision: 1.51 $"
7 __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
8
9 import sys, time, random, types, logging
10
11
12 import wx
13
14
15 if __name__ == '__main__':
16 sys.path.insert(0, '../../')
17 from Gnumed.pycommon import gmI18N, gmGuiBroker, gmExceptions, gmBorg, gmTools
18 from Gnumed.pycommon import gmCfg2, gmDateTime
19 from Gnumed.business import gmPerson, gmDemographicRecord, gmMedication, gmPathLab, gmPersonSearch
20 from Gnumed.business import gmVaccination, gmPersonSearch
21 from Gnumed.wxpython import gmGuiHelpers, gmPlugin, gmPatSearchWidgets, gmNarrativeWidgets
22
23
24 _log = logging.getLogger('gm.scripting')
25 _cfg = gmCfg2.gmCfgData()
26
27
28 known_placeholders = [
29 'lastname',
30 'firstname',
31 'title',
32 'date_of_birth',
33 'progress_notes',
34 'soap',
35 'soap_s',
36 'soap_o',
37 'soap_a',
38 'soap_p',
39 u'client_version',
40 u'current_provider',
41 u'allergy_state'
42 ]
43
44
45
46 known_variant_placeholders = [
47 u'soap',
48 u'progress_notes',
49
50
51 u'emr_journal',
52
53
54
55
56
57
58
59 u'date_of_birth',
60 u'adr_street',
61 u'adr_number',
62 u'adr_location',
63 u'adr_postcode',
64 u'gender_mapper',
65 u'current_meds',
66 u'current_meds_table',
67 u'current_meds_notes',
68 u'lab_table',
69 u'latest_vaccs_table',
70 u'today',
71 u'tex_escape',
72 u'allergies',
73 u'allergy_list',
74 u'problems',
75 u'name'
76 ]
77
78 default_placeholder_regex = r'\$<.+?>\$'
79
80
81
82
83
84
85
86
87 default_placeholder_start = u'$<'
88 default_placeholder_end = u'>$'
89
91 """Replaces placeholders in forms, fields, etc.
92
93 - patient related placeholders operate on the currently active patient
94 - is passed to the forms handling code, for example
95
96 Note that this cannot be called from a non-gui thread unless
97 wrapped in wx.CallAfter.
98
99 There are currently three types of placeholders:
100
101 simple static placeholders
102 - those are listed in known_placeholders
103 - they are used as-is
104
105 extended static placeholders
106 - those are like the static ones but have "::::<NUMBER>" appended
107 where <NUMBER> is the maximum length
108
109 variant placeholders
110 - those are listed in known_variant_placeholders
111 - they are parsed into placeholder, data, and maximum length
112 - the length is optional
113 - data is passed to the handler
114 """
116
117 self.pat = gmPerson.gmCurrentPatient()
118 self.debug = False
119
120 self.invalid_placeholder_template = _('invalid placeholder [%s]')
121
122
123
125 """Map self['placeholder'] to self.placeholder.
126
127 This is useful for replacing placeholders parsed out
128 of documents as strings.
129
130 Unknown/invalid placeholders still deliver a result but
131 it will be glaringly obvious if debugging is enabled.
132 """
133 _log.debug('replacing [%s]', placeholder)
134
135 original_placeholder = placeholder
136
137 if placeholder.startswith(default_placeholder_start):
138 placeholder = placeholder[len(default_placeholder_start):]
139 if placeholder.endswith(default_placeholder_end):
140 placeholder = placeholder[:-len(default_placeholder_end)]
141 else:
142 _log.debug('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
143 if self.debug:
144 return self.invalid_placeholder_template % original_placeholder
145 return None
146
147
148 if placeholder in known_placeholders:
149 return getattr(self, placeholder)
150
151
152 parts = placeholder.split('::::', 1)
153 if len(parts) == 2:
154 name, lng = parts
155 try:
156 return getattr(self, name)[:int(lng)]
157 except:
158 _log.exception('placeholder handling error: %s', original_placeholder)
159 if self.debug:
160 return self.invalid_placeholder_template % original_placeholder
161 return None
162
163
164 parts = placeholder.split('::')
165 if len(parts) == 2:
166 name, data = parts
167 lng = None
168 if len(parts) == 3:
169 name, data, lng = parts
170 try:
171 lng = int(lng)
172 except:
173 _log.exception('placeholder length definition error: %s, discarding length', original_placeholder)
174 lng = None
175 if len(parts) > 3:
176 _log.warning('invalid placeholder layout: %s', original_placeholder)
177 if self.debug:
178 return self.invalid_placeholder_template % original_placeholder
179 return None
180
181 handler = getattr(self, '_get_variant_%s' % name, None)
182 if handler is None:
183 _log.warning('no handler <_get_variant_%s> for placeholder %s', name, original_placeholder)
184 if self.debug:
185 return self.invalid_placeholder_template % original_placeholder
186 return None
187
188 try:
189 if lng is None:
190 return handler(data = data)
191 return handler(data = data)[:lng]
192 except:
193 _log.exception('placeholder handling error: %s', original_placeholder)
194 if self.debug:
195 return self.invalid_placeholder_template % original_placeholder
196 return None
197
198 _log.error('something went wrong, should never get here')
199 return None
200
201
202
203
204
206 """This does nothing, used as a NOOP properties setter."""
207 pass
208
211
214
217
219 return self._get_variant_date_of_birth(data='%x')
220
222 return self._get_variant_soap()
223
225 return self._get_variant_soap(data = u's')
226
228 return self._get_variant_soap(data = u'o')
229
231 return self._get_variant_soap(data = u'a')
232
234 return self._get_variant_soap(data = u'p')
235
237 return self._get_variant_soap(soap_cats = None)
238
240 return gmTools.coalesce (
241 _cfg.get(option = u'client_version'),
242 u'%s' % self.__class__.__name__
243 )
244
260
262 allg_state = self.pat.get_emr().allergy_state
263
264 if allg_state['last_confirmed'] is None:
265 date_confirmed = u''
266 else:
267 date_confirmed = u' (%s)' % allg_state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
268
269 tmp = u'%s%s' % (
270 allg_state.state_string,
271 date_confirmed
272 )
273 return tmp
274
275
276
277 placeholder_regex = property(lambda x: default_placeholder_regex, _setter_noop)
278
279
280 lastname = property(_get_lastname, _setter_noop)
281 firstname = property(_get_firstname, _setter_noop)
282 title = property(_get_title, _setter_noop)
283 date_of_birth = property(_get_dob, _setter_noop)
284
285 progress_notes = property(_get_progress_notes, _setter_noop)
286 soap = property(_get_progress_notes, _setter_noop)
287 soap_s = property(_get_soap_s, _setter_noop)
288 soap_o = property(_get_soap_o, _setter_noop)
289 soap_a = property(_get_soap_a, _setter_noop)
290 soap_p = property(_get_soap_p, _setter_noop)
291 soap_admin = property(_get_soap_admin, _setter_noop)
292
293 allergy_state = property(_get_allergy_state, _setter_noop)
294
295 client_version = property(_get_client_version, _setter_noop)
296
297 current_provider = property(_get_current_provider, _setter_noop)
298
299
300
302
303 cats = list(u'soap').append(None)
304 template = u'%s'
305 interactive = True
306 line_length = 9999
307 target_format = None
308 time_range = None
309
310 if data is not None:
311 data_parts = data.split('//')
312
313
314 cats = []
315
316 for c in list(data_parts[0]):
317 if c == u' ':
318 c = None
319 cats.append(c)
320
321 if cats == u'':
322 cats = cats = list(u'soap').append(None)
323
324
325 if len(data_parts) > 0:
326 template = data_parts[1]
327
328
329 if len(data_parts) > 1:
330 try:
331 line_length = int(data_parts[2])
332 except:
333 line_length = 9999
334
335
336 if len(data_parts) > 2:
337 try:
338 time_range = 7 * int(data_parts[3])
339 except:
340 time_range = None
341
342
343 if len(data_parts) > 3:
344 target_format = data_parts[4]
345
346
347 narr = self.pat.get_emr().get_as_journal(soap_cats = cats, time_range = time_range)
348
349 if len(narr) == 0:
350 return u''
351
352 if target_format == u'tex':
353 keys = narr[0].keys()
354 lines = []
355 line_dict = {}
356 for n in narr:
357 for key in keys:
358 if isinstance(n[key], basestring):
359 line_dict[key] = gmTools.tex_escape_string(text = n[key])
360 continue
361 line_dict[key] = n[key]
362 try:
363 lines.append((template % line_dict)[:line_length])
364 except KeyError:
365 return u'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
366 else:
367 try:
368 lines = [ (template % n)[:line_length] for n in narr ]
369 except KeyError:
370 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
371
372 return u'\n'.join(lines)
373
375 return self._get_variant_soap(data=data)
376
378
379
380 cats = list(u'soap').append(None)
381 template = u'%s'
382
383 if data is not None:
384 data_parts = data.split('//')
385
386
387 cats = []
388
389 for c in list(data_parts[0]):
390 if c == u' ':
391 c = None
392 cats.append(c)
393
394 if cats == u'':
395 cats = cats = list(u'soap').append(None)
396
397
398 if len(data_parts) > 0:
399 template = data_parts[1]
400
401 narr = gmNarrativeWidgets.select_narrative_from_episodes(soap_cats = cats)
402
403 if len(narr) == 0:
404 return u''
405
406 try:
407 narr = [ template % n['narrative'] for n in narr ]
408 except KeyError:
409 return u'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
410
411 return u'\n'.join(narr)
412
431
434
435
437 values = data.split('//', 2)
438
439 if len(values) == 2:
440 male_value, female_value = values
441 other_value = u'<unkown gender>'
442 elif len(values) == 3:
443 male_value, female_value, other_value = values
444 else:
445 return _('invalid gender mapping layout: [%s]') % data
446
447 if self.pat['gender'] == u'm':
448 return male_value
449
450 if self.pat['gender'] == u'f':
451 return female_value
452
453 return other_value
454
456
457
458 adrs = self.pat.get_addresses(address_type=data)
459 if len(adrs) == 0:
460 return _('no street for address type [%s]') % data
461 return adrs[0]['street']
462
464 adrs = self.pat.get_addresses(address_type=data)
465 if len(adrs) == 0:
466 return _('no number for address type [%s]') % data
467 return adrs[0]['number']
468
470 adrs = self.pat.get_addresses(address_type=data)
471 if len(adrs) == 0:
472 return _('no location for address type [%s]') % data
473 return adrs[0]['urb']
474
475 - def _get_variant_adr_postcode(self, data=u'?'):
476 adrs = self.pat.get_addresses(address_type=data)
477 if len(adrs) == 0:
478 return _('no postcode for address type [%s]') % data
479 return adrs[0]['postcode']
480
482 if data is None:
483 return [_('template is missing')]
484
485 template, separator = data.split('//', 2)
486
487 emr = self.pat.get_emr()
488 return separator.join([ template % a for a in emr.get_allergies() ])
489
491
492 if data is None:
493 return [_('template is missing')]
494
495 emr = self.pat.get_emr()
496 return u'\n'.join([ data % a for a in emr.get_allergies() ])
497
499
500 if data is None:
501 return [_('template is missing')]
502
503 emr = self.pat.get_emr()
504 current_meds = emr.get_current_substance_intake (
505 include_inactive = False,
506 include_unapproved = False,
507 order_by = u'brand, substance'
508 )
509
510
511
512 return u'\n'.join([ data % m for m in current_meds ])
513
515
516 options = data.split('//')
517
518 if u'latex' in options:
519 return gmMedication.format_substance_intake (
520 emr = self.pat.get_emr(),
521 output_format = u'latex',
522 table_type = u'by-brand'
523 )
524
525 _log.error('no known current medications table formatting style in [%]', data)
526 return _('unknown current medication table formatting style')
527
529
530 options = data.split('//')
531
532 if u'latex' in options:
533 return gmMedication.format_substance_intake_notes (
534 emr = self.pat.get_emr(),
535 output_format = u'latex',
536 table_type = u'by-brand'
537 )
538
539 _log.error('no known current medications notes formatting style in [%]', data)
540 return _('unknown current medication notes formatting style')
541
556
558
559 options = data.split('//')
560
561 emr = self.pat.get_emr()
562
563 if u'latex' in options:
564 return gmVaccination.format_latest_vaccinations(output_format = u'latex', emr = emr)
565
566 _log.error('no known vaccinations table formatting style in [%s]', data)
567 return _('unknown vaccinations table formatting style [%s]') % data
568
570
571 if data is None:
572 return [_('template is missing')]
573
574 probs = self.pat.get_emr().get_problems()
575
576 return u'\n'.join([ data % p for p in probs ])
577
580
583
584
585
586
587
589 """Functions a macro can legally use.
590
591 An instance of this class is passed to the GNUmed scripting
592 listener. Hence, all actions a macro can legally take must
593 be defined in this class. Thus we achieve some screening for
594 security and also thread safety handling.
595 """
596
597 - def __init__(self, personality = None):
598 if personality is None:
599 raise gmExceptions.ConstructorError, 'must specify personality'
600 self.__personality = personality
601 self.__attached = 0
602 self._get_source_personality = None
603 self.__user_done = False
604 self.__user_answer = 'no answer yet'
605 self.__pat = gmPerson.gmCurrentPatient()
606
607 self.__auth_cookie = str(random.random())
608 self.__pat_lock_cookie = str(random.random())
609 self.__lock_after_load_cookie = str(random.random())
610
611 _log.info('slave mode personality is [%s]', personality)
612
613
614
615 - def attach(self, personality = None):
616 if self.__attached:
617 _log.error('attach with [%s] rejected, already serving a client', personality)
618 return (0, _('attach rejected, already serving a client'))
619 if personality != self.__personality:
620 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
621 return (0, _('attach to personality [%s] rejected') % personality)
622 self.__attached = 1
623 self.__auth_cookie = str(random.random())
624 return (1, self.__auth_cookie)
625
626 - def detach(self, auth_cookie=None):
627 if not self.__attached:
628 return 1
629 if auth_cookie != self.__auth_cookie:
630 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
631 return 0
632 self.__attached = 0
633 return 1
634
636 if not self.__attached:
637 return 1
638 self.__user_done = False
639
640 wx.CallAfter(self._force_detach)
641 return 1
642
644 ver = _cfg.get(option = u'client_version')
645 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
646
648 """Shuts down this client instance."""
649 if not self.__attached:
650 return 0
651 if auth_cookie != self.__auth_cookie:
652 _log.error('non-authenticated shutdown_gnumed()')
653 return 0
654 wx.CallAfter(self._shutdown_gnumed, forced)
655 return 1
656
658 """Raise ourselves to the top of the desktop."""
659 if not self.__attached:
660 return 0
661 if auth_cookie != self.__auth_cookie:
662 _log.error('non-authenticated raise_gnumed()')
663 return 0
664 return "cMacroPrimitives.raise_gnumed() not implemented"
665
667 if not self.__attached:
668 return 0
669 if auth_cookie != self.__auth_cookie:
670 _log.error('non-authenticated get_loaded_plugins()')
671 return 0
672 gb = gmGuiBroker.GuiBroker()
673 return gb['horstspace.notebook.gui'].keys()
674
676 """Raise a notebook plugin within GNUmed."""
677 if not self.__attached:
678 return 0
679 if auth_cookie != self.__auth_cookie:
680 _log.error('non-authenticated raise_notebook_plugin()')
681 return 0
682
683 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
684 return 1
685
687 """Load external patient, perhaps create it.
688
689 Callers must use get_user_answer() to get status information.
690 It is unsafe to proceed without knowing the completion state as
691 the controlled client may be waiting for user input from a
692 patient selection list.
693 """
694 if not self.__attached:
695 return (0, _('request rejected, you are not attach()ed'))
696 if auth_cookie != self.__auth_cookie:
697 _log.error('non-authenticated load_patient_from_external_source()')
698 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
699 if self.__pat.locked:
700 _log.error('patient is locked, cannot load from external source')
701 return (0, _('current patient is locked'))
702 self.__user_done = False
703 wx.CallAfter(self._load_patient_from_external_source)
704 self.__lock_after_load_cookie = str(random.random())
705 return (1, self.__lock_after_load_cookie)
706
708 if not self.__attached:
709 return (0, _('request rejected, you are not attach()ed'))
710 if auth_cookie != self.__auth_cookie:
711 _log.error('non-authenticated lock_load_patient()')
712 return (0, _('rejected lock_load_patient(), not authenticated'))
713
714 if lock_after_load_cookie != self.__lock_after_load_cookie:
715 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
716 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
717 self.__pat.locked = True
718 self.__pat_lock_cookie = str(random.random())
719 return (1, self.__pat_lock_cookie)
720
722 if not self.__attached:
723 return (0, _('request rejected, you are not attach()ed'))
724 if auth_cookie != self.__auth_cookie:
725 _log.error('non-authenticated lock_into_patient()')
726 return (0, _('rejected lock_into_patient(), not authenticated'))
727 if self.__pat.locked:
728 _log.error('patient is already locked')
729 return (0, _('already locked into a patient'))
730 searcher = gmPersonSearch.cPatientSearcher_SQL()
731 if type(search_params) == types.DictType:
732 idents = searcher.get_identities(search_dict=search_params)
733 print "must use dto, not search_dict"
734 print xxxxxxxxxxxxxxxxx
735 else:
736 idents = searcher.get_identities(search_term=search_params)
737 if idents is None:
738 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
739 if len(idents) == 0:
740 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
741
742 if len(idents) > 1:
743 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
744 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
745 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
746 self.__pat.locked = True
747 self.__pat_lock_cookie = str(random.random())
748 return (1, self.__pat_lock_cookie)
749
751 if not self.__attached:
752 return (0, _('request rejected, you are not attach()ed'))
753 if auth_cookie != self.__auth_cookie:
754 _log.error('non-authenticated unlock_patient()')
755 return (0, _('rejected unlock_patient, not authenticated'))
756
757 if not self.__pat.locked:
758 return (1, '')
759
760 if unlock_cookie != self.__pat_lock_cookie:
761 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
762 return (0, 'patient unlock request rejected, wrong cookie provided')
763 self.__pat.locked = False
764 return (1, '')
765
767 if not self.__attached:
768 return 0
769 if auth_cookie != self.__auth_cookie:
770 _log.error('non-authenticated select_identity()')
771 return 0
772 return "cMacroPrimitives.assume_staff_identity() not implemented"
773
775 if not self.__user_done:
776 return (0, 'still waiting')
777 self.__user_done = False
778 return (1, self.__user_answer)
779
780
781
783 msg = _(
784 'Someone tries to forcibly break the existing\n'
785 'controlling connection. This may or may not\n'
786 'have legitimate reasons.\n\n'
787 'Do you want to allow breaking the connection ?'
788 )
789 can_break_conn = gmGuiHelpers.gm_show_question (
790 aMessage = msg,
791 aTitle = _('forced detach attempt')
792 )
793 if can_break_conn:
794 self.__user_answer = 1
795 else:
796 self.__user_answer = 0
797 self.__user_done = True
798 if can_break_conn:
799 self.__pat.locked = False
800 self.__attached = 0
801 return 1
802
804 top_win = wx.GetApp().GetTopWindow()
805 if forced:
806 top_win.Destroy()
807 else:
808 top_win.Close()
809
818
819
820
821 if __name__ == '__main__':
822
823 if len(sys.argv) < 2:
824 sys.exit()
825
826 if sys.argv[1] != 'test':
827 sys.exit()
828
829 gmI18N.activate_locale()
830 gmI18N.install_domain()
831
832
834 handler = gmPlaceholderHandler()
835 handler.debug = True
836
837 for placeholder in ['a', 'b']:
838 print handler[placeholder]
839
840 pat = gmPersonSearch.ask_for_patient()
841 if pat is None:
842 return
843
844 gmPatSearchWidgets.set_active_patient(patient = pat)
845
846 print 'DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d']
847
848 app = wx.PyWidgetTester(size = (200, 50))
849 for placeholder in known_placeholders:
850 print placeholder, "=", handler[placeholder]
851
852 ph = 'progress_notes::ap'
853 print '%s: %s' % (ph, handler[ph])
854
856
857 tests = [
858
859 '$<lastname>$',
860 '$<lastname::::3>$',
861 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
862
863
864 'lastname',
865 '$<lastname',
866 '$<lastname::',
867 '$<lastname::>$',
868 '$<lastname::abc>$',
869 '$<lastname::abc::>$',
870 '$<lastname::abc::3>$',
871 '$<lastname::abc::xyz>$',
872 '$<lastname::::>$',
873 '$<lastname::::xyz>$',
874
875 '$<date_of_birth::%Y-%m-%d>$',
876 '$<date_of_birth::%Y-%m-%d::3>$',
877 '$<date_of_birth::%Y-%m-%d::>$',
878
879
880 '$<adr_location::home::35>$',
881 '$<gender_mapper::male//female//other::5>$',
882 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\n::50>$',
883 '$<allergy_list::%(descriptor)s, >$',
884 '$<current_meds_table::latex//by-brand>$'
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899 ]
900
901 tests = [
902 '$<latest_vaccs_table::latex>$'
903 ]
904
905 pat = gmPersonSearch.ask_for_patient()
906 if pat is None:
907 return
908
909 gmPatSearchWidgets.set_active_patient(patient = pat)
910
911 handler = gmPlaceholderHandler()
912 handler.debug = True
913
914 for placeholder in tests:
915 print placeholder, "=>", handler[placeholder]
916 print "--------------"
917 raw_input()
918
919
920
921
922
923
924
925
926
927
928
930 from Gnumed.pycommon import gmScriptingListener
931 import xmlrpclib
932 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
933
934 s = xmlrpclib.ServerProxy('http://localhost:9999')
935 print "should fail:", s.attach()
936 print "should fail:", s.attach('wrong cookie')
937 print "should work:", s.version()
938 print "should fail:", s.raise_gnumed()
939 print "should fail:", s.raise_notebook_plugin('test plugin')
940 print "should fail:", s.lock_into_patient('kirk, james')
941 print "should fail:", s.unlock_patient()
942 status, conn_auth = s.attach('unit test')
943 print "should work:", status, conn_auth
944 print "should work:", s.version()
945 print "should work:", s.raise_gnumed(conn_auth)
946 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
947 print "should work:", status, pat_auth
948 print "should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie')
949 print "should work", s.unlock_patient(conn_auth, pat_auth)
950 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
951 status, pat_auth = s.lock_into_patient(conn_auth, data)
952 print "should work:", status, pat_auth
953 print "should work", s.unlock_patient(conn_auth, pat_auth)
954 print s.detach('bogus detach cookie')
955 print s.detach(conn_auth)
956 del s
957
958 listener.shutdown()
959
961
962 import re as regex
963
964 tests = [
965 ' $<lastname>$ ',
966 ' $<lastname::::3>$ ',
967
968
969 '$<date_of_birth::%Y-%m-%d>$',
970 '$<date_of_birth::%Y-%m-%d::3>$',
971 '$<date_of_birth::%Y-%m-%d::>$',
972
973 '$<adr_location::home::35>$',
974 '$<gender_mapper::male//female//other::5>$',
975 '$<current_meds::==> %(brand)s %(preparation)s (%(substance)s) <==\\n::50>$',
976 '$<allergy_list::%(descriptor)s, >$',
977
978 '\\noindent Patient: $<lastname>$, $<firstname>$',
979 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
980 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(brand)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
981 ]
982
983 tests = [
984
985 'junk $<lastname::::3>$ junk',
986 'junk $<lastname::abc::3>$ junk',
987 'junk $<lastname::abc>$ junk',
988 'junk $<lastname>$ junk',
989
990 'junk $<lastname>$ junk $<firstname>$ junk',
991 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
992 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
993 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
994
995 ]
996
997 print "testing placeholder regex:", default_placeholder_regex
998 print ""
999
1000 for t in tests:
1001 print 'line: "%s"' % t
1002 print "placeholders:"
1003 for p in regex.findall(default_placeholder_regex, t, regex.IGNORECASE):
1004 print ' => "%s"' % p
1005 print " "
1006
1022
1023
1024
1025
1026
1027
1028 test_placeholder()
1029
1030
1031