1
2 """GNUmed macro primitives.
3
4 This module implements functions a macro can legally use.
5 """
6
7 __author__ = "K.Hilbert <karsten.hilbert@gmx.net>"
8
9 import sys
10 import time
11 import random
12 import types
13 import logging
14 import os
15 import io
16 import datetime
17 import urllib.parse
18 import codecs
19 import re as regex
20
21
22 import wx
23
24
25 if __name__ == '__main__':
26 sys.path.insert(0, '../../')
27 from Gnumed.pycommon import gmI18N
28 if __name__ == '__main__':
29 gmI18N.activate_locale()
30 gmI18N.install_domain()
31 from Gnumed.pycommon import gmGuiBroker
32 from Gnumed.pycommon import gmTools
33 from Gnumed.pycommon import gmBorg
34 from Gnumed.pycommon import gmExceptions
35 from Gnumed.pycommon import gmCfg2
36 from Gnumed.pycommon import gmDateTime
37 from Gnumed.pycommon import gmMimeLib
38 from Gnumed.pycommon import gmShellAPI
39 from Gnumed.pycommon import gmCrypto
40
41 from Gnumed.business import gmPerson
42 from Gnumed.business import gmStaff
43 from Gnumed.business import gmDemographicRecord
44 from Gnumed.business import gmMedication
45 from Gnumed.business import gmPathLab
46 from Gnumed.business import gmPersonSearch
47 from Gnumed.business import gmVaccination
48 from Gnumed.business import gmKeywordExpansion
49 from Gnumed.business import gmPraxis
50
51 from Gnumed.wxpython import gmGuiHelpers
52 from Gnumed.wxpython import gmNarrativeWorkflows
53 from Gnumed.wxpython import gmPatSearchWidgets
54 from Gnumed.wxpython import gmPersonContactWidgets
55 from Gnumed.wxpython import gmPlugin
56 from Gnumed.wxpython import gmEMRStructWidgets
57 from Gnumed.wxpython import gmEncounterWidgets
58 from Gnumed.wxpython import gmListWidgets
59 from Gnumed.wxpython import gmDemographicsWidgets
60 from Gnumed.wxpython import gmDocumentWidgets
61 from Gnumed.wxpython import gmKeywordExpansionWidgets
62 from Gnumed.wxpython import gmPraxisWidgets
63 from Gnumed.wxpython import gmAddressWidgets
64
65
66 _log = logging.getLogger('gm.scripting')
67 _cfg = gmCfg2.gmCfgData()
68
69
70
71
72
73
74 known_injectable_placeholders = [
75 'form_name_long',
76 'form_name_short',
77 'form_version',
78 'form_version_internal',
79 'form_last_modified'
80 ]
81
82
83 __known_variant_placeholders = {
84
85 'free_text': u"""show a dialog for entering some free text:
86 args: <message>//<preset>
87 <message>: shown in input dialog, must not contain either
88 of '::' and whatever the arguments divider is
89 set to (default '//'),
90 <preset>: whatever to initially show inside the input field,
91 caches input per <message>""",
92
93 'text_snippet': """a text snippet, taken from the keyword expansion mechanism:
94 args: <snippet name>//<template>""",
95
96 'data_snippet': """a binary snippet, taken from the keyword expansion mechanism:
97 args: <snippet name>//<template>//<optional target mime type>//<optional target extension>
98 returns full path to an exported copy of the
99 data rather than the data itself,
100 template: string template for outputting the path
101 target mime type: a mime type into which to convert the image, no conversion if not given
102 target extension: target file name extension, derived from target mime type if not given
103 """,
104 u'qrcode': u"""generate QR code file for a text snippet:
105 returns: path to QR code png file
106 args: <text>//<template>
107 text: utf8 text, will always be encoded in 'binary' mode with utf8 as encoding
108 template: %s-template into which to insert the QR code png file path
109 """,
110
111
112 'range_of': """select range of enclosed text (note that this cannot take into account non-length characters such as enclosed LaTeX code
113 args: <enclosed text>
114 """,
115 'if_not_empty': """format text based on template if not empty
116 args: <possibly-empty-text>//<template-if-not-empty>//<alternative-text-if-empty>
117 """,
118
119 u'tex_escape': u"args: string to escape, mostly obsolete now",
120
121 u'url_escape': u"""Escapes a string suitable for use as _data_ in an URL
122 args: text to escape
123 """,
124
125
126 'ph_cfg': u"""Set placeholder handler options.
127 args: option name//option value//macro return string
128 option names:
129 ellipsis: what to use as ellipsis (if anything) when
130 shortening strings or string regions, setting the
131 value to NONE will switch off ellipis handling,
132 default is switched off
133 argumentsdivider: what to use as divider when splitting
134 an argument string into parts, default is '//',
135 note that the 'config' placeholder will ALWAYS
136 use '//' to split its argument string, regardless
137 of which setting of <argumentsdivider> is in effect,
138 use DEFAULT to reset this setting back to the
139 default '//'
140 encoding: the encoding in which data emitted by GNUmed
141 as placeholder replacement needs to be valid in,
142 note that GNUmed will still emit unicode to replacement
143 consumers but it will ensure the data emitted _can_
144 be encoded by this target encoding (by roundtripping
145 unicode-encoding-unicode)
146 valid from where this placeholder is located at
147 until further change,
148 use DEFAULT to reset encoding back to the default
149 which is to not ensure compatibility,
150 if the encoding ends in '-strict' then the placeholder
151 replacement will fail if the roundtrip fails
152 """,
153 u'if_debugging': u"""set text based on whether debugging is active
154 args: <text-if-debugging>//<text-if-not-debugging>
155 """,
156
157 'today': "args: strftime format",
158
159 'gender_mapper': """maps gender of patient to a string:
160 args: <value when person is male> // <is female> // <is other>
161 eg. 'male//female//other'
162 or: 'Lieber Patient//Liebe Patientin'""",
163 'client_version': "the version of the current client as a string (no 'v' in front)",
164
165 'gen_adr_street': """part of a generic address, cached, selected from database:
166 args: optional template//optional selection message//optional cache ID
167 template: %s-style formatting template
168 message: text message shown in address selection list
169 cache ID: used to differentiate separate cached invocations of this placeholder
170 """,
171 'gen_adr_number': """part of a generic address, cached, selected from database:
172 args: optional template//optional selection message//optional cache ID
173 template: %s-style formatting template
174 message: text message shown in address selection list
175 cache ID: used to differentiate separate cached invocations of this placeholder
176 """,
177 'gen_adr_subunit': """part of a generic address, cached, selected from database:
178 args: optional template//optional selection message//optional cache ID
179 template: %s-style formatting template
180 message: text message shown in address selection list
181 cache ID: used to differentiate separate cached invocations of this placeholder
182 """,
183 'gen_adr_location': """part of a generic address, cached, selected from database:
184 args: optional template//optional selection message//optional cache ID
185 template: %s-style formatting template
186 message: text message shown in address selection list
187 cache ID: used to differentiate separate cached invocations of this placeholder
188 """,
189 'gen_adr_suburb': """part of a generic address, cached, selected from database:
190 args: optional template//optional selection message//optional cache ID
191 template: %s-style formatting template
192 message: text message shown in address selection list
193 cache ID: used to differentiate separate cached invocations of this placeholder
194 """,
195 'gen_adr_postcode': """part of a generic address, cached, selected from database:
196 args: optional template//optional selection message//optional cache ID
197 template: %s-style formatting template
198 message: text message shown in address selection list
199 cache ID: used to differentiate separate cached invocations of this placeholder
200 """,
201 'gen_adr_region': """part of a generic address, cached, selected from database:
202 args: optional template//optional selection message//optional cache ID
203 template: %s-style formatting template
204 message: text message shown in address selection list
205 cache ID: used to differentiate separate cached invocations of this placeholder
206 """,
207 'gen_adr_country': """part of a generic address, cached, selected from database:
208 args: optional template//optional selection message//optional cache ID
209 template: %s-style formatting template
210 message: text message shown in address selection list
211 cache ID: used to differentiate separate cached invocations of this placeholder
212 """,
213
214 'receiver_name': """the receiver name, cached, selected from database:
215 receivers are presented for selection from people/addresses related
216 to the patient in some way or other,
217 args: optional template//optional cache ID
218 template: %s-style formatting template
219 cache ID: used to differentiate separate cached invocations of this placeholder
220 """,
221 'receiver_street': """part of a receiver address, cached, selected from database:
222 receivers are presented for selection from people/addresses related
223 to the patient in some way or other,
224 args: optional template//optional cache ID
225 template: %s-style formatting template
226 cache ID: used to differentiate separate cached invocations of this placeholder
227 """,
228 'receiver_number': """part of a receiver address, cached, selected from database:
229 receivers are presented for selection from people/addresses related
230 to the patient in some way or other,
231 args: optional template//optional cache ID
232 template: %s-style formatting template
233 cache ID: used to differentiate separate cached invocations of this placeholder
234 """,
235 'receiver_subunit': """part of a receiver address, cached, selected from database:
236 receivers are presented for selection from people/addresses related
237 to the patient in some way or other,
238 args: optional template//optional cache ID
239 template: %s-style formatting template
240 cache ID: used to differentiate separate cached invocations of this placeholder
241 """,
242 'receiver_location': """part of a receiver address, cached, selected from database:
243 receivers are presented for selection from people/addresses related
244 to the patient in some way or other,
245 args: optional template//optional cache ID
246 template: %s-style formatting template
247 cache ID: used to differentiate separate cached invocations of this placeholder
248 """,
249 'receiver_suburb': """part of a receiver address, cached, selected from database:
250 receivers are presented for selection from people/addresses related
251 to the patient in some way or other,
252 args: optional template//optional cache ID
253 template: %s-style formatting template
254 cache ID: used to differentiate separate cached invocations of this placeholder
255 """,
256 'receiver_postcode': """part of a receiver address, cached, selected from database:
257 receivers are presented for selection from people/addresses related
258 to the patient in some way or other,
259 args: optional template//optional cache ID
260 template: %s-style formatting template
261 cache ID: used to differentiate separate cached invocations of this placeholder
262 """,
263 'receiver_region': """part of a receiver address, cached, selected from database:
264 receivers are presented for selection from people/addresses related
265 to the patient in some way or other,
266 args: optional template//optional cache ID
267 template: %s-style formatting template
268 cache ID: used to differentiate separate cached invocations of this placeholder
269 """,
270 'receiver_country': """part of a receiver address, cached, selected from database:
271 receivers are presented for selection from people/addresses related
272 to the patient in some way or other,
273 args: optional template//optional cache ID
274 template: %s-style formatting template
275 cache ID: used to differentiate separate cached invocations of this placeholder
276 """,
277
278
279 'name': "args: template for name parts arrangement",
280 'date_of_birth': "args: strftime date/time format directive",
281
282 'patient_address': "args: <type of address>//<optional formatting template>",
283 'adr_street': "args: <type of address>, cached per type",
284 'adr_number': "args: <type of address>, cached per type",
285 'adr_subunit': "args: <type of address>, cached per type",
286 'adr_location': "args: <type of address>, cached per type",
287 'adr_suburb': "args: <type of address>, cached per type",
288 'adr_postcode': "args: <type of address>, cached per type",
289 'adr_region': "args: <type of address>, cached per type",
290 'adr_country': "args: <type of address>, cached per type",
291
292 'patient_comm': "args: <comm channel type as per database>//<%(field)s-template>",
293
294 'patient_vcf': """returns path to VCF for current patient
295 args: <template>
296 template: %s-template for path
297 """,
298 'patient_gdt': """returns path to GDT for current patient
299 args: <template>
300 template: %s-template for path
301 """,
302 u'patient_mcf': u"""returns MECARD for current patient
303 args: <format>//<template>
304 format: fmt=qr|mcf|txt
305 qr: QR code png file path,
306 mcf: MECARD .mcf file path,
307 txt: MECARD string,
308 default - if omitted - is "txt",
309 template: tmpl=<%s-template string>, "%s" if omitted
310 """,
311
312 'patient_tags': "args: <%(field)s-template>//<separator>",
313
314 'patient_photo': """outputs URL to exported patient photo (cached per mime type and extension):
315 args: <template>//<optional target mime type>//<optional target extension>,
316 returns full path to an exported copy of the
317 image rather than the image data itself,
318 returns u'' if no mugshot available,
319 template: string template for outputting the path
320 target mime type: a mime type into which to convert the image, no conversion if not given
321 target extension: target file name extension, derived from target mime type if not given""",
322 'external_id': "args: <type of ID>//<issuer of ID>",
323
324
325
326 'soap': "get all of SOAPU/ADMIN, no template in args needed",
327 'soap_s': "get subset of SOAPU/ADMIN, no template in args needed",
328 'soap_o': "get subset of SOAPU/ADMIN, no template in args needed",
329 'soap_a': "get subset of SOAPU/ADMIN, no template in args needed",
330 'soap_p': "get subset of SOAPU/ADMIN, no template in args needed",
331 'soap_u': "get subset of SOAPU/ADMIN, no template in args needed",
332 'soap_admin': "get subset of SOAPU/ADMIN, no template in args needed",
333
334 'progress_notes': """get progress notes:
335 args: categories//template
336 categories: string with 'soapu '; ' ' == None == admin
337 template: u'something %s something' (do not include // in template !)""",
338
339 'soap_for_encounters': """lets the user select a list of encounters for which:
340 LaTeX formatted progress notes are emitted,
341 args: soap categories // strftime date format""",
342
343 'soap_by_issue': """lets the user select a list of issues and then SOAP entries from those issues:
344 args: soap categories // strftime date format // template""",
345
346 'soap_by_episode': """lets the user select a list of episodes and then SOAP entries from those episodes:
347 args: soap categories // strftime date format // template""",
348
349 'emr_journal': """returns EMR journal view entries:
350 args format: <categories>//<template>//<line length>//<time range>//<target format>
351 categories: string with any of "s", "o", "a", "p", "u", " "; (" " == None == admin category)
352 template: something %s something else (Do not include // in the template !)
353 line length: the maximum length of individual lines, not the total placeholder length
354 time range: the number of weeks going back in time if given as a single number, or else it must be a valid PostgreSQL interval definition (w/o the ::interval)""",
355
356 'substance_abuse': """returns substance abuse entries:
357 args: line template
358 """,
359
360 'current_meds': """returns current medications:
361 args: line template//<select>
362 <select>: if this is present the user will be asked which meds to export""",
363
364 'current_meds_for_rx': """formats substance intakes either by substance (non-product intakes) or by producdt (once per product intake, even if multi-component):
365 args: <line template>
366 <line_template>: template into which to insert each intake, keys from
367 clin.v_substance_intakes, special additional keys:
368 %(contains)s -- list of components
369 %(amount2dispense)s -- how much/many to dispense""",
370
371 'current_meds_AMTS': """emit LaTeX longtable lines with appropriate page breaks:
372 also creates per-page AMTS QR codes and sets the
373 following internal placeholders:
374 amts_png_file_1
375 amts_png_file_2
376 amts_png_file_3
377 amts_data_file_1
378 amts_data_file_2
379 amts_data_file_3
380 amts_png_file_current_page
381 amts_data_file_utf8
382 amts_png_file_utf8
383 the last of which contains the LaTeX command \\thepage (such that
384 LaTeX can use this in, say, page headers) but omitting the .png
385 (for which LaTeX will look by itself),
386 note that you will have to use the 2nd- or 3rd-pass placeholder
387 format if you plan to insert the above because they will only be
388 available by first (or second) pass processing of the initial
389 placeholder "current_meds_AMTS"
390 """,
391 'current_meds_AMTS_enhanced': """emit LaTeX longtable lines with appropriate page breaks:
392 this returns the same content as current_meds_AMTS except that
393 it does not truncate output data whenever possible
394 """,
395
396 'current_meds_table': "emits a LaTeX table, no arguments",
397 'current_meds_notes': "emits a LaTeX table, no arguments",
398 'lab_table': "emits a LaTeX table, no arguments",
399 'test_results': "args: <%(field)s-template>//<date format>//<line separator (EOL)>",
400 'latest_vaccs_table': "emits a LaTeX table, no arguments",
401 'vaccination_history': "args: <%(field)s-template//date format> to format one vaccination per line",
402 'allergy_state': "no arguments",
403 'allergies': "args: line template, one allergy per line",
404 'allergy_list': "args holds: template per allergy, all allergies on one line",
405 'problems': "args holds: line template, one problem per line",
406 'diagnoses': 'args: line template, one diagnosis per line',
407 'PHX': "Past medical HiXtory; args: line template//separator//strftime date format",
408 'encounter_list': "args: per-encounter template, each ends up on one line",
409
410 'documents': """retrieves documents from the archive:
411 args: <select>//<description>//<template>//<path template>//<path>
412 select: let user select which documents to include, optional, if not given: all documents included
413 description: whether to include descriptions, optional
414 template: something %(field)s something else (do not include '//' or '::' itself in the template)
415 path template: the template for outputting the path to exported
416 copies of the document pages, if not given no pages are exported,
417 this template can contain "%(name)s" and/or "%(fullpath)s" which
418 is replaced by the appropriate value for each exported file
419 path: into which path to export copies of the document pages, temp dir if not given""",
420
421 'reminders': """patient reminders:
422 args: <template>//<date format>
423 template: something %(field)s something else (do not include '//' or '::' itself in the template)""",
424
425 'external_care': """External care entries:
426 args: <template>
427 template: something %(field)s something else (do not include '//' or '::' itself in the template)""",
428
429
430 'current_provider': "no arguments",
431 'current_provider_name': """formatted name of current provider:
432 args: <template>,
433 template: something %(field)s something else (do not include '//' or '::' itself in the template)
434 """,
435 'current_provider_title': """formatted name of current provider:
436 args: <optional template>,
437 template: something %(title)s something else (do not include '//' or '::' itself in the template)
438 """,
439 'current_provider_firstnames': """formatted name of current provider:
440 args: <optional template>,
441 template: something %(firstnames)s something else (do not include '//' or '::' itself in the template)
442 """,
443 'current_provider_lastnames': """formatted name of current provider:
444 args: <optional template>,
445 template: something %(lastnames)s something else (do not include '//' or '::' itself in the template)
446 """,
447 'current_provider_external_id': "args: <type of ID>//<issuer of ID>",
448 'primary_praxis_provider': "primary provider for current patient in this praxis",
449 'primary_praxis_provider_external_id': "args: <type of ID>//<issuer of ID>",
450
451
452
453 'praxis': """retrieve current branch of your praxis:
454 args: <template>//select
455 template: something %(field)s something else (do not include '//' or '::' itself in the template)
456 select: if this is present allow selection of the branch rather than using the current branch""",
457
458 'praxis_address': "args: <optional formatting template>",
459 'praxis_comm': "args: type//<optional formatting template>",
460 'praxis_id': "args: <type of ID>//<issuer of ID>//<optional formatting template>",
461 'praxis_vcf': """returns path to VCF for current praxis branch
462 args: <template>
463 template: %s-template for path
464 """,
465 u'praxis_mcf': u"""returns MECARD for current praxis branch
466 args: <format>//<template>
467 format: fmt=qr|mcf|txt
468 qr: QR code png file path,
469 mcf: MECARD .mcf file path,
470 txt: MECARD string,
471 default - if omitted - is "txt",
472 template: tmpl=<%s-template string>, "%s" if omitted
473 """,
474 u'praxis_scan2pay': u"""return scan2pay data or QR code for current praxis
475 args: <format>,
476 format: fmt=qr|txt
477 qr: QR code png file path,
478 txt: scan2pay data string,
479 default - if omitted: qr
480 """,
481
482
483 'bill': """retrieve a bill
484 args: <template>//<date format>
485 template: something %(field)s something else (do not include '//' or '::' itself in the template)
486 date format: strftime date format""",
487 'bill_scan2pay': u"""return scan2pay data or QR code for a bill
488 args: <format>,
489 format: fmt=qr|txt
490 qr: QR code png file path,
491 txt: scan2pay data string,
492 default - if omitted: qr
493 """,
494 'bill_item': """retrieve the items of a previously retrieved (and therefore cached until the next retrieval) bill
495 args: <template>//<date format>
496 template: something %(field)s something else (do not include '//' or '::' itself in the template)
497 date format: strftime date format""",
498 'bill_adr_street': "args: optional template (%s-style formatting template); cached per bill",
499 'bill_adr_number': "args: optional template (%s-style formatting template); cached per bill",
500 'bill_adr_subunit': "args: optional template (%s-style formatting template); cached per bill",
501 'bill_adr_location': "args: optional template (%s-style formatting template); cached per bill",
502 'bill_adr_suburb': "args: optional template (%s-style formatting template); cached per bill",
503 'bill_adr_postcode': "args: optional template (%s-style formatting template); cached per bill",
504 'bill_adr_region': "args: optional template (%s-style formatting template); cached per bill",
505 'bill_adr_country': "args: optional template (%s-style formatting template); cached per bill"
506
507 }
508
509 known_variant_placeholders = __known_variant_placeholders.keys()
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528 default_placeholder_regex = r'\$<[^<:]+::.*?::\d*?>\$|\$<[^<:]+::.*?::\d+-\d+>\$'
529 first_pass_placeholder_regex = r'|'.join ([
530 r'\$<[^<:]+::.*?(?=::\d*?>\$)::\d*?>\$',
531 r'\$<[^<:]+::.*?(?=::\d+-\d+>\$)::\d+-\d+>\$'
532 ])
533 second_pass_placeholder_regex = r'|'.join ([
534 r'\$<<[^<:]+?::.*?(?=::\d*?>>\$)::\d*?>>\$',
535 r'\$<<[^<:]+?::.*?(?=::\d+-\d+>>\$)::\d+-\d+>>\$'
536 ])
537 third_pass_placeholder_regex = r'|'.join ([
538 r'\$<<<[^<:]+?::.*?(?=::\d*?>>>\$)::\d*?>>>\$',
539 r'\$<<<[^<:]+?::.*?(?=::\d+-\d+>>>\$)::\d+-\d+>>>\$'
540 ])
541
542 default_placeholder_start = '$<'
543 default_placeholder_end = '>$'
544
545
547
548 fname = gmTools.get_unique_filename(prefix = 'gm-placeholders-', suffix = '.txt')
549 ph_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'replace')
550
551 ph_file.write('Here you can find some more documentation on placeholder use:\n')
552 ph_file.write('\n http://wiki.gnumed.de/bin/view/Gnumed/GmManualLettersForms\n\n\n')
553
554 ph_file.write('Variable placeholders:\n')
555 ph_file.write('Usage: $<PLACEHOLDER_NAME::ARGUMENTS::REGION_DEFINITION>$)\n')
556 ph_file.write(' REGION_DEFINITION:\n')
557 ph_file.write('* a single number specifying the maximum output length or\n')
558 ph_file.write('* a number, a "-", followed by a second number specifying the region of the string to return\n')
559 ph_file.write('ARGUMENTS:\n')
560 ph_file.write('* depend on the actual placeholder (see there)\n')
561 ph_file.write('* if a template is supported it will be used to %-format the output\n')
562 ph_file.write('* templates may be either %s-style or %(name)s-style\n')
563 ph_file.write('* templates cannot contain "::"\n')
564 ph_file.write('* templates cannot contain whatever the arguments divider is set to (default "//")\n')
565 for ph in known_variant_placeholders:
566 txt = __known_variant_placeholders[ph]
567 ph_file.write('\n')
568 ph_file.write(' ---=== %s ===---\n' % ph)
569 ph_file.write('\n')
570 ph_file.write(txt)
571 ph_file.write('\n\n')
572 ph_file.write('\n')
573
574 ph_file.write('Known injectable placeholders (use like: $<PLACEHOLDER_NAME::ARGUMENTS::MAX OUTPUT LENGTH>$):\n')
575 for ph in known_injectable_placeholders:
576 ph_file.write(' %s\n' % ph)
577 ph_file.write('\n')
578
579 ph_file.close()
580 gmMimeLib.call_viewer_on_file(aFile = fname, block = False)
581
582
584 """Returns values for placeholders.
585
586 - patient related placeholders operate on the currently active patient
587 - is passed to the forms handling code, for example
588
589 Return values when .debug is False:
590 - errors with placeholders return None
591 - placeholders failing to resolve to a value return an empty string
592
593 Return values when .debug is True:
594 - errors with placeholders return an error string
595 - placeholders failing to resolve to a value return a warning string
596
597 There are several types of placeholders:
598
599 injectable placeholders
600 - they must be set up before use by set_placeholder()
601 - they should be removed after use by unset_placeholder()
602 - the syntax is like extended static placeholders
603 - known ones are listed in known_injectable_placeholders
604 - per-form ones can be used but must exist before
605 the form is processed
606
607 variant placeholders
608 - those are listed in known_variant_placeholders
609 - they are parsed into placeholder, data, and maximum length
610 - the length is optional
611 - data is passed to the handler
612
613 Note that this cannot be called from a non-gui thread unless
614 wrapped in wx.CallAfter().
615 """
617
618 self.pat = gmPerson.gmCurrentPatient()
619 self.debug = False
620
621 self.invalid_placeholder_template = _('invalid placeholder >>>>>%s<<<<<')
622
623 self.__injected_placeholders = {}
624 self.__cache = {}
625
626 self.__esc_style = None
627 self.__esc_func = lambda x:x
628
629 self.__ellipsis = None
630 self.__args_divider = '//'
631 self.__data_encoding = None
632 self.__data_encoding_strict = False
633
634
635
636
638 _log.debug('setting [%s]', key)
639 try:
640 known_injectable_placeholders.index(key)
641 except ValueError:
642 _log.debug('injectable placeholder [%s] unknown', key)
643 if known_only:
644 raise
645 self.__injected_placeholders[key] = value
646
647
649 _log.debug('unsetting [%s]', key)
650 try:
651 del self.__injected_placeholders[key]
652 except KeyError:
653 _log.debug('injectable placeholder [%s] unknown', key)
654
655
657 self.__cache[key] = value
658
660 del self.__cache[key]
661
662
666
667 escape_style = property(lambda x:x, _set_escape_style)
668
669
678
679 escape_function = property(lambda x:x, _set_escape_function)
680
681
686
687 ellipsis = property(lambda x: self.__ellipsis, _set_ellipsis)
688
689
691 if divider == 'DEFAULT':
692 divider = '//'
693 self.__args_divider = divider
694
695 arguments_divider = property(lambda x: self.__args_divider, _set_arguments_divider)
696
697
699 if encoding == 'NONE':
700 self.__data_encoding = None
701 self.__data_encoding_strict = False
702
703 self.__data_encoding_strict = False
704 if encoding.endswith('-strict'):
705 self.__data_encoding_strict = True
706 encoding = encoding[:-7]
707 try:
708 codecs.lookup(encoding)
709 self.__data_encoding = encoding
710 except LookupError:
711 _log.error('<codecs> module can NOT handle encoding [%s]' % enc)
712
713 data_encoding = property(lambda x: self.__data_encoding, _set_data_encoding)
714
715
716 placeholder_regex = property(lambda x: default_placeholder_regex, lambda x:x)
717
718 first_pass_placeholder_regex = property(lambda x: first_pass_placeholder_regex, lambda x:x)
719 second_pass_placeholder_regex = property(lambda x: second_pass_placeholder_regex, lambda x:x)
720 third_pass_placeholder_regex = property(lambda x: third_pass_placeholder_regex, lambda x:x)
721
722
724 region_str = region_str.strip()
725
726 if region_str == '':
727 return None, None
728
729 try:
730 pos_last_char = int(region_str)
731 return 0, pos_last_char
732 except (TypeError, ValueError):
733 _log.debug('region definition not a simple length')
734
735
736 first_last = region_str.split('-')
737 if len(first_last) != 2:
738 _log.error('invalid placeholder region definition: %s', region_str)
739 raise ValueError
740
741 try:
742 pos_first_char = int(first_last[0].strip())
743 pos_last_char = int(first_last[1].strip())
744 except (TypeError, ValueError):
745 _log.error('invalid placeholder region definition: %s', region_str)
746 raise ValueError
747
748
749 if pos_first_char > 0:
750 pos_first_char -= 1
751
752 return pos_first_char, pos_last_char
753
754
756 if self.__data_encoding is None:
757 return data_str
758
759 try:
760 codecs.encode(data_str, self.__data_encoding, 'strict')
761 return data_str
762 except UnicodeEncodeError:
763 _log.error('cannot strict-encode string into [%s]: %s', self.__data_encoding, data_str)
764
765 if self.__data_encoding_strict:
766 return 'not compatible with encoding [%s]: %s' % (self.__data_encoding, data_str)
767
768 try:
769 import unidecode
770 except ImportError:
771 _log.debug('cannot transliterate, <unidecode> module not installed')
772 return codecs.encode(data_str, self.__data_encoding, 'replace').decode(self.__data_encoding)
773
774 return unidecode.unidecode(data_str).decode('utf8')
775
776
777
778
780 """Map self['placeholder'] to self.placeholder.
781
782 This is useful for replacing placeholders parsed out
783 of documents as strings.
784
785 Unknown/invalid placeholders still deliver a result but
786 it will be glaringly obvious if debugging is enabled.
787 """
788 _log.debug('replacing [%s]', placeholder)
789
790 original_placeholder_def = placeholder
791
792
793 if placeholder.startswith(default_placeholder_start):
794 placeholder = placeholder.lstrip('$').lstrip('<')
795 if placeholder.endswith(default_placeholder_end):
796 placeholder = placeholder.rstrip('$').rstrip('>')
797 else:
798 _log.error('placeholder must either start with [%s] and end with [%s] or neither of both', default_placeholder_start, default_placeholder_end)
799 if self.debug:
800 return self._escape(self.invalid_placeholder_template % original_placeholder_def)
801 return None
802
803
804 parts = placeholder.split('::::', 1)
805 if len(parts) == 2:
806 ph_name, region_str = parts
807 is_an_injectable = True
808 try:
809 val = self.__injected_placeholders[ph_name]
810 except KeyError:
811 is_an_injectable = False
812 except:
813 _log.exception('injectable placeholder handling error: %s', original_placeholder_def)
814 if self.debug:
815 return self._escape(self.invalid_placeholder_template % original_placeholder_def)
816 return None
817 if is_an_injectable:
818 if val is None:
819 if self.debug:
820 return self._escape('injectable placeholder [%s]: no value available' % ph_name)
821 return placeholder
822 try:
823 pos_first_char, pos_last_char = self.__parse_region_definition(region_str)
824 except ValueError:
825 if self.debug:
826 return self._escape(self.invalid_placeholder_template % original_placeholder_def)
827 return None
828 if pos_last_char is None:
829 return self.__make_compatible_with_encoding(val)
830
831 if len(val) > (pos_last_char - pos_first_char):
832
833 if self.__ellipsis is not None:
834 return self.__make_compatible_with_encoding(val[pos_first_char:(pos_last_char-len(self.__ellipsis))] + self.__ellipsis)
835 return self.__make_compatible_with_encoding(val[pos_first_char:pos_last_char])
836
837
838 if len(placeholder.split('::', 2)) < 3:
839 _log.error('invalid placeholder structure: %s', original_placeholder_def)
840 if self.debug:
841 return self._escape(self.invalid_placeholder_template % original_placeholder_def)
842 return None
843
844 ph_name, data_and_lng = placeholder.split('::', 1)
845 options, region_str = data_and_lng.rsplit('::', 1)
846 _log.debug('placeholder parts: name=[%s]; region_def=[%s]; options=>>>%s<<<', ph_name, region_str, options)
847 try:
848 pos_first_char, pos_last_char = self.__parse_region_definition(region_str)
849 except ValueError:
850 if self.debug:
851 return self._escape(self.invalid_placeholder_template % original_placeholder_def)
852 return None
853
854 handler = getattr(self, '_get_variant_%s' % ph_name, None)
855 if handler is None:
856 _log.warning('no handler <_get_variant_%s> for placeholder %s', ph_name, original_placeholder_def)
857 if self.debug:
858 return self._escape(self.invalid_placeholder_template % original_placeholder_def)
859 return None
860
861 try:
862 val = handler(data = options)
863 if pos_last_char is None:
864 return self.__make_compatible_with_encoding(val)
865
866 if len(val) > (pos_last_char - pos_first_char):
867
868 if self.__ellipsis is not None:
869 return self.__make_compatible_with_encoding(val[pos_first_char:(pos_last_char-len(self.__ellipsis))] + self.__ellipsis)
870 return self.__make_compatible_with_encoding(val[pos_first_char:pos_last_char])
871 except:
872 _log.exception('placeholder handling error: %s', original_placeholder_def)
873 if self.debug:
874 return self._escape(self.invalid_placeholder_template % original_placeholder_def)
875 return None
876
877 _log.error('something went wrong, should never get here')
878 return None
879
880
881
882
884 options = data.split('//')
885 name = options[0]
886 val = options[1]
887 if name == 'ellipsis':
888 self.ellipsis = val
889 elif name == 'argumentsdivider':
890 self.arguments_divider = val
891 elif name == 'encoding':
892 self.data_encoding = val
893 if len(options) > 2:
894 return options[2] % {'name': name, 'value': val}
895 return ''
896
898 return self._escape (
899 gmTools.coalesce (
900 _cfg.get(option = 'client_version'),
901 '%s' % self.__class__.__name__
902 )
903 )
904
933
949
951
952 select = False
953 include_descriptions = False
954 template = '%s'
955 path_template = None
956 export_path = None
957
958 data_parts = data.split(self.__args_divider)
959
960 if 'select' in data_parts:
961 select = True
962 data_parts.remove('select')
963
964 if 'description' in data_parts:
965 include_descriptions = True
966 data_parts.remove('description')
967
968 template = data_parts[0]
969
970 if len(data_parts) > 1:
971 path_template = data_parts[1]
972
973 if len(data_parts) > 2:
974 export_path = data_parts[2]
975
976
977 if export_path is not None:
978 export_path = os.path.normcase(os.path.expanduser(export_path))
979 gmTools.mkdir(export_path)
980
981
982 if select:
983 docs = gmDocumentWidgets.manage_documents(msg = _('Select the patient documents to reference from the new document.'), single_selection = False)
984 else:
985 docs = self.pat.document_folder.documents
986
987 if docs is None:
988 return ''
989
990 lines = []
991 for doc in docs:
992 lines.append(template % doc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
993 if include_descriptions:
994 for desc in doc.get_descriptions(max_lng = None):
995 lines.append(self._escape(desc['text'] + '\n'))
996 if path_template is not None:
997 for part_name in doc.save_parts_to_files(export_dir = export_path):
998 path, name = os.path.split(part_name)
999 lines.append(path_template % {'fullpath': part_name, 'name': name})
1000
1001 return '\n'.join(lines)
1002
1004
1005 encounters = gmEncounterWidgets.select_encounters(single_selection = False)
1006 if not encounters:
1007 return ''
1008
1009 template = data
1010
1011 lines = []
1012 for enc in encounters:
1013 try:
1014 lines.append(template % enc.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style))
1015 except:
1016 lines.append('error formatting encounter')
1017 _log.exception('problem formatting encounter list')
1018 _log.error('template: %s', template)
1019 _log.error('encounter: %s', encounter)
1020
1021 return '\n'.join(lines)
1022
1024 """Select encounters from list and format SOAP thereof.
1025
1026 data: soap_cats (' ' -> None -> admin) // date format
1027 """
1028
1029 cats = None
1030 date_format = None
1031
1032 if data is not None:
1033 data_parts = data.split(self.__args_divider)
1034
1035
1036 if len(data_parts[0]) > 0:
1037 cats = []
1038 if ' ' in data_parts[0]:
1039 cats.append(None)
1040 data_parts[0] = data_parts[0].replace(' ', '')
1041 cats.extend(list(data_parts[0]))
1042
1043
1044 if len(data_parts) > 1:
1045 if len(data_parts[1]) > 0:
1046 date_format = data_parts[1]
1047
1048 encounters = gmEncounterWidgets.select_encounters(single_selection = False)
1049 if not encounters:
1050 return ''
1051
1052 chunks = []
1053 for enc in encounters:
1054 chunks.append(enc.format_latex (
1055 date_format = date_format,
1056 soap_cats = cats,
1057 soap_order = 'soap_rank, date'
1058 ))
1059
1060 return ''.join(chunks)
1061
1063
1064 cats = list('soapu')
1065 cats.append(None)
1066 template = '%s'
1067 interactive = True
1068 line_length = 9999
1069 time_range = None
1070
1071 if data is not None:
1072 data_parts = data.split(self.__args_divider)
1073
1074
1075 cats = []
1076
1077 for c in list(data_parts[0]):
1078 if c == ' ':
1079 c = None
1080 cats.append(c)
1081
1082 if cats == '':
1083 cats = list('soapu').append(None)
1084
1085
1086 if len(data_parts) > 1:
1087 template = data_parts[1]
1088
1089
1090 if len(data_parts) > 2:
1091 try:
1092 line_length = int(data_parts[2])
1093 except:
1094 line_length = 9999
1095
1096
1097 if len(data_parts) > 3:
1098 try:
1099 time_range = 7 * int(data_parts[3])
1100 except:
1101
1102
1103 time_range = data_parts[3]
1104
1105
1106 narr = self.pat.emr.get_as_journal(soap_cats = cats, time_range = time_range)
1107
1108 if len(narr) == 0:
1109 return ''
1110
1111 keys = narr[0].keys()
1112 lines = []
1113 line_dict = {}
1114 for n in narr:
1115 for key in keys:
1116 if isinstance(n[key], str):
1117 line_dict[key] = self._escape(text = n[key])
1118 continue
1119 line_dict[key] = n[key]
1120 try:
1121 lines.append((template % line_dict)[:line_length])
1122 except KeyError:
1123 return 'invalid key in template [%s], valid keys: %s]' % (template, str(keys))
1124
1125 return '\n'.join(lines)
1126
1128 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = 'issue')
1129
1131 return self.__get_variant_soap_by_issue_or_episode(data = data, mode = 'episode')
1132
1134
1135
1136 cats = list('soapu')
1137 cats.append(None)
1138
1139 date_format = None
1140 template = '%s'
1141
1142 if data is not None:
1143 data_parts = data.split(self.__args_divider)
1144
1145
1146 if len(data_parts[0]) > 0:
1147 cats = []
1148 if ' ' in data_parts[0]:
1149 cats.append(None)
1150 cats.extend(list(data_parts[0].replace(' ', '')))
1151
1152
1153 if len(data_parts) > 1:
1154 if len(data_parts[1]) > 0:
1155 date_format = data_parts[1]
1156
1157
1158 if len(data_parts) > 2:
1159 if len(data_parts[2]) > 0:
1160 template = data_parts[2]
1161
1162 if mode == 'issue':
1163 narr = gmNarrativeWorkflows.select_narrative_by_issue(soap_cats = cats)
1164 else:
1165 narr = gmNarrativeWorkflows.select_narrative_by_episode(soap_cats = cats)
1166
1167 if narr is None:
1168 return ''
1169
1170 if len(narr) == 0:
1171 return ''
1172
1173 try:
1174 narr = [ template % n.fields_as_dict(date_format = date_format, escape_style = self.__esc_style) for n in narr ]
1175 except KeyError:
1176 return 'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
1177
1178 return '\n'.join(narr)
1179
1181 return self._get_variant_soap(data = data)
1182
1184 return self._get_variant_soap(data = 's')
1185
1187 return self._get_variant_soap(data = 'o')
1188
1190 return self._get_variant_soap(data = 'a')
1191
1193 return self._get_variant_soap(data = 'p')
1194
1196 return self._get_variant_soap(data = 'u')
1197
1199 return self._get_variant_soap(data = ' ')
1200
1202
1203
1204 cats = list('soapu')
1205 cats.append(None)
1206 template = '%(narrative)s'
1207
1208 if data is not None:
1209 data_parts = data.split(self.__args_divider)
1210
1211
1212 cats = []
1213
1214 for cat in list(data_parts[0]):
1215 if cat == ' ':
1216 cat = None
1217 cats.append(cat)
1218
1219 if cats == '':
1220 cats = list('soapu')
1221 cats.append(None)
1222
1223
1224 if len(data_parts) > 1:
1225 template = data_parts[1]
1226
1227
1228 narr = gmNarrativeWorkflows.select_narrative(soap_cats = cats)
1229
1230 if narr is None:
1231 return ''
1232
1233 if len(narr) == 0:
1234 return ''
1235
1236
1237
1238
1239 if '%s' in template:
1240 narr = [ self._escape(n['narrative']) for n in narr ]
1241 else:
1242 narr = [ n.fields_as_dict(escape_style = self.__esc_style) for n in narr ]
1243
1244 try:
1245 narr = [ template % n for n in narr ]
1246 except KeyError:
1247 return 'invalid key in template [%s], valid keys: %s]' % (template, str(narr[0].keys()))
1248 except TypeError:
1249 return 'cannot mix "%%s" and "%%(field)s" in template [%s]' % template
1250
1251 return '\n'.join(narr)
1252
1253
1255 return self._get_variant_name(data = '%(title)s')
1256
1258 return self._get_variant_name(data = '%(firstnames)s')
1259
1261 return self._get_variant_name(data = '%(lastnames)s')
1262
1264 if data is None:
1265 return [_('template is missing')]
1266
1267 name = self.pat.get_active_name()
1268
1269 parts = {
1270 'title': self._escape(gmTools.coalesce(name['title'], '')),
1271 'firstnames': self._escape(name['firstnames']),
1272 'lastnames': self._escape(name['lastnames']),
1273 'preferred': self._escape(gmTools.coalesce (
1274 value2test = name['preferred'],
1275 return_instead = ' ',
1276 template4value = ' "%s" '
1277 ))
1278 }
1279
1280 return data % parts
1281
1282
1285
1286
1287
1289
1290 values = data.split('//', 2)
1291
1292 if len(values) == 2:
1293 male_value, female_value = values
1294 other_value = '<unkown gender>'
1295 elif len(values) == 3:
1296 male_value, female_value, other_value = values
1297 else:
1298 return _('invalid gender mapping layout: [%s]') % data
1299
1300 if self.pat['gender'] == 'm':
1301 return self._escape(male_value)
1302
1303 if self.pat['gender'] == 'f':
1304 return self._escape(female_value)
1305
1306 return self._escape(other_value)
1307
1308
1309
1311
1312 template = '%s'
1313 msg = _('Select the address you want to use !')
1314 cache_id = ''
1315 options = data.split('//', 4)
1316 if len(options) > 0:
1317 template = options[0]
1318 if template.strip() == '':
1319 template = '%s'
1320 if len(options) > 1:
1321 msg = options[1]
1322 if len(options) > 2:
1323 cache_id = options[2]
1324
1325 cache_key = 'generic_address::' + cache_id
1326 try:
1327 adr2use = self.__cache[cache_key]
1328 _log.debug('cache hit (%s): [%s]', cache_key, adr2use)
1329 except KeyError:
1330 adr2use = None
1331
1332 if adr2use is None:
1333 dlg = gmAddressWidgets.cAddressSelectionDlg(None, -1)
1334 dlg.message = msg
1335 choice = dlg.ShowModal()
1336 adr2use = dlg.address
1337 dlg.DestroyLater()
1338 if choice == wx.ID_CANCEL:
1339 return ''
1340 self.__cache[cache_key] = adr2use
1341
1342 return template % self._escape(adr2use[part])
1343
1345 return self.__get_variant_gen_adr_part(data = data, part = 'street')
1346
1348 return self.__get_variant_gen_adr_part(data = data, part = 'number')
1349
1351 return self.__get_variant_gen_adr_part(data = data, part = 'subunit')
1352
1354 return self.__get_variant_gen_adr_part(data = data, part = 'urb')
1355
1357 return self.__get_variant_gen_adr_part(data = data, part = 'suburb')
1358
1360 return self.__get_variant_gen_adr_part(data = data, part = 'postcode')
1361
1363 return self.__get_variant_gen_adr_part(data = data, part = 'l10n_region')
1364
1366 return self.__get_variant_gen_adr_part(data = data, part = 'l10n_country')
1367
1369
1370 template = '%s'
1371 cache_id = ''
1372 options = data.split('//', 3)
1373 if len(options) > 0:
1374 template = options[0]
1375 if template.strip() == '':
1376 template = '%s'
1377 if len(options) > 1:
1378 cache_id = options[1]
1379
1380 cache_key = 'receiver::' + cache_id
1381 try:
1382 name, adr = self.__cache[cache_key]
1383 _log.debug('cache hit (%s): [%s:%s]', cache_key, name, adr)
1384 except KeyError:
1385 name = None
1386 adr = None
1387
1388 if name is None:
1389 from Gnumed.wxpython import gmFormWidgets
1390 dlg = gmFormWidgets.cReceiverSelectionDlg(None, -1)
1391 dlg.patient = self.pat
1392 choice = dlg.ShowModal()
1393 name = dlg.name
1394 adr = dlg.address
1395 dlg.DestroyLater()
1396 if choice == wx.ID_CANCEL:
1397 return ''
1398 self.__cache[cache_key] = (name, adr)
1399
1400 if part == 'name':
1401 return template % self._escape(name)
1402
1403 return template % self._escape(gmTools.coalesce(adr[part], ''))
1404
1406 return self.__get_variant_receiver_part(data = data, part = 'name')
1407
1409 return self.__get_variant_receiver_part(data = data, part = 'street')
1410
1412 return self.__get_variant_receiver_part(data = data, part = 'number')
1413
1415 return self.__get_variant_receiver_part(data = data, part = 'subunit')
1416
1418 return self.__get_variant_receiver_part(data = data, part = 'urb')
1419
1421 return self.__get_variant_receiver_part(data = data, part = 'suburb')
1422
1424 return self.__get_variant_receiver_part(data = data, part = 'postcode')
1425
1427 return self.__get_variant_receiver_part(data = data, part = 'l10n_region')
1428
1430 return self.__get_variant_receiver_part(data = data, part = 'l10n_country')
1431
1433
1434 data_parts = data.split(self.__args_divider)
1435
1436
1437 adr_type = data_parts[0].strip()
1438 orig_type = adr_type
1439 if adr_type != '':
1440 adrs = self.pat.get_addresses(address_type = adr_type)
1441 if len(adrs) == 0:
1442 _log.warning('no address for type [%s]', adr_type)
1443 adr_type = ''
1444 if adr_type == '':
1445 _log.debug('asking user for address type')
1446 adr = gmPersonContactWidgets.select_address(missing = orig_type, person = self.pat)
1447 if adr is None:
1448 if self.debug:
1449 return _('no address type replacement selected')
1450 return ''
1451 adr_type = adr['address_type']
1452 adr = self.pat.get_addresses(address_type = adr_type)[0]
1453
1454
1455 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_region)s, %(l10n_country)s')
1456 if len(data_parts) > 1:
1457 if data_parts[1].strip() != '':
1458 template = data_parts[1]
1459
1460 try:
1461 return template % adr.fields_as_dict(escape_style = self.__esc_style)
1462 except Exception:
1463 _log.exception('error formatting address')
1464 _log.error('template: %s', template)
1465
1466 return None
1467
1469 requested_type = data.strip()
1470 cache_key = 'adr-type-%s' % requested_type
1471 try:
1472 type2use = self.__cache[cache_key]
1473 _log.debug('cache hit (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
1474 except KeyError:
1475 type2use = requested_type
1476 if type2use != '':
1477 adrs = self.pat.get_addresses(address_type = type2use)
1478 if len(adrs) == 0:
1479 _log.warning('no address of type [%s] for <%s> field extraction', requested_type, part)
1480 type2use = ''
1481 if type2use == '':
1482 _log.debug('asking user for replacement address type')
1483 adr = gmPersonContactWidgets.select_address(missing = requested_type, person = self.pat)
1484 if adr is None:
1485 _log.debug('no replacement selected')
1486 if self.debug:
1487 return self._escape(_('no address type replacement selected'))
1488 return ''
1489 type2use = adr['address_type']
1490 self.__cache[cache_key] = type2use
1491 _log.debug('caching (%s): [%s] -> [%s]', cache_key, requested_type, type2use)
1492
1493 part_data = self.pat.get_addresses(address_type = type2use)[0][part]
1494 if part_data is None:
1495 part_data = ''
1496 return self._escape(part_data)
1497
1498
1500 return self.__get_variant_adr_part(data = data, part = 'street')
1501
1503 return self.__get_variant_adr_part(data = data, part = 'number')
1504
1506 return self.__get_variant_adr_part(data = data, part = 'subunit')
1507
1509 return self.__get_variant_adr_part(data = data, part = 'urb')
1510
1512 return self.__get_variant_adr_part(data = data, part = 'suburb')
1513
1514 - def _get_variant_adr_postcode(self, data='?'):
1515 return self.__get_variant_adr_part(data = data, part = 'postcode')
1516
1518 return self.__get_variant_adr_part(data = data, part = 'l10n_region')
1519
1521 return self.__get_variant_adr_part(data = data, part = 'l10n_country')
1522
1524 comm_type = None
1525 template = '%(url)s'
1526 if data is not None:
1527 data_parts = data.split(self.__args_divider)
1528 if len(data_parts) > 0:
1529 comm_type = data_parts[0]
1530 if len(data_parts) > 1:
1531 template = data_parts[1]
1532
1533 comms = self.pat.get_comm_channels(comm_medium = comm_type)
1534 if len(comms) == 0:
1535 if self.debug:
1536 return self._escape(_('no URL for comm channel [%s]') % data)
1537 return ''
1538
1539 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1540
1542
1543 template = '%s'
1544 target_mime = None
1545 target_ext = None
1546 if data is not None:
1547 parts = data.split(self.__args_divider)
1548 template = parts[0]
1549 if len(parts) > 1:
1550 target_mime = parts[1].strip()
1551 if len(parts) > 2:
1552 target_ext = parts[2].strip()
1553 if target_ext is None:
1554 if target_mime is not None:
1555 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
1556
1557 cache_key = 'patient_photo_path::%s::%s' % (target_mime, target_ext)
1558 try:
1559 fname = self.__cache[cache_key]
1560 _log.debug('cache hit on [%s]: %s', cache_key, fname)
1561 except KeyError:
1562 mugshot = self.pat.document_folder.latest_mugshot
1563 if mugshot is None:
1564 if self.debug:
1565 return self._escape(_('no mugshot available'))
1566 return ''
1567 fname = mugshot.save_to_file (
1568 target_mime = target_mime,
1569 target_extension = target_ext,
1570 ignore_conversion_problems = True
1571 )
1572 if fname is None:
1573 if self.debug:
1574 return self._escape(_('cannot export or convert latest mugshot'))
1575 return ''
1576 self.__cache[cache_key] = fname
1577
1578 return template % fname
1579
1580
1582 options = data.split(self.__args_divider)
1583 template = options[0].strip()
1584 if template == '':
1585 template = '%s'
1586
1587 return template % self.pat.export_as_vcard()
1588
1589
1591 template = u'%s'
1592 format = 'txt'
1593 options = data.split(self.__args_divider)
1594 _log.debug('options: %s', options)
1595 for o in options:
1596 if o.strip().startswith('fmt='):
1597 format = o.strip()[4:]
1598 if format not in ['qr', 'mcf', 'txt']:
1599 return self._escape(_('patient_mcf: invalid format (qr/mcf/txt)'))
1600 continue
1601 if o.strip().startswith('tmpl='):
1602 template = o.strip()[5:]
1603 continue
1604 _log.debug('template: %s' % template)
1605 _log.debug('format: %s' % format)
1606
1607 if format == 'txt':
1608 return template % self._escape(self.pat.MECARD)
1609
1610 if format == 'mcf':
1611 return template % self.pat.export_as_mecard()
1612
1613 if format == 'qr':
1614 qr_filename = gmTools.create_qrcode(text = self.pat.MECARD)
1615 if qr_filename is None:
1616 return self._escape('patient_mcf-cannot_create_QR_code')
1617 return template % qr_filename
1618
1619 return None
1620
1621
1623 options = data.split(self.__args_divider)
1624 template = options[0].strip()
1625 if template == '':
1626 template = '%s'
1627
1628 return template % self.pat.export_as_gdt()
1629
1630
1647
1648
1649
1650
1651
1652
1654 options = data.split(self.__args_divider)
1655
1656 if 'select' in options:
1657 options.remove('select')
1658 branch = 'select branch'
1659 else:
1660 branch = gmPraxis.cPraxisBranch(aPK_obj = gmPraxis.gmCurrentPraxisBranch()['pk_praxis_branch'])
1661
1662 template = '%s'
1663 if len(options) > 0:
1664 template = options[0]
1665 if template.strip() == '':
1666 template = '%s'
1667
1668 return template % branch.fields_as_dict(escape_style = self.__esc_style)
1669
1670
1672
1673 cache_key = 'current_branch_vcf_path'
1674 try:
1675 vcf_name = self.__cache[cache_key]
1676 _log.debug('cache hit (%s): [%s]', cache_key, vcf_name)
1677 except KeyError:
1678 vcf_name = gmPraxis.gmCurrentPraxisBranch().vcf
1679 self.__cache[cache_key] = vcf_name
1680
1681 template = '%s'
1682 if data.strip() != '':
1683 template = data
1684
1685 return template % vcf_name
1686
1687
1689 template = u'%s'
1690 format = 'txt'
1691 options = data.split(self.__args_divider)
1692 _log.debug('options: %s', options)
1693 for o in options:
1694 if o.strip().startswith('fmt='):
1695 format = o.strip()[4:]
1696 if format not in ['qr', 'mcf', 'txt']:
1697 return self._escape(_('praxis_mcf: invalid format (qr/mcf/txt)'))
1698 continue
1699 if o.strip().startswith('tmpl='):
1700 template = o.strip()[5:]
1701 continue
1702 _log.debug('template: %s' % template)
1703 _log.debug('format: %s' % format)
1704
1705 if format == 'txt':
1706 return template % self._escape(gmPraxis.gmCurrentPraxisBranch().MECARD)
1707
1708 if format == 'mcf':
1709 return template % gmPraxis.gmCurrentPraxisBranch().export_as_mecard()
1710
1711 if format == 'qr':
1712 qr_filename = gmTools.create_qrcode(text = gmPraxis.gmCurrentPraxisBranch().MECARD)
1713 if qr_filename is None:
1714 return self._escape('praxis_mcf-cannot_create_QR_code')
1715 return template % qr_filename
1716
1717 return None
1718
1719
1721
1722 format = 'qr'
1723 options = data.split(self.__args_divider)
1724 _log.debug('options: %s', options)
1725 for o in options:
1726 if o.strip().startswith('fmt='):
1727 format = o.strip()[4:]
1728 if format not in ['qr', 'txt']:
1729 return self._escape(_('praxis_scan2pay: invalid format (qr/txt)'))
1730 continue
1731
1732
1733
1734
1735 _log.debug('format: %s' % format)
1736
1737 data_str = gmPraxis.gmCurrentPraxisBranch().scan2pay_data
1738 if data_str is None:
1739 if self.debug:
1740 return self._escape('praxis_scan2pay-cannot_create_data_file')
1741 return ''
1742
1743 if format == 'txt':
1744 return self._escape(data_str)
1745
1746
1747 if format == 'qr':
1748 qr_filename = gmTools.create_qrcode(text = data_str)
1749 if qr_filename is None:
1750 if self.debug:
1751 return self._escape('praxis_scan2pay-cannot_create_QR_code')
1752 return ''
1753
1754 return qr_filename
1755
1756 return None
1757
1758
1760 options = data.split(self.__args_divider)
1761
1762
1763 template = _('%(street)s %(number)s, %(postcode)s %(urb)s, %(l10n_region)s, %(l10n_country)s')
1764 if len(options) > 0:
1765 if options[0].strip() != '':
1766 template = options[0]
1767
1768 adr = gmPraxis.gmCurrentPraxisBranch().address
1769 if adr is None:
1770 if self.debug:
1771 return _('no address recorded')
1772 return ''
1773 try:
1774 return template % adr.fields_as_dict(escape_style = self.__esc_style)
1775 except Exception:
1776 _log.exception('error formatting address')
1777 _log.error('template: %s', template)
1778
1779 return None
1780
1781
1783 options = data.split(self.__args_divider)
1784 comm_type = options[0]
1785 template = '%(url)s'
1786 if len(options) > 1:
1787 template = options[1]
1788
1789 comms = gmPraxis.gmCurrentPraxisBranch().get_comm_channels(comm_medium = comm_type)
1790 if len(comms) == 0:
1791 if self.debug:
1792 return self._escape(_('no URL for comm channel [%s]') % data)
1793 return ''
1794
1795 return template % comms[0].fields_as_dict(escape_style = self.__esc_style)
1796
1797
1799 options = data.split(self.__args_divider)
1800 id_type = options[0].strip()
1801 if id_type == '':
1802 return self._escape('praxis external ID: type is missing')
1803
1804 if len(options) > 1:
1805 issuer = options[1].strip()
1806 if issuer == '':
1807 issue = None
1808 else:
1809 issuer = None
1810
1811 if len(options) > 2:
1812 template = options[2]
1813 else:
1814 template = '%(name)s: %(value)s (%(issuer)s)'
1815
1816 ids = gmPraxis.gmCurrentPraxisBranch().get_external_ids(id_type = id_type, issuer = issuer)
1817 if len(ids) == 0:
1818 if self.debug:
1819 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1820 return ''
1821
1822 return template % self._escape_dict(the_dict = ids[0], none_string = '')
1823
1824
1825
1826
1828 prov = gmStaff.gmCurrentProvider()
1829
1830 tmp = '%s%s. %s' % (
1831 gmTools.coalesce(prov['title'], '', '%s '),
1832 prov['firstnames'][:1],
1833 prov['lastnames']
1834 )
1835 return self._escape(tmp)
1836
1837
1839 if data is None:
1840 template = u'%(title)s'
1841 elif data.strip() == u'':
1842 data = u'%(title)s'
1843 return self._get_variant_current_provider_name(data = data)
1844
1845
1847 if data is None:
1848 data = u'%(firstnames)s'
1849 elif data.strip() == u'':
1850 data = u'%(firstnames)s'
1851 return self._get_variant_current_provider_name(data = data)
1852
1853
1855 if data is None:
1856 data = u'%(lastnames)s'
1857 elif data.strip() == u'':
1858 data = u'%(lastnames)s'
1859 return self._get_variant_current_provider_name(data = data)
1860
1861
1875
1876
1878 data_parts = data.split(self.__args_divider)
1879 if len(data_parts) < 2:
1880 return self._escape('current provider external ID: template is missing')
1881
1882 id_type = data_parts[0].strip()
1883 if id_type == '':
1884 return self._escape('current provider external ID: type is missing')
1885
1886 issuer = data_parts[1].strip()
1887 if issuer == '':
1888 return self._escape('current provider external ID: issuer is missing')
1889
1890 prov = gmStaff.gmCurrentProvider()
1891 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1892
1893 if len(ids) == 0:
1894 if self.debug:
1895 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1896 return ''
1897
1898 return self._escape(ids[0]['value'])
1899
1900
1902 prov = self.pat.primary_provider
1903 if prov is None:
1904 return self._get_variant_current_provider()
1905
1906 title = gmTools.coalesce (
1907 prov['title'],
1908 gmPerson.map_gender2salutation(prov['gender'])
1909 )
1910
1911 tmp = '%s %s. %s' % (
1912 title,
1913 prov['firstnames'][:1],
1914 prov['lastnames']
1915 )
1916 return self._escape(tmp)
1917
1918
1920 data_parts = data.split(self.__args_divider)
1921 if len(data_parts) < 2:
1922 return self._escape('primary in-praxis provider external ID: template is missing')
1923
1924 id_type = data_parts[0].strip()
1925 if id_type == '':
1926 return self._escape('primary in-praxis provider external ID: type is missing')
1927
1928 issuer = data_parts[1].strip()
1929 if issuer == '':
1930 return self._escape('primary in-praxis provider external ID: issuer is missing')
1931
1932 prov = self.pat.primary_provider
1933 if prov is None:
1934 if self.debug:
1935 return self._escape(_('no primary in-praxis provider'))
1936 return ''
1937
1938 ids = prov.identity.get_external_ids(id_type = id_type, issuer = issuer)
1939
1940 if len(ids) == 0:
1941 if self.debug:
1942 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1943 return ''
1944
1945 return self._escape(ids[0]['value'])
1946
1947
1949 data_parts = data.split(self.__args_divider)
1950 if len(data_parts) < 2:
1951 return self._escape('patient external ID: template is missing')
1952
1953 id_type = data_parts[0].strip()
1954 if id_type == '':
1955 return self._escape('patient external ID: type is missing')
1956
1957 issuer = data_parts[1].strip()
1958 if issuer == '':
1959 return self._escape('patient external ID: issuer is missing')
1960
1961 ids = self.pat.get_external_ids(id_type = id_type, issuer = issuer)
1962
1963 if len(ids) == 0:
1964 if self.debug:
1965 return self._escape(_('no external ID [%s] by [%s]') % (id_type, issuer))
1966 return ''
1967
1968 return self._escape(ids[0]['value'])
1969
1970
1972 allg_state = self.pat.emr.allergy_state
1973
1974 if allg_state['last_confirmed'] is None:
1975 date_confirmed = ''
1976 else:
1977 date_confirmed = ' (%s)' % gmDateTime.pydt_strftime (
1978 allg_state['last_confirmed'],
1979 format = '%Y %B %d'
1980 )
1981
1982 tmp = '%s%s' % (
1983 allg_state.state_string,
1984 date_confirmed
1985 )
1986 return self._escape(tmp)
1987
1988
1996
1997
2004
2005
2007 return self._get_variant_current_meds_AMTS(data=data, strict=False)
2008
2009
2011
2012
2013 emr = self.pat.emr
2014 from Gnumed.wxpython import gmMedicationWidgets
2015 intakes2export = gmMedicationWidgets.manage_substance_intakes(emr = emr)
2016 if intakes2export is None:
2017 return ''
2018 if len(intakes2export) == 0:
2019 return ''
2020
2021
2022 unique_intakes = {}
2023 for intake in intakes2export:
2024 if intake['pk_drug_product'] is None:
2025 unique_intakes[intake['pk_substance']] = intake
2026 else:
2027 unique_intakes[intake['product']] = intake
2028 del intakes2export
2029 unique_intakes = unique_intakes.values()
2030
2031
2032 self.__create_amts_datamatrix_files(intakes = unique_intakes)
2033
2034
2035 intake_as_latex_rows = []
2036 for intake in unique_intakes:
2037 intake_as_latex_rows.append(intake._get_as_amts_latex(strict = strict))
2038 del unique_intakes
2039
2040
2041
2042 intake_as_latex_rows.extend(emr.allergy_state._get_as_amts_latex(strict = strict))
2043
2044 for allg in emr.get_allergies():
2045 intake_as_latex_rows.append(allg._get_as_amts_latex(strict = strict))
2046
2047
2048 table_rows = intake_as_latex_rows[:15]
2049 if len(intake_as_latex_rows) > 15:
2050 table_rows.append('\\newpage')
2051 table_rows.extend(intake_as_latex_rows[15:30])
2052 if len(intake_as_latex_rows) > 30:
2053 table_rows.append('\\newpage')
2054 table_rows.extend(intake_as_latex_rows[30:45])
2055
2056 if strict:
2057 return '\n'.join(table_rows)
2058
2059
2060 if len(intake_as_latex_rows) > 45:
2061 table_rows.append('\\newpage')
2062 table_rows.extend(intake_as_latex_rows[30:45])
2063
2064 if len(intake_as_latex_rows) > 60:
2065 table_rows.append('\\newpage')
2066 table_rows.extend(intake_as_latex_rows[30:45])
2067
2068 return '\n'.join(table_rows)
2069
2070
2072
2073
2074 for idx in [1,2,3]:
2075 self.set_placeholder(key = 'amts_data_file_%s' % idx, value = './missing-file.txt', known_only = False)
2076 self.set_placeholder(key = 'amts_png_file_%s' % idx, value = './missing-file.png', known_only = False)
2077 self.set_placeholder(key = 'amts_png_file_current_page', value = './missing-file-current-page.png', known_only = False)
2078 self.set_placeholder(key = 'amts_png_file_utf8', value = './missing-file-utf8.png', known_only = False)
2079 self.set_placeholder(key = 'amts_data_file_utf8', value = './missing-file-utf8.txt', known_only = False)
2080
2081
2082 found, dmtx_creator = gmShellAPI.detect_external_binary(binary = 'gm-create_datamatrix')
2083 _log.debug(dmtx_creator)
2084 if not found:
2085 _log.error('gm-create_datamatrix(.bat/.exe) not found')
2086 return
2087
2088 png_dir = gmTools.mk_sandbox_dir()
2089 _log.debug('sandboxing AMTS datamatrix PNGs in: %s', png_dir)
2090
2091 from Gnumed.business import gmForms
2092
2093
2094
2095 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = False)
2096 _log.debug('amts data template definition file: %s', amts_data_template_def_file)
2097 form = gmForms.cTextForm(template_file = amts_data_template_def_file)
2098
2099 amts_sections = '<S>%s</S>' % ''.join ([
2100 i._get_as_amts_data(strict = False) for i in intakes
2101 ])
2102
2103 emr = self.pat.emr
2104 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([
2105 a._get_as_amts_data(strict = False) for a in emr.get_allergies()
2106 ])
2107 self.set_placeholder(key = 'amts_intakes_as_data_enhanced', value = amts_sections, known_only = False)
2108
2109 self.set_placeholder(key = 'amts_total_pages', value = '1', known_only = False)
2110 success = form.substitute_placeholders(data_source = self)
2111 self.unset_placeholder(key = 'amts_intakes_as_data_enhanced')
2112
2113 self.unset_placeholder(key = 'amts_total_pages')
2114 if not success:
2115 _log.error('cannot substitute into amts data file form template')
2116 return
2117 data_file = form.re_editable_filenames[0]
2118 png_file = os.path.join(png_dir, 'gm4amts-datamatrix-utf8.png')
2119 cmd = '%s %s %s' % (dmtx_creator, data_file, png_file)
2120 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True)
2121 if not success:
2122 _log.error('error running [%s]' % cmd)
2123 return
2124 self.set_placeholder(key = 'amts_data_file_utf8', value = data_file, known_only = False)
2125 self.set_placeholder(key = 'amts_png_file_utf8', value = png_file, known_only = False)
2126
2127
2128 total_pages = (len(intakes) / 15.0)
2129 if total_pages > int(total_pages):
2130 total_pages += 1
2131 total_pages = int(total_pages)
2132 _log.debug('total pages: %s', total_pages)
2133
2134 png_file_base = os.path.join(png_dir, 'gm4amts-datamatrix-page-')
2135 for this_page in range(1,total_pages+1):
2136 intakes_this_page = intakes[(this_page-1)*15:this_page*15]
2137 amts_data_template_def_file = gmMedication.generate_amts_data_template_definition_file(strict = True)
2138 _log.debug('amts data template definition file: %s', amts_data_template_def_file)
2139 form = gmForms.cTextForm(template_file = amts_data_template_def_file)
2140
2141 amts_sections = '<S>%s</S>' % ''.join ([
2142 i._get_as_amts_data(strict = False) for i in intakes_this_page
2143 ])
2144 if this_page == total_pages:
2145
2146 emr = self.pat.emr
2147 amts_sections += emr.allergy_state._get_as_amts_data(strict = False) % ''.join ([
2148 a._get_as_amts_data(strict = False) for a in emr.get_allergies()
2149 ])
2150 self.set_placeholder(key = 'amts_intakes_as_data', value = amts_sections, known_only = False)
2151
2152 if total_pages == 1:
2153 pg_idx = ''
2154 else:
2155 pg_idx = '%s' % this_page
2156 self.set_placeholder(key = 'amts_page_idx', value = pg_idx, known_only = False)
2157 self.set_placeholder(key = 'amts_total_pages', value = '%s' % total_pages, known_only = False)
2158 success = form.substitute_placeholders(data_source = self)
2159 self.unset_placeholder(key = 'amts_intakes_as_data')
2160
2161 self.unset_placeholder(key = 'amts_page_idx')
2162 self.unset_placeholder(key = 'amts_total_pages')
2163 if not success:
2164 _log.error('cannot substitute into amts data file form template')
2165 return
2166
2167 data_file = form.re_editable_filenames[0]
2168 png_file = '%s%s.png' % (png_file_base, this_page)
2169 latin1_data_file = gmTools.recode_file (
2170 source_file = data_file,
2171 source_encoding = 'utf8',
2172 target_encoding = 'latin1',
2173 base_dir = os.path.split(data_file)[0]
2174 )
2175 cmd = '%s %s %s' % (dmtx_creator, latin1_data_file, png_file)
2176 success = gmShellAPI.run_command_in_shell(command = cmd, blocking = True)
2177 if not success:
2178 _log.error('error running [%s]' % cmd)
2179 return
2180
2181
2182 self.set_placeholder(key = 'amts_data_file_%s' % this_page, value = latin1_data_file, known_only = False)
2183 self.set_placeholder(key = 'amts_png_file_%s' % this_page, value = png_file, known_only = False)
2184
2185 self.set_placeholder(key = 'amts_png_file_current_page', value = png_file_base + '\\thepage', known_only = False)
2186
2187
2189 if data is None:
2190 return self._escape(_('current_meds_for_rx: template is missing'))
2191
2192 emr = self.pat.emr
2193 from Gnumed.wxpython import gmMedicationWidgets
2194 current_meds = gmMedicationWidgets.manage_substance_intakes(emr = emr)
2195 if current_meds is None:
2196 return ''
2197
2198 intakes2show = {}
2199 for intake in current_meds:
2200 fields_dict = intake.fields_as_dict(date_format = '%Y %b %d', escape_style = self.__esc_style)
2201 fields_dict['medically_formatted_start'] = self._escape(intake.medically_formatted_start)
2202 if intake['pk_drug_product'] is None:
2203 fields_dict['product'] = self._escape(_('generic %s') % fields_dict['substance'])
2204 fields_dict['contains'] = self._escape('%s %s%s' % (fields_dict['substance'], fields_dict['amount'], fields_dict['unit']))
2205 intakes2show[fields_dict['product']] = fields_dict
2206 else:
2207 comps = [ c.split('::') for c in intake.containing_drug['components'] ]
2208 fields_dict['contains'] = self._escape('; '.join([ '%s %s%s' % (c[0], c[1], c[2]) for c in comps ]))
2209 intakes2show[intake['product']] = fields_dict
2210
2211 intakes2dispense = {}
2212 for product, intake in intakes2show.items():
2213 msg = _('Dispense how much/many of "%(product)s (%(contains)s)" ?') % intake
2214 amount2dispense = wx.GetTextFromUser(msg, _('Amount to dispense ?'))
2215 if amount2dispense == '':
2216 continue
2217 intake['amount2dispense'] = amount2dispense
2218 intakes2dispense[product] = intake
2219
2220 return '\n'.join([ data % intake for intake in intakes2dispense.values() ])
2221
2222
2237
2238
2272
2279
2286
2292
2324
2330
2332 options = data.split(self.__args_divider)
2333 template = options[0]
2334 if len(options) > 1:
2335 date_format = options[1]
2336 else:
2337 date_format = '%Y %b %d'
2338 vaccinations_as_dict = []
2339 for v in self.pat.emr.get_vaccinations(order_by = 'date_given DESC, vaccine'):
2340 v_as_dict = v.fields_as_dict(date_format = date_format, escape_style = self.__esc_style)
2341 v_as_dict['l10n_indications'] = [ ind['l10n_indication'] for ind in v['indications'] ]
2342 vaccinations_as_dict.append(v_as_dict)
2343
2344 return u'\n'.join([ template % v for v in vaccinations_as_dict ])
2345
2346
2348
2349 if data is None:
2350 if self.debug:
2351 _log.error('PHX: missing placeholder arguments')
2352 return self._escape(_('PHX: Invalid placeholder options.'))
2353 return ''
2354
2355 _log.debug('arguments: %s', data)
2356
2357 data_parts = data.split(self.__args_divider)
2358 template = '%s'
2359 separator = '\n'
2360 date_format = '%Y %b %d'
2361 try:
2362 template = data_parts[0]
2363 separator = data_parts[1]
2364 date_format = data_parts[2]
2365 except IndexError:
2366 pass
2367
2368 phxs = gmEMRStructWidgets.select_health_issues(emr = self.pat.emr)
2369 if phxs is None:
2370 if self.debug:
2371 return self._escape(_('no PHX for this patient (available or selected)'))
2372 return ''
2373
2374 return separator.join ([
2375 template % phx.fields_as_dict (
2376 date_format = date_format,
2377 escape_style = self.__esc_style,
2378 bool_strings = (self._escape(_('yes')), self._escape(_('no')))
2379 ) for phx in phxs
2380 ])
2381
2382
2389
2390
2392
2393 if data is None:
2394 return self._escape(_('template is missing'))
2395 template = data
2396 dxs = self.pat.emr.candidate_diagnoses
2397 if len(dxs) == 0:
2398 _log.debug('no diagnoses available')
2399 return ''
2400 selected = gmListWidgets.get_choices_from_list (
2401 msg = _('Select the relevant diagnoses:'),
2402 caption = _('Diagnosis selection'),
2403 columns = [ _('Diagnosis'), _('Marked confidential'), _('Certainty'), _('Source') ],
2404 choices = [[
2405 dx['diagnosis'],
2406 gmTools.bool2subst(dx['explicitely_confidential'], _('yes'), _('no'), _('unknown')),
2407 gmTools.coalesce(dx['diagnostic_certainty_classification'], ''),
2408 dx['source']
2409 ] for dx in dxs
2410 ],
2411 data = dxs,
2412 single_selection = False,
2413 can_return_empty = True
2414 )
2415 if selected is None:
2416 _log.debug('user did not select any diagnoses')
2417 return ''
2418 if len(selected) == 0:
2419 _log.debug('user did not select any diagnoses')
2420 return ''
2421
2422 return '\n'.join(template % self._escape_dict(dx, none_string = '?', bool_strings = [_('yes'), _('no')]) for dx in selected)
2423
2424
2427
2428
2431
2432
2434 return self._escape(urllib.parse.quote(data.encode('utf8')))
2435
2436
2437 - def _get_variant_text_snippet(self, data=None):
2438 data_parts = data.split(self.__args_divider)
2439 keyword = data_parts[0]
2440 template = '%s'
2441 if len(data_parts) > 1:
2442 template = data_parts[1]
2443
2444 expansion = gmKeywordExpansionWidgets.expand_keyword(keyword = keyword, show_list_if_needed = True)
2445
2446 if expansion is None:
2447 if self.debug:
2448 return self._escape(_('no textual expansion found for keyword <%s>') % keyword)
2449 return ''
2450
2451
2452 return template % expansion
2453
2454
2456 parts = data.split(self.__args_divider)
2457 keyword = parts[0]
2458 template = '%s'
2459 target_mime = None
2460 target_ext = None
2461 if len(parts) > 1:
2462 template = parts[1]
2463 if len(parts) > 2:
2464 target_mime = parts[2].strip()
2465 if len(parts) > 3:
2466 target_ext = parts[3].strip()
2467 if target_ext is None:
2468 if target_mime is not None:
2469 target_ext = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
2470
2471 expansion = gmKeywordExpansion.get_expansion (
2472 keyword = keyword,
2473 textual_only = False,
2474 binary_only = True
2475 )
2476 if expansion is None:
2477 if self.debug:
2478 return self._escape(_('no binary expansion found for keyword <%s>') % keyword)
2479 return ''
2480
2481 filename = expansion.save_to_file()
2482 if filename is None:
2483 if self.debug:
2484 return self._escape(_('cannot export data of binary expansion keyword <%s>') % keyword)
2485 return ''
2486
2487 if expansion['is_encrypted']:
2488 pwd = wx.GetPasswordFromUser (
2489 message = _('Enter your GnuPG passphrase for decryption of [%s]') % expansion['keyword'],
2490 caption = _('GnuPG passphrase prompt'),
2491 default_value = ''
2492 )
2493 filename = gmCrypto.gpg_decrypt_file(filename = filename, passphrase = pwd)
2494 if filename is None:
2495 if self.debug:
2496 return self._escape(_('cannot decrypt data of binary expansion keyword <%s>') % keyword)
2497 return ''
2498
2499 target_fname = gmTools.get_unique_filename (
2500 prefix = '%s-converted-' % os.path.splitext(filename)[0],
2501 suffix = target_ext
2502 )
2503 if not gmMimeLib.convert_file(filename = filename, target_mime = target_mime, target_filename = target_fname):
2504 if self.debug:
2505 return self._escape(_('cannot convert data of binary expansion keyword <%s>') % keyword)
2506
2507 return template % filename
2508
2509 return template % target_fname
2510
2511
2513 options = data.split(self.__args_divider)
2514 if len(options) == 0:
2515 return None
2516 text4qr = options[0]
2517 if len(options) > 1:
2518 template = options[1]
2519 else:
2520 template = u'%s'
2521 qr_filename = gmTools.create_qrcode(text = text4qr)
2522 if qr_filename is None:
2523 return self._escape('cannot_create_QR_code')
2524
2525 return template % qr_filename
2526
2527
2529 if data is None:
2530 return None
2531
2532
2533
2534 return data
2535
2536
2538 if data is None:
2539 return None
2540
2541 parts = data.split(self.__args_divider)
2542 if len(parts) < 3:
2543 return 'IF_NOT_EMPTY lacks <instead> definition'
2544 txt = parts[0]
2545 template = parts[1]
2546 instead = parts[2]
2547
2548 if txt.strip() == '':
2549 return instead
2550 if '%s' in template:
2551 return template % txt
2552 return template
2553
2554
2556
2557 if data is None:
2558 return None
2559 parts = data.split(self.__args_divider)
2560 if len(parts) < 2:
2561 return self._escape(u'IF_DEBUGGING lacks proper definition')
2562 debug_str = parts[0]
2563 non_debug_str = parts[1]
2564 if self.debug:
2565 return debug_str
2566 return non_debug_str
2567
2568
2569 - def _get_variant_free_text(self, data=None):
2570
2571 if data is None:
2572 parts = []
2573 msg = _('generic text')
2574 cache_key = 'free_text::%s' % datetime.datetime.now()
2575 else:
2576 parts = data.split(self.__args_divider)
2577 msg = parts[0]
2578 cache_key = 'free_text::%s' % msg
2579
2580 try:
2581 return self.__cache[cache_key]
2582 except KeyError:
2583 pass
2584
2585 if len(parts) > 1:
2586 preset = parts[1]
2587 else:
2588 preset = ''
2589
2590 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
2591 None,
2592 -1,
2593 title = _('Replacing <free_text> placeholder'),
2594 msg = _('Below you can enter free text.\n\n [%s]') % msg,
2595 text = preset
2596 )
2597 dlg.enable_user_formatting = True
2598 decision = dlg.ShowModal()
2599 text = dlg.value.strip()
2600 is_user_formatted = dlg.is_user_formatted
2601 dlg.DestroyLater()
2602
2603 if decision != wx.ID_SAVE:
2604 if self.debug:
2605 return self._escape(_('Text input cancelled by user.'))
2606 return self._escape('')
2607
2608
2609 if is_user_formatted:
2610 self.__cache[cache_key] = text
2611 return text
2612
2613 text = self._escape(text)
2614 self.__cache[cache_key] = text
2615 return text
2616
2617
2638
2639
2641 try:
2642 bill = self.__cache['bill']
2643 except KeyError:
2644 from Gnumed.wxpython import gmBillingWidgets
2645 bill = gmBillingWidgets.manage_bills(patient = self.pat)
2646 if bill is None:
2647 if self.debug:
2648 return self._escape(_('no bill selected'))
2649 return ''
2650 self.__cache['bill'] = bill
2651
2652 format = 'qr'
2653 options = data.split(self.__args_divider)
2654 _log.debug('options: %s', options)
2655 for o in options:
2656 if o.strip().startswith('fmt='):
2657 format = o.strip()[4:]
2658 if format not in ['qr', 'txt']:
2659 return self._escape(_('praxis_scan2pay: invalid format (qr/txt)'))
2660 continue
2661 _log.debug('format: %s' % format)
2662
2663 from Gnumed.business import gmBilling
2664 data_str = gmBilling.get_scan2pay_data (
2665 gmPraxis.gmCurrentPraxisBranch(),
2666 bill,
2667 provider = gmStaff.gmCurrentProvider()
2668 )
2669 if data_str is None:
2670 if self.debug:
2671 return self._escape('bill_scan2pay-cannot_create_data_file')
2672 return ''
2673
2674 if format == 'txt':
2675 return self._escape(data_str)
2676
2677 if format == 'qr':
2678 qr_filename = gmTools.create_qrcode(text = data_str)
2679 if qr_filename is not None:
2680 return qr_filename
2681 if self.debug:
2682 return self._escape('bill_scan2pay-cannot_create_QR_code')
2683 return ''
2684
2685 return None
2686
2687
2708
2709
2711 try:
2712 bill = self.__cache['bill']
2713 except KeyError:
2714 from Gnumed.wxpython import gmBillingWidgets
2715 bill = gmBillingWidgets.manage_bills(patient = self.pat)
2716 if bill is None:
2717 if self.debug:
2718 return self._escape(_('no bill selected'))
2719 return ''
2720 self.__cache['bill'] = bill
2721 self.__cache['bill-adr'] = bill.address
2722
2723 try:
2724 bill_adr = self.__cache['bill-adr']
2725 except KeyError:
2726 bill_adr = bill.address
2727 self.__cache['bill-adr'] = bill_adr
2728
2729 if bill_adr is None:
2730 if self.debug:
2731 return self._escape(_('[%s] bill has no address') % part)
2732 return ''
2733
2734 if bill_adr[part] is None:
2735 return self._escape('')
2736
2737 if data is None:
2738 return self._escape(bill_adr[part])
2739
2740 if data == '':
2741 return self._escape(bill_adr[part])
2742
2743 return data % self._escape(bill_adr[part])
2744
2745
2747 return self.__get_variant_bill_adr_part(data = data, part = 'street')
2748
2749
2751 return self.__get_variant_bill_adr_part(data = data, part = 'number')
2752
2753
2755 return self.__get_variant_bill_adr_part(data = data, part = 'subunit')
2756
2758 return self.__get_variant_bill_adr_part(data = data, part = 'urb')
2759
2760
2762 return self.__get_variant_bill_adr_part(data = data, part = 'suburb')
2763
2764
2766 return self.__get_variant_bill_adr_part(data = data, part = 'postcode')
2767
2768
2770 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_region')
2771
2772
2774 return self.__get_variant_bill_adr_part(data = data, part = 'l10n_country')
2775
2776
2777
2778
2780 if self.__esc_func is None:
2781 return text
2782 assert (text is not None), 'text=None passed to _escape()'
2783 return self.__esc_func(text)
2784
2785
2786 - def _escape_dict(self, the_dict=None, date_format='%Y %b %d %H:%M', none_string='', bool_strings=None):
2787 if bool_strings is None:
2788 bools = {True: _('true'), False: _('false')}
2789 else:
2790 bools = {True: bool_strings[0], False: bool_strings[1]}
2791 data = {}
2792 for field in the_dict.keys():
2793
2794
2795
2796
2797 val = the_dict[field]
2798 if val is None:
2799 data[field] = none_string
2800 continue
2801 if isinstance(val, bool):
2802 data[field] = bools[val]
2803 continue
2804 if isinstance(val, datetime.datetime):
2805 data[field] = gmDateTime.pydt_strftime(val, format = date_format)
2806 if self.__esc_style in ['latex', 'tex']:
2807 data[field] = gmTools.tex_escape_string(data[field])
2808 elif self.__esc_style in ['xetex', 'xelatex']:
2809 data[field] = gmTools.xetex_escape_string(data[field])
2810 continue
2811 try:
2812 data[field] = str(val, encoding = 'utf8', errors = 'replace')
2813 except TypeError:
2814 try:
2815 data[field] = str(val)
2816 except (UnicodeDecodeError, TypeError):
2817 val = '%s' % str(val)
2818 data[field] = val.decode('utf8', 'replace')
2819 if self.__esc_style in ['latex', 'tex']:
2820 data[field] = gmTools.tex_escape_string(data[field])
2821 elif self.__esc_style in ['xetex', 'xelatex']:
2822 data[field] = gmTools.xetex_escape_string(data[field])
2823 return data
2824
2825
2827
2828 _log.debug('testing for placeholders with pattern: %s', first_pass_placeholder_regex)
2829
2830 data_source = gmPlaceholderHandler()
2831 original_line = ''
2832
2833 while True:
2834
2835 line = wx.GetTextFromUser (
2836 _('Enter some text containing a placeholder:'),
2837 _('Testing placeholders'),
2838 centre = True,
2839 default_value = original_line
2840 )
2841 if line.strip() == '':
2842 break
2843 original_line = line
2844
2845 placeholders_in_line = regex.findall(first_pass_placeholder_regex, line, regex.IGNORECASE)
2846 if len(placeholders_in_line) == 0:
2847 continue
2848 for placeholder in placeholders_in_line:
2849 try:
2850 val = data_source[placeholder]
2851 except:
2852 val = _('error with placeholder [%s]') % placeholder
2853 if val is None:
2854 val = _('error with placeholder [%s]') % placeholder
2855 line = line.replace(placeholder, val)
2856
2857 msg = _(
2858 'Input: %s\n'
2859 '\n'
2860 'Output:\n'
2861 '%s'
2862 ) % (
2863 original_line,
2864 line
2865 )
2866 gmGuiHelpers.gm_show_info (
2867 title = _('Testing placeholders'),
2868 info = msg
2869 )
2870
2871
2873 """Functions a macro can legally use.
2874
2875 An instance of this class is passed to the GNUmed scripting
2876 listener. Hence, all actions a macro can legally take must
2877 be defined in this class. Thus we achieve some screening for
2878 security and also thread safety handling.
2879 """
2880
2881 - def __init__(self, personality = None):
2882 if personality is None:
2883 raise gmExceptions.ConstructorError('must specify personality')
2884 self.__personality = personality
2885 self.__attached = 0
2886 self._get_source_personality = None
2887 self.__user_done = False
2888 self.__user_answer = 'no answer yet'
2889 self.__pat = gmPerson.gmCurrentPatient()
2890
2891 self.__auth_cookie = str(random.random())
2892 self.__pat_lock_cookie = str(random.random())
2893 self.__lock_after_load_cookie = str(random.random())
2894
2895 _log.info('slave mode personality is [%s]', personality)
2896
2897
2898
2899 - def attach(self, personality = None):
2900 if self.__attached:
2901 _log.error('attach with [%s] rejected, already serving a client', personality)
2902 return (0, _('attach rejected, already serving a client'))
2903 if personality != self.__personality:
2904 _log.error('rejecting attach to personality [%s], only servicing [%s]' % (personality, self.__personality))
2905 return (0, _('attach to personality [%s] rejected') % personality)
2906 self.__attached = 1
2907 self.__auth_cookie = str(random.random())
2908 return (1, self.__auth_cookie)
2909
2910 - def detach(self, auth_cookie=None):
2911 if not self.__attached:
2912 return 1
2913 if auth_cookie != self.__auth_cookie:
2914 _log.error('rejecting detach() with cookie [%s]' % auth_cookie)
2915 return 0
2916 self.__attached = 0
2917 return 1
2918
2920 if not self.__attached:
2921 return 1
2922 self.__user_done = False
2923
2924 wx.CallAfter(self._force_detach)
2925 return 1
2926
2928 ver = _cfg.get(option = 'client_version')
2929 return "GNUmed %s, %s $Revision: 1.51 $" % (ver, self.__class__.__name__)
2930
2932 """Shuts down this client instance."""
2933 if not self.__attached:
2934 return 0
2935 if auth_cookie != self.__auth_cookie:
2936 _log.error('non-authenticated shutdown_gnumed()')
2937 return 0
2938 wx.CallAfter(self._shutdown_gnumed, forced)
2939 return 1
2940
2942 """Raise ourselves to the top of the desktop."""
2943 if not self.__attached:
2944 return 0
2945 if auth_cookie != self.__auth_cookie:
2946 _log.error('non-authenticated raise_gnumed()')
2947 return 0
2948 return "cMacroPrimitives.raise_gnumed() not implemented"
2949
2951 if not self.__attached:
2952 return 0
2953 if auth_cookie != self.__auth_cookie:
2954 _log.error('non-authenticated get_loaded_plugins()')
2955 return 0
2956 gb = gmGuiBroker.GuiBroker()
2957 return gb['horstspace.notebook.gui'].keys()
2958
2960 """Raise a notebook plugin within GNUmed."""
2961 if not self.__attached:
2962 return 0
2963 if auth_cookie != self.__auth_cookie:
2964 _log.error('non-authenticated raise_notebook_plugin()')
2965 return 0
2966
2967 wx.CallAfter(gmPlugin.raise_notebook_plugin, a_plugin)
2968 return 1
2969
2971 """Load external patient, perhaps create it.
2972
2973 Callers must use get_user_answer() to get status information.
2974 It is unsafe to proceed without knowing the completion state as
2975 the controlled client may be waiting for user input from a
2976 patient selection list.
2977 """
2978 if not self.__attached:
2979 return (0, _('request rejected, you are not attach()ed'))
2980 if auth_cookie != self.__auth_cookie:
2981 _log.error('non-authenticated load_patient_from_external_source()')
2982 return (0, _('rejected load_patient_from_external_source(), not authenticated'))
2983 if self.__pat.locked:
2984 _log.error('patient is locked, cannot load from external source')
2985 return (0, _('current patient is locked'))
2986 self.__user_done = False
2987 wx.CallAfter(self._load_patient_from_external_source)
2988 self.__lock_after_load_cookie = str(random.random())
2989 return (1, self.__lock_after_load_cookie)
2990
2992 if not self.__attached:
2993 return (0, _('request rejected, you are not attach()ed'))
2994 if auth_cookie != self.__auth_cookie:
2995 _log.error('non-authenticated lock_load_patient()')
2996 return (0, _('rejected lock_load_patient(), not authenticated'))
2997
2998 if lock_after_load_cookie != self.__lock_after_load_cookie:
2999 _log.warning('patient lock-after-load request rejected due to wrong cookie [%s]' % lock_after_load_cookie)
3000 return (0, 'patient lock-after-load request rejected, wrong cookie provided')
3001 self.__pat.locked = True
3002 self.__pat_lock_cookie = str(random.random())
3003 return (1, self.__pat_lock_cookie)
3004
3006 if not self.__attached:
3007 return (0, _('request rejected, you are not attach()ed'))
3008 if auth_cookie != self.__auth_cookie:
3009 _log.error('non-authenticated lock_into_patient()')
3010 return (0, _('rejected lock_into_patient(), not authenticated'))
3011 if self.__pat.locked:
3012 _log.error('patient is already locked')
3013 return (0, _('already locked into a patient'))
3014 searcher = gmPersonSearch.cPatientSearcher_SQL()
3015 if type(search_params) == dict:
3016 idents = searcher.get_identities(search_dict=search_params)
3017 raise Exception("must use dto, not search_dict")
3018 else:
3019 idents = searcher.get_identities(search_term=search_params)
3020 if idents is None:
3021 return (0, _('error searching for patient with [%s]/%s') % (search_term, search_dict))
3022 if len(idents) == 0:
3023 return (0, _('no patient found for [%s]/%s') % (search_term, search_dict))
3024
3025 if len(idents) > 1:
3026 return (0, _('several matching patients found for [%s]/%s') % (search_term, search_dict))
3027 if not gmPatSearchWidgets.set_active_patient(patient = idents[0]):
3028 return (0, _('cannot activate patient [%s] (%s/%s)') % (str(idents[0]), search_term, search_dict))
3029 self.__pat.locked = True
3030 self.__pat_lock_cookie = str(random.random())
3031 return (1, self.__pat_lock_cookie)
3032
3034 if not self.__attached:
3035 return (0, _('request rejected, you are not attach()ed'))
3036 if auth_cookie != self.__auth_cookie:
3037 _log.error('non-authenticated unlock_patient()')
3038 return (0, _('rejected unlock_patient, not authenticated'))
3039
3040 if not self.__pat.locked:
3041 return (1, '')
3042
3043 if unlock_cookie != self.__pat_lock_cookie:
3044 _log.warning('patient unlock request rejected due to wrong cookie [%s]' % unlock_cookie)
3045 return (0, 'patient unlock request rejected, wrong cookie provided')
3046 self.__pat.locked = False
3047 return (1, '')
3048
3050 if not self.__attached:
3051 return 0
3052 if auth_cookie != self.__auth_cookie:
3053 _log.error('non-authenticated select_identity()')
3054 return 0
3055 return "cMacroPrimitives.assume_staff_identity() not implemented"
3056
3058 if not self.__user_done:
3059 return (0, 'still waiting')
3060 self.__user_done = False
3061 return (1, self.__user_answer)
3062
3063
3064
3066 msg = _(
3067 'Someone tries to forcibly break the existing\n'
3068 'controlling connection. This may or may not\n'
3069 'have legitimate reasons.\n\n'
3070 'Do you want to allow breaking the connection ?'
3071 )
3072 can_break_conn = gmGuiHelpers.gm_show_question (
3073 aMessage = msg,
3074 aTitle = _('forced detach attempt')
3075 )
3076 if can_break_conn:
3077 self.__user_answer = 1
3078 else:
3079 self.__user_answer = 0
3080 self.__user_done = True
3081 if can_break_conn:
3082 self.__pat.locked = False
3083 self.__attached = 0
3084 return 1
3085
3087 top_win = wx.GetApp().GetTopWindow()
3088 if forced:
3089 top_win.DestroyLater()
3090 else:
3091 top_win.Close()
3092
3101
3102
3103
3104 if __name__ == '__main__':
3105
3106 if len(sys.argv) < 2:
3107 sys.exit()
3108
3109 if sys.argv[1] != 'test':
3110 sys.exit()
3111
3112 gmI18N.activate_locale()
3113 gmI18N.install_domain()
3114
3115
3117 handler = gmPlaceholderHandler()
3118 handler.debug = True
3119
3120 for placeholder in ['a', 'b']:
3121 print(handler[placeholder])
3122
3123 pat = gmPersonSearch.ask_for_patient()
3124 if pat is None:
3125 return
3126
3127 gmPatSearchWidgets.set_active_patient(patient = pat)
3128
3129 print('DOB (YYYY-MM-DD):', handler['date_of_birth::%Y-%m-%d'])
3130
3131 app = wx.PyWidgetTester(size = (200, 50))
3132
3133 ph = 'progress_notes::ap'
3134 print('%s: %s' % (ph, handler[ph]))
3135
3137
3138 tests = [
3139
3140 '$<lastname>$',
3141 '$<lastname::::3>$',
3142 '$<name::%(title)s %(firstnames)s%(preferred)s%(lastnames)s>$',
3143
3144
3145 'lastname',
3146 '$<lastname',
3147 '$<lastname::',
3148 '$<lastname::>$',
3149 '$<lastname::abc>$',
3150 '$<lastname::abc::>$',
3151 '$<lastname::abc::3>$',
3152 '$<lastname::abc::xyz>$',
3153 '$<lastname::::>$',
3154 '$<lastname::::xyz>$',
3155
3156 '$<date_of_birth::%Y-%m-%d>$',
3157 '$<date_of_birth::%Y-%m-%d::3>$',
3158 '$<date_of_birth::%Y-%m-%d::>$',
3159
3160
3161 '$<adr_location::home::35>$',
3162 '$<gender_mapper::male//female//other::5>$',
3163 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\n::50>$',
3164 '$<allergy_list::%(descriptor)s, >$',
3165 '$<current_meds_table::latex//>$'
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180 ]
3181
3182
3183
3184
3185
3186 pat = gmPersonSearch.ask_for_patient()
3187 if pat is None:
3188 return
3189
3190 gmPatSearchWidgets.set_active_patient(patient = pat)
3191
3192 handler = gmPlaceholderHandler()
3193 handler.debug = True
3194
3195 for placeholder in tests:
3196 print(placeholder, "=>", handler[placeholder])
3197 print("--------------")
3198 input()
3199
3200
3201
3202
3203
3204
3205
3206
3207
3209 from Gnumed.pycommon import gmScriptingListener
3210 import xmlrpc.client
3211
3212 listener = gmScriptingListener.cScriptingListener(macro_executor = cMacroPrimitives(personality='unit test'), port=9999)
3213
3214 s = xmlrpc.client.ServerProxy('http://localhost:9999')
3215 print("should fail:", s.attach())
3216 print("should fail:", s.attach('wrong cookie'))
3217 print("should work:", s.version())
3218 print("should fail:", s.raise_gnumed())
3219 print("should fail:", s.raise_notebook_plugin('test plugin'))
3220 print("should fail:", s.lock_into_patient('kirk, james'))
3221 print("should fail:", s.unlock_patient())
3222 status, conn_auth = s.attach('unit test')
3223 print("should work:", status, conn_auth)
3224 print("should work:", s.version())
3225 print("should work:", s.raise_gnumed(conn_auth))
3226 status, pat_auth = s.lock_into_patient(conn_auth, 'kirk, james')
3227 print("should work:", status, pat_auth)
3228 print("should fail:", s.unlock_patient(conn_auth, 'bogus patient unlock cookie'))
3229 print("should work", s.unlock_patient(conn_auth, pat_auth))
3230 data = {'firstname': 'jame', 'lastnames': 'Kirk', 'gender': 'm'}
3231 status, pat_auth = s.lock_into_patient(conn_auth, data)
3232 print("should work:", status, pat_auth)
3233 print("should work", s.unlock_patient(conn_auth, pat_auth))
3234 print(s.detach('bogus detach cookie'))
3235 print(s.detach(conn_auth))
3236 del s
3237
3238 listener.shutdown()
3239
3241
3242 import re as regex
3243
3244 tests = [
3245 ' $<lastname>$ ',
3246 ' $<lastname::::3>$ ',
3247
3248
3249 '$<date_of_birth::%Y-%m-%d>$',
3250 '$<date_of_birth::%Y-%m-%d::3>$',
3251 '$<date_of_birth::%Y-%m-%d::>$',
3252
3253 '$<adr_location::home::35>$',
3254 '$<gender_mapper::male//female//other::5>$',
3255 '$<current_meds::==> %(product)s %(preparation)s (%(substance)s) <==\\n::50>$',
3256 '$<allergy_list::%(descriptor)s, >$',
3257
3258 '\\noindent Patient: $<lastname>$, $<firstname>$',
3259 '$<allergies::%(descriptor)s & %(l10n_type)s & {\\footnotesize %(reaction)s} \tabularnewline \hline >$',
3260 '$<current_meds:: \item[%(substance)s] {\\footnotesize (%(product)s)} %(preparation)s %(amount)s%(unit)s: %(schedule)s >$'
3261 ]
3262
3263 tests = [
3264
3265 'junk $<lastname::::3>$ junk',
3266 'junk $<lastname::abc::3>$ junk',
3267 'junk $<lastname::abc>$ junk',
3268 'junk $<lastname>$ junk',
3269
3270 'junk $<lastname>$ junk $<firstname>$ junk',
3271 'junk $<lastname::abc>$ junk $<fiststname::abc>$ junk',
3272 'junk $<lastname::abc::3>$ junk $<firstname::abc::3>$ junk',
3273 'junk $<lastname::::3>$ junk $<firstname::::3>$ junk'
3274
3275 ]
3276
3277 tests = [
3278
3279
3280
3281
3282
3283 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::>>>$ junk',
3284 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::250>>>$ junk',
3285 'junk $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3-4>>>$ junk',
3286
3287 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::->>>$ junk',
3288 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3->>>$ junk',
3289 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::-4>>>$ should fail',
3290 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail>>>$ junk',
3291 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail->>>$ junk',
3292 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::-should_fail>>>$ junk',
3293 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail-4>>>$ junk',
3294 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::3-should_fail>>>$ junk',
3295 'should fail $<<<current_meds::%(product)s (%(substance)s): Dispense $<free_text::Dispense how many of %(product)s %(preparation)s (%(substance)s) ?::20>$ (%(preparation)s) \\n::should_fail-should_fail>>>$ junk',
3296 ]
3297
3298 tests = [
3299 'junk $<<<should pass::template::>>>$ junk',
3300 'junk $<<<should pass::template::10>>>$ junk',
3301 'junk $<<<should pass::template::10-20>>>$ junk',
3302 'junk $<<<should pass::template $<<dummy::template 2::10>>$::>>>$ junk',
3303 'junk $<<<should pass::template $<dummy::template 2::10>$::>>>$ junk',
3304
3305 'junk $<<<should pass::template::>>>$ junk $<<<should pass 2::template 2::>>>$ junk',
3306 'junk $<<<should pass::template::>>>$ junk $<<should pass 2::template 2::>>$ junk',
3307 'junk $<<<should pass::template::>>>$ junk $<should pass 2::template 2::>$ junk',
3308
3309 'junk $<<<should fail::template $<<<dummy::template 2::10>>>$::>>>$ junk',
3310
3311 'junk $<<<should fail::template::10->>>$ junk',
3312 'junk $<<<should fail::template::10->>>$ junk',
3313 'junk $<<<should fail::template::10->>>$ junk',
3314 'junk $<<<should fail::template::10->>>$ junk',
3315 'junk $<first_pass::junk $<<<3rd_pass::template::20>>>$ junk::8-10>$ junk'
3316 ]
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332 all_tests = {
3333 first_pass_placeholder_regex: [
3334
3335 ('junk $<first_level::template::>$ junk', ['$<first_level::template::>$']),
3336 ('junk $<first_level::template::10>$ junk', ['$<first_level::template::10>$']),
3337 ('junk $<first_level::template::10-12>$ junk', ['$<first_level::template::10-12>$']),
3338
3339
3340 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<first_level::$<<insert::insert_template::0>>$::10-12>$']),
3341 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<first_level::$<<<insert::insert_template::0>>>$::10-12>$']),
3342
3343
3344 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<insert::insert_template::0>$']),
3345 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<insert::insert_template::0>$']),
3346
3347
3348 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<first_level 1::template 1::>$']),
3349 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<first_level 1::template 1::>$']),
3350
3351
3352 ('junk $<first_level 1::template 1::>$ junk $<first_level 2::template 2::>$ junk', ['$<first_level 1::template 1::>$', '$<first_level 2::template 2::>$']),
3353
3354
3355 ('returns illegal match: junk $<first_level::$<insert::insert_template::0>$::10-12>$ junk', ['$<first_level::$<insert::insert_template::0>$::10-12>$']),
3356 ],
3357 second_pass_placeholder_regex: [
3358
3359 ('junk $<<second_level::template::>>$ junk', ['$<<second_level::template::>>$']),
3360 ('junk $<<second_level::template::10>>$ junk', ['$<<second_level::template::10>>$']),
3361 ('junk $<<second_level::template::10-12>>$ junk', ['$<<second_level::template::10-12>>$']),
3362
3363
3364 ('junk $<<second_level::$<insert::insert_template::0>$::10-12>>$ junk', ['$<<second_level::$<insert::insert_template::0>$::10-12>>$']),
3365 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$']),
3366
3367
3368 ('junk $<first_level::$<<insert::insert_template::0>>$::10-12>$ junk', ['$<<insert::insert_template::0>>$']),
3369 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<insert::insert_template::0>>$']),
3370
3371
3372 ('junk $<first_level 1::template 1::>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 2::template 2::>>$']),
3373 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<second_level 1::template 1::>>$']),
3374
3375
3376 ('junk $<<second_level 1::template 1::>>$ junk $<<second_level 2::template 2::>>$ junk', ['$<<second_level 1::template 1::>>$', '$<<second_level 2::template 2::>>$']),
3377
3378
3379 ('returns illegal match: junk $<<second_level::$<<insert::insert_template::0>>$::10-12>>$ junk', ['$<<second_level::$<<insert::insert_template::0>>$::10-12>>$']),
3380
3381 ],
3382 third_pass_placeholder_regex: [
3383
3384 ('junk $<<<third_level::template::>>>$ junk', ['$<<<third_level::template::>>>$']),
3385 ('junk $<<<third_level::template::10>>>$ junk', ['$<<<third_level::template::10>>>$']),
3386 ('junk $<<<third_level::template::10-12>>>$ junk', ['$<<<third_level::template::10-12>>>$']),
3387
3388
3389 ('junk $<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$ junk', ['$<<<third_level::$<<insert::insert_template::0>>$::10-12>>>$']),
3390 ('junk $<<<third_level::$<insert::insert_template::0>$::10-12>>>$ junk', ['$<<<third_level::$<insert::insert_template::0>$::10-12>>>$']),
3391
3392
3393 ('junk $<<second_level::$<<<insert::insert_template::0>>>$::10-12>>$ junk', ['$<<<insert::insert_template::0>>>$']),
3394 ('junk $<first_level::$<<<insert::insert_template::0>>>$::10-12>$ junk', ['$<<<insert::insert_template::0>>>$']),
3395
3396
3397 ('junk $<first_level 1::template 1::>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']),
3398 ('junk $<<second_level 1::template 1::>>$ junk $<<<third_level 2::template 2::>>>$ junk', ['$<<<third_level 2::template 2::>>>$']),
3399
3400
3401 ('returns illegal match: junk $<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$ junk', ['$<<<third_level::$<<<insert::insert_template::0>>>$::10-12>>>$']),
3402 ]
3403 }
3404
3405 for pattern in [first_pass_placeholder_regex, second_pass_placeholder_regex, third_pass_placeholder_regex]:
3406 print("")
3407 print("-----------------------------")
3408 print("regex:", pattern)
3409 tests = all_tests[pattern]
3410 for t in tests:
3411 line, expected_results = t
3412 phs = regex.findall(pattern, line, regex.IGNORECASE)
3413 if len(phs) > 0:
3414 if phs == expected_results:
3415 continue
3416
3417 print("")
3418 print("failed")
3419 print("line:", line)
3420
3421 if len(phs) == 0:
3422 print("no match")
3423 continue
3424
3425 if len(phs) > 1:
3426 print("several matches")
3427 for r in expected_results:
3428 print("expected:", r)
3429 for p in phs:
3430 print("found:", p)
3431 continue
3432
3433 print("unexpected match")
3434 print("expected:", expected_results)
3435 print("found: ", phs)
3436
3437
3439
3440 phs = [
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512 u'$<bill_scan2pay::fmt=txt::>$',
3513 u'$<bill_scan2pay::fmt=qr::>$'
3514 ]
3515
3516 handler = gmPlaceholderHandler()
3517 handler.debug = True
3518
3519 gmStaff.set_current_provider_to_logged_on_user()
3520 gmPraxisWidgets.set_active_praxis_branch(no_parent = True)
3521 pat = gmPersonSearch.ask_for_patient()
3522 if pat is None:
3523 return
3524 gmPatSearchWidgets.set_active_patient(patient = pat)
3525
3526
3527
3528 for ph in phs:
3529 print(ph)
3530 print(" result:")
3531 print(' %s' % handler[ph])
3532
3533
3534
3542
3543
3546
3547
3548
3549 app = wx.App()
3550
3551
3552
3553
3554
3555
3556 test_placeholder()
3557
3558