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