| 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 template_file.close()
839
840 if self.template is not None:
841 # remove temporary placeholders
842 data_source.unset_placeholder(u'form_name_long')
843 data_source.unset_placeholder(u'form_name_short')
844 data_source.unset_placeholder(u'form_version')
845
846 return
847 #--------------------------------------------------------
849
850 mimetypes = [
851 u'application/x-latex',
852 u'application/x-tex',
853 u'text/plain'
854 ]
855
856 for mimetype in mimetypes:
857 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.instance_filename)
858 if editor_cmd is not None:
859 break
860
861 if editor_cmd is None:
862 # LaTeX code is text: also consider text *viewers*
863 # since pretty much any of them will be an editor as well
864 for mimetype in mimetypes:
865 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.instance_filename)
866 if editor_cmd is not None:
867 break
868
869 # last resort
870 if editor_cmd is None:
871 if os.name == 'nt':
872 editor_cmd = u'notepad.exe %s' % self.instance_filename
873 else:
874 editor_cmd = u'sensible-editor %s' % self.instance_filename
875
876 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
877 self.re_editable_filenames = [self.instance_filename]
878
879 return result
880 #--------------------------------------------------------
882
883 if instance_file is None:
884 instance_file = self.instance_filename
885
886 try:
887 open(instance_file, 'r').close()
888 except:
889 _log.exception('cannot access form instance file [%s]', instance_file)
890 gmLog2.log_stack_trace()
891 return None
892
893 self.instance_filename = instance_file
894
895 _log.debug('ignoring <format> directive [%s], generating PDF', format)
896
897 # create sandbox for LaTeX to play in
898 sandbox_dir = os.path.splitext(self.template_filename)[0]
899 _log.debug('LaTeX sandbox directory: [%s]', sandbox_dir)
900
901 old_cwd = os.getcwd()
902 _log.debug('CWD: [%s]', old_cwd)
903
904 gmTools.mkdir(sandbox_dir)
905
906 os.chdir(sandbox_dir)
907 try:
908 sandboxed_instance_filename = os.path.join(sandbox_dir, os.path.split(self.instance_filename)[1])
909 shutil.move(self.instance_filename, sandboxed_instance_filename)
910
911 # LaTeX can need up to three runs to get cross references et al right
912 if platform.system() == 'Windows':
913 draft_cmd = r'pdflatex.exe -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename
914 final_cmd = r'pdflatex.exe -interaction nonstopmode %s' % sandboxed_instance_filename
915 else:
916 draft_cmd = r'pdflatex -draftmode -interaction nonstopmode %s' % sandboxed_instance_filename
917 final_cmd = r'pdflatex -interaction nonstopmode %s' % sandboxed_instance_filename
918 for run_cmd in [draft_cmd, draft_cmd, final_cmd]:
919 if not gmShellAPI.run_command_in_shell(command = run_cmd, blocking = True, acceptable_return_codes = [0, 1]):
920 _log.error('problem running pdflatex, cannot generate form output')
921 gmDispatcher.send(signal = 'statustext', msg = _('Error running pdflatex. Cannot turn LaTeX template into PDF.'), beep = True)
922 os.chdir(old_cwd)
923 return None
924 finally:
925 os.chdir(old_cwd)
926
927 sandboxed_pdf_name = u'%s.pdf' % os.path.splitext(sandboxed_instance_filename)[0]
928 target_dir = os.path.split(self.instance_filename)[0]
929 try:
930 shutil.move(sandboxed_pdf_name, target_dir)
931 except IOError:
932 _log.exception('cannot move sandboxed PDF: %s -> %s', sandboxed_pdf_name, target_dir)
933 gmDispatcher.send(signal = 'statustext', msg = _('Sandboxed PDF output file cannot be moved.'), beep = True)
934 return None
935
936 final_pdf_name = u'%s.pdf' % os.path.splitext(self.instance_filename)[0]
937
938 try:
939 open(final_pdf_name, 'r').close()
940 except IOError:
941 _log.exception('cannot open target PDF: %s', final_pdf_name)
942 gmDispatcher.send(signal = 'statustext', msg = _('PDF output file cannot be opened.'), beep = True)
943 return None
944
945 self.final_output_filenames = [final_pdf_name]
946
947 return final_pdf_name
948 #------------------------------------------------------------
949 form_engines[u'L'] = cLaTeXForm
950 #============================================================
951 # Gnuplot template forms
952 #------------------------------------------------------------
954 """A forms engine wrapping Gnuplot."""
955
956 #--------------------------------------------------------
960 #--------------------------------------------------------
962 """Allow editing the instance of the template."""
963 self.re_editable_filenames = []
964 return True
965 #--------------------------------------------------------
967 """Generate output suitable for further processing outside this class, e.g. printing.
968
969 Expects .data_filename to be set.
970 """
971 self.conf_filename = gmTools.get_unique_filename(prefix = 'gm2gpl-', suffix = '.conf')
972 fname_file = codecs.open(self.conf_filename, 'wb', 'utf8')
973 fname_file.write('# setting the gnuplot data file\n')
974 fname_file.write("gm2gpl_datafile = '%s'\n" % self.data_filename)
975 fname_file.close()
976
977 # FIXME: cater for configurable path
978 if platform.system() == 'Windows':
979 exec_name = 'gnuplot.exe'
980 else:
981 exec_name = 'gnuplot'
982
983 args = [exec_name, '-p', self.conf_filename, self.template_filename]
984 _log.debug('plotting args: %s' % str(args))
985
986 try:
987 gp = subprocess.Popen (
988 args = args,
989 close_fds = True
990 )
991 except (OSError, ValueError, subprocess.CalledProcessError):
992 _log.exception('there was a problem executing gnuplot')
993 gmDispatcher.send(signal = u'statustext', msg = _('Error running gnuplot. Cannot plot data.'), beep = True)
994 return
995
996 gp.communicate()
997
998 self.final_output_filenames = [
999 self.conf_filename,
1000 self.data_filename,
1001 self.template_filename
1002 ]
1003
1004 return
1005 #------------------------------------------------------------
1006 form_engines[u'G'] = cGnuplotForm
1007
1008 #============================================================
1009 # fPDF form engine
1010 #------------------------------------------------------------
1012 """A forms engine wrapping PDF forms.
1013
1014 Johann Felix Soden <johfel@gmx.de> helped with this.
1015
1016 http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf
1017
1018 http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/fdf_data_exchange.pdf
1019 """
1020
1022
1023 super(cPDFForm, self).__init__(template_file = template_file)
1024
1025 # detect pdftk
1026 found, self.pdftk_binary = gmShellAPI.detect_external_binary(binary = r'pdftk')
1027 if not found:
1028 raise ImportError('<pdftk(.exe)> not found')
1029 return # should be superfluous, actually
1030
1031 enc = sys.getfilesystemencoding()
1032 self.pdftk_binary = self.pdftk_binary.encode(enc)
1033
1034 base_name, ext = os.path.splitext(self.template_filename)
1035 self.fdf_dumped_filename = (u'%s.fdf' % base_name).encode(enc)
1036 self.fdf_replaced_filename = (u'%s-replaced.fdf' % base_name).encode(enc)
1037 self.pdf_filled_filename = (u'%s-filled.pdf' % base_name).encode(enc)
1038 self.pdf_flattened_filename = (u'%s-filled-flattened.pdf' % base_name).encode(enc)
1039 #--------------------------------------------------------
1041
1042 # dump form fields from template
1043 cmd_line = [
1044 self.pdftk_binary,
1045 self.template_filename,
1046 r'generate_fdf',
1047 r'output',
1048 self.fdf_dumped_filename
1049 ]
1050 _log.debug(u' '.join(cmd_line))
1051 try:
1052 pdftk = subprocess.Popen(cmd_line)
1053 except OSError:
1054 _log.exception('cannot run <pdftk> (dump data from form)')
1055 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot extract fields from PDF form template.'), beep = True)
1056 return False
1057
1058 pdftk.communicate()
1059 if pdftk.returncode != 0:
1060 _log.error('<pdftk> returned [%s], failed to dump data from PDF form into FDF', pdftk.returncode)
1061 return False
1062
1063 # parse dumped FDF file for "/V (...)" records
1064 # and replace placeholders therein
1065 fdf_dumped_file = open(self.fdf_dumped_filename, 'rbU')
1066 fdf_replaced_file = codecs.open(self.fdf_replaced_filename, 'wb')
1067
1068 string_value_regex = r'\s*/V\s*\(.+\)\s*$'
1069 for line in fdf_dumped_file:
1070 if not regex.match(string_value_regex, line):
1071 fdf_replaced_file.write(line)
1072 continue
1073
1074 # strip cruft around the string value
1075 raw_str_val = line.strip() # remove framing whitespace
1076 raw_str_val = raw_str_val[2:] # remove leading "/V"
1077 raw_str_val = raw_str_val.lstrip() # remove whitespace between "/V" and "("
1078 raw_str_val = raw_str_val[1:] # remove opening "("
1079 raw_str_val = raw_str_val[2:] # remove BOM-16-BE
1080 raw_str_val = raw_str_val.rstrip() # remove trailing whitespace
1081 raw_str_val = raw_str_val[:-1] # remove closing ")"
1082
1083 # work on FDF escapes
1084 raw_str_val = raw_str_val.replace('\(', '(') # remove escaping of "("
1085 raw_str_val = raw_str_val.replace('\)', ')') # remove escaping of ")"
1086
1087 # by now raw_str_val should contain the actual
1088 # string value, albeit encoded as UTF-16, so
1089 # decode it into a unicode object,
1090 # split multi-line fields on "\n" literal
1091 raw_str_lines = raw_str_val.split('\x00\\n')
1092 value_template_lines = []
1093 for raw_str_line in raw_str_lines:
1094 value_template_lines.append(raw_str_line.decode('utf_16_be'))
1095
1096 replaced_lines = []
1097 for value_template in value_template_lines:
1098 # find any placeholders within
1099 placeholders_in_value = regex.findall(data_source.placeholder_regex, value_template, regex.IGNORECASE)
1100 for placeholder in placeholders_in_value:
1101 try:
1102 replacement = data_source[placeholder]
1103 except:
1104 _log.exception(replacement)
1105 replacement = _('error with placeholder [%s]') % placeholder
1106 if replacement is None:
1107 replacement = _('error with placeholder [%s]') % placeholder
1108 value_template = value_template.replace(placeholder, replacement)
1109
1110 value_template = value_template.encode('utf_16_be')
1111
1112 if len(placeholders_in_value) > 0:
1113 value_template = value_template.replace(r'(', r'\(')
1114 value_template = value_template.replace(r')', r'\)')
1115
1116 replaced_lines.append(value_template)
1117
1118 replaced_line = '\x00\\n'.join(replaced_lines)
1119
1120 fdf_replaced_file.write('/V (')
1121 fdf_replaced_file.write(codecs.BOM_UTF16_BE)
1122 fdf_replaced_file.write(replaced_line)
1123 fdf_replaced_file.write(')\n')
1124
1125 fdf_replaced_file.close()
1126 fdf_dumped_file.close()
1127
1128 # merge replaced data back into form
1129 cmd_line = [
1130 self.pdftk_binary,
1131 self.template_filename,
1132 r'fill_form',
1133 self.fdf_replaced_filename,
1134 r'output',
1135 self.pdf_filled_filename
1136 ]
1137 _log.debug(u' '.join(cmd_line))
1138 try:
1139 pdftk = subprocess.Popen(cmd_line)
1140 except OSError:
1141 _log.exception('cannot run <pdftk> (merge data into form)')
1142 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot fill in PDF form template.'), beep = True)
1143 return False
1144
1145 pdftk.communicate()
1146 if pdftk.returncode != 0:
1147 _log.error('<pdftk> returned [%s], failed to merge FDF data into PDF form', pdftk.returncode)
1148 return False
1149
1150 return True
1151 #--------------------------------------------------------
1153 mimetypes = [
1154 u'application/pdf',
1155 u'application/x-pdf'
1156 ]
1157
1158 for mimetype in mimetypes:
1159 editor_cmd = gmMimeLib.get_editor_cmd(mimetype, self.pdf_filled_filename)
1160 if editor_cmd is not None:
1161 break
1162
1163 if editor_cmd is None:
1164 _log.debug('editor cmd not found, trying viewer cmd')
1165 for mimetype in mimetypes:
1166 editor_cmd = gmMimeLib.get_viewer_cmd(mimetype, self.pdf_filled_filename)
1167 if editor_cmd is not None:
1168 break
1169
1170 if editor_cmd is None:
1171 return False
1172
1173 result = gmShellAPI.run_command_in_shell(command = editor_cmd, blocking = True)
1174
1175 path, fname = os.path.split(self.pdf_filled_filename)
1176 candidate = os.path.join(gmTools.gmPaths().home_dir, fname)
1177
1178 if os.access(candidate, os.R_OK):
1179 _log.debug('filled-in PDF found: %s', candidate)
1180 os.rename(self.pdf_filled_filename, self.pdf_filled_filename + '.bak')
1181 shutil.move(candidate, path)
1182 else:
1183 _log.debug('filled-in PDF not found: %s', candidate)
1184
1185 self.re_editable_filenames = [self.pdf_filled_filename]
1186
1187 return result
1188 #--------------------------------------------------------
1190 """Generate output suitable for further processing outside this class, e.g. printing."""
1191
1192 # eventually flatten the filled in form so we
1193 # can keep both a flattened and an editable copy:
1194 cmd_line = [
1195 self.pdftk_binary,
1196 self.pdf_filled_filename,
1197 r'output',
1198 self.pdf_flattened_filename,
1199 r'flatten'
1200 ]
1201 _log.debug(u' '.join(cmd_line))
1202 try:
1203 pdftk = subprocess.Popen(cmd_line)
1204 except OSError:
1205 _log.exception('cannot run <pdftk> (flatten filled in form)')
1206 gmDispatcher.send(signal = u'statustext', msg = _('Error running pdftk. Cannot flatten filled in PDF form.'), beep = True)
1207 return None
1208
1209 pdftk.communicate()
1210 if pdftk.returncode != 0:
1211 _log.error('<pdftk> returned [%s], failed to flatten filled in PDF form', pdftk.returncode)
1212 return None
1213
1214 self.final_output_filenames = [self.pdf_flattened_filename]
1215
1216 return self.pdf_flattened_filename
1217 #------------------------------------------------------------
1218 form_engines[u'P'] = cPDFForm
1219
1220 #============================================================
1221 # older code
1222 #------------------------------------------------------------
1224 """A forms engine wrapping LaTeX.
1225 """
1229
1231 try:
1232 latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params])
1233 # create a 'sandbox' directory for LaTeX to play in
1234 self.tmp = tempfile.mktemp ()
1235 os.makedirs (self.tmp)
1236 self.oldcwd = os.getcwd ()
1237 os.chdir (self.tmp)
1238 stdin = os.popen ("latex", "w", 2048)
1239 stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout
1240 # FIXME: send LaTeX output to the logger
1241 stdin.close ()
1242 if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True):
1243 raise FormError ('DVIPS returned error')
1244 except EnvironmentError, e:
1245 _log.error(e.strerror)
1246 raise FormError (e.strerror)
1247 return file ("texput.ps")
1248
1250 """
1251 For testing purposes, runs Xdvi on the intermediate TeX output
1252 WARNING: don't try this on Windows
1253 """
1254 gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)
1255
1257 if "%F" in command:
1258 command.replace ("%F", "texput.ps")
1259 else:
1260 command = "%s < texput.ps" % command
1261 try:
1262 if not gmShellAPI.run_command_in_shell(command, blocking=True):
1263 _log.error("external command %s returned non-zero" % command)
1264 raise FormError ('external command %s returned error' % command)
1265 except EnvironmentError, e:
1266 _log.error(e.strerror)
1267 raise FormError (e.strerror)
1268 return True
1269
1271 command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print')
1272 self.exe (command)
1273
1282
1283
1284
1285
1286 #================================================================
1287 # define a class for HTML forms (for printing)
1288 #================================================================
1290 """This class can create XML document from requested data,
1291 then process it with XSLT template and display results
1292 """
1293
1294 # FIXME: make the path configurable ?
1295 _preview_program = u'oowriter ' #this program must be in the system PATH
1296
1298
1299 if template is None:
1300 raise ValueError(u'%s: cannot create form instance without a template' % __name__)
1301
1302 cFormEngine.__init__(self, template = template)
1303
1304 self._FormData = None
1305
1306 # here we know/can assume that the template was stored as a utf-8
1307 # encoded string so use that conversion to create unicode:
1308 #self._XSLTData = unicode(str(template.template_data), 'UTF-8')
1309 # but in fact, unicode() knows how to handle buffers, so simply:
1310 self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict')
1311
1312 # we must still devise a method of extracting the SQL query:
1313 # - either by retrieving it from a particular tag in the XSLT or
1314 # - by making the stored template actually be a dict which, unpickled,
1315 # has the keys "xslt" and "sql"
1316 self._SQL_query = u'select 1' #this sql query must output valid xml
1317 #--------------------------------------------------------
1318 # external API
1319 #--------------------------------------------------------
1321 """get data from backend and process it with XSLT template to produce readable output"""
1322
1323 # extract SQL (this is wrong but displays what is intended)
1324 xslt = libxml2.parseDoc(self._XSLTData)
1325 root = xslt.children
1326 for child in root:
1327 if child.type == 'element':
1328 self._SQL_query = child.content
1329 break
1330
1331 # retrieve data from backend
1332 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx = False)
1333
1334 __header = '<?xml version="1.0" encoding="UTF-8"?>\n'
1335 __body = rows[0][0]
1336
1337 # process XML data according to supplied XSLT, producing HTML
1338 self._XMLData =__header + __body
1339 style = libxslt.parseStylesheetDoc(xslt)
1340 xml = libxml2.parseDoc(self._XMLData)
1341 html = style.applyStylesheet(xml, None)
1342 self._FormData = html.serialize()
1343
1344 style.freeStylesheet()
1345 xml.freeDoc()
1346 html.freeDoc()
1347 #--------------------------------------------------------
1349 if self._FormData is None:
1350 raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed'
1351
1352 fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html')
1353 #html_file = os.open(fname, 'wb')
1354 #html_file.write(self._FormData.encode('UTF-8'))
1355 html_file = codecs.open(fname, 'wb', 'utf8', 'strict') # or 'replace' ?
1356 html_file.write(self._FormData)
1357 html_file.close()
1358
1359 cmd = u'%s %s' % (self.__class__._preview_program, fname)
1360
1361 if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False):
1362 _log.error('%s: cannot launch report preview program' % __name__)
1363 return False
1364
1365 #os.unlink(self.filename) #delete file
1366 #FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK)
1367
1368 return True
1369 #--------------------------------------------------------
1373
1374
1375 #=====================================================
1376 #class LaTeXFilter(Cheetah.Filters.Filter):
1379 """
1380 Convience function to escape ISO-Latin-1 strings for TeX output
1381 WARNING: not all ISO-Latin-1 characters are expressible in TeX
1382 FIXME: nevertheless, there are a few more we could support
1383
1384 Also intelligently convert lists and tuples into TeX-style table lines
1385 """
1386 if type (item) is types.UnicodeType or type (item) is types.StringType:
1387 item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX?
1388 item = item.replace ("&", "\\&")
1389 item = item.replace ("$", "\\$")
1390 item = item.replace ('"', "") # okay, that's not right, but easiest solution for now
1391 item = item.replace ("\n", "\\\\ ")
1392 if len (item.strip ()) == 0:
1393 item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it
1394 # FIXME: cover all of ISO-Latin-1 which can be expressed in TeX
1395 if type (item) is types.UnicodeType:
1396 item = item.encode ('latin-1', 'replace')
1397 trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}',
1398 '\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions
1399 '\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`',
1400 '\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}',
1401 '\xc7':'\\c{C}', '\xc8':'\\`{E}',
1402 '\xa1': '!`',
1403 '\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'}
1404 for k, i in trans.items ():
1405 item = item.replace (k, i)
1406 elif type (item) is types.ListType or type (item) is types.TupleType:
1407 item = string.join ([self.filter (i, ' & ') for i in item], table_sep)
1408 elif item is None:
1409 item = '\\relax % Python None\n'
1410 elif type (item) is types.IntType or type (item) is types.FloatType:
1411 item = str (item)
1412 else:
1413 item = str (item)
1414 _log.warning("unknown type %s, string %s" % (type (item), item))
1415 return item
1416
1417
1418 #===========================================================
1421
1422 #============================================================
1423 # convenience functions
1424 #------------------------------------------------------------
1426 """
1427 Instantiates a FormEngine based on the form ID or name from the backend
1428 """
1429 try:
1430 # it's a number: match to form ID
1431 id = int (id)
1432 cmd = 'select template, engine, pk from paperwork_templates where pk = %s'
1433 except ValueError:
1434 # it's a string, match to the form's name
1435 # FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ?
1436 cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s'
1437 result = gmPG.run_ro_query ('reference', cmd, None, id)
1438 if result is None:
1439 _log.error('error getting form [%s]' % id)
1440 raise gmExceptions.FormError ('error getting form [%s]' % id)
1441 if len(result) == 0:
1442 _log.error('no form [%s] found' % id)
1443 raise gmExceptions.FormError ('no such form found [%s]' % id)
1444 if result[0][1] == 'L':
1445 return LaTeXForm (result[0][2], result[0][0])
1446 elif result[0][1] == 'T':
1447 return TextForm (result[0][2], result[0][0])
1448 else:
1449 _log.error('no form engine [%s] for form [%s]' % (result[0][1], id))
1450 raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))
1451 #-------------------------------------------------------------
1458 #-------------------------------------------------------------
1459
1460 test_letter = """
1461 \\documentclass{letter}
1462 \\address{ $DOCTOR \\\\
1463 $DOCTORADDRESS}
1464 \\signature{$DOCTOR}
1465
1466 \\begin{document}
1467 \\begin{letter}{$RECIPIENTNAME \\\\
1468 $RECIPIENTADDRESS}
1469
1470 \\opening{Dear $RECIPIENTNAME}
1471
1472 \\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\
1473
1474 $TEXT
1475
1476 \\ifnum$INCLUDEMEDS>0
1477 \\textbf{Medications List}
1478
1479 \\begin{tabular}{lll}
1480 $MEDSLIST
1481 \\end{tabular}
1482 \\fi
1483
1484 \\ifnum$INCLUDEDISEASES>0
1485 \\textbf{Disease List}
1486
1487 \\begin{tabular}{l}
1488 $DISEASELIST
1489 \\end{tabular}
1490 \\fi
1491
1492 \\closing{$CLOSING}
1493
1494 \\end{letter}
1495 \\end{document}
1496 """
1497
1498
1500 f = open('../../test-area/ian/terry-form.tex')
1501 params = {
1502 'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle",
1503 'DOCTORSNAME': 'Ian Haywood',
1504 'DOCTORSADDRESS': '1 Smith St\nMelbourne',
1505 'PATIENTNAME':'Joe Bloggs',
1506 'PATIENTADDRESS':'18 Fred St\nMelbourne',
1507 'REQUEST':'echocardiogram',
1508 'THERAPY':'on warfarin',
1509 'CLINICALNOTES':"""heard new murmur
1510 Here's some
1511 crap to demonstrate how it can cover multiple lines.""",
1512 'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany',
1513 'ROUTINE':1,
1514 'URGENT':0,
1515 'FAX':1,
1516 'PHONE':1,
1517 'PENSIONER':1,
1518 'VETERAN':0,
1519 'PADS':0,
1520 'INSTRUCTIONS':u'Take the blue pill, Neo'
1521 }
1522 form = LaTeXForm (1, f.read())
1523 form.process (params)
1524 form.xdvi ()
1525 form.cleanup ()
1526
1528 form = LaTeXForm (2, test_letter)
1529 params = {'RECIPIENTNAME':'Dr. Richard Terry',
1530 'RECIPIENTADDRESS':'1 Main St\nNewcastle',
1531 'DOCTOR':'Dr. Ian Haywood',
1532 'DOCTORADDRESS':'1 Smith St\nMelbourne',
1533 'PATIENTNAME':'Joe Bloggs',
1534 'PATIENTADDRESS':'18 Fred St, Melbourne',
1535 'TEXT':"""This is the main text of the referral letter""",
1536 'DOB':'12/3/65',
1537 'INCLUDEMEDS':1,
1538 'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]],
1539 'INCLUDEDISEASES':0, 'DISEASELIST':'',
1540 'CLOSING':'Yours sincerely,'
1541 }
1542 form.process (params)
1543 print os.getcwd ()
1544 form.xdvi ()
1545 form.cleanup ()
1546 #------------------------------------------------------------
1548 template = open('../../test-area/ian/Formularkopf-DE.tex')
1549 form = LaTeXForm(template=template.read())
1550 params = {
1551 'PATIENT LASTNAME': 'Kirk',
1552 'PATIENT FIRSTNAME': 'James T.',
1553 'PATIENT STREET': 'Hauptstrasse',
1554 'PATIENT ZIP': '02999',
1555 'PATIENT TOWN': 'Gross Saerchen',
1556 'PATIENT DOB': '22.03.1931'
1557 }
1558 form.process(params)
1559 form.xdvi()
1560 form.cleanup()
1561
1562 #============================================================
1563 # main
1564 #------------------------------------------------------------
1565 if __name__ == '__main__':
1566
1567 if len(sys.argv) < 2:
1568 sys.exit()
1569
1570 if sys.argv[1] != 'test':
1571 sys.exit()
1572
1573 from Gnumed.pycommon import gmDateTime
1574 gmDateTime.init()
1575
1576 #--------------------------------------------------------
1577 # OOo
1578 #--------------------------------------------------------
1580 init_ooo()
1581 #--------------------------------------------------------
1586 #--------------------------------------------------------
1588 srv = gmOOoConnector()
1589 doc = srv.open_document(filename = sys.argv[2])
1590 print "document:", doc
1591 #--------------------------------------------------------
1593 doc = cOOoLetter(template_file = sys.argv[2])
1594 doc.open_in_ooo()
1595 print "document:", doc
1596 raw_input('press <ENTER> to continue')
1597 doc.show()
1598 #doc.replace_placeholders()
1599 #doc.save_in_ooo('~/test_cOOoLetter.odt')
1600 # doc = None
1601 # doc.close_in_ooo()
1602 raw_input('press <ENTER> to continue')
1603 #--------------------------------------------------------
1605 try:
1606 doc = open_uri_in_ooo(filename=sys.argv[1])
1607 except:
1608 _log.exception('cannot open [%s] in OOo' % sys.argv[1])
1609 raise
1610
1611 class myCloseListener(unohelper.Base, oooXCloseListener):
1612 def disposing(self, evt):
1613 print "disposing:"
1614 def notifyClosing(self, evt):
1615 print "notifyClosing:"
1616 def queryClosing(self, evt, owner):
1617 # owner is True/False whether I am the owner of the doc
1618 print "queryClosing:"
1619
1620 l = myCloseListener()
1621 doc.addCloseListener(l)
1622
1623 tfs = doc.getTextFields().createEnumeration()
1624 print tfs
1625 print dir(tfs)
1626 while tfs.hasMoreElements():
1627 tf = tfs.nextElement()
1628 if tf.supportsService('com.sun.star.text.TextField.JumpEdit'):
1629 print tf.getPropertyValue('PlaceHolder')
1630 print " ", tf.getPropertyValue('Hint')
1631
1632 # doc.close(True) # closes but leaves open the dedicated OOo window
1633 doc.dispose() # closes and disposes of the OOo window
1634 #--------------------------------------------------------
1636 pat = gmPersonSearch.ask_for_patient()
1637 if pat is None:
1638 return
1639 gmPerson.set_active_patient(patient = pat)
1640
1641 doc = cOOoLetter(template_file = sys.argv[2])
1642 doc.open_in_ooo()
1643 print doc
1644 doc.show()
1645 #doc.replace_placeholders()
1646 #doc.save_in_ooo('~/test_cOOoLetter.odt')
1647 doc = None
1648 # doc.close_in_ooo()
1649 raw_input('press <ENTER> to continue')
1650 #--------------------------------------------------------
1651 # other
1652 #--------------------------------------------------------
1654 template = cFormTemplate(aPK_obj = sys.argv[2])
1655 print template
1656 print template.export_to_file()
1657 #--------------------------------------------------------
1659 template = cFormTemplate(aPK_obj = sys.argv[2])
1660 template.update_template_from_file(filename = sys.argv[3])
1661 #--------------------------------------------------------
1663 pat = gmPersonSearch.ask_for_patient()
1664 if pat is None:
1665 return
1666 gmPerson.set_active_patient(patient = pat)
1667
1668 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1669
1670 path = os.path.abspath(sys.argv[2])
1671 form = cLaTeXForm(template_file = path)
1672
1673 from Gnumed.wxpython import gmMacro
1674 ph = gmMacro.gmPlaceholderHandler()
1675 ph.debug = True
1676 instance_file = form.substitute_placeholders(data_source = ph)
1677 pdf_name = form.generate_output(instance_file = instance_file)
1678 print "final PDF file is:", pdf_name
1679 #--------------------------------------------------------
1681 pat = gmPersonSearch.ask_for_patient()
1682 if pat is None:
1683 return
1684 gmPerson.set_active_patient(patient = pat)
1685
1686 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1687
1688 path = os.path.abspath(sys.argv[2])
1689 form = cPDFForm(template_file = path)
1690
1691 from Gnumed.wxpython import gmMacro
1692 ph = gmMacro.gmPlaceholderHandler()
1693 ph.debug = True
1694 instance_file = form.substitute_placeholders(data_source = ph)
1695 pdf_name = form.generate_output(instance_file = instance_file)
1696 print "final PDF file is:", pdf_name
1697 #--------------------------------------------------------
1699 pat = gmPersonSearch.ask_for_patient()
1700 if pat is None:
1701 return
1702 gmPerson.set_active_patient(patient = pat)
1703
1704 gmStaff.gmCurrentProvider(provider = gmStaff.cStaff())
1705
1706 path = os.path.abspath(sys.argv[2])
1707 form = cAbiWordForm(template_file = path)
1708
1709 from Gnumed.wxpython import gmMacro
1710 ph = gmMacro.gmPlaceholderHandler()
1711 ph.debug = True
1712 instance_file = form.substitute_placeholders(data_source = ph)
1713 form.edit()
1714 final_name = form.generate_output(instance_file = instance_file)
1715 print "final file is:", final_name
1716 #--------------------------------------------------------
1717 #--------------------------------------------------------
1718 # now run the tests
1719 #test_au()
1720 #test_de()
1721
1722 # OOo
1723 #test_init_ooo()
1724 #test_ooo_connect()
1725 #test_open_ooo_doc_from_srv()
1726 #test_open_ooo_doc_from_letter()
1727 #play_with_ooo()
1728 #test_cOOoLetter()
1729
1730 #test_cFormTemplate()
1731 #set_template_from_file()
1732 #test_latex_form()
1733 #test_pdf_form()
1734 test_abiword_form()
1735
1736 #============================================================
1737
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Thu Feb 9 04:01:20 2012 | http://epydoc.sourceforge.net |