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