| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: latin-1 -*-
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 __version__ = "$Revision: 1.79 $"
10 __author__ ="Ian Haywood <ihaywood@gnu.org>, karsten.hilbert@gmx.net"
11
12
13 import os, sys, time, os.path, logging
14 import codecs
15 import re as regex
16 import shutil
17 import random, platform, subprocess
18 import socket # needed for OOo on Windows
19 #, libxml2, libxslt
20 import shlex
21
22
23 if __name__ == '__main__':
24 sys.path.insert(0, '../../')
25 from Gnumed.pycommon import gmI18N
26 gmI18N.activate_locale()
27 gmI18N.install_domain(domain='gnumed')
28 from Gnumed.pycommon import gmTools
29 from Gnumed.pycommon import gmDispatcher
30 from Gnumed.pycommon import gmExceptions
31 from Gnumed.pycommon import gmMatchProvider
32 from Gnumed.pycommon import gmBorg
33 from Gnumed.pycommon import gmLog2
34 from Gnumed.pycommon import gmMimeLib
35 from Gnumed.pycommon import gmShellAPI
36 from Gnumed.pycommon import gmCfg
37 from Gnumed.pycommon import gmBusinessDBObject
38 from Gnumed.pycommon import gmPG2
39
40 from Gnumed.business import gmPerson
41 from Gnumed.business import gmStaff
42 from Gnumed.business import gmPersonSearch
43 from Gnumed.business import gmSurgery
44
45
46 _log = logging.getLogger('gm.forms')
47 _log.info(__version__)
48
49 #============================================================
50 # this order is also used in choice boxes for the engine
51 form_engine_abbrevs = [u'O', u'L', u'I', u'G', u'P', u'A']
52
53 form_engine_names = {
54 u'O': 'OpenOffice',
55 u'L': 'LaTeX',
56 u'I': 'Image editor',
57 u'G': 'Gnuplot script',
58 u'P': 'PDF forms',
59 u'A': 'AbiWord'
60 }
61
62 form_engine_template_wildcards = {
63 u'O': u'*.o?t',
64 u'L': u'*.tex',
65 u'G': u'*.gpl',
66 u'P': u'*.pdf',
67 u'A': u'*.abw'
68 }
69
70 # is filled in further below after each engine is defined
71 form_engines = {}
72
73 #============================================================
74 # match providers
75 #============================================================
77
79
80 query = u"""
81 SELECT
82 name_long AS data,
83 name_long AS list_label,
84 name_long AS field_label
85 FROM ref.v_paperwork_templates
86 WHERE name_long %(fragment_condition)s
87 ORDER BY list_label
88 """
89 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
90 #============================================================
92
94
95 query = u"""
96 SELECT
97 name_short AS data,
98 name_short AS list_label,
99 name_short AS field_label
100 FROM ref.v_paperwork_templates
101 WHERE name_short %(fragment_condition)s
102 ORDER BY name_short
103 """
104 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
105 #============================================================
107
109
110 query = u"""
111 SELECT DISTINCT ON (list_label)
112 pk AS data,
113 _(name) || ' (' || name || ')' AS list_label,
114 _(name) AS field_label
115 FROM ref.form_types
116 WHERE
117 _(name) %(fragment_condition)s
118 OR
119 name %(fragment_condition)s
120 ORDER BY list_label
121 """
122 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
123 #============================================================
125
126 _cmd_fetch_payload = u'select * from ref.v_paperwork_templates where pk_paperwork_template = %s'
127
128 _cmds_store_payload = [
129 u"""update ref.paperwork_templates set
130 name_short = %(name_short)s,
131 name_long = %(name_long)s,
132 fk_template_type = %(pk_template_type)s,
133 instance_type = %(instance_type)s,
134 engine = %(engine)s,
135 in_use = %(in_use)s,
136 filename = %(filename)s,
137 external_version = %(external_version)s
138 where
139 pk = %(pk_paperwork_template)s and
140 xmin = %(xmin_paperwork_template)s
141 """,
142 u"""select xmin_paperwork_template from ref.v_paperwork_templates where pk_paperwork_template = %(pk_paperwork_template)s"""
143 ]
144
145 _updatable_fields = [
146 u'name_short',
147 u'name_long',
148 u'external_version',
149 u'pk_template_type',
150 u'instance_type',
151 u'engine',
152 u'in_use',
153 u'filename'
154 ]
155
156 _suffix4engine = {
157 u'O': u'.ott',
158 u'L': u'.tex',
159 u'T': u'.txt',
160 u'X': u'.xslt',
161 u'I': u'.img',
162 u'P': u'.pdf'
163 }
164
165 #--------------------------------------------------------
167 """The template itself better not be arbitrarily large unless you can handle that.
168
169 Note that the data type returned will be a buffer."""
170
171 cmd = u'SELECT data FROM ref.paperwork_templates WHERE pk = %(pk)s'
172 rows, idx = gmPG2.run_ro_queries (queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False)
173
174 if len(rows) == 0:
175 raise gmExceptions.NoSuchBusinessObjectError('cannot retrieve data for template pk = %s' % self.pk_obj)
176
177 return rows[0][0]
178
179 template_data = property(_get_template_data, lambda x:x)
180 #--------------------------------------------------------
182 """Export form template from database into file."""
183
184 if filename is None:
185 if self._payload[self._idx['filename']] is None:
186 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]]
187 else:
188 suffix = os.path.splitext(self._payload[self._idx['filename']].strip())[1].strip()
189 if suffix in [u'', u'.']:
190 suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]]
191
192 filename = gmTools.get_unique_filename (
193 prefix = 'gm-%s-Template-' % self._payload[self._idx['engine']],
194 suffix = suffix
195 )
196
197 data_query = {
198 'cmd': u'SELECT substring(data from %(start)s for %(size)s) FROM ref.paperwork_templates WHERE pk = %(pk)s',
199 'args': {'pk': self.pk_obj}
200 }
201
202 data_size_query = {
203 'cmd': u'select octet_length(data) from ref.paperwork_templates where pk = %(pk)s',
204 'args': {'pk': self.pk_obj}
205 }
206
207 result = gmPG2.bytea2file (
208 data_query = data_query,
209 filename = filename,
210 data_size_query = data_size_query,
211 chunk_size = chunksize
212 )
213 if result is False:
214 return None
215
216 return filename
217 #--------------------------------------------------------
219 gmPG2.file2bytea (
220 filename = filename,
221 query = u'update ref.paperwork_templates set data = %(data)s::bytea where pk = %(pk)s and xmin = %(xmin)s',
222 args = {'pk': self.pk_obj, 'xmin': self._payload[self._idx['xmin_paperwork_template']]}
223 )
224 # adjust for xmin change
225 self.refetch_payload()
226 #--------------------------------------------------------
228 fname = self.export_to_file()
229 engine = form_engines[self._payload[self._idx['engine']]]
230 form = engine(template_file = fname)
231 form.template = self
232 return form
233 #============================================================
235 cmd = u'select pk from ref.paperwork_templates where name_long = %(lname)s and external_version = %(ver)s'
236 args = {'lname': name_long, 'ver': external_version}
237 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False)
238
239 if len(rows) == 0:
240 _log.error('cannot load form template [%s - %s]', name_long, external_version)
241 return None
242
243 return cFormTemplate(aPK_obj = rows[0]['pk'])
244 #------------------------------------------------------------
245 -def get_form_templates(engine=None, active_only=False, template_types=None, excluded_types=None):
246 """Load form templates."""
247
248 args = {'eng': engine, 'in_use': active_only}
249 where_parts = [u'1 = 1']
250
251 if engine is not None:
252 where_parts.append(u'engine = %(eng)s')
253
254 if active_only:
255 where_parts.append(u'in_use IS true')
256
257 if template_types is not None:
258 args['incl_types'] = tuple(template_types)
259 where_parts.append(u'template_type IN %(incl_types)s')
260
261 if excluded_types is not None:
262 args['excl_types'] = tuple(excluded_types)
263 where_parts.append(u'template_type NOT IN %(excl_types)s')
264
265 cmd = u"SELECT * FROM ref.v_paperwork_templates WHERE %s ORDER BY in_use desc, name_long" % u'\nAND '.join(where_parts)
266
267 rows, idx = gmPG2.run_ro_queries (
268 queries = [{'cmd': cmd, 'args': args}],
269 get_col_idx = True
270 )
271 templates = [ cFormTemplate(row = {'pk_field': 'pk_paperwork_template', 'data': r, 'idx': idx}) for r in rows ]
272
273 return templates
274 #------------------------------------------------------------
276
277 cmd = u'insert into ref.paperwork_templates (fk_template_type, name_short, name_long, external_version) values (%(type)s, %(nshort)s, %(nlong)s, %(ext_version)s)'
278 rows, idx = gmPG2.run_rw_queries (
279 queries = [
280 {'cmd': cmd, 'args': {'type': template_type, 'nshort': name_short, 'nlong': name_long, 'ext_version': 'new'}},
281 {'cmd': u"select currval(pg_get_serial_sequence('ref.paperwork_templates', 'pk'))"}
282 ],
283 return_data = True
284 )
285 template = cFormTemplate(aPK_obj = rows[0][0])
286 return template
287 #------------------------------------------------------------
289 rows, idx = gmPG2.run_rw_queries (
290 queries = [
291 {'cmd': u'delete from ref.paperwork_templates where pk=%(pk)s', 'args': {'pk': template['pk_paperwork_template']}}
292 ]
293 )
294 return True
295 #============================================================
296 # OpenOffice/LibreOffice API
297 #============================================================
298 uno = None
299 cOOoDocumentCloseListener = None
300 writer_binary = None
301
302 #-----------------------------------------------------------
304
305 try:
306 which = subprocess.Popen (
307 args = ('which', 'soffice'),
308 stdout = subprocess.PIPE,
309 stdin = subprocess.PIPE,
310 stderr = subprocess.PIPE,
311 universal_newlines = True
312 )
313 except (OSError, ValueError, subprocess.CalledProcessError):
314 _log.exception('there was a problem executing [which soffice]')
315 return
316
317 soffice_path, err = which.communicate()
318 soffice_path = soffice_path.strip('\n')
319 uno_path = os.path.abspath ( os.path.join (
320 os.path.dirname(os.path.realpath(soffice_path)),
321 '..',
322 'basis-link',
323 'program'
324 ))
325
326 _log.info('UNO should be at [%s], appending to sys.path', uno_path)
327
328 sys.path.append(uno_path)
329 #-----------------------------------------------------------
331 """FIXME: consider this:
332
333 try:
334 import uno
335 except:
336 print "This Script needs to be run with the python from OpenOffice.org"
337 print "Example: /opt/OpenOffice.org/program/python %s" % (
338 os.path.basename(sys.argv[0]))
339 print "Or you need to insert the right path at the top, where uno.py is."
340 print "Default: %s" % default_path
341 """
342 global uno
343 if uno is not None:
344 return
345
346 try:
347 import uno
348 except ImportError:
349 __configure_path_to_UNO()
350 import uno
351
352 global unohelper, oooXCloseListener, oooNoConnectException, oooPropertyValue
353
354 import unohelper
355 from com.sun.star.util import XCloseListener as oooXCloseListener
356 from com.sun.star.connection import NoConnectException as oooNoConnectException
357 from com.sun.star.beans import PropertyValue as oooPropertyValue
358
359 #----------------------------------
360 class _cOOoDocumentCloseListener(unohelper.Base, oooXCloseListener):
361 """Listens for events sent by OOo during the document closing
362 sequence and notifies the GNUmed client GUI so it can
363 import the closed document into the database.
364 """
365 def __init__(self, document=None):
366 self.document = document
367
368 def queryClosing(self, evt, owner):
369 # owner is True/False whether I am the owner of the doc
370 pass
371
372 def notifyClosing(self, evt):
373 pass
374
375 def disposing(self, evt):
376 self.document.on_disposed_by_ooo()
377 self.document = None
378 #----------------------------------
379
380 global cOOoDocumentCloseListener
381 cOOoDocumentCloseListener = _cOOoDocumentCloseListener
382
383 # search for writer binary
384 global writer_binary
385 found, binary = gmShellAPI.find_first_binary(binaries = [
386 'lowriter',
387 'oowriter'
388 ])
389 if found:
390 _log.debug('OOo/LO writer binary found: %s', binary)
391 writer_binary = binary
392 else:
393 _log.debug('OOo/LO writer binary NOT found')
394 raise ImportError('LibreOffice/OpenOffice (lowriter/oowriter) not found')
395
396 _log.debug('python UNO bridge successfully initialized')
397
398 #------------------------------------------------------------
400 """This class handles the connection to OOo.
401
402 Its Singleton instance stays around once initialized.
403 """
404 # FIXME: need to detect closure of OOo !
406
407 init_ooo()
408
409 self.__setup_connection_string()
410
411 self.resolver_uri = "com.sun.star.bridge.UnoUrlResolver"
412 self.desktop_uri = "com.sun.star.frame.Desktop"
413
414 self.max_connect_attempts = 5
415
416 self.local_context = uno.getComponentContext()
417 self.uri_resolver = self.local_context.ServiceManager.createInstanceWithContext(self.resolver_uri, self.local_context)
418
419 self.__desktop = None
420 #--------------------------------------------------------
421 # external API
422 #--------------------------------------------------------
424 if self.__desktop is None:
425 _log.debug('no desktop, no cleanup')
426 return
427
428 try:
429 self.__desktop.terminate()
430 except:
431 _log.exception('cannot terminate OOo desktop')
432 #--------------------------------------------------------
434 """<filename> must be absolute"""
435 if self.desktop is None:
436 _log.error('cannot access OOo desktop')
437 return None
438
439 filename = os.path.expanduser(filename)
440 filename = os.path.abspath(filename)
441 document_uri = uno.systemPathToFileUrl(filename)
442
443 _log.debug('%s -> %s', filename, document_uri)
444
445 doc = self.desktop.loadComponentFromURL(document_uri, "_blank", 0, ())
446 return doc
447 #--------------------------------------------------------
448 # internal helpers
449 #--------------------------------------------------------
451 # later factor this out !
452 dbcfg = gmCfg.cCfgSQL()
453 self.ooo_startup_settle_time = dbcfg.get2 (
454 option = u'external.ooo.startup_settle_time',
455 workplace = gmSurgery.gmCurrentPractice().active_workplace,
456 bias = u'workplace',
457 default = 3.0
458 )
459 #--------------------------------------------------------
461
462 # socket:
463 # ooo_port = u'2002'
464 # #self.ooo_start_cmd = 'oowriter -invisible -norestore -nofirststartwizard -nologo -accept="socket,host=localhost,port=%s;urp;StarOffice.ServiceManager"' % ooo_port
465 # self.ooo_start_cmd = 'oowriter -invisible -norestore -accept="socket,host=localhost,port=%s;urp;"' % ooo_port
466 # self.remote_context_uri = "uno:socket,host=localhost,port=%s;urp;StarOffice.ComponentContext" % ooo_port
467
468 # pipe:
469 pipe_name = "uno-gm2lo-%s" % str(random.random())[2:]
470 _log.debug('expecting OOo/LO server on named pipe [%s]', pipe_name)
471 self.ooo_start_cmd = '%s --invisible --norestore --accept="pipe,name=%s;urp" &' % (
472 writer_binary,
473 pipe_name
474 )
475 _log.debug('startup command: %s', self.ooo_start_cmd)
476
477 self.remote_context_uri = "uno:pipe,name=%s;urp;StarOffice.ComponentContext" % pipe_name
478 _log.debug('remote context URI: %s', self.remote_context_uri)
479 #--------------------------------------------------------
481 _log.info('trying to start OOo server')
482 _log.debug('startup command: %s', self.ooo_start_cmd)
483 os.system(self.ooo_start_cmd)
484 self.__get_startup_settle_time()
485 _log.debug('waiting %s seconds for OOo to start up', self.ooo_startup_settle_time)
486 time.sleep(self.ooo_startup_settle_time)
487 #--------------------------------------------------------
488 # properties
489 #--------------------------------------------------------
491 if self.__desktop is not None:
492 return self.__desktop
493
494 self.remote_context = None
495
496 attempts = self.max_connect_attempts
497 while attempts > 0:
498
499 _log.debug(u'attempt %s/%s', self.max_connect_attempts - attempts + 1, self.max_connect_attempts)
500
501 try:
502 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri)
503 break
504 except oooNoConnectException:
505 _log.exception('cannot connect to OOo')
506
507 # first loop ?
508 if attempts == self.max_connect_attempts:
509 self.__startup_ooo()
510 else:
511 time.sleep(1)
512
513 attempts = attempts - 1
514
515 if self.remote_context is None:
516 raise OSError(-1, u'cannot connect to OpenOffice', self.remote_context_uri)
517
518 _log.debug('connection seems established')
519 self.__desktop = self.remote_context.ServiceManager.createInstanceWithContext(self.desktop_uri, self.remote_context)
520 _log.debug('got OOo desktop handle')
521 return self.__desktop
522
523 desktop = property(_get_desktop, lambda x:x)
524 #------------------------------------------------------------
526
528
529 self.template_file = template_file
530 self.instance_type = instance_type
531 self.ooo_doc = None
532 #--------------------------------------------------------
533 # external API
534 #--------------------------------------------------------
536 # connect to OOo
537 ooo_srv = gmOOoConnector()
538
539 # open doc in OOo
540 self.ooo_doc = ooo_srv.open_document(filename = self.template_file)
541 if self.ooo_doc is None:
542 _log.error('cannot open document in OOo')
543 return False
544
545 # listen for close events
546 pat = gmPerson.gmCurrentPatient()
547 pat.locked = True
548 listener = cOOoDocumentCloseListener(document = self)
549 self.ooo_doc.addCloseListener(listener)
550
551 return True
552 #--------------------------------------------------------
555 #--------------------------------------------------------
557
558 # new style embedded, implicit placeholders
559 searcher = self.ooo_doc.createSearchDescriptor()
560 searcher.SearchCaseSensitive = False
561 searcher.SearchRegularExpression = True
562 searcher.SearchWords = True
563 searcher.SearchString = handler.placeholder_regex
564
565 placeholder_instance = self.ooo_doc.findFirst(searcher)
566 while placeholder_instance is not None:
567 try:
568 val = handler[placeholder_instance.String]
569 except:
570 val = _('error with placeholder [%s]') % placeholder_instance.String
571 _log.exception(val)
572
573 if val is None:
574 val = _('error with placeholder [%s]') % placeholder_instance.String
575
576 placeholder_instance.String = val
577 placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher)
578
579 if not old_style_too:
580 return
581
582 # old style "explicit" placeholders
583 text_fields = self.ooo_doc.getTextFields().createEnumeration()
584 while text_fields.hasMoreElements():
585 text_field = text_fields.nextElement()
586
587 # placeholder ?
588 if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'):
589 continue
590 # placeholder of type text ?
591 if text_field.PlaceHolderType != 0:
592 continue
593
594 replacement = handler[text_field.PlaceHolder]
595 if replacement is None:
596 continue
597
598 text_field.Anchor.setString(replacement)
599 #--------------------------------------------------------
601 if filename is not None:
602 target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename)))
603 save_args = (
604 oooPropertyValue('Overwrite', 0, True, 0),
605 oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0)
606
607 )
608 # "store AS url" stores the doc, marks it unmodified and updates
609 # the internal media descriptor - as opposed to "store TO url"
610 self.ooo_doc.storeAsURL(target_url, save_args)
611 else:
612 self.ooo_doc.store()
613 #--------------------------------------------------------
615 self.ooo_doc.dispose()
616 pat = gmPerson.gmCurrentPatient()
617 pat.locked = False
618 self.ooo_doc = None
619 #--------------------------------------------------------
621 # get current file name from OOo, user may have used Save As
622 filename = uno.fileUrlToSystemPath(self.ooo_doc.URL)
623 # tell UI to import the file
624 gmDispatcher.send (
625 signal = u'import_document_from_file',
626 filename = filename,
627 document_type = self.instance_type,
628 unlock_patient = True
629 )
630 self.ooo_doc = None
631 #--------------------------------------------------------
632 # internal helpers
633 #--------------------------------------------------------
634
635 #============================================================
637 """Ancestor for forms."""
638
642 #--------------------------------------------------------
644 """Parse the template into an instance and replace placeholders with values."""
645 raise NotImplementedError
646 #--------------------------------------------------------
650 #--------------------------------------------------------
652 """Generate output suitable for further processing outside this class, e.g. printing."""
653 raise NotImplementedError
654 #--------------------------------------------------------
659 #--------------------------------------------------------
661 """
662 A sop to TeX which can't act as a true filter: to delete temporary files
663 """
664 pass
665 #--------------------------------------------------------
667 """
668 Executes the provided command.
669 If command cotains %F. it is substituted with the filename
670 Otherwise, the file is fed in on stdin
671 """
672 pass
673 #--------------------------------------------------------
675 """Stores the parameters in the backend.
676
677 - link_obj can be a cursor, a connection or a service name
678 - assigning a cursor to link_obj allows the calling code to
679 group the call to store() into an enclosing transaction
680 (for an example see gmReferral.send_referral()...)
681 """
682 # some forms may not have values ...
683 if params is None:
684 params = {}
685 patient_clinical = self.patient.get_emr()
686 encounter = patient_clinical.active_encounter['pk_encounter']
687 # FIXME: get_active_episode is no more
688 #episode = patient_clinical.get_active_episode()['pk_episode']
689 # generate "forever unique" name
690 cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s";
691 rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def)
692 form_name = None
693 if rows is None:
694 _log.error('error retrieving form def for [%s]' % self.pk_def)
695 elif len(rows) == 0:
696 _log.error('no form def for [%s]' % self.pk_def)
697 else:
698 form_name = rows[0][0]
699 # we didn't get a name but want to store the form anyhow
700 if form_name is None:
701 form_name=time.time() # hopefully unique enough
702 # in one transaction
703 queries = []
704 # - store form instance in form_instance
705 cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)"
706 queries.append((cmd, [self.pk_def, form_name, episode, encounter]))
707 # - store params in form_data
708 for key in params.keys():
709 cmd = """
710 insert into form_data(fk_instance, place_holder, value)
711 values ((select currval('form_instances_pk_seq')), %s, %s::text)
712 """
713 queries.append((cmd, [key, params[key]]))
714 # - get inserted PK
715 queries.append(("select currval ('form_instances_pk_seq')", []))
716 status, err = gmPG.run_commit('historica', queries, True)
717 if status is None:
718 _log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err))
719 return None
720 return status
721
722 #================================================================
723 # OOo template forms
724 #----------------------------------------------------------------
726 """A forms engine wrapping OOo."""
727
729 super(self.__class__, self).__init__(template_file = template_file)
730
731 path, ext = os.path.splitext(self.template_filename)
732 if ext in [r'', r'.']:
733 ext = r'.odt'
734 self.instance_filename = r'%s-instance%s' % (path, ext)
735
736 #================================================================
737 # AbiWord template forms
738 #----------------------------------------------------------------
740 """A forms engine wrapping AbiWord."""
741
742 placeholder_regex = r'\$<.+?>\$'
743
745
746 super(cAbiWordForm, self).__init__(template_file = template_file)
747
748 # detect abiword
749 found, self.abiword_binary = gmShellAPI.detect_external_binary(binary = r'abiword')
750 if not found:
751 raise ImportError('<abiword(.exe)> not found')
752 #--------------------------------------------------------
754 # should *actually* properly parse the XML
755
756 path, ext = os.path.splitext(self.template_filename)
757 if ext in [r'', r'.']:
758 ext = r'.abw'
759 self.instance_filename = r'%s-instance%s' % (path, ext)
760
761 template_file = codecs.open(self.template_filename, 'rU', 'utf8')
762 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8')
763
764 if self.template is not None:
765 # inject placeholder values
766 data_source.set_placeholder(u'form_name_long', self.template['name_long'])
767 data_source.set_placeholder(u'form_name_short', self.template['name_short'])
768 data_source.set_placeholder(u'form_version', self.template['external_version'])
769
770 for line in template_file:
771
772 if line.strip() in [u'', u'\r', u'\n', u'\r\n']:
773 instance_file.write(line)
774 continue
775
776 # 1) find placeholders in this line
777 placeholders_in_line = regex.findall(cAbiWordForm.placeholder_regex, line, regex.IGNORECASE)
778 # 2) and replace them
779 for placeholder in placeholders_in_line:
780 try:
781 val = data_source[placeholder.replace(u'<', u'<').replace(u'>', u'>')]
782 except:
783 val = _('error with placeholder [%s]') % gmTools.xml_escape_string(placeholder)
784 _log.exception(val)
785
786 if val is None:
787 val = _('error with placeholder [%s]') % gmTools.xml_escape_string(placeholder)
788
789 line = line.replace(placeholder, val)
790
791 instance_file.write(line)
792
793 instance_file.close()
794 template_file.close()
795
796 if self.template is not None:
797 # remove temporary placeholders
798 data_source.unset_placeholder(u'form_name_long')
799 data_source.unset_placeholder(u'form_name_short')
800 data_source.unset_placeholder(u'form_version')
801
802 return
803 #--------------------------------------------------------
805 enc = sys.getfilesystemencoding()
806 cmd = (r'%s %s' % (self.abiword_binary, self.instance_filename.encode(enc))).encode(enc)
807 result = gmShellAPI.run_command_in_shell(command = cmd, blocking = True)
808 self.re_editable_filenames = []
809 return result
810 #--------------------------------------------------------
814 #----------------------------------------------------------------
815 form_engines[u'A'] = cAbiWordForm
816
817 #================================================================
818 # LaTeX template forms
819 #----------------------------------------------------------------
821 """A forms engine wrapping LaTeX."""
822
824 super(self.__class__, self).__init__(template_file = template_file)
825 path, ext = os.path.splitext(self.template_filename)
826 if ext in [r'', r'.']:
827 ext = r'.tex'
828 self.instance_filename = r'%s-instance%s' % (path, ext)
829 #--------------------------------------------------------
831
832 template_file = codecs.open(self.template_filename, 'rU', 'utf8')
833 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8')
834
835 if self.template is not None:
836 # inject placeholder values
837 data_source.set_placeholder(u'form_name_long', self.template['name_long'])
838 data_source.set_placeholder(u'form_name_short', self.template['name_short'])
839 data_source.set_placeholder(u'form_version', self.template['external_version'])
840
841 for line in template_file:
842
843 if line.strip() in [u'', u'\r', u'\n', u'\r\n']:
844 instance_file.write(line)
845 continue
846
847 # 1) find placeholders in this line
848 placeholders_in_line = regex.findall(data_source.placeholder_regex, line, regex.IGNORECASE)
849 # 2) and replace them
850 for placeholder in placeholders_in_line:
851 try:
852 val = data_source[placeholder]
853 except:
854 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder)
855 _log.exception(val)
856
857 if val is None:
858 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder)
859
860 line = line.replace(placeholder, val)
861
862 instance_file.write(line)
863
864 instance_file.close()
865 self.re_editable_filenames = [self.instance_filename]
866 template_file.close()
867
868 if self.template is not None:
869 # remove temporary placeholders
870 data_source.unset_placeholder(u'form_name_long')
871 data_source.unset_placeholder(u'form_name_short')
872 data_source.unset_placeholder(u'form_version')
873
874 return
875 #--------------------------------------------------------
877
878 mimetypes = [
879 u'application/x-latex',
880 u'application/x-tex',
881 u'text/plain'
882 ]
883
884 for mimetype in mimetypes:
885 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename)
886 if editor_cmd is not None:
887 break
888
889 if editor_cmd is None:
890 # LaTeX code is text: also consider text *viewers*
891 # since pretty much any of them will be an editor as well
892 for mimetype in mimetypes:
893 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename)
894 if editor_cmd is not None:
895 break
896
897 # last resort
898 if editor_cmd is None:
899 if os.name == 'nt':
900 editor_cmd = u'notepad.exe %s' % self.instance_filename
901 else:
902 editor_cmd = u'sensible-editor %s' % self.instance_filename
903
904 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
905 self.re_editable_filenames = [self.instance_filename]
906
907 return result
908 #--------------------------------------------------------
910
911 if instance_file is None:
912 instance_file = self.instance_filename
913
914 try:
915 open(instance_file, 'r').close()
916 except:
917 _log.exception('cannot access form instance file [%s]', instance_file)
918 gmLog2.log_stack_trace()
919 return None
920
921 self.instance_filename = instance_file
922
923 _log.debug('ignoring <format> directive [%s], generating PDF', format)
924
925 # create sandbox for LaTeX to play in
926 sandbox_dir = os.path.splitext(self.template_filename)[0]
927 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir)
928
929 old_cwd = os.getcwd()
930 _log.debug('CWD: [%s]', old_cwd)
931
932 gmTools.mkdir(sandbox_dir)
933
934 os.chdir(sandbox_dir)
935 try:
936 sandboxed_instance_filename = os.path.join(sandbox_dir, os.path.split(self.instance_filename)[1])
937 shutil.move(self.instance_filename, sandboxed_instance_filename)
938
939 # LaTeX can need up to three runs to get cross references et al right
940 if platform.system() == 'Windows':
941 draft_cmd = r'pdflatex.exe -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename
942 final_cmd = r'pdflatex.exe -interaction nonstopmode %s' % sandboxed_instance_filename
943 else:
944 draft_cmd = r'pdflatex -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename
945 final_cmd = r'pdflatex -interaction nonstopmode %s' % sandboxed_instance_filename
946 for run_cmd in [draft_cmd, draft_cmd, final_cmd]:
947 if not gmShellAPI.run_command_in_shell(command = run_cmd, blocking = True, acceptable_return_codes = [0, 1]):
948 _log.error('problem running pdflatex, cannot generate form output')
949 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True)
950 os.chdir(old_cwd)
951 return None
952 finally:
953 os.chdir(old_cwd)
954
955 sandboxed_pdf_name = u'%s.pdf' % os.path.splitext(sandboxed_instance_filename)[0]
956 target_dir = os.path.split(self.instance_filename)[0]
957 try:
958 shutil.move(sandboxed_pdf_name, target_dir)
959 except IOError:
960 _log.exception('cannot move sandboxed PDF: %s -> %s', sandboxed_pdf_name, target_dir)
961 gmDispatcher.send(signal = 'statustext', msg = _('Sandboxed PDF output file cannot be moved.'), beep = True)
962 return None
963
964 final_pdf_name = u'%s.pdf' % os.path.splitext(self.instance_filename)[0]
965
966 try:
967 open(final_pdf_name, 'r').close()
968 except IOError:
969 _log.exception('cannot open target PDF: %s', final_pdf_name)
970 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True)
971 return None
972
973 self.final_output_filenames = [final_pdf_name]
974
975 return final_pdf_name
976 #------------------------------------------------------------
977 form_engines[u'L'] = cLaTeXForm
978 #============================================================
979 # Gnuplot template forms
980 #------------------------------------------------------------
982 """A forms engine wrapping Gnuplot."""
983
984 #--------------------------------------------------------
988 #--------------------------------------------------------
990 """Allow editing the instance of the template."""
991 self.re_editable_filenames = []
992 return True
993 #--------------------------------------------------------
995 """Generate output suitable for further processing outside this class, e.g. printing.
996
997 Expects .data_filename to be set.
998 """
999 self.conf_filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.conf')
1000 fname_file = codecs.open(self.conf_filename, 'wb', 'utf8')
1001 fname_file.write('# setting the gnuplot data file\n')
1002 fname_file.write("gm2gpl_datafile = '%s'\n" % self.data_filename)
1003 fname_file.close()
1004
1005 # FIXME: cater for configurable path
1006 if platform.system() == 'Windows':
1007 exec_name = 'gnuplot.exe'
1008 else:
1009 exec_name = 'gnuplot'
1010
1011 args = [exec_name, '-p', self.conf_filename, self.template_filename]
1012 _log.debug('plotting args: %s' % str(args))
1013
1014 try:
1015 gp = subprocess.Popen (
1016 args = args,
1017 close_fds = True
1018 )
1019 except (OSError, ValueError, subprocess.CalledProcessError):
1020 _log.exception('there was a problem executing gnuplot')
1021 gmDispatcher.send(signal = u'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True)
1022 return
1023
1024 gp.communicate()
1025
1026 self.final_output_filenames = [
1027 self.conf_filename,
1028 self.data_filename,
1029 self.template_filename
1030 ]
1031
1032 return
1033 #------------------------------------------------------------
1034 form_engines[u'G'] = cGnuplotForm
1035
1036 #============================================================
1037 # fPDF form engine
1038 #------------------------------------------------------------
1040 """A forms engine wrapping PDF forms.
1041
1042 Johann Felix Soden <johfel@gmx.de> helped with this.
1043
1044 http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf
1045
1046 http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/fdf_data_exchange.pdf
1047 """
1048
1050
1051 super(cPDFForm, self).__init__(template_file = template_file)
1052
1053 # detect pdftk
1054 found, self.pdftk_binary = gmShellAPI.detect_external_binary(binary = r'pdftk')
1055 if not found:
1056 raise ImportError('<pdftk(.exe)> not found')
1057 return # should be superfluous, actually
1058
1059 enc = sys.getfilesystemencoding()
1060 self.pdftk_binary = self.pdftk_binary.encode(enc)
1061
1062 base_name, ext = os.path.splitext(self.template_filename)
1063 self.fdf_dumped_filename = (u'%s.fdf' % base_name).encode(enc)
1064 self.fdf_replaced_filename = (u'%s-replaced.fdf' % base_name).encode(enc)
1065 self.pdf_filled_filename = (u'%s-filled.pdf' % base_name).encode(enc)
1066 self.pdf_flattened_filename = (u'%s-filled-flattened.pdf' % base_name).encode(enc)
1067 #--------------------------------------------------------
1069
1070 # dump form fields from template
1071 cmd_line = [
1072 self.pdftk_binary,
1073 self.template_filename,
1074 r'generate_fdf',
1075 r'output',
1076 self.fdf_dumped_filename
1077 ]
1078 _log.debug(u' '.join(cmd_line))
1079 try:
1080 pdftk = subprocess.Popen(cmd_line)
1081 except OSError:
1082 _log.exception('cannot run <pdftk> (dump data from form)')
1083 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot extract fields from PDF form template.'), beep = True)
1084 return False
1085
1086 pdftk.communicate()
1087 if pdftk.returncode != 0:
1088 _log.error('<pdftk> returned [%s], failed to dump data from PDF form into FDF', pdftk.returncode)
1089 return False
1090
1091 # parse dumped FDF file for "/V (...)" records
1092 # and replace placeholders therein
1093 fdf_dumped_file = open(self.fdf_dumped_filename, 'rbU')
1094 fdf_replaced_file = codecs.open(self.fdf_replaced_filename, 'wb')
1095
1096 string_value_regex = r'\s*/V\s*\(.+\)\s*$'
1097 for line in fdf_dumped_file:
1098 if not regex.match(string_value_regex, line):
1099 fdf_replaced_file.write(line)
1100 continue
1101
1102 # strip cruft around the string value
1103 raw_str_val = line.strip() # remove framing whitespace
1104 raw_str_val = raw_str_val[2:] # remove leading "/V"
1105 raw_str_val = raw_str_val.lstrip() # remove whitespace between "/V" and "("
1106 raw_str_val = raw_str_val[1:] # remove opening "("
1107 raw_str_val = raw_str_val[2:] # remove BOM-16-BE
1108 raw_str_val = raw_str_val.rstrip() # remove trailing whitespace
1109 raw_str_val = raw_str_val[:-1] # remove closing ")"
1110
1111 # work on FDF escapes
1112 raw_str_val = raw_str_val.replace('\(', '(') # remove escaping of "("
1113 raw_str_val = raw_str_val.replace('\)', ')') # remove escaping of ")"
1114
1115 # by now raw_str_val should contain the actual
1116 # string value, albeit encoded as UTF-16, so
1117 # decode it into a unicode object,
1118 # split multi-line fields on "\n" literal
1119 raw_str_lines = raw_str_val.split('\x00\\n')
1120 value_template_lines = []
1121 for raw_str_line in raw_str_lines:
1122 value_template_lines.append(raw_str_line.decode('utf_16_be'))
1123
1124 replaced_lines = []
1125 for value_template in value_template_lines:
1126 # find any placeholders within
1127 placeholders_in_value = regex.findall(data_source.placeholder_regex, value_template, regex.IGNORECASE)
1128 for placeholder in placeholders_in_value:
1129 try:
1130 replacement = data_source[placeholder]
1131 except:
1132 _log.exception(replacement)
1133 replacement = _('error with placeholder [%s]') % placeholder
1134 if replacement is None:
1135 replacement = _('error with placeholder [%s]') % placeholder
1136 value_template = value_template.replace(placeholder, replacement)
1137
1138 value_template = value_template.encode('utf_16_be')
1139
1140 if len(placeholders_in_value) > 0:
1141 value_template = value_template.replace(r'(', r'\(')
1142 value_template = value_template.replace(r')', r'\)')
1143
1144 replaced_lines.append(value_template)
1145
1146 replaced_line = '\x00\\n'.join(replaced_lines)
1147
1148 fdf_replaced_file.write('/V (')
1149 fdf_replaced_file.write(codecs.BOM_UTF16_BE)
1150 fdf_replaced_file.write(replaced_line)
1151 fdf_replaced_file.write(')\n')
1152
1153 fdf_replaced_file.close()
1154 fdf_dumped_file.close()
1155
1156 # merge replaced data back into form
1157 cmd_line = [
1158 self.pdftk_binary,
1159 self.template_filename,
1160 r'fill_form',
1161 self.fdf_replaced_filename,
1162 r'output',
1163 self.pdf_filled_filename
1164 ]
1165 _log.debug(u' '.join(cmd_line))
1166 try:
1167 pdftk = subprocess.Popen(cmd_line)
1168 except OSError:
1169 _log.exception('cannot run <pdftk> (merge data into form)')
1170 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot fill in PDF form template.'), beep = True)
1171 return False
1172
1173 pdftk.communicate()
1174 if pdftk.returncode != 0:
1175 _log.error('<pdftk> returned [%s], failed to merge FDF data into PDF form', pdftk.returncode)
1176 return False
1177
1178 return True
1179 #--------------------------------------------------------
1181 mimetypes = [
1182 u'application/pdf',
1183 u'application/x-pdf'
1184 ]
1185
1186 for mimetype in mimetypes:
1187 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.pdf_filled_filename)
1188 if editor_cmd is not None:
1189 break
1190
1191 if editor_cmd is None:
1192 _log.debug('editor cmd not found, trying viewer cmd')
1193 for mimetype in mimetypes:
1194 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.pdf_filled_filename)
1195 if editor_cmd is not None:
1196 break
1197
1198 if editor_cmd is None:
1199 return False
1200
1201 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
1202
1203 path, fname = os.path.split(self.pdf_filled_filename)
1204 candidate = os.path.join(gmTools.gmPaths().home_dir, fname)
1205
1206 if os.access(candidate, os.R_OK):
1207 _log.debug('filled-in PDF found: %s', candidate)
1208 os.rename(self.pdf_filled_filename, self.pdf_filled_filename + '.bak')
1209 shutil.move(candidate, path)
1210 else:
1211 _log.debug('filled-in PDF not found: %s', candidate)
1212
1213 self.re_editable_filenames = [self.pdf_filled_filename]
1214
1215 return result
1216 #--------------------------------------------------------
1218 """Generate output suitable for further processing outside this class, e.g. printing."""
1219
1220 # eventually flatten the filled in form so we
1221 # can keep both a flattened and an editable copy:
1222 cmd_line = [
1223 self.pdftk_binary,
1224 self.pdf_filled_filename,
1225 r'output',
1226 self.pdf_flattened_filename,
1227 r'flatten'
1228 ]
1229 _log.debug(u' '.join(cmd_line))
1230 try:
1231 pdftk = subprocess.Popen(cmd_line)
1232 except OSError:
1233 _log.exception('cannot run <pdftk> (flatten filled in form)')
1234 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot flatten filled in PDF form.'), beep = True)
1235 return None
1236
1237 pdftk.communicate()
1238 if pdftk.returncode != 0:
1239 _log.error('<pdftk> returned [%s], failed to flatten filled in PDF form', pdftk.returncode)
1240 return None
1241
1242 self.final_output_filenames = [self.pdf_flattened_filename]
1243
1244 return self.pdf_flattened_filename
1245 #------------------------------------------------------------
1246 form_engines[u'P'] = cPDFForm
1247
1248 #============================================================
1249 # older code
1250 #------------------------------------------------------------
1252 """A forms engine wrapping LaTeX.
1253 """
1257
1259 try:
1260 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params])
1261 # create a 'sandbox' directory for LaTeX to play in
1262 self.tmp = tempfile.mktemp ()
1263 os.makedirs (self.tmp)
1264 self.oldcwd = os.getcwd ()
1265 os.chdir (self.tmp)
1266 stdin = os.popen ("latex", "w", 2048)
1267 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout
1268 # FIXME: send LaTeX output to the logger
1269 stdin.close ()
1270 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True):
1271 raise FormError ('DVIPS returned error')
1272 except EnvironmentError, e:
1273 _log.error(e.strerror)
1274 raise FormError (e.strerror)
1275 return file ("texput.ps")
1276
1278 """
1279 For testing purposes, runs Xdvi on the intermediate TeX output
1280 WARNING: don't try this on Windows
1281 """
1282 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)
1283
1285 if "%F" in command:
1286 command.replace ("%F", "texput.ps")
1287 else:
1288 command = "%s < texput.ps" % command
1289 try:
1290 if not gmShellAPI.run_command_in_shell(command, blocking=True):
1291 _log.error("external command %s returned non-zero" % command)
1292 raise FormError ('external command %s returned error' % command)
1293 except EnvironmentError, e:
1294 _log.error(e.strerror)
1295 raise FormError (e.strerror)
1296 return True
1297
1299 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print')
1300 self.exe (command)
1301
1310
1311
1312
1313
1314 #================================================================
1315 # define a class for HTML forms (for printing)
1316 #================================================================
1318 """This class can create XML document from requested data,
1319 then process it with XSLT template and display results
1320 """
1321
1322 # FIXME: make the path configurable ?
1323 _preview_program = u'oowriter ' #this program must be in the system PATH
1324
1326
1327 if template is None:
1328 raise ValueError(u'%s: cannot create form instance without a template' % __name__)
1329
1330 cFormEngine.__init__(self, template = template)
1331
1332 self._FormData = None
1333
1334 # here we know/can assume that the template was stored as a utf-8
1335 # encoded string so use that conversion to create unicode:
1336 #self._XSLTData = unicode(str(template.template_data), 'UTF-8')
1337 # but in fact, unicode() knows how to handle buffers, so simply:
1338 self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict')
1339
1340 # we must still devise a method of extracting the SQL query:
1341 # - either by retrieving it from a particular tag in the XSLT or
1342 # - by making the stored template actually be a dict which, unpickled,
1343 # has the keys "xslt" and "sql"
1344 self._SQL_query = u'select 1' #this sql query must output valid xml
1345 #--------------------------------------------------------
1346 # external API
1347 #--------------------------------------------------------
1349 """get data from backend and process it with XSLT template to produce readable output"""
1350
1351 # extract SQL (this is wrong but displays what is intended)
1352 xslt = libxml2.parseDoc(self._XSLTData)
1353 root = xslt.children
1354 for child in root:
1355 if child.type == 'element':
1356 self._SQL_query = child.content
1357 break
1358
1359 # retrieve data from backend
1360 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False)
1361
1362 __header = '<?xml version="1.0" encoding="UTF-8"?>\n'
1363 __body = rows[0][0]
1364
1365 # process XML data according to supplied XSLT, producing HTML
1366 self._XMLData =__header + __body
1367 style = libxslt.parseStylesheetDoc(xslt)
1368 xml = libxml2.parseDoc(self._XMLData)
1369 html = style.applyStylesheet(xml, None)
1370 self._FormData = html.serialize()
1371
1372 style.freeStylesheet()
1373 xml.freeDoc()
1374 html.freeDoc()
1375 #--------------------------------------------------------
1377 if self._FormData is None:
1378 raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed'
1379
1380 fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html')
1381 #html_file = os.open(fname, 'wb')
1382 #html_file.write(self._FormData.encode('UTF-8'))
1383 html_file = codecs.open(fname, 'wb', 'utf8', 'strict') # or 'replace' ?
1384 html_file.write(self._FormData)
1385 html_file.close()
1386
1387 cmd = u'%s %s' % (self.__class__._preview_program, fname)
1388
1389 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False):
1390 _log.error('%s: cannot launch report preview program' % __name__)
1391 return False
1392
1393 #os.unlink(self.filename) #delete file
1394 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK)
1395
1396 return True
1397 #--------------------------------------------------------
1401
1402
1403 #=====================================================
1404 #class LaTeXFilter(Cheetah.Filters.Filter):
1407 """
1408 Convience function to escape ISO-Latin-1 strings for TeX output
1409 WARNING: not all ISO-Latin-1 characters are expressible in TeX
1410 FIXME: nevertheless, there are a few more we could support
1411
1412 Also intelligently convert lists and tuples into TeX-style table lines
1413 """
1414 if type (item) is types.UnicodeType or type (item) is types.StringType:
1415 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX?
1416 item = item.replace ("&", "\\&")
1417 item = item.replace ("$", "\\$")
1418 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now
1419 item = item.replace ("\n", "\\\\ ")
1420 if len (item.strip ()) == 0:
1421 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it
1422 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX
1423 if type (item) is types.UnicodeType:
1424 item = item.encode ('latin-1', 'replace')
1425 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}',
1426 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions
1427 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`',
1428 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}',
1429 '\xc7':'\\c{C}', '\xc8':'\\`{E}',
1430 '\xa1': '!`',
1431 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'}
1432 for k, i in trans.items ():
1433 item = item.replace (k, i)
1434 elif type (item) is types.ListType or type (item) is types.TupleType:
1435 item = string.join ([self.filter (i, ' & ') for i in item], table_sep)
1436 elif item is None:
1437 item = '\\relax % Python None\n'
1438 elif type (item) is types.IntType or type (item) is types.FloatType:
1439 item = str (item)
1440 else:
1441 item = str (item)
1442 _log.warning("unknown type %s, string %s" % (type (item), item))
1443 return item
1444
1445
1446 #===========================================================
1449
1450 #============================================================
1451 # convenience functions
1452 #------------------------------------------------------------
1454 """
1455 Instantiates a FormEngine based on the form ID or name from the backend
1456 """
1457 try:
1458 # it's a number: match to form ID
1459 id = int (id)
1460 cmd = 'select template, engine, pk from paperwork_templates where pk = %s'
1461 except ValueError:
1462 # it's a string, match to the form's name
1463 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ?
1464 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s'
1465 result = gmPG.run_ro_query ('reference', cmd, None, id)
1466 if result is None:
1467 _log.error('error getting form [%s]' % id)
1468 raise gmExceptions.FormError ('error getting form [%s]' % id)
1469 if len(result) == 0:
1470 _log.error('no form [%s] found' % id)
1471 raise gmExceptions.FormError ('no such form found [%s]' % id)
1472 if result[0][1] == 'L':
1473 return LaTeXForm (result[0][2], result[0][0])
1474 elif result[0][1] == 'T':
1475 return TextForm (result[0][2], result[0][0])
1476 else:
1477 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id))
1478 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))
1479 #-------------------------------------------------------------
1486 #-------------------------------------------------------------
1487
1488 test_letter = """
1489 \\documentclass{letter}
1490 \\address{ $DOCTOR \\\\
1491 $DOCTORADDRESS}
1492 \\signature{$DOCTOR}
1493
1494 \\begin{document}
1495 \\begin{letter}{$RECIPIENTNAME \\\\
1496 $RECIPIENTADDRESS}
1497
1498 \\opening{Dear $RECIPIENTNAME}
1499
1500 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\
1501
1502 $TEXT
1503
1504 \\ifnum$INCLUDEMEDS>0
1505 \\textbf{Medications List}
1506
1507 \\begin{tabular}{lll}
1508 $MEDSLIST
1509 \\end{tabular}
1510 \\fi
1511
1512 \\ifnum$INCLUDEDISEASES>0
1513 \\textbf{Disease List}
1514
1515 \\begin{tabular}{l}
1516 $DISEASELIST
1517 \\end{tabular}
1518 \\fi
1519
1520 \\closing{$CLOSING}
1521
1522 \\end{letter}
1523 \\end{document}
1524 """
1525
1526
1528 f = open('../../test-area/ian/terry-form.tex')
1529 params = {
1530 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle",
1531 'DOCTORSNAME': 'Ian Haywood',
1532 'DOCTORSADDRESS': '1 Smith St\nMelbourne',
1533 'PATIENTNAME':'Joe Bloggs',
1534 'PATIENTADDRESS':'18 Fred St\nMelbourne',
1535 'REQUEST':'echocardiogram',
1536 'THERAPY':'on warfarin',
1537 'CLINICALNOTES':"""heard new murmur
1538 Here's some
1539 crap to demonstrate how it can cover multiple lines.""",
1540 'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany',
1541 'ROUTINE':1,
1542 'URGENT':0,
1543 'FAX':1,
1544 'PHONE':1,
1545 'PENSIONER':1,
1546 'VETERAN':0,
1547 'PADS':0,
1548 'INSTRUCTIONS':u'Take the blue pill, Neo'
1549 }
1550 form = LaTeXForm (1, f.read())
1551 form.process (params)
1552 form.xdvi ()
1553 form.cleanup ()
1554
1556 form = LaTeXForm (2, test_letter)
1557 params = {'RECIPIENTNAME':'Dr. Richard Terry',
1558 'RECIPIENTADDRESS':'1 Main St\nNewcastle',
1559 'DOCTOR':'Dr. Ian Haywood',
1560 'DOCTORADDRESS':'1 Smith St\nMelbourne',
1561 'PATIENTNAME':'Joe Bloggs',
1562 'PATIENTADDRESS':'18 Fred St, Melbourne',
1563 'TEXT':"""This is the main text of the referral letter""",
1564 'DOB':'12/3/65',
1565 'INCLUDEMEDS':1,
1566 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]],
1567 'INCLUDEDISEASES':0, 'DISEASELIST':'',
1568 'CLOSING':'Yours sincerely,'
1569 }
1570 form.process (params)
1571 print os.getcwd ()
1572 form.xdvi ()
1573 form.cleanup ()
1574 #------------------------------------------------------------
1576 template = open('../../test-area/ian/Formularkopf-DE.tex')
1577 form = LaTeXForm(template=template.read())
1578 params = {
1579 'PATIENT LASTNAME': 'Kirk',
1580 'PATIENT FIRSTNAME': 'James T.',
1581 'PATIENT STREET': 'Hauptstrasse',
1582 'PATIENT ZIP': '02999',
1583 'PATIENT TOWN': 'Gross Saerchen',
1584 'PATIENT DOB': '22.03.1931'
1585 }
1586 form.process(params)
1587 form.xdvi()
1588 form.cleanup()
1589
1590 #============================================================
1591 # main
1592 #------------------------------------------------------------
1593 if __name__ == '__main__':
1594
1595 if len(sys.argv) < 2:
1596 sys.exit()
1597
1598 if sys.argv[1] != 'test':
1599 sys.exit()
1600
1601 from Gnumed.pycommon import gmDateTime
1602 gmDateTime.init()
1603
1604 #--------------------------------------------------------
1605 # OOo
1606 #--------------------------------------------------------
1608 init_ooo()
1609 #--------------------------------------------------------
1614 #--------------------------------------------------------
1616 srv = gmOOoConnector()
1617 doc = srv.open_document(filename = sys.argv[2])
1618 print "document:", doc
1619 #--------------------------------------------------------
1621 doc = cOOoLetter(template_file = sys.argv[2])
1622 doc.open_in_ooo()
1623 print "document:", doc
1624 raw_input('press <ENTER> to continue')
1625 doc.show()
1626 #doc.replace_placeholders()
1627 #doc.save_in_ooo('~/test_cOOoLetter.odt')
1628 # doc = None
1629 # doc.close_in_ooo()
1630 raw_input('press <ENTER> to continue')
1631 #--------------------------------------------------------
1633 try:
1634 doc = open_uri_in_ooo(filename=sys.argv[1])
1635 except:
1636 _log.exception('cannot open [%s] in OOo' % sys.argv[1])
1637 raise
1638
1639 class myCloseListener(unohelper.Base, oooXCloseListener):
1640 def disposing(self, evt):
1641 print "disposing:"
1642 def notifyClosing(self, evt):
1643 print "notifyClosing:"
1644 def queryClosing(self, evt, owner):
1645 # owner is True/False whether I am the owner of the doc
1646 print "queryClosing:"
1647
1648 l = myCloseListener()
1649 doc.addCloseListener(l)
1650
1651 tfs = doc.getTextFields().createEnumeration()
1652 print tfs
1653 print dir(tfs)
1654 while tfs.hasMoreElements():
1655 tf = tfs.nextElement()
1656 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'):
1657 print tf.getPropertyValue('PlaceHolder')
1658 print " ", tf.getPropertyValue('Hint')
1659
1660 # doc.close(True) # closes but leaves open the dedicated OOo window
1661 doc.dispose() # closes and disposes of the OOo window
1662 #--------------------------------------------------------
1664 pat = gmPersonSearch.ask_for_patient()
1665 if pat is None:
1666 return
1667 gmPerson.set_active_patient(patient = pat)
1668
1669 doc = cOOoLetter(template_file = sys.argv[2])
1670 doc.open_in_ooo()
1671 print doc
1672 doc.show()
1673 #doc.replace_placeholders()
1674 #doc.save_in_ooo('~/test_cOOoLetter.odt')
1675 doc = None
1676 # doc.close_in_ooo()
1677 raw_input('press <ENTER> to continue')
1678 #--------------------------------------------------------
1679 # other
1680 #--------------------------------------------------------
1682 template = cFormTemplate(aPK_obj = sys.argv[2])
1683 print template
1684 print template.export_to_file()
1685 #--------------------------------------------------------
1687 template = cFormTemplate(aPK_obj = sys.argv[2])
1688 template.update_template_from_file(filename = sys.argv[3])
1689 #--------------------------------------------------------
1691 pat = gmPersonSearch.ask_for_patient()
1692 if pat is None:
1693 return
1694 gmPerson.set_active_patient(patient = pat)
1695
1696 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1697
1698 path = os.path.abspath(sys.argv[2])
1699 form = cLaTeXForm(template_file = path)
1700
1701 from Gnumed.wxpython import gmMacro
1702 ph = gmMacro.gmPlaceholderHandler()
1703 ph.debug = True
1704 instance_file = form.substitute_placeholders(data_source = ph)
1705 pdf_name = form.generate_output(instance_file = instance_file)
1706 print "final PDF file is:", pdf_name
1707 #--------------------------------------------------------
1709 pat = gmPersonSearch.ask_for_patient()
1710 if pat is None:
1711 return
1712 gmPerson.set_active_patient(patient = pat)
1713
1714 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1715
1716 path = os.path.abspath(sys.argv[2])
1717 form = cPDFForm(template_file = path)
1718
1719 from Gnumed.wxpython import gmMacro
1720 ph = gmMacro.gmPlaceholderHandler()
1721 ph.debug = True
1722 instance_file = form.substitute_placeholders(data_source = ph)
1723 pdf_name = form.generate_output(instance_file = instance_file)
1724 print "final PDF file is:", pdf_name
1725 #--------------------------------------------------------
1727 pat = gmPersonSearch.ask_for_patient()
1728 if pat is None:
1729 return
1730 gmPerson.set_active_patient(patient = pat)
1731
1732 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1733
1734 path = os.path.abspath(sys.argv[2])
1735 form = cAbiWordForm(template_file = path)
1736
1737 from Gnumed.wxpython import gmMacro
1738 ph = gmMacro.gmPlaceholderHandler()
1739 ph.debug = True
1740 instance_file = form.substitute_placeholders(data_source = ph)
1741 form.edit()
1742 final_name = form.generate_output(instance_file = instance_file)
1743 print "final file is:", final_name
1744 #--------------------------------------------------------
1745 #--------------------------------------------------------
1746 # now run the tests
1747 #test_au()
1748 #test_de()
1749
1750 # OOo
1751 #test_init_ooo()
1752 #test_ooo_connect()
1753 #test_open_ooo_doc_from_srv()
1754 #test_open_ooo_doc_from_letter()
1755 #play_with_ooo()
1756 #test_cOOoLetter()
1757
1758 #test_cFormTemplate()
1759 #set_template_from_file()
1760 #test_latex_form()
1761 #test_pdf_form()
1762 test_abiword_form()
1763
1764 #============================================================
1765
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jun 25 03:58:38 2012 | http://epydoc.sourceforge.net |