| 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.ooo_start_cmd = 'oowriter -invisible -accept="socket,host=localhost,port=2002;urp;"'
410 #self.remote_context_uri = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext"
411
412 pipe_name = "uno-gm2lo-%s" % str(random.random())[2:]
413 _log.debug('pipe name: %s', pipe_name)
414
415 #self.ooo_start_cmd = '%s -invisible -norestore -accept="pipe,name=%s;urp"' % (
416 self.ooo_start_cmd = '%s --norestore --accept="pipe,name=%s;urp" &' % (
417 writer_binary,
418 pipe_name
419 )
420 _log.debug('startup command: %s', self.ooo_start_cmd)
421
422 self.remote_context_uri = "uno:pipe,name=%s;urp;StarOffice.ComponentContext" % pipe_name
423 _log.debug('remote context URI: %s', self.remote_context_uri)
424
425 self.resolver_uri = "com.sun.star.bridge.UnoUrlResolver"
426 self.desktop_uri = "com.sun.star.frame.Desktop"
427
428 self.local_context = uno.getComponentContext()
429 self.uri_resolver = self.local_context.ServiceManager.createInstanceWithContext(self.resolver_uri, self.local_context)
430
431 self.__desktop = None
432 #--------------------------------------------------------
434 if self.__desktop is None:
435 _log.debug('no desktop, no cleanup')
436 return
437
438 try:
439 self.__desktop.terminate()
440 except:
441 _log.exception('cannot terminate OOo desktop')
442 #--------------------------------------------------------
444 """<filename> must be absolute"""
445
446 if self.desktop is None:
447 _log.error('cannot access OOo desktop')
448 return None
449
450 filename = os.path.expanduser(filename)
451 filename = os.path.abspath(filename)
452 document_uri = uno.systemPathToFileUrl(filename)
453
454 _log.debug('%s -> %s', filename, document_uri)
455
456 doc = self.desktop.loadComponentFromURL(document_uri, "_blank", 0, ())
457 return doc
458 #--------------------------------------------------------
459 # internal helpers
460 #--------------------------------------------------------
462 # later factor this out !
463 dbcfg = gmCfg.cCfgSQL()
464 self.ooo_startup_settle_time = dbcfg.get2 (
465 option = u'external.ooo.startup_settle_time',
466 workplace = gmSurgery.gmCurrentPractice().active_workplace,
467 bias = u'workplace',
468 default = 3.0
469 )
470 #--------------------------------------------------------
471 # properties
472 #--------------------------------------------------------
474 if self.__desktop is not None:
475 return self.__desktop
476
477 try:
478 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri)
479 except oooNoConnectException:
480 _log.exception('cannot connect to OOo server')
481 _log.info('trying to start OOo server')
482 os.system(self.ooo_start_cmd)
483 self.__get_startup_settle_time()
484 _log.debug('waiting %s seconds for OOo to start up', self.ooo_startup_settle_time)
485 time.sleep(self.ooo_startup_settle_time) # OOo sometimes needs a bit
486 try:
487 self.remote_context = self.uri_resolver.resolve(self.remote_context_uri)
488 except oooNoConnectException:
489 _log.exception('cannot start (or connect to started) OOo server')
490 return None
491
492 self.__desktop = self.remote_context.ServiceManager.createInstanceWithContext(self.desktop_uri, self.remote_context)
493 _log.debug('connection seems established')
494 return self.__desktop
495
496 desktop = property(_get_desktop, lambda x:x)
497 #------------------------------------------------------------
499
501
502 self.template_file = template_file
503 self.instance_type = instance_type
504 self.ooo_doc = None
505 #--------------------------------------------------------
506 # external API
507 #--------------------------------------------------------
509 # connect to OOo
510 ooo_srv = gmOOoConnector()
511
512 # open doc in OOo
513 self.ooo_doc = ooo_srv.open_document(filename = self.template_file)
514 if self.ooo_doc is None:
515 _log.error('cannot open document in OOo')
516 return False
517
518 # listen for close events
519 pat = gmPerson.gmCurrentPatient()
520 pat.locked = True
521 listener = cOOoDocumentCloseListener(document = self)
522 self.ooo_doc.addCloseListener(listener)
523
524 return True
525 #--------------------------------------------------------
528 #--------------------------------------------------------
530
531 # new style embedded, implicit placeholders
532 searcher = self.ooo_doc.createSearchDescriptor()
533 searcher.SearchCaseSensitive = False
534 searcher.SearchRegularExpression = True
535 searcher.SearchWords = True
536 searcher.SearchString = handler.placeholder_regex
537
538 placeholder_instance = self.ooo_doc.findFirst(searcher)
539 while placeholder_instance is not None:
540 try:
541 val = handler[placeholder_instance.String]
542 except:
543 val = _('error with placeholder [%s]') % placeholder_instance.String
544 _log.exception(val)
545
546 if val is None:
547 val = _('error with placeholder [%s]') % placeholder_instance.String
548
549 placeholder_instance.String = val
550 placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher)
551
552 if not old_style_too:
553 return
554
555 # old style "explicit" placeholders
556 text_fields = self.ooo_doc.getTextFields().createEnumeration()
557 while text_fields.hasMoreElements():
558 text_field = text_fields.nextElement()
559
560 # placeholder ?
561 if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'):
562 continue
563 # placeholder of type text ?
564 if text_field.PlaceHolderType != 0:
565 continue
566
567 replacement = handler[text_field.PlaceHolder]
568 if replacement is None:
569 continue
570
571 text_field.Anchor.setString(replacement)
572 #--------------------------------------------------------
574 if filename is not None:
575 target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename)))
576 save_args = (
577 oooPropertyValue('Overwrite', 0, True, 0),
578 oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0)
579
580 )
581 # "store AS url" stores the doc, marks it unmodified and updates
582 # the internal media descriptor - as opposed to "store TO url"
583 self.ooo_doc.storeAsURL(target_url, save_args)
584 else:
585 self.ooo_doc.store()
586 #--------------------------------------------------------
588 self.ooo_doc.dispose()
589 pat = gmPerson.gmCurrentPatient()
590 pat.locked = False
591 self.ooo_doc = None
592 #--------------------------------------------------------
594 # get current file name from OOo, user may have used Save As
595 filename = uno.fileUrlToSystemPath(self.ooo_doc.URL)
596 # tell UI to import the file
597 gmDispatcher.send (
598 signal = u'import_document_from_file',
599 filename = filename,
600 document_type = self.instance_type,
601 unlock_patient = True
602 )
603 self.ooo_doc = None
604 #--------------------------------------------------------
605 # internal helpers
606 #--------------------------------------------------------
607
608 #============================================================
610 """Ancestor for forms."""
611
615 #--------------------------------------------------------
617 """Parse the template into an instance and replace placeholders with values."""
618 raise NotImplementedError
619 #--------------------------------------------------------
623 #--------------------------------------------------------
625 """Generate output suitable for further processing outside this class, e.g. printing."""
626 raise NotImplementedError
627 #--------------------------------------------------------
632 #--------------------------------------------------------
634 """
635 A sop to TeX which can't act as a true filter: to delete temporary files
636 """
637 pass
638 #--------------------------------------------------------
640 """
641 Executes the provided command.
642 If command cotains %F. it is substituted with the filename
643 Otherwise, the file is fed in on stdin
644 """
645 pass
646 #--------------------------------------------------------
648 """Stores the parameters in the backend.
649
650 - link_obj can be a cursor, a connection or a service name
651 - assigning a cursor to link_obj allows the calling code to
652 group the call to store() into an enclosing transaction
653 (for an example see gmReferral.send_referral()...)
654 """
655 # some forms may not have values ...
656 if params is None:
657 params = {}
658 patient_clinical = self.patient.get_emr()
659 encounter = patient_clinical.active_encounter['pk_encounter']
660 # FIXME: get_active_episode is no more
661 #episode = patient_clinical.get_active_episode()['pk_episode']
662 # generate "forever unique" name
663 cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s";
664 rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def)
665 form_name = None
666 if rows is None:
667 _log.error('error retrieving form def for [%s]' % self.pk_def)
668 elif len(rows) == 0:
669 _log.error('no form def for [%s]' % self.pk_def)
670 else:
671 form_name = rows[0][0]
672 # we didn't get a name but want to store the form anyhow
673 if form_name is None:
674 form_name=time.time() # hopefully unique enough
675 # in one transaction
676 queries = []
677 # - store form instance in form_instance
678 cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)"
679 queries.append((cmd, [self.pk_def, form_name, episode, encounter]))
680 # - store params in form_data
681 for key in params.keys():
682 cmd = """
683 insert into form_data(fk_instance, place_holder, value)
684 values ((select currval('form_instances_pk_seq')), %s, %s::text)
685 """
686 queries.append((cmd, [key, params[key]]))
687 # - get inserted PK
688 queries.append(("select currval ('form_instances_pk_seq')", []))
689 status, err = gmPG.run_commit('historica', queries, True)
690 if status is None:
691 _log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err))
692 return None
693 return status
694
695 #================================================================
696 # OOo template forms
697 #----------------------------------------------------------------
699 """A forms engine wrapping OOo."""
700
702 super(self.__class__, self).__init__(template_file = template_file)
703
704 path, ext = os.path.splitext(self.template_filename)
705 if ext in [r'', r'.']:
706 ext = r'.odt'
707 self.instance_filename = r'%s-instance%s' % (path, ext)
708
709 #================================================================
710 # AbiWord template forms
711 #----------------------------------------------------------------
713 """A forms engine wrapping AbiWord."""
714
715 placeholder_regex = r'\$<.+?>\$'
716
718
719 super(cAbiWordForm, self).__init__(template_file = template_file)
720
721 # detect abiword
722 found, self.abiword_binary = gmShellAPI.detect_external_binary(binary = r'abiword')
723 if not found:
724 raise ImportError('<abiword(.exe)> not found')
725 #--------------------------------------------------------
727 # should *actually* properly parse the XML
728
729 path, ext = os.path.splitext(self.template_filename)
730 if ext in [r'', r'.']:
731 ext = r'.abw'
732 self.instance_filename = r'%s-instance%s' % (path, ext)
733
734 template_file = codecs.open(self.template_filename, 'rU', 'utf8')
735 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8')
736
737 if self.template is not None:
738 # inject placeholder values
739 data_source.set_placeholder(u'form_name_long', self.template['name_long'])
740 data_source.set_placeholder(u'form_name_short', self.template['name_short'])
741 data_source.set_placeholder(u'form_version', self.template['external_version'])
742
743 for line in template_file:
744
745 if line.strip() in [u'', u'\r', u'\n', u'\r\n']:
746 instance_file.write(line)
747 continue
748
749 # 1) find placeholders in this line
750 placeholders_in_line = regex.findall(cAbiWordForm.placeholder_regex, line, regex.IGNORECASE)
751 # 2) and replace them
752 for placeholder in placeholders_in_line:
753 try:
754 val = data_source[placeholder.replace(u'<', u'<').replace(u'>', u'>')]
755 except:
756 val = _('error with placeholder [%s]') % gmTools.xml_escape_string(placeholder)
757 _log.exception(val)
758
759 if val is None:
760 val = _('error with placeholder [%s]') % gmTools.xml_escape_string(placeholder)
761
762 line = line.replace(placeholder, val)
763
764 instance_file.write(line)
765
766 instance_file.close()
767 template_file.close()
768
769 if self.template is not None:
770 # remove temporary placeholders
771 data_source.unset_placeholder(u'form_name_long')
772 data_source.unset_placeholder(u'form_name_short')
773 data_source.unset_placeholder(u'form_version')
774
775 return
776 #--------------------------------------------------------
778 enc = sys.getfilesystemencoding()
779 cmd = (r'%s %s' % (self.abiword_binary, self.instance_filename.encode(enc))).encode(enc)
780 result = gmShellAPI.run_command_in_shell(command = cmd, blocking = True)
781 self.re_editable_filenames = []
782 return result
783 #--------------------------------------------------------
787 #----------------------------------------------------------------
788 form_engines[u'A'] = cAbiWordForm
789
790 #================================================================
791 # LaTeX template forms
792 #----------------------------------------------------------------
794 """A forms engine wrapping LaTeX."""
795
797 super(self.__class__, self).__init__(template_file = template_file)
798 path, ext = os.path.splitext(self.template_filename)
799 if ext in [r'', r'.']:
800 ext = r'.tex'
801 self.instance_filename = r'%s-instance%s' % (path, ext)
802 #--------------------------------------------------------
804
805 template_file = codecs.open(self.template_filename, 'rU', 'utf8')
806 instance_file = codecs.open(self.instance_filename, 'wb', 'utf8')
807
808 if self.template is not None:
809 # inject placeholder values
810 data_source.set_placeholder(u'form_name_long', self.template['name_long'])
811 data_source.set_placeholder(u'form_name_short', self.template['name_short'])
812 data_source.set_placeholder(u'form_version', self.template['external_version'])
813
814 for line in template_file:
815
816 if line.strip() in [u'', u'\r', u'\n', u'\r\n']:
817 instance_file.write(line)
818 continue
819
820 # 1) find placeholders in this line
821 placeholders_in_line = regex.findall(data_source.placeholder_regex, line, regex.IGNORECASE)
822 # 2) and replace them
823 for placeholder in placeholders_in_line:
824 try:
825 val = data_source[placeholder]
826 except:
827 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder)
828 _log.exception(val)
829
830 if val is None:
831 val = _('error with placeholder [%s]') % gmTools.tex_escape_string(placeholder)
832
833 line = line.replace(placeholder, val)
834
835 instance_file.write(line)
836
837 instance_file.close()
838 self.re_editable_filenames = [self.instance_filename]
839 template_file.close()
840
841 if self.template is not None:
842 # remove temporary placeholders
843 data_source.unset_placeholder(u'form_name_long')
844 data_source.unset_placeholder(u'form_name_short')
845 data_source.unset_placeholder(u'form_version')
846
847 return
848 #--------------------------------------------------------
850
851 mimetypes = [
852 u'application/x-latex',
853 u'application/x-tex',
854 u'text/plain'
855 ]
856
857 for mimetype in mimetypes:
858 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename)
859 if editor_cmd is not None:
860 break
861
862 if editor_cmd is None:
863 # LaTeX code is text: also consider text *viewers*
864 # since pretty much any of them will be an editor as well
865 for mimetype in mimetypes:
866 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename)
867 if editor_cmd is not None:
868 break
869
870 # last resort
871 if editor_cmd is None:
872 if os.name == 'nt':
873 editor_cmd = u'notepad.exe %s' % self.instance_filename
874 else:
875 editor_cmd = u'sensible-editor %s' % self.instance_filename
876
877 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
878 self.re_editable_filenames = [self.instance_filename]
879
880 return result
881 #--------------------------------------------------------
883
884 if instance_file is None:
885 instance_file = self.instance_filename
886
887 try:
888 open(instance_file, 'r').close()
889 except:
890 _log.exception('cannot access form instance file [%s]', instance_file)
891 gmLog2.log_stack_trace()
892 return None
893
894 self.instance_filename = instance_file
895
896 _log.debug('ignoring <format> directive [%s], generating PDF', format)
897
898 # create sandbox for LaTeX to play in
899 sandbox_dir = os.path.splitext(self.template_filename)[0]
900 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir)
901
902 old_cwd = os.getcwd()
903 _log.debug('CWD: [%s]', old_cwd)
904
905 gmTools.mkdir(sandbox_dir)
906
907 os.chdir(sandbox_dir)
908 try:
909 sandboxed_instance_filename = os.path.join(sandbox_dir, os.path.split(self.instance_filename)[1])
910 shutil.move(self.instance_filename, sandboxed_instance_filename)
911
912 # LaTeX can need up to three runs to get cross references et al right
913 if platform.system() == 'Windows':
914 draft_cmd = r'pdflatex.exe -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename
915 final_cmd = r'pdflatex.exe -interaction nonstopmode %s' % sandboxed_instance_filename
916 else:
917 draft_cmd = r'pdflatex -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename
918 final_cmd = r'pdflatex -interaction nonstopmode %s' % sandboxed_instance_filename
919 for run_cmd in [draft_cmd, draft_cmd, final_cmd]:
920 if not gmShellAPI.run_command_in_shell(command = run_cmd, blocking = True, acceptable_return_codes = [0, 1]):
921 _log.error('problem running pdflatex, cannot generate form output')
922 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True)
923 os.chdir(old_cwd)
924 return None
925 finally:
926 os.chdir(old_cwd)
927
928 sandboxed_pdf_name = u'%s.pdf' % os.path.splitext(sandboxed_instance_filename)[0]
929 target_dir = os.path.split(self.instance_filename)[0]
930 try:
931 shutil.move(sandboxed_pdf_name, target_dir)
932 except IOError:
933 _log.exception('cannot move sandboxed PDF: %s -> %s', sandboxed_pdf_name, target_dir)
934 gmDispatcher.send(signal = 'statustext', msg = _('Sandboxed PDF output file cannot be moved.'), beep = True)
935 return None
936
937 final_pdf_name = u'%s.pdf' % os.path.splitext(self.instance_filename)[0]
938
939 try:
940 open(final_pdf_name, 'r').close()
941 except IOError:
942 _log.exception('cannot open target PDF: %s', final_pdf_name)
943 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True)
944 return None
945
946 self.final_output_filenames = [final_pdf_name]
947
948 return final_pdf_name
949 #------------------------------------------------------------
950 form_engines[u'L'] = cLaTeXForm
951 #============================================================
952 # Gnuplot template forms
953 #------------------------------------------------------------
955 """A forms engine wrapping Gnuplot."""
956
957 #--------------------------------------------------------
961 #--------------------------------------------------------
963 """Allow editing the instance of the template."""
964 self.re_editable_filenames = []
965 return True
966 #--------------------------------------------------------
968 """Generate output suitable for further processing outside this class, e.g. printing.
969
970 Expects .data_filename to be set.
971 """
972 self.conf_filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.conf')
973 fname_file = codecs.open(self.conf_filename, 'wb', 'utf8')
974 fname_file.write('# setting the gnuplot data file\n')
975 fname_file.write("gm2gpl_datafile = '%s'\n" % self.data_filename)
976 fname_file.close()
977
978 # FIXME: cater for configurable path
979 if platform.system() == 'Windows':
980 exec_name = 'gnuplot.exe'
981 else:
982 exec_name = 'gnuplot'
983
984 args = [exec_name, '-p', self.conf_filename, self.template_filename]
985 _log.debug('plotting args: %s' % str(args))
986
987 try:
988 gp = subprocess.Popen (
989 args = args,
990 close_fds = True
991 )
992 except (OSError, ValueError, subprocess.CalledProcessError):
993 _log.exception('there was a problem executing gnuplot')
994 gmDispatcher.send(signal = u'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True)
995 return
996
997 gp.communicate()
998
999 self.final_output_filenames = [
1000 self.conf_filename,
1001 self.data_filename,
1002 self.template_filename
1003 ]
1004
1005 return
1006 #------------------------------------------------------------
1007 form_engines[u'G'] = cGnuplotForm
1008
1009 #============================================================
1010 # fPDF form engine
1011 #------------------------------------------------------------
1013 """A forms engine wrapping PDF forms.
1014
1015 Johann Felix Soden <johfel@gmx.de> helped with this.
1016
1017 http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf
1018
1019 http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/fdf_data_exchange.pdf
1020 """
1021
1023
1024 super(cPDFForm, self).__init__(template_file = template_file)
1025
1026 # detect pdftk
1027 found, self.pdftk_binary = gmShellAPI.detect_external_binary(binary = r'pdftk')
1028 if not found:
1029 raise ImportError('<pdftk(.exe)> not found')
1030 return # should be superfluous, actually
1031
1032 enc = sys.getfilesystemencoding()
1033 self.pdftk_binary = self.pdftk_binary.encode(enc)
1034
1035 base_name, ext = os.path.splitext(self.template_filename)
1036 self.fdf_dumped_filename = (u'%s.fdf' % base_name).encode(enc)
1037 self.fdf_replaced_filename = (u'%s-replaced.fdf' % base_name).encode(enc)
1038 self.pdf_filled_filename = (u'%s-filled.pdf' % base_name).encode(enc)
1039 self.pdf_flattened_filename = (u'%s-filled-flattened.pdf' % base_name).encode(enc)
1040 #--------------------------------------------------------
1042
1043 # dump form fields from template
1044 cmd_line = [
1045 self.pdftk_binary,
1046 self.template_filename,
1047 r'generate_fdf',
1048 r'output',
1049 self.fdf_dumped_filename
1050 ]
1051 _log.debug(u' '.join(cmd_line))
1052 try:
1053 pdftk = subprocess.Popen(cmd_line)
1054 except OSError:
1055 _log.exception('cannot run <pdftk> (dump data from form)')
1056 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot extract fields from PDF form template.'), beep = True)
1057 return False
1058
1059 pdftk.communicate()
1060 if pdftk.returncode != 0:
1061 _log.error('<pdftk> returned [%s], failed to dump data from PDF form into FDF', pdftk.returncode)
1062 return False
1063
1064 # parse dumped FDF file for "/V (...)" records
1065 # and replace placeholders therein
1066 fdf_dumped_file = open(self.fdf_dumped_filename, 'rbU')
1067 fdf_replaced_file = codecs.open(self.fdf_replaced_filename, 'wb')
1068
1069 string_value_regex = r'\s*/V\s*\(.+\)\s*$'
1070 for line in fdf_dumped_file:
1071 if not regex.match(string_value_regex, line):
1072 fdf_replaced_file.write(line)
1073 continue
1074
1075 # strip cruft around the string value
1076 raw_str_val = line.strip() # remove framing whitespace
1077 raw_str_val = raw_str_val[2:] # remove leading "/V"
1078 raw_str_val = raw_str_val.lstrip() # remove whitespace between "/V" and "("
1079 raw_str_val = raw_str_val[1:] # remove opening "("
1080 raw_str_val = raw_str_val[2:] # remove BOM-16-BE
1081 raw_str_val = raw_str_val.rstrip() # remove trailing whitespace
1082 raw_str_val = raw_str_val[:-1] # remove closing ")"
1083
1084 # work on FDF escapes
1085 raw_str_val = raw_str_val.replace('\(', '(') # remove escaping of "("
1086 raw_str_val = raw_str_val.replace('\)', ')') # remove escaping of ")"
1087
1088 # by now raw_str_val should contain the actual
1089 # string value, albeit encoded as UTF-16, so
1090 # decode it into a unicode object,
1091 # split multi-line fields on "\n" literal
1092 raw_str_lines = raw_str_val.split('\x00\\n')
1093 value_template_lines = []
1094 for raw_str_line in raw_str_lines:
1095 value_template_lines.append(raw_str_line.decode('utf_16_be'))
1096
1097 replaced_lines = []
1098 for value_template in value_template_lines:
1099 # find any placeholders within
1100 placeholders_in_value = regex.findall(data_source.placeholder_regex, value_template, regex.IGNORECASE)
1101 for placeholder in placeholders_in_value:
1102 try:
1103 replacement = data_source[placeholder]
1104 except:
1105 _log.exception(replacement)
1106 replacement = _('error with placeholder [%s]') % placeholder
1107 if replacement is None:
1108 replacement = _('error with placeholder [%s]') % placeholder
1109 value_template = value_template.replace(placeholder, replacement)
1110
1111 value_template = value_template.encode('utf_16_be')
1112
1113 if len(placeholders_in_value) > 0:
1114 value_template = value_template.replace(r'(', r'\(')
1115 value_template = value_template.replace(r')', r'\)')
1116
1117 replaced_lines.append(value_template)
1118
1119 replaced_line = '\x00\\n'.join(replaced_lines)
1120
1121 fdf_replaced_file.write('/V (')
1122 fdf_replaced_file.write(codecs.BOM_UTF16_BE)
1123 fdf_replaced_file.write(replaced_line)
1124 fdf_replaced_file.write(')\n')
1125
1126 fdf_replaced_file.close()
1127 fdf_dumped_file.close()
1128
1129 # merge replaced data back into form
1130 cmd_line = [
1131 self.pdftk_binary,
1132 self.template_filename,
1133 r'fill_form',
1134 self.fdf_replaced_filename,
1135 r'output',
1136 self.pdf_filled_filename
1137 ]
1138 _log.debug(u' '.join(cmd_line))
1139 try:
1140 pdftk = subprocess.Popen(cmd_line)
1141 except OSError:
1142 _log.exception('cannot run <pdftk> (merge data into form)')
1143 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot fill in PDF form template.'), beep = True)
1144 return False
1145
1146 pdftk.communicate()
1147 if pdftk.returncode != 0:
1148 _log.error('<pdftk> returned [%s], failed to merge FDF data into PDF form', pdftk.returncode)
1149 return False
1150
1151 return True
1152 #--------------------------------------------------------
1154 mimetypes = [
1155 u'application/pdf',
1156 u'application/x-pdf'
1157 ]
1158
1159 for mimetype in mimetypes:
1160 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.pdf_filled_filename)
1161 if editor_cmd is not None:
1162 break
1163
1164 if editor_cmd is None:
1165 _log.debug('editor cmd not found, trying viewer cmd')
1166 for mimetype in mimetypes:
1167 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.pdf_filled_filename)
1168 if editor_cmd is not None:
1169 break
1170
1171 if editor_cmd is None:
1172 return False
1173
1174 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
1175
1176 path, fname = os.path.split(self.pdf_filled_filename)
1177 candidate = os.path.join(gmTools.gmPaths().home_dir, fname)
1178
1179 if os.access(candidate, os.R_OK):
1180 _log.debug('filled-in PDF found: %s', candidate)
1181 os.rename(self.pdf_filled_filename, self.pdf_filled_filename + '.bak')
1182 shutil.move(candidate, path)
1183 else:
1184 _log.debug('filled-in PDF not found: %s', candidate)
1185
1186 self.re_editable_filenames = [self.pdf_filled_filename]
1187
1188 return result
1189 #--------------------------------------------------------
1191 """Generate output suitable for further processing outside this class, e.g. printing."""
1192
1193 # eventually flatten the filled in form so we
1194 # can keep both a flattened and an editable copy:
1195 cmd_line = [
1196 self.pdftk_binary,
1197 self.pdf_filled_filename,
1198 r'output',
1199 self.pdf_flattened_filename,
1200 r'flatten'
1201 ]
1202 _log.debug(u' '.join(cmd_line))
1203 try:
1204 pdftk = subprocess.Popen(cmd_line)
1205 except OSError:
1206 _log.exception('cannot run <pdftk> (flatten filled in form)')
1207 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot flatten filled in PDF form.'), beep = True)
1208 return None
1209
1210 pdftk.communicate()
1211 if pdftk.returncode != 0:
1212 _log.error('<pdftk> returned [%s], failed to flatten filled in PDF form', pdftk.returncode)
1213 return None
1214
1215 self.final_output_filenames = [self.pdf_flattened_filename]
1216
1217 return self.pdf_flattened_filename
1218 #------------------------------------------------------------
1219 form_engines[u'P'] = cPDFForm
1220
1221 #============================================================
1222 # older code
1223 #------------------------------------------------------------
1225 """A forms engine wrapping LaTeX.
1226 """
1230
1232 try:
1233 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params])
1234 # create a 'sandbox' directory for LaTeX to play in
1235 self.tmp = tempfile.mktemp ()
1236 os.makedirs (self.tmp)
1237 self.oldcwd = os.getcwd ()
1238 os.chdir (self.tmp)
1239 stdin = os.popen ("latex", "w", 2048)
1240 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout
1241 # FIXME: send LaTeX output to the logger
1242 stdin.close ()
1243 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True):
1244 raise FormError ('DVIPS returned error')
1245 except EnvironmentError, e:
1246 _log.error(e.strerror)
1247 raise FormError (e.strerror)
1248 return file ("texput.ps")
1249
1251 """
1252 For testing purposes, runs Xdvi on the intermediate TeX output
1253 WARNING: don't try this on Windows
1254 """
1255 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)
1256
1258 if "%F" in command:
1259 command.replace ("%F", "texput.ps")
1260 else:
1261 command = "%s < texput.ps" % command
1262 try:
1263 if not gmShellAPI.run_command_in_shell(command, blocking=True):
1264 _log.error("external command %s returned non-zero" % command)
1265 raise FormError ('external command %s returned error' % command)
1266 except EnvironmentError, e:
1267 _log.error(e.strerror)
1268 raise FormError (e.strerror)
1269 return True
1270
1272 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print')
1273 self.exe (command)
1274
1283
1284
1285
1286
1287 #================================================================
1288 # define a class for HTML forms (for printing)
1289 #================================================================
1291 """This class can create XML document from requested data,
1292 then process it with XSLT template and display results
1293 """
1294
1295 # FIXME: make the path configurable ?
1296 _preview_program = u'oowriter ' #this program must be in the system PATH
1297
1299
1300 if template is None:
1301 raise ValueError(u'%s: cannot create form instance without a template' % __name__)
1302
1303 cFormEngine.__init__(self, template = template)
1304
1305 self._FormData = None
1306
1307 # here we know/can assume that the template was stored as a utf-8
1308 # encoded string so use that conversion to create unicode:
1309 #self._XSLTData = unicode(str(template.template_data), 'UTF-8')
1310 # but in fact, unicode() knows how to handle buffers, so simply:
1311 self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict')
1312
1313 # we must still devise a method of extracting the SQL query:
1314 # - either by retrieving it from a particular tag in the XSLT or
1315 # - by making the stored template actually be a dict which, unpickled,
1316 # has the keys "xslt" and "sql"
1317 self._SQL_query = u'select 1' #this sql query must output valid xml
1318 #--------------------------------------------------------
1319 # external API
1320 #--------------------------------------------------------
1322 """get data from backend and process it with XSLT template to produce readable output"""
1323
1324 # extract SQL (this is wrong but displays what is intended)
1325 xslt = libxml2.parseDoc(self._XSLTData)
1326 root = xslt.children
1327 for child in root:
1328 if child.type == 'element':
1329 self._SQL_query = child.content
1330 break
1331
1332 # retrieve data from backend
1333 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False)
1334
1335 __header = '<?xml version="1.0" encoding="UTF-8"?>\n'
1336 __body = rows[0][0]
1337
1338 # process XML data according to supplied XSLT, producing HTML
1339 self._XMLData =__header + __body
1340 style = libxslt.parseStylesheetDoc(xslt)
1341 xml = libxml2.parseDoc(self._XMLData)
1342 html = style.applyStylesheet(xml, None)
1343 self._FormData = html.serialize()
1344
1345 style.freeStylesheet()
1346 xml.freeDoc()
1347 html.freeDoc()
1348 #--------------------------------------------------------
1350 if self._FormData is None:
1351 raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed'
1352
1353 fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html')
1354 #html_file = os.open(fname, 'wb')
1355 #html_file.write(self._FormData.encode('UTF-8'))
1356 html_file = codecs.open(fname, 'wb', 'utf8', 'strict') # or 'replace' ?
1357 html_file.write(self._FormData)
1358 html_file.close()
1359
1360 cmd = u'%s %s' % (self.__class__._preview_program, fname)
1361
1362 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False):
1363 _log.error('%s: cannot launch report preview program' % __name__)
1364 return False
1365
1366 #os.unlink(self.filename) #delete file
1367 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK)
1368
1369 return True
1370 #--------------------------------------------------------
1374
1375
1376 #=====================================================
1377 #class LaTeXFilter(Cheetah.Filters.Filter):
1380 """
1381 Convience function to escape ISO-Latin-1 strings for TeX output
1382 WARNING: not all ISO-Latin-1 characters are expressible in TeX
1383 FIXME: nevertheless, there are a few more we could support
1384
1385 Also intelligently convert lists and tuples into TeX-style table lines
1386 """
1387 if type (item) is types.UnicodeType or type (item) is types.StringType:
1388 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX?
1389 item = item.replace ("&", "\\&")
1390 item = item.replace ("$", "\\$")
1391 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now
1392 item = item.replace ("\n", "\\\\ ")
1393 if len (item.strip ()) == 0:
1394 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it
1395 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX
1396 if type (item) is types.UnicodeType:
1397 item = item.encode ('latin-1', 'replace')
1398 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}',
1399 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions
1400 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`',
1401 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}',
1402 '\xc7':'\\c{C}', '\xc8':'\\`{E}',
1403 '\xa1': '!`',
1404 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'}
1405 for k, i in trans.items ():
1406 item = item.replace (k, i)
1407 elif type (item) is types.ListType or type (item) is types.TupleType:
1408 item = string.join ([self.filter (i, ' & ') for i in item], table_sep)
1409 elif item is None:
1410 item = '\\relax % Python None\n'
1411 elif type (item) is types.IntType or type (item) is types.FloatType:
1412 item = str (item)
1413 else:
1414 item = str (item)
1415 _log.warning("unknown type %s, string %s" % (type (item), item))
1416 return item
1417
1418
1419 #===========================================================
1422
1423 #============================================================
1424 # convenience functions
1425 #------------------------------------------------------------
1427 """
1428 Instantiates a FormEngine based on the form ID or name from the backend
1429 """
1430 try:
1431 # it's a number: match to form ID
1432 id = int (id)
1433 cmd = 'select template, engine, pk from paperwork_templates where pk = %s'
1434 except ValueError:
1435 # it's a string, match to the form's name
1436 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ?
1437 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s'
1438 result = gmPG.run_ro_query ('reference', cmd, None, id)
1439 if result is None:
1440 _log.error('error getting form [%s]' % id)
1441 raise gmExceptions.FormError ('error getting form [%s]' % id)
1442 if len(result) == 0:
1443 _log.error('no form [%s] found' % id)
1444 raise gmExceptions.FormError ('no such form found [%s]' % id)
1445 if result[0][1] == 'L':
1446 return LaTeXForm (result[0][2], result[0][0])
1447 elif result[0][1] == 'T':
1448 return TextForm (result[0][2], result[0][0])
1449 else:
1450 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id))
1451 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))
1452 #-------------------------------------------------------------
1459 #-------------------------------------------------------------
1460
1461 test_letter = """
1462 \\documentclass{letter}
1463 \\address{ $DOCTOR \\\\
1464 $DOCTORADDRESS}
1465 \\signature{$DOCTOR}
1466
1467 \\begin{document}
1468 \\begin{letter}{$RECIPIENTNAME \\\\
1469 $RECIPIENTADDRESS}
1470
1471 \\opening{Dear $RECIPIENTNAME}
1472
1473 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\
1474
1475 $TEXT
1476
1477 \\ifnum$INCLUDEMEDS>0
1478 \\textbf{Medications List}
1479
1480 \\begin{tabular}{lll}
1481 $MEDSLIST
1482 \\end{tabular}
1483 \\fi
1484
1485 \\ifnum$INCLUDEDISEASES>0
1486 \\textbf{Disease List}
1487
1488 \\begin{tabular}{l}
1489 $DISEASELIST
1490 \\end{tabular}
1491 \\fi
1492
1493 \\closing{$CLOSING}
1494
1495 \\end{letter}
1496 \\end{document}
1497 """
1498
1499
1501 f = open('../../test-area/ian/terry-form.tex')
1502 params = {
1503 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle",
1504 'DOCTORSNAME': 'Ian Haywood',
1505 'DOCTORSADDRESS': '1 Smith St\nMelbourne',
1506 'PATIENTNAME':'Joe Bloggs',
1507 'PATIENTADDRESS':'18 Fred St\nMelbourne',
1508 'REQUEST':'echocardiogram',
1509 'THERAPY':'on warfarin',
1510 'CLINICALNOTES':"""heard new murmur
1511 Here's some
1512 crap to demonstrate how it can cover multiple lines.""",
1513 'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany',
1514 'ROUTINE':1,
1515 'URGENT':0,
1516 'FAX':1,
1517 'PHONE':1,
1518 'PENSIONER':1,
1519 'VETERAN':0,
1520 'PADS':0,
1521 'INSTRUCTIONS':u'Take the blue pill, Neo'
1522 }
1523 form = LaTeXForm (1, f.read())
1524 form.process (params)
1525 form.xdvi ()
1526 form.cleanup ()
1527
1529 form = LaTeXForm (2, test_letter)
1530 params = {'RECIPIENTNAME':'Dr. Richard Terry',
1531 'RECIPIENTADDRESS':'1 Main St\nNewcastle',
1532 'DOCTOR':'Dr. Ian Haywood',
1533 'DOCTORADDRESS':'1 Smith St\nMelbourne',
1534 'PATIENTNAME':'Joe Bloggs',
1535 'PATIENTADDRESS':'18 Fred St, Melbourne',
1536 'TEXT':"""This is the main text of the referral letter""",
1537 'DOB':'12/3/65',
1538 'INCLUDEMEDS':1,
1539 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]],
1540 'INCLUDEDISEASES':0, 'DISEASELIST':'',
1541 'CLOSING':'Yours sincerely,'
1542 }
1543 form.process (params)
1544 print os.getcwd ()
1545 form.xdvi ()
1546 form.cleanup ()
1547 #------------------------------------------------------------
1549 template = open('../../test-area/ian/Formularkopf-DE.tex')
1550 form = LaTeXForm(template=template.read())
1551 params = {
1552 'PATIENT LASTNAME': 'Kirk',
1553 'PATIENT FIRSTNAME': 'James T.',
1554 'PATIENT STREET': 'Hauptstrasse',
1555 'PATIENT ZIP': '02999',
1556 'PATIENT TOWN': 'Gross Saerchen',
1557 'PATIENT DOB': '22.03.1931'
1558 }
1559 form.process(params)
1560 form.xdvi()
1561 form.cleanup()
1562
1563 #============================================================
1564 # main
1565 #------------------------------------------------------------
1566 if __name__ == '__main__':
1567
1568 if len(sys.argv) < 2:
1569 sys.exit()
1570
1571 if sys.argv[1] != 'test':
1572 sys.exit()
1573
1574 from Gnumed.pycommon import gmDateTime
1575 gmDateTime.init()
1576
1577 #--------------------------------------------------------
1578 # OOo
1579 #--------------------------------------------------------
1581 init_ooo()
1582 #--------------------------------------------------------
1587 #--------------------------------------------------------
1589 srv = gmOOoConnector()
1590 doc = srv.open_document(filename = sys.argv[2])
1591 print "document:", doc
1592 #--------------------------------------------------------
1594 doc = cOOoLetter(template_file = sys.argv[2])
1595 doc.open_in_ooo()
1596 print "document:", doc
1597 raw_input('press <ENTER> to continue')
1598 doc.show()
1599 #doc.replace_placeholders()
1600 #doc.save_in_ooo('~/test_cOOoLetter.odt')
1601 # doc = None
1602 # doc.close_in_ooo()
1603 raw_input('press <ENTER> to continue')
1604 #--------------------------------------------------------
1606 try:
1607 doc = open_uri_in_ooo(filename=sys.argv[1])
1608 except:
1609 _log.exception('cannot open [%s] in OOo' % sys.argv[1])
1610 raise
1611
1612 class myCloseListener(unohelper.Base, oooXCloseListener):
1613 def disposing(self, evt):
1614 print "disposing:"
1615 def notifyClosing(self, evt):
1616 print "notifyClosing:"
1617 def queryClosing(self, evt, owner):
1618 # owner is True/False whether I am the owner of the doc
1619 print "queryClosing:"
1620
1621 l = myCloseListener()
1622 doc.addCloseListener(l)
1623
1624 tfs = doc.getTextFields().createEnumeration()
1625 print tfs
1626 print dir(tfs)
1627 while tfs.hasMoreElements():
1628 tf = tfs.nextElement()
1629 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'):
1630 print tf.getPropertyValue('PlaceHolder')
1631 print " ", tf.getPropertyValue('Hint')
1632
1633 # doc.close(True) # closes but leaves open the dedicated OOo window
1634 doc.dispose() # closes and disposes of the OOo window
1635 #--------------------------------------------------------
1637 pat = gmPersonSearch.ask_for_patient()
1638 if pat is None:
1639 return
1640 gmPerson.set_active_patient(patient = pat)
1641
1642 doc = cOOoLetter(template_file = sys.argv[2])
1643 doc.open_in_ooo()
1644 print doc
1645 doc.show()
1646 #doc.replace_placeholders()
1647 #doc.save_in_ooo('~/test_cOOoLetter.odt')
1648 doc = None
1649 # doc.close_in_ooo()
1650 raw_input('press <ENTER> to continue')
1651 #--------------------------------------------------------
1652 # other
1653 #--------------------------------------------------------
1655 template = cFormTemplate(aPK_obj = sys.argv[2])
1656 print template
1657 print template.export_to_file()
1658 #--------------------------------------------------------
1660 template = cFormTemplate(aPK_obj = sys.argv[2])
1661 template.update_template_from_file(filename = sys.argv[3])
1662 #--------------------------------------------------------
1664 pat = gmPersonSearch.ask_for_patient()
1665 if pat is None:
1666 return
1667 gmPerson.set_active_patient(patient = pat)
1668
1669 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1670
1671 path = os.path.abspath(sys.argv[2])
1672 form = cLaTeXForm(template_file = path)
1673
1674 from Gnumed.wxpython import gmMacro
1675 ph = gmMacro.gmPlaceholderHandler()
1676 ph.debug = True
1677 instance_file = form.substitute_placeholders(data_source = ph)
1678 pdf_name = form.generate_output(instance_file = instance_file)
1679 print "final PDF file is:", pdf_name
1680 #--------------------------------------------------------
1682 pat = gmPersonSearch.ask_for_patient()
1683 if pat is None:
1684 return
1685 gmPerson.set_active_patient(patient = pat)
1686
1687 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1688
1689 path = os.path.abspath(sys.argv[2])
1690 form = cPDFForm(template_file = path)
1691
1692 from Gnumed.wxpython import gmMacro
1693 ph = gmMacro.gmPlaceholderHandler()
1694 ph.debug = True
1695 instance_file = form.substitute_placeholders(data_source = ph)
1696 pdf_name = form.generate_output(instance_file = instance_file)
1697 print "final PDF file is:", pdf_name
1698 #--------------------------------------------------------
1700 pat = gmPersonSearch.ask_for_patient()
1701 if pat is None:
1702 return
1703 gmPerson.set_active_patient(patient = pat)
1704
1705 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1706
1707 path = os.path.abspath(sys.argv[2])
1708 form = cAbiWordForm(template_file = path)
1709
1710 from Gnumed.wxpython import gmMacro
1711 ph = gmMacro.gmPlaceholderHandler()
1712 ph.debug = True
1713 instance_file = form.substitute_placeholders(data_source = ph)
1714 form.edit()
1715 final_name = form.generate_output(instance_file = instance_file)
1716 print "final file is:", final_name
1717 #--------------------------------------------------------
1718 #--------------------------------------------------------
1719 # now run the tests
1720 #test_au()
1721 #test_de()
1722
1723 # OOo
1724 #test_init_ooo()
1725 #test_ooo_connect()
1726 #test_open_ooo_doc_from_srv()
1727 #test_open_ooo_doc_from_letter()
1728 #play_with_ooo()
1729 #test_cOOoLetter()
1730
1731 #test_cFormTemplate()
1732 #set_template_from_file()
1733 #test_latex_form()
1734 #test_pdf_form()
1735 test_abiword_form()
1736
1737 #============================================================
1738
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri May 11 03:58:49 2012 | http://epydoc.sourceforge.net |