| 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.keys():
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
1067
1068 # create sandbox for LaTeX to play in (and don't assume
1069 # much of anything about the template_file except that it
1070 # is at our disposal for reading)
1071 sandbox_dir = gmTools.mk_sandbox_dir(prefix = gmTools.fname_stem(template_file) + '_')
1072 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir)
1073 shutil.copy(template_file, sandbox_dir)
1074 template_file = os.path.join(sandbox_dir, os.path.split(template_file)[1])
1075
1076 super(self.__class__, self).__init__(template_file = template_file)
1077
1078 self.__sandbox_dir = sandbox_dir
1079
1080 # set up PDF generator
1081 if platform.system() == 'Windows':
1082 executable = 'pdflatex.exe'
1083 else:
1084 executable = 'pdflatex'
1085 self._final_cmd_line = [
1086 executable,
1087 '-recorder',
1088 '-interaction=nonstopmode',
1089 "-output-directory=%s" % self.__sandbox_dir
1090 ]
1091 self._draft_cmd_line = self._final_cmd_line + ['-draftmode']
1092
1093 #--------------------------------------------------------
1095 # remove extra linefeeds which the docutils ReST2LaTeX
1096 # converter likes to add but which makes pdflatex go
1097 # crazy when ending up inside KOMAScript variables
1098 return gmTools.rst2latex_snippet(text).strip()
1099
1100 #--------------------------------------------------------
1102
1103 # debugging
1104 #data_source.debug = True
1105
1106 if self.template is not None:
1107 # inject placeholder values
1108 data_source.set_placeholder('form_name_long', self.template['name_long'])
1109 data_source.set_placeholder('form_name_short', self.template['name_short'])
1110 data_source.set_placeholder('form_version', self.template['external_version'])
1111 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s'))
1112 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M'))
1113 # add site-local identifying information to template for debugging
1114 f = open(self.template_filename, 'at', encoding = 'utf8')
1115 f.write('\n')
1116 f.write('%------------------------------------------------------------------\n')
1117 for line in self.template.format():
1118 f.write('% ')
1119 f.write(line)
1120 f.write('\n')
1121 f.write('%------------------------------------------------------------------\n')
1122 f.close()
1123
1124 data_source.escape_function = gmTools.tex_escape_string
1125 data_source.escape_style = 'latex'
1126
1127 path, ext = os.path.splitext(self.template_filename)
1128 if ext in [r'', r'.']:
1129 ext = r'.tex'
1130
1131 filenames = [
1132 self.template_filename,
1133 r'%s-result-pass-1%s' % (path, ext),
1134 r'%s-result-pass-2%s' % (path, ext),
1135 r'%s-result-pass-3%s' % (path, ext),
1136 r'%s-result-pass-4%s' % (path, ext),
1137 r'%s-result-pass-5%s' % (path, ext)
1138 ]
1139 regexen = [
1140 'dummy',
1141 r'\$1{0,1}<[^<].+?>1{0,1}\$',
1142 r'\$2<[^<].+?>2\$',
1143 r'\$3<[^<].+?>3\$',
1144 r'\$4<[^<].+?>4\$',
1145 r'\$5<[^<].+?>5\$'
1146 ]
1147
1148 current_pass = 1
1149 while current_pass < 6:
1150 _log.debug('placeholder substitution pass #%s', current_pass)
1151 found_placeholders = self.__substitute_placeholders (
1152 input_filename = filenames[current_pass-1],
1153 output_filename = filenames[current_pass],
1154 data_source = data_source,
1155 placeholder_regex = regexen[current_pass]
1156 )
1157 current_pass += 1
1158
1159 # remove temporary placeholders
1160 data_source.unset_placeholder('form_name_long')
1161 data_source.unset_placeholder('form_name_short')
1162 data_source.unset_placeholder('form_version')
1163 data_source.unset_placeholder('form_version_internal')
1164 data_source.unset_placeholder('form_last_modified')
1165
1166 self.instance_filename = self.re_editable_filenames[0]
1167
1168 return
1169
1170 #--------------------------------------------------------
1171 - def __substitute_placeholders(self, data_source=None, input_filename=None, output_filename=None, placeholder_regex=None):
1172
1173 _log.debug('[%s] -> [%s]', input_filename, output_filename)
1174 _log.debug('searching for placeholders with pattern: %s', placeholder_regex)
1175
1176 template_file = io.open(input_filename, mode = 'rt', encoding = 'utf8')
1177 instance_file = io.open(output_filename, mode = 'wt', encoding = 'utf8')
1178
1179 for line in template_file:
1180 # empty lines
1181 if line.strip() in ['', '\r', '\n', '\r\n']:
1182 instance_file.write(line)
1183 continue
1184 # TeX-comment-only lines
1185 if line.lstrip().startswith('%'):
1186 instance_file.write(line)
1187 continue
1188
1189 # 1) find placeholders in this line
1190 placeholders_in_line = regex.findall(placeholder_regex, line, regex.IGNORECASE)
1191 if len(placeholders_in_line) == 0:
1192 instance_file.write(line)
1193 continue
1194
1195 # 2) replace them
1196 _log.debug('replacing in non-empty, non-comment line: >>>%s<<<', line.rstrip(u'\n'))
1197 _log.debug('%s placeholder(s) detected', len(placeholders_in_line))
1198 for placeholder in placeholders_in_line:
1199 if 'free_text' in placeholder:
1200 # enable reStructuredText processing
1201 data_source.escape_function = self._rst2latex_transform
1202 else:
1203 data_source.escape_function = gmTools.tex_escape_string
1204 original_ph_def = placeholder
1205 _log.debug('placeholder: >>>%s<<<', original_ph_def)
1206 # normalize start/end
1207 if placeholder.startswith('$<'):
1208 placeholder = '$1<' + placeholder[2:]
1209 if placeholder.endswith('>$'):
1210 placeholder = placeholder[:-2] + '>1$'
1211 _log.debug('normalized : >>>%s<<<', placeholder)
1212 # remove start/end
1213 placeholder = placeholder[3:-3]
1214 _log.debug('stripped : >>>%s<<<', placeholder)
1215 try:
1216 val = data_source[placeholder]
1217 except Exception:
1218 _log.exception('error with placeholder [%s]', original_ph_def)
1219 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % original_ph_def)
1220 if val is None:
1221 _log.debug('error with placeholder [%s]', original_ph_def)
1222 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % original_ph_def)
1223 _log.debug('value : >>>%s<<<', val)
1224 line = line.replace(original_ph_def, val)
1225 instance_file.write(line)
1226
1227 instance_file.close()
1228 self.re_editable_filenames = [output_filename]
1229 template_file.close()
1230
1231 return
1232
1233 #--------------------------------------------------------
1235
1236 mimetypes = [
1237 'application/x-latex',
1238 'application/x-tex',
1239 'text/latex',
1240 'text/tex',
1241 'text/plain'
1242 ]
1243
1244 for mimetype in mimetypes:
1245 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename)
1246 if editor_cmd is not None:
1247 break
1248
1249 if editor_cmd is None:
1250 # LaTeX code is text: also consider text *viewers*
1251 # since pretty much any of them will be an editor as well
1252 for mimetype in mimetypes:
1253 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename)
1254 if editor_cmd is not None:
1255 break
1256
1257 if editor_cmd is None:
1258 return False
1259
1260 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
1261 self.re_editable_filenames = [self.instance_filename]
1262 return result
1263
1264 #--------------------------------------------------------
1266
1267 if instance_file is None:
1268 instance_file = self.instance_filename
1269
1270 try:
1271 open(instance_file, 'r').close()
1272 except Exception:
1273 _log.exception('cannot access form instance file [%s]', instance_file)
1274 gmLog2.log_stack_trace()
1275 return None
1276
1277 self.instance_filename = instance_file
1278
1279 _log.debug('ignoring <format> directive [%s], generating PDF', format)
1280 draft_cmd = self._draft_cmd_line + [self.instance_filename]
1281 final_cmd = self._final_cmd_line + [self.instance_filename]
1282 # LaTeX can need up to three runs to get cross references et al right
1283 for run_cmd in [draft_cmd, draft_cmd, final_cmd]:
1284 success, ret_code, stdout = gmShellAPI.run_process (
1285 cmd_line = run_cmd,
1286 acceptable_return_codes = [0],
1287 encoding = 'utf8',
1288 verbose = _cfg.get(option = 'debug')
1289 )
1290 if not success:
1291 _log.error('problem running pdflatex, cannot generate form output, trying diagnostics')
1292 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True)
1293 found, binary = gmShellAPI.find_first_binary(binaries = ['lacheck', 'miktex-lacheck.exe'])
1294 if not found:
1295 _log.debug('lacheck not found')
1296 else:
1297 cmd_line = [binary, self.instance_filename]
1298 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = True)
1299 found, binary = gmShellAPI.find_first_binary(binaries = ['chktex', 'ChkTeX.exe'])
1300 if not found:
1301 _log.debug('chcktex not found')
1302 else:
1303 cmd_line = [binary, '--verbosity=2', '--headererr', self.instance_filename]
1304 success, ret_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = True)
1305 return None
1306
1307 sandboxed_pdf_name = '%s.pdf' % os.path.splitext(self.instance_filename)[0]
1308 target_dir = os.path.normpath(os.path.join(os.path.split(sandboxed_pdf_name)[0], '..'))
1309 final_pdf_name = os.path.join (
1310 target_dir,
1311 os.path.split(sandboxed_pdf_name)[1]
1312 )
1313 _log.debug('copying sandboxed PDF: %s -> %s', sandboxed_pdf_name, final_pdf_name)
1314 try:
1315 shutil.copy2(sandboxed_pdf_name, target_dir)
1316 except IOError:
1317 _log.exception('cannot open/move sandboxed PDF')
1318 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True)
1319 return None
1320
1321 self.final_output_filenames = [final_pdf_name]
1322
1323 return final_pdf_name
1324
1325 #------------------------------------------------------------
1326 form_engines['L'] = cLaTeXForm
1327
1328 #================================================================
1329 # Xe(La)TeX template forms
1330 #----------------------------------------------------------------
1331 # Xe(La)TeX: http://www.scholarsfonts.net/xetextt.pdf
1333 """A forms engine wrapping Xe(La)TeX."""
1334
1336
1337 # create sandbox for LaTeX to play in (and don't assume
1338 # much of anything about the template_file except that it
1339 # is at our disposal)
1340 sandbox_dir = gmTools.mk_sandbox_dir(prefix = gmTools.fname_stem(template_file) + '_')
1341 _log.debug('Xe(La)TeX sandbox directory: [%s]', sandbox_dir)
1342 shutil.copy(template_file, sandbox_dir)
1343 template_file = os.path.join(sandbox_dir, os.path.split(template_file)[1])
1344
1345 super(self.__class__, self).__init__(template_file = template_file)
1346
1347 self.__sandbox_dir = sandbox_dir
1348 #--------------------------------------------------------
1350
1351 if self.template is not None:
1352 # inject placeholder values
1353 data_source.set_placeholder('form_name_long', self.template['name_long'])
1354 data_source.set_placeholder('form_name_short', self.template['name_short'])
1355 data_source.set_placeholder('form_version', self.template['external_version'])
1356 data_source.set_placeholder('form_version_internal', gmTools.coalesce(self.template['gnumed_revision'], '', '%s'))
1357 data_source.set_placeholder('form_last_modified', gmDateTime.pydt_strftime(self.template['last_modified'], '%Y-%b-%d %H:%M'))
1358
1359 data_source.escape_function = gmTools.xetex_escape_string
1360 data_source.escape_style = 'xetex'
1361
1362 path, ext = os.path.splitext(self.template_filename)
1363 if ext in [r'', r'.']:
1364 ext = r'.tex'
1365
1366 filenames = [
1367 self.template_filename,
1368 r'%s-result_run1%s' % (path, ext),
1369 r'%s-result_run2%s' % (path, ext),
1370 r'%s-result_run3%s' % (path, ext)
1371 ]
1372
1373 found_placeholders = True
1374 current_run = 1
1375 while found_placeholders and (current_run < 4):
1376 _log.debug('placeholder substitution run #%s', current_run)
1377 found_placeholders = self.__substitute_placeholders (
1378 input_filename = filenames[current_run-1],
1379 output_filename = filenames[current_run],
1380 data_source = data_source
1381 )
1382 current_run += 1
1383
1384 if self.template is not None:
1385 # remove temporary placeholders
1386 data_source.unset_placeholder('form_name_long')
1387 data_source.unset_placeholder('form_name_short')
1388 data_source.unset_placeholder('form_version')
1389 data_source.unset_placeholder('form_version_internal')
1390 data_source.unset_placeholder('form_last_modified')
1391
1392 self.instance_filename = self.re_editable_filenames[0]
1393
1394 return
1395 #--------------------------------------------------------
1396 - def __substitute_placeholders(self, data_source=None, input_filename=None, output_filename=None):
1397 _log.debug('[%s] -> [%s]', input_filename, output_filename)
1398
1399 found_placeholders = False
1400
1401 template_file = io.open(input_filename, mode = 'rt', encoding = 'utf8')
1402 instance_file = io.open(output_filename, mode = 'wt', encoding = 'utf8')
1403
1404 for line in template_file:
1405
1406 if line.strip() in ['', '\r', '\n', '\r\n']: # empty lines
1407 instance_file.write(line)
1408 continue
1409 if line.startswith('%'): # TeX comment
1410 instance_file.write(line)
1411 continue
1412
1413 for placeholder_regex in [data_source.first_pass_placeholder_regex, data_source.second_pass_placeholder_regex, data_source.third_pass_placeholder_regex]:
1414 # 1) find placeholders in this line
1415 placeholders_in_line = regex.findall(placeholder_regex, line, regex.IGNORECASE)
1416 if len(placeholders_in_line) == 0:
1417 continue
1418 _log.debug('%s placeholders found with pattern: %s', len(placeholders_in_line), placeholder_regex)
1419 found_placeholders = True
1420 # 2) replace them
1421 for placeholder in placeholders_in_line:
1422 try:
1423 val = data_source[placeholder]
1424 except Exception:
1425 _log.exception('error with placeholder [%s]', placeholder)
1426 val = gmTools.tex_escape_string(_('error with placeholder [%s]') % placeholder)
1427
1428 if val is None:
1429 _log.debug('error with placeholder [%s]', placeholder)
1430 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder)
1431
1432 line = line.replace(placeholder, val)
1433
1434 instance_file.write(line)
1435
1436 instance_file.close()
1437 self.re_editable_filenames = [output_filename]
1438 template_file.close()
1439
1440 return found_placeholders
1441 #--------------------------------------------------------
1443
1444 mimetypes = [
1445 'application/x-xetex',
1446 'application/x-latex',
1447 'application/x-tex',
1448 'text/plain'
1449 ]
1450
1451 for mimetype in mimetypes:
1452 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename)
1453 if editor_cmd is not None:
1454 break
1455
1456 if editor_cmd is None:
1457 # Xe(La)TeX code is utf8: also consider text *viewers*
1458 # since pretty much any of them will be an editor as well
1459 for mimetype in mimetypes:
1460 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename)
1461 if editor_cmd is not None:
1462 break
1463
1464 if editor_cmd is None:
1465 return False
1466
1467 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
1468 self.re_editable_filenames = [self.instance_filename]
1469 return result
1470 #--------------------------------------------------------
1472
1473 if instance_file is None:
1474 instance_file = self.instance_filename
1475
1476 try:
1477 open(instance_file, 'r').close()
1478 except Exception:
1479 _log.exception('cannot access form instance file [%s]', instance_file)
1480 gmLog2.log_stack_trace()
1481 return None
1482
1483 self.instance_filename = instance_file
1484
1485 _log.debug('ignoring <format> directive [%s], generating PDF', format)
1486
1487 # Xe(La)TeX can need up to three runs to get cross references et al right
1488 if platform.system() == 'Windows':
1489 # not yet supported: -draftmode
1490 # does not support: -shell-escape
1491 draft_cmd = r'xelatex.exe -interaction=nonstopmode -output-directory=%s %s' % (self.__sandbox_dir, self.instance_filename)
1492 final_cmd = r'xelatex.exe -interaction=nonstopmode -output-directory=%s %s' % (self.__sandbox_dir, self.instance_filename)
1493 else:
1494 # not yet supported: -draftmode
1495 draft_cmd = r'xelatex -interaction=nonstopmode -output-directory=%s -shell-escape %s' % (self.__sandbox_dir, self.instance_filename)
1496 final_cmd = r'xelatex -interaction=nonstopmode -output-directory=%s -shell-escape %s' % (self.__sandbox_dir, self.instance_filename)
1497
1498 for run_cmd in [draft_cmd, draft_cmd, final_cmd]:
1499 if not gmShellAPI.run_command_in_shell(command = run_cmd, blocking = True, acceptable_return_codes = [0, 1]):
1500 _log.error('problem running xelatex, cannot generate form output')
1501 gmDispatcher.send(signal = 'statustext', msg = _('Error running xelatex. Cannot turn Xe(La)TeX template into PDF.'), beep = True)
1502 return None
1503
1504 sandboxed_pdf_name = '%s.pdf' % os.path.splitext(self.instance_filename)[0]
1505 target_dir = os.path.normpath(os.path.join(os.path.split(sandboxed_pdf_name)[0], '..'))
1506 final_pdf_name = os.path.join (
1507 target_dir,
1508 os.path.split(sandboxed_pdf_name)[1]
1509 )
1510 _log.debug('copying sandboxed PDF: %s -> %s', sandboxed_pdf_name, final_pdf_name)
1511 try:
1512 shutil.copy2(sandboxed_pdf_name, target_dir)
1513 except IOError:
1514 _log.exception('cannot open/move sandboxed PDF')
1515 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True)
1516 return None
1517
1518 self.final_output_filenames = [final_pdf_name]
1519
1520 return final_pdf_name
1521
1522 #------------------------------------------------------------
1523 form_engines['X'] = cXeTeXForm
1524
1525 #============================================================
1526 # Gnuplot template forms
1527 #------------------------------------------------------------
1528 _GNUPLOT_WRAPPER_SCRIPT = """# --------------------------------------------------------------
1529 # GNUplot wrapper script used by GNUmed
1530 #
1531 # This script is used to make gnuplot
1532 #
1533 # display some debugging information
1534 #
1535 # load GNUmed specific settings such as the timestamp
1536 # format, encoding, symbol for missing values,
1537 #
1538 # load data specific values such as y(2)label or plot
1539 # title which GNUmed will have set up while exporting
1540 # test results for plotting,
1541 #
1542 # know the datafile name from the variable <gm2gpl_datafile>
1543 # which the user provided plotting script can then use
1544 # to access data like so:
1545 #
1546 # plot gm2gpl_datafile ...
1547 #
1548 # --------------------------------------------------------------
1549
1550 # logging verbosity, depending on GNUmed client debug state
1551 gmd_log_verbose = %s
1552
1553
1554 # -- debugging ----
1555 show version long
1556 if (gmd_log_verbose == 1) {
1557 print "-- <show all> at startup ----"
1558 show all
1559 print "-- <show variables all> at startup ----"
1560 show variables all
1561 }
1562
1563
1564 # -- data format setup ----
1565 set encoding utf8
1566 set timefmt "%%Y-%%m-%%d_%%H:%%M" # timestamp input formatting, not for output
1567
1568
1569 # -- data file setup ----
1570 gm2gpl_datafile = '%s'
1571 set datafile missing "<?>"
1572 set xdata time
1573 set x2data time
1574
1575
1576 # -- process additional definitions from GNUmed ----
1577 gm2gpl_datafile_conf = gm2gpl_datafile.'.conf'
1578 load gm2gpl_datafile_conf
1579
1580
1581 # -- actually run the user provided plotting script ----
1582 call '%s'
1583
1584
1585 # -- debugging ----
1586 if (gmd_log_verbose == 1) {
1587 print "-- <show all> after running user provided plotting script ----"
1588 show all
1589 print "-- <show variables all> after running user provided plotting script ----"
1590 show variables all
1591
1592 # PNG output:
1593 #set terminal png enhanced transparent nointerlace truecolor #medium #crop
1594 ##set output 'test_terminal.png'
1595 ##test
1596 #set output gm2gpl_datafile.'.dbg.png'
1597 #replot
1598
1599 # ASCII art output:
1600 #set terminal dumb size 120,45 feed enhanced ansirgb
1601 ##set output 'test_terminal.txt'
1602 ##test
1603 #set output gm2gpl_datafile.'.dbg.txt'
1604 #replot
1605 #set terminal dumb size 120,45 feed enhanced mono
1606 ##set output 'test_terminal.ascii.txt'
1607 ##test
1608 #set output gm2gpl_datafile.'.dbg.ascii.txt'
1609 #replot
1610 }
1611 """
1612
1614 """A forms engine wrapping Gnuplot."""
1615
1616 #--------------------------------------------------------
1620 #--------------------------------------------------------
1622 """Allow editing the instance of the template."""
1623 self.re_editable_filenames = []
1624 return True
1625
1626 #--------------------------------------------------------
1628 """Generate output suitable for further processing outside this class, e.g. printing.
1629
1630 Expects .data_filename to be set.
1631 """
1632 wrapper_filename = gmTools.get_unique_filename (
1633 prefix = 'gm2gpl-wrapper-',
1634 suffix = '.gpl',
1635 tmp_dir = gmTools.fname_dir(self.data_filename)
1636 )
1637 wrapper_script = io.open(wrapper_filename, mode = 'wt', encoding = 'utf8')
1638 wrapper_script.write(_GNUPLOT_WRAPPER_SCRIPT % (
1639 gmTools.bool2subst(_cfg.get(option = 'debug'), '1', '0', '0'),
1640 self.data_filename,
1641 self.template_filename
1642 ))
1643 wrapper_script.close()
1644 # FIXME: cater for configurable path
1645 if platform.system() == 'Windows':
1646 exec_name = 'gnuplot.exe'
1647 else:
1648 exec_name = 'gnuplot'
1649 cmd_line = [
1650 exec_name,
1651 '-p', # persist plot window after gnuplot exits (in case the wxt terminal is used)
1652 wrapper_filename
1653 ]
1654 success, exit_code, stdout = gmShellAPI.run_process(cmd_line = cmd_line, encoding = 'utf8', verbose = _cfg.get(option = 'debug'))
1655 if not success:
1656 gmDispatcher.send(signal = 'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True)
1657 return
1658
1659 self.final_output_filenames = [
1660 self.data_filename,
1661 self.template_filename,
1662 wrapper_filename
1663 ]
1664 return
1665
1666 #------------------------------------------------------------
1667 form_engines['G'] = cGnuplotForm
1668
1669 #============================================================
1670 # fPDF form engine
1671 #------------------------------------------------------------
1673 """A forms engine wrapping PDF forms.
1674
1675 Johann Felix Soden <johfel@gmx.de> helped with this.
1676
1677 http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf
1678
1679 http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/fdf_data_exchange.pdf
1680 """
1681
1683
1684 super(cPDFForm, self).__init__(template_file = template_file)
1685
1686 # detect pdftk
1687 found, self.pdftk_binary = gmShellAPI.detect_external_binary(binary = r'pdftk')
1688 if not found:
1689 raise ImportError('<pdftk(.exe)> not found')
1690 return # should be superfluous, actually
1691
1692 enc = sys.getfilesystemencoding()
1693 self.pdftk_binary = self.pdftk_binary.encode(enc)
1694
1695 base_name, ext = os.path.splitext(self.template_filename)
1696 self.fdf_dumped_filename = ('%s.fdf' % base_name).encode(enc)
1697 self.fdf_replaced_filename = ('%s-replaced.fdf' % base_name).encode(enc)
1698 self.pdf_filled_filename = ('%s-filled.pdf' % base_name).encode(enc)
1699 self.pdf_flattened_filename = ('%s-filled-flattened.pdf' % base_name).encode(enc)
1700 #--------------------------------------------------------
1702
1703 # dump form fields from template
1704 cmd_line = [
1705 self.pdftk_binary,
1706 self.template_filename,
1707 r'generate_fdf',
1708 r'output',
1709 self.fdf_dumped_filename
1710 ]
1711 _log.debug(' '.join(cmd_line))
1712 try:
1713 pdftk = subprocess.Popen(cmd_line)
1714 except OSError:
1715 _log.exception('cannot run <pdftk> (dump data from form)')
1716 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot extract fields from PDF form template.'), beep = True)
1717 return False
1718
1719 pdftk.communicate()
1720 if pdftk.returncode != 0:
1721 _log.error('<pdftk> returned [%s], failed to dump data from PDF form into FDF', pdftk.returncode)
1722 return False
1723
1724 # parse dumped FDF file for "/V (...)" records
1725 # and replace placeholders therein
1726 fdf_dumped_file = io.open(self.fdf_dumped_filename, mode = 'rt', encoding = 'utf8')
1727 fdf_replaced_file = io.open(self.fdf_replaced_filename, mode = 'wt', encoding = 'utf8')
1728
1729 string_value_regex = r'\s*/V\s*\(.+\)\s*$'
1730 for line in fdf_dumped_file:
1731 if not regex.match(string_value_regex, line):
1732 fdf_replaced_file.write(line)
1733 continue
1734
1735 # strip cruft around the string value
1736 raw_str_val = line.strip() # remove framing whitespace
1737 raw_str_val = raw_str_val[2:] # remove leading "/V"
1738 raw_str_val = raw_str_val.lstrip() # remove whitespace between "/V" and "("
1739 raw_str_val = raw_str_val[1:] # remove opening "("
1740 raw_str_val = raw_str_val[2:] # remove BOM-16-BE
1741 raw_str_val = raw_str_val.rstrip() # remove trailing whitespace
1742 raw_str_val = raw_str_val[:-1] # remove closing ")"
1743
1744 # work on FDF escapes
1745 raw_str_val = raw_str_val.replace('\(', '(') # remove escaping of "("
1746 raw_str_val = raw_str_val.replace('\)', ')') # remove escaping of ")"
1747
1748 # by now raw_str_val should contain the actual
1749 # string value, albeit encoded as UTF-16, so
1750 # decode it into a unicode object,
1751 # split multi-line fields on "\n" literal
1752 raw_str_lines = raw_str_val.split('\x00\\n')
1753 value_template_lines = []
1754 for raw_str_line in raw_str_lines:
1755 value_template_lines.append(raw_str_line.decode('utf_16_be'))
1756
1757 replaced_lines = []
1758 for value_template in value_template_lines:
1759 # find any placeholders within
1760 placeholders_in_value = regex.findall(data_source.placeholder_regex, value_template, regex.IGNORECASE)
1761 for placeholder in placeholders_in_value:
1762 try:
1763 replacement = data_source[placeholder]
1764 except Exception:
1765 _log.exception(replacement)
1766 replacement = _('error with placeholder [%s]') % placeholder
1767 if replacement is None:
1768 replacement = _('error with placeholder [%s]') % placeholder
1769 value_template = value_template.replace(placeholder, replacement)
1770
1771 value_template = value_template.encode('utf_16_be')
1772
1773 if len(placeholders_in_value) > 0:
1774 value_template = value_template.replace(r'(', r'\(')
1775 value_template = value_template.replace(r')', r'\)')
1776
1777 replaced_lines.append(value_template)
1778
1779 replaced_line = '\x00\\n'.join(replaced_lines)
1780
1781 fdf_replaced_file.write('/V (')
1782 fdf_replaced_file.write(codecs.BOM_UTF16_BE)
1783 fdf_replaced_file.write(replaced_line)
1784 fdf_replaced_file.write(')\n')
1785
1786 fdf_replaced_file.close()
1787 fdf_dumped_file.close()
1788
1789 # merge replaced data back into form
1790 cmd_line = [
1791 self.pdftk_binary,
1792 self.template_filename,
1793 r'fill_form',
1794 self.fdf_replaced_filename,
1795 r'output',
1796 self.pdf_filled_filename
1797 ]
1798 _log.debug(' '.join(cmd_line))
1799 try:
1800 pdftk = subprocess.Popen(cmd_line)
1801 except OSError:
1802 _log.exception('cannot run <pdftk> (merge data into form)')
1803 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot fill in PDF form template.'), beep = True)
1804 return False
1805
1806 pdftk.communicate()
1807 if pdftk.returncode != 0:
1808 _log.error('<pdftk> returned [%s], failed to merge FDF data into PDF form', pdftk.returncode)
1809 return False
1810
1811 return True
1812 #--------------------------------------------------------
1814 mimetypes = [
1815 'application/pdf',
1816 'application/x-pdf'
1817 ]
1818
1819 for mimetype in mimetypes:
1820 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.pdf_filled_filename)
1821 if editor_cmd is not None:
1822 break
1823
1824 if editor_cmd is None:
1825 _log.debug('editor cmd not found, trying viewer cmd')
1826 for mimetype in mimetypes:
1827 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.pdf_filled_filename)
1828 if editor_cmd is not None:
1829 break
1830
1831 if editor_cmd is None:
1832 return False
1833
1834 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
1835
1836 path, fname = os.path.split(self.pdf_filled_filename)
1837 candidate = os.path.join(gmTools.gmPaths().home_dir, fname)
1838
1839 if os.access(candidate, os.R_OK):
1840 _log.debug('filled-in PDF found: %s', candidate)
1841 os.rename(self.pdf_filled_filename, self.pdf_filled_filename + '.bak')
1842 shutil.move(candidate, path)
1843 else:
1844 _log.debug('filled-in PDF not found: %s', candidate)
1845
1846 self.re_editable_filenames = [self.pdf_filled_filename]
1847
1848 return result
1849 #--------------------------------------------------------
1851 """Generate output suitable for further processing outside this class, e.g. printing."""
1852
1853 # eventually flatten the filled in form so we
1854 # can keep both a flattened and an editable copy:
1855 cmd_line = [
1856 self.pdftk_binary,
1857 self.pdf_filled_filename,
1858 r'output',
1859 self.pdf_flattened_filename,
1860 r'flatten'
1861 ]
1862 _log.debug(' '.join(cmd_line))
1863 try:
1864 pdftk = subprocess.Popen(cmd_line)
1865 except OSError:
1866 _log.exception('cannot run <pdftk> (flatten filled in form)')
1867 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdftk. Cannot flatten filled in PDF form.'), beep = True)
1868 return None
1869
1870 pdftk.communicate()
1871 if pdftk.returncode != 0:
1872 _log.error('<pdftk> returned [%s], failed to flatten filled in PDF form', pdftk.returncode)
1873 return None
1874
1875 self.final_output_filenames = [self.pdf_flattened_filename]
1876
1877 return self.pdf_flattened_filename
1878 #------------------------------------------------------------
1879 form_engines['P'] = cPDFForm
1880
1881 #============================================================
1882 # older code
1883 #------------------------------------------------------------
1885 """A forms engine wrapping LaTeX.
1886 """
1890
1892 try:
1893 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params])
1894 # create a 'sandbox' directory for LaTeX to play in
1895 self.tmp = tempfile.mktemp ()
1896 os.makedirs (self.tmp)
1897 self.oldcwd = os.getcwd()
1898 os.chdir (self.tmp)
1899 stdin = os.popen ("latex", "w", 2048)
1900 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout
1901 # FIXME: send LaTeX output to the logger
1902 stdin.close ()
1903 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True):
1904 raise FormError ('DVIPS returned error')
1905 except EnvironmentError as e:
1906 _log.error(e.strerror)
1907 raise FormError (e.strerror)
1908 return open("texput.ps")
1909
1911 """
1912 For testing purposes, runs Xdvi on the intermediate TeX output
1913 WARNING: don't try this on Windows
1914 """
1915 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)
1916
1918 if "%F" in command:
1919 command.replace ("%F", "texput.ps")
1920 else:
1921 command = "%s < texput.ps" % command
1922 try:
1923 if not gmShellAPI.run_command_in_shell(command, blocking=True):
1924 _log.error("external command %s returned non-zero" % command)
1925 raise FormError ('external command %s returned error' % command)
1926 except EnvironmentError as e:
1927 _log.error(e.strerror)
1928 raise FormError (e.strerror)
1929 return True
1930
1932 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print')
1933 self.exe (command)
1934
1936 """
1937 Delete all the LaTeX output iles
1938 """
1939 for i in os.listdir ('.'):
1940 os.unlink (i)
1941 os.chdir (self.oldcwd)
1942 os.rmdir (self.tmp)
1943
1944
1945
1946
1947 #================================================================
1948 # define a class for HTML forms (for printing)
1949 #================================================================
1951 """This class can create XML document from requested data,
1952 then process it with XSLT template and display results
1953 """
1954
1955 # FIXME: make the path configurable ?
1956 _preview_program = 'oowriter ' #this program must be in the system PATH
1957
1959
1960 if template is None:
1961 raise ValueError('%s: cannot create form instance without a template' % __name__)
1962
1963 cFormEngine.__init__(self, template = template)
1964
1965 self._FormData = None
1966
1967 # here we know/can assume that the template was stored as a utf-8
1968 # encoded string so use that conversion to create unicode:
1969 #self._XSLTData = str(str(template.template_data), 'UTF-8')
1970 # but in fact, str() knows how to handle buffers, so simply:
1971 self._XSLTData = str(self.template.template_data, 'UTF-8', 'strict')
1972
1973 # we must still devise a method of extracting the SQL query:
1974 # - either by retrieving it from a particular tag in the XSLT or
1975 # - by making the stored template actually be a dict which, unpickled,
1976 # has the keys "xslt" and "sql"
1977 self._SQL_query = 'select 1' #this sql query must output valid xml
1978 #--------------------------------------------------------
1979 # external API
1980 #--------------------------------------------------------
1982 """get data from backend and process it with XSLT template to produce readable output"""
1983
1984 # extract SQL (this is wrong but displays what is intended)
1985 xslt = libxml2.parseDoc(self._XSLTData)
1986 root = xslt.children
1987 for child in root:
1988 if child.type == 'element':
1989 self._SQL_query = child.content
1990 break
1991
1992 # retrieve data from backend
1993 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False)
1994
1995 __header = '<?xml version="1.0" encoding="UTF-8"?>\n'
1996 __body = rows[0][0]
1997
1998 # process XML data according to supplied XSLT, producing HTML
1999 self._XMLData =__header + __body
2000 style = libxslt.parseStylesheetDoc(xslt)
2001 xml = libxml2.parseDoc(self._XMLData)
2002 html = style.applyStylesheet(xml, None)
2003 self._FormData = html.serialize()
2004
2005 style.freeStylesheet()
2006 xml.freeDoc()
2007 html.freeDoc()
2008 #--------------------------------------------------------
2010 if self._FormData is None:
2011 raise ValueError('Preview request for empty form. Make sure the form is properly initialized and process() was performed')
2012
2013 fname = gmTools.get_unique_filename(prefix = 'gm_XSLT_form-', suffix = '.html')
2014 #html_file = os.open(fname, 'wb')
2015 #html_file.write(self._FormData.encode('UTF-8'))
2016 html_file = io.open(fname, mode = 'wt', encoding = 'utf8', errors = 'strict') # or 'replace' ?
2017 html_file.write(self._FormData)
2018 html_file.close()
2019
2020 cmd = '%s %s' % (self.__class__._preview_program, fname)
2021
2022 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False):
2023 _log.error('%s: cannot launch report preview program' % __name__)
2024 return False
2025
2026 #os.unlink(self.filename) #delete file
2027 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK)
2028
2029 return True
2030 #--------------------------------------------------------
2034
2035
2036 #=====================================================
2037 #class LaTeXFilter(Cheetah.Filters.Filter):
2040 """
2041 Convience function to escape ISO-Latin-1 strings for TeX output
2042 WARNING: not all ISO-Latin-1 characters are expressible in TeX
2043 FIXME: nevertheless, there are a few more we could support
2044
2045 Also intelligently convert lists and tuples into TeX-style table lines
2046 """
2047 if type(item) is str:
2048 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX?
2049 item = item.replace ("&", "\\&")
2050 item = item.replace ("$", "\\$")
2051 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now
2052 item = item.replace ("\n", "\\\\ ")
2053 if len (item.strip ()) == 0:
2054 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it
2055 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX
2056 item = item.encode ('latin-1', 'replace')
2057 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}',
2058 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions
2059 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`',
2060 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}',
2061 '\xc7':'\\c{C}', '\xc8':'\\`{E}',
2062 '\xa1': '!`',
2063 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'
2064 }
2065 for k, i in trans.items ():
2066 item = item.replace (k, i)
2067 elif type(item) is list or type(item) is tuple:
2068 item = string.join ([self.conv_enc(i, ' & ') for i in item], table_sep)
2069 elif item is None:
2070 item = '\\relax % Python None\n'
2071 elif type(item) is int or type(item) is float:
2072 item = str(item)
2073 else:
2074 item = str(item)
2075 _log.warning("unknown type %s, string %s" % (type(item), item))
2076 return item
2077
2078
2079 #===========================================================
2082
2083 #============================================================
2084 # convenience functions
2085 #------------------------------------------------------------
2087 """
2088 Instantiates a FormEngine based on the form ID or name from the backend
2089 """
2090 try:
2091 # it's a number: match to form ID
2092 id = int (id)
2093 cmd = 'select template, engine, pk from paperwork_templates where pk = %s'
2094 except ValueError:
2095 # it's a string, match to the form's name
2096 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ?
2097 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s'
2098 result = gmPG.run_ro_query ('reference', cmd, None, id)
2099 if result is None:
2100 _log.error('error getting form [%s]' % id)
2101 raise gmExceptions.FormError ('error getting form [%s]' % id)
2102 if len(result) == 0:
2103 _log.error('no form [%s] found' % id)
2104 raise gmExceptions.FormError ('no such form found [%s]' % id)
2105 if result[0][1] == 'L':
2106 return LaTeXForm (result[0][2], result[0][0])
2107 elif result[0][1] == 'T':
2108 return TextForm (result[0][2], result[0][0])
2109 else:
2110 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id))
2111 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))
2112 #-------------------------------------------------------------
2119 #-------------------------------------------------------------
2120
2121 test_letter = """
2122 \\documentclass{letter}
2123 \\address{ $DOCTOR \\\\
2124 $DOCTORADDRESS}
2125 \\signature{$DOCTOR}
2126
2127 \\begin{document}
2128 \\begin{letter}{$RECIPIENTNAME \\\\
2129 $RECIPIENTADDRESS}
2130
2131 \\opening{Dear $RECIPIENTNAME}
2132
2133 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\
2134
2135 $TEXT
2136
2137 \\ifnum$INCLUDEMEDS>0
2138 \\textbf{Medications List}
2139
2140 \\begin{tabular}{lll}
2141 $MEDSLIST
2142 \\end{tabular}
2143 \\fi
2144
2145 \\ifnum$INCLUDEDISEASES>0
2146 \\textbf{Disease List}
2147
2148 \\begin{tabular}{l}
2149 $DISEASELIST
2150 \\end{tabular}
2151 \\fi
2152
2153 \\closing{$CLOSING}
2154
2155 \\end{letter}
2156 \\end{document}
2157 """
2158
2159
2161 f = io.open('../../test-area/ian/terry-form.tex')
2162 params = {
2163 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle",
2164 'DOCTORSNAME': 'Ian Haywood',
2165 'DOCTORSADDRESS': '1 Smith St\nMelbourne',
2166 'PATIENTNAME':'Joe Bloggs',
2167 'PATIENTADDRESS':'18 Fred St\nMelbourne',
2168 'REQUEST':'echocardiogram',
2169 'THERAPY':'on warfarin',
2170 'CLINICALNOTES':"""heard new murmur
2171 Here's some
2172 crap to demonstrate how it can cover multiple lines.""",
2173 'COPYADDRESS':'Jack Jones\nHannover, Germany',
2174 'ROUTINE':1,
2175 'URGENT':0,
2176 'FAX':1,
2177 'PHONE':1,
2178 'PENSIONER':1,
2179 'VETERAN':0,
2180 'PADS':0,
2181 'INSTRUCTIONS':'Take the blue pill, Neo'
2182 }
2183 form = LaTeXForm (1, f.read())
2184 form.process (params)
2185 form.xdvi ()
2186 form.cleanup ()
2187
2189 form = LaTeXForm (2, test_letter)
2190 params = {'RECIPIENTNAME':'Dr. Richard Terry',
2191 'RECIPIENTADDRESS':'1 Main St\nNewcastle',
2192 'DOCTOR':'Dr. Ian Haywood',
2193 'DOCTORADDRESS':'1 Smith St\nMelbourne',
2194 'PATIENTNAME':'Joe Bloggs',
2195 'PATIENTADDRESS':'18 Fred St, Melbourne',
2196 'TEXT':"""This is the main text of the referral letter""",
2197 'DOB':'12/3/65',
2198 'INCLUDEMEDS':1,
2199 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]],
2200 'INCLUDEDISEASES':0, 'DISEASELIST':'',
2201 'CLOSING':'Yours sincerely,'
2202 }
2203 form.process (params)
2204 print(os.getcwd())
2205 form.xdvi()
2206 form.cleanup()
2207
2208 #------------------------------------------------------------
2210 template = io.open('../../test-area/ian/Formularkopf-DE.tex')
2211 form = LaTeXForm(template=template.read())
2212 params = {
2213 'PATIENT LASTNAME': 'Kirk',
2214 'PATIENT FIRSTNAME': 'James T.',
2215 'PATIENT STREET': 'Hauptstrasse',
2216 'PATIENT ZIP': '02999',
2217 'PATIENT TOWN': 'Gross Saerchen',
2218 'PATIENT DOB': '22.03.1931'
2219 }
2220 form.process(params)
2221 form.xdvi()
2222 form.cleanup()
2223
2224 #============================================================
2225 # main
2226 #------------------------------------------------------------
2227 if __name__ == '__main__':
2228
2229 if len(sys.argv) < 2:
2230 sys.exit()
2231
2232 if sys.argv[1] != 'test':
2233 sys.exit()
2234
2235 gmDateTime.init()
2236
2237 #--------------------------------------------------------
2238 # OOo
2239 #--------------------------------------------------------
2241 init_ooo()
2242 #--------------------------------------------------------
2247 #--------------------------------------------------------
2249 srv = gmOOoConnector()
2250 doc = srv.open_document(filename = sys.argv[2])
2251 print("document:", doc)
2252 #--------------------------------------------------------
2254 doc = cOOoLetter(template_file = sys.argv[2])
2255 doc.open_in_ooo()
2256 print("document:", doc)
2257 input('press <ENTER> to continue')
2258 doc.show()
2259 #doc.replace_placeholders()
2260 #doc.save_in_ooo('~/test_cOOoLetter.odt')
2261 # doc = None
2262 # doc.close_in_ooo()
2263 input('press <ENTER> to continue')
2264 #--------------------------------------------------------
2266 doc = open_uri_in_ooo(filename=sys.argv[1])
2267
2268 class myCloseListener(unohelper.Base, oooXCloseListener):
2269 def disposing(self, evt):
2270 print("disposing:")
2271 def notifyClosing(self, evt):
2272 print("notifyClosing:")
2273 def queryClosing(self, evt, owner):
2274 # owner is True/False whether I am the owner of the doc
2275 print("queryClosing:")
2276
2277 l = myCloseListener()
2278 doc.addCloseListener(l)
2279
2280 tfs = doc.getTextFields().createEnumeration()
2281 print(tfs)
2282 print(dir(tfs))
2283 while tfs.hasMoreElements():
2284 tf = tfs.nextElement()
2285 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'):
2286 print(tf.getPropertyValue('PlaceHolder'))
2287 print(" ", tf.getPropertyValue('Hint'))
2288
2289 # doc.close(True) # closes but leaves open the dedicated OOo window
2290 doc.dispose() # closes and disposes of the OOo window
2291 #--------------------------------------------------------
2293 pat = gmPersonSearch.ask_for_patient()
2294 if pat is None:
2295 return
2296 gmPerson.set_active_patient(patient = pat)
2297
2298 doc = cOOoLetter(template_file = sys.argv[2])
2299 doc.open_in_ooo()
2300 print(doc)
2301 doc.show()
2302 #doc.replace_placeholders()
2303 #doc.save_in_ooo('~/test_cOOoLetter.odt')
2304 doc = None
2305 # doc.close_in_ooo()
2306 input('press <ENTER> to continue')
2307 #--------------------------------------------------------
2308 # other
2309 #--------------------------------------------------------
2311 template = cFormTemplate(aPK_obj = sys.argv[2])
2312 print(template)
2313 print(template.save_to_file())
2314 #--------------------------------------------------------
2316 template = cFormTemplate(aPK_obj = sys.argv[2])
2317 template.update_template_from_file(filename = sys.argv[3])
2318 #--------------------------------------------------------
2320 pat = gmPersonSearch.ask_for_patient()
2321 if pat is None:
2322 return
2323 gmPerson.set_active_patient(patient = pat)
2324
2325 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
2326
2327 path = os.path.abspath(sys.argv[2])
2328 form = cLaTeXForm(template_file = path)
2329
2330 from Gnumed.wxpython import gmMacro
2331 ph = gmMacro.gmPlaceholderHandler()
2332 ph.debug = True
2333 instance_file = form.substitute_placeholders(data_source = ph)
2334 pdf_name = form.generate_output(instance_file = instance_file)
2335 print("final PDF file is:", pdf_name)
2336 #--------------------------------------------------------
2338 pat = gmPersonSearch.ask_for_patient()
2339 if pat is None:
2340 return
2341 gmPerson.set_active_patient(patient = pat)
2342
2343 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
2344
2345 path = os.path.abspath(sys.argv[2])
2346 form = cPDFForm(template_file = path)
2347
2348 from Gnumed.wxpython import gmMacro
2349 ph = gmMacro.gmPlaceholderHandler()
2350 ph.debug = True
2351 instance_file = form.substitute_placeholders(data_source = ph)
2352 pdf_name = form.generate_output(instance_file = instance_file)
2353 print("final PDF file is:", pdf_name)
2354 #--------------------------------------------------------
2356 pat = gmPersonSearch.ask_for_patient()
2357 if pat is None:
2358 return
2359 gmPerson.set_active_patient(patient = pat)
2360
2361 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
2362
2363 path = os.path.abspath(sys.argv[2])
2364 form = cAbiWordForm(template_file = path)
2365
2366 from Gnumed.wxpython import gmMacro
2367 ph = gmMacro.gmPlaceholderHandler()
2368 ph.debug = True
2369 instance_file = form.substitute_placeholders(data_source = ph)
2370 form.edit()
2371 final_name = form.generate_output(instance_file = instance_file)
2372 print("final file is:", final_name)
2373 #--------------------------------------------------------
2375
2376 from Gnumed.business import gmPraxis
2377
2378 branches = gmPraxis.get_praxis_branches()
2379 praxis = gmPraxis.gmCurrentPraxisBranch(branches[0])
2380 print(praxis)
2381
2382 pat = gmPersonSearch.ask_for_patient()
2383 if pat is None:
2384 return
2385 gmPerson.set_active_patient(patient = pat)
2386
2387 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
2388
2389 path = os.path.abspath(sys.argv[2])
2390 form = cTextForm(template_file = path)
2391
2392 from Gnumed.wxpython import gmMacro
2393 ph = gmMacro.gmPlaceholderHandler()
2394 ph.debug = True
2395 print("placeholder substitution worked:", form.substitute_placeholders(data_source = ph))
2396 print(form.re_editable_filenames)
2397 form.edit()
2398 form.generate_output()
2399 #--------------------------------------------------------
2400 #--------------------------------------------------------
2401 #--------------------------------------------------------
2402 # now run the tests
2403 #test_au()
2404 #test_de()
2405
2406 # OOo
2407 #test_init_ooo()
2408 #test_ooo_connect()
2409 #test_open_ooo_doc_from_srv()
2410 #test_open_ooo_doc_from_letter()
2411 #play_with_ooo()
2412 #test_cOOoLetter()
2413
2414 #test_cFormTemplate()
2415 #set_template_from_file()
2416 test_latex_form()
2417 #test_pdf_form()
2418 #test_abiword_form()
2419 #test_text_form()
2420
2421 #============================================================
2422
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jun 26 01:55:29 2020 | http://epydoc.sourceforge.net |