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