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