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