# -*- coding: latin-1 -*-
"""GNUmed forms classes
Business layer for printing all manners of forms, letters, scripts etc.
license: GPL
"""
#============================================================
# $Source: /sources/gnumed/gnumed/gnumed/client/business/gmForms.py,v $
# $Id: gmForms.py,v 1.63 2009/09/13 18:25:54 ncq Exp $
__version__ = "$Revision: 1.63 $"
__author__ ="Ian Haywood
, address@hidden"
import os, sys, time, os.path, logging, libxml2, libxslt
if __name__ == '__main__':
sys.path.insert(0, '../../')
from Gnumed.pycommon import gmTools, gmBorg, gmMatchProvider, gmExceptions, gmDispatcher
from Gnumed.pycommon import gmPG2, gmBusinessDBObject, gmCfg, gmShellAPI
from Gnumed.business import gmPerson, gmSurgery
_log = logging.getLogger('gm.forms')
_log.info(__version__)
engine_ooo = 'O'
engine_names = {
u'O': 'OpenOffice',
u'L': 'LaTeX',
u'X': 'XSLT'
}
#============================================================
def get_form_templates(engine=None, active_only=False):
"""Load form templates."""
if active_only:
query = u"select * from ref.v_paperwork_templates where engine = %(eng)s and in_use is True"
else:
query = u"select * from ref.v_paperwork_templates where engine = %(eng)s order by in_use desc"
rows, idx = gmPG2.run_ro_queries (
queries = [{'cmd': query, 'args': {'eng': engine, 'in_use': active_only}}],
get_col_idx = True
)
templates = [ cFormTemplate(row = {'pk_field': 'pk_paperwork_template', 'data': r, 'idx': idx}) for r in rows ]
return templates
#============================================================
# match providers
#============================================================
class cFormTemplateNameLong_MatchProvider(gmMatchProvider.cMatchProvider_SQL2):
def __init__(self):
query = u"""
select name_long, name_long
from ref.v_paperwork_templates
where name_long %(fragment_condition)s
order by name_long
"""
gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
#============================================================
class cFormTemplateNameShort_MatchProvider(gmMatchProvider.cMatchProvider_SQL2):
def __init__(self):
query = u"""
select name_short, name_short
from ref.v_paperwork_templates
where name_short %(fragment_condition)s
order by name_short
"""
gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
#============================================================
class cFormTemplateType_MatchProvider(gmMatchProvider.cMatchProvider_SQL2):
def __init__(self):
query = u"""
select * from (
select pk, _(name) as l10n_name from ref.form_types
where _(name) %(fragment_condition)s
union
select pk, _(name) as l10n_name from ref.form_types
where name %(fragment_condition)s
) as union_result
order by l10n_name
"""
gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = [query])
#============================================================
class cFormTemplate(gmBusinessDBObject.cBusinessDBObject):
_cmd_fetch_payload = u'select * from ref.v_paperwork_templates where pk_paperwork_template = %s'
_cmds_store_payload = [
u"""update ref.paperwork_templates set
name_short = %(name_short)s,
name_long = %(name_long)s,
fk_template_type = %(pk_template_type)s,
instance_type = %(instance_type)s,
engine = %(engine)s,
in_use = %(in_use)s,
filename = %(filename)s,
external_version = %(external_version)s
where
pk = %(pk_paperwork_template)s and
xmin = %(xmin_paperwork_template)s
""",
u"""select xmin_paperwork_template from ref.v_paperwork_templates where pk_paperwork_template = %(pk_paperwork_template)s"""
]
_updatable_fields = [
u'name_short',
u'name_long',
u'external_version',
u'pk_template_type',
u'instance_type',
u'engine',
u'in_use',
u'filename'
]
_suffix4engine = {
u'O': u'.ott',
u'L': u'.tex',
u'T': u'.txt',
u'X': u'.xslt'
}
#--------------------------------------------------------
def _get_template_data(self):
"""The template itself better not be arbitrarily large unless you can handle that.
Note that the data type returned will be a buffer."""
cmd = u'SELECT data FROM ref.paperwork_templates WHERE pk = %(pk)s'
rows, idx = gmPG2.run_ro_queries (queries = [{'cmd': cmd, 'args': {'pk': self.pk_obj}}], get_col_idx = False)
if len(rows) == 0:
raise gmExceptions.NoSuchBusinessObjectError('cannot retrieve data for template pk = %s' % self.pk_obj)
return rows[0][0]
template_data = property(_get_template_data, lambda x:x)
#--------------------------------------------------------
def export_to_file(self, filename=None, chunksize=0):
"""Export form template from database into file."""
if filename is None:
if self._payload[self._idx['filename']] is None:
suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]]
else:
suffix = os.path.splitext(self._payload[self._idx['filename']].strip())[1].strip()
if suffix in [u'', u'.']:
suffix = self.__class__._suffix4engine[self._payload[self._idx['engine']]]
filename = gmTools.get_unique_filename (
prefix = 'gm-%s-Template-' % self._payload[self._idx['engine']],
suffix = suffix,
tmp_dir = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp'))
)
data_query = {
'cmd': u'SELECT substring(data from %(start)s for %(size)s) FROM ref.paperwork_templates WHERE pk = %(pk)s',
'args': {'pk': self.pk_obj}
}
data_size_query = {
'cmd': u'select octet_length(data) from ref.paperwork_templates where pk = %(pk)s',
'args': {'pk': self.pk_obj}
}
result = gmPG2.bytea2file (
data_query = data_query,
filename = filename,
data_size_query = data_size_query,
chunk_size = chunksize
)
if result is False:
return None
return filename
#--------------------------------------------------------
def update_template_from_file(self, filename=None):
gmPG2.file2bytea (
filename = filename,
query = u'update ref.paperwork_templates set data = %(data)s::bytea where pk = %(pk)s and xmin = %(xmin)s',
args = {'pk': self.pk_obj, 'xmin': self._payload[self._idx['xmin_paperwork_template']]}
)
# adjust for xmin change
self.refetch_payload()
#============================================================
def create_form_template(template_type=None, name_short=None, name_long=None):
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)'
rows, idx = gmPG2.run_rw_queries (
queries = [
{'cmd': cmd, 'args': {'type': template_type, 'nshort': name_short, 'nlong': name_long, 'ext_version': 'new'}},
{'cmd': u"select currval(pg_get_serial_sequence('ref.paperwork_templates', 'pk'))"}
],
return_data = True
)
template = cFormTemplate(aPK_obj = rows[0][0])
return template
#------------------------------------------------------------
def delete_form_template(template=None):
rows, idx = gmPG2.run_rw_queries (
queries = [
{'cmd': u'delete from ref.paperwork_templates where pk=%(pk)s', 'args': {'pk': template['pk_paperwork_template']}}
]
)
return True
#============================================================
# OpenOffice API
#============================================================
uno = None
cOOoDocumentCloseListener = None
def init_ooo():
"""FIXME: consider this:
try:
import uno
except:
print "This Script needs to be run with the python from OpenOffice.org"
print "Example: /opt/OpenOffice.org/program/python %s" % (
os.path.basename(sys.argv[0]))
print "Or you need to insert the right path at the top, where uno.py is."
print "Default: %s" % default_path
"""
global uno
if uno is not None:
return
global unohelper, oooXCloseListener, oooNoConnectException, oooPropertyValue
import uno, unohelper
from com.sun.star.util import XCloseListener as oooXCloseListener
from com.sun.star.connection import NoConnectException as oooNoConnectException
from com.sun.star.beans import PropertyValue as oooPropertyValue
#----------------------------------
class _cOOoDocumentCloseListener(unohelper.Base, oooXCloseListener):
"""Listens for events sent by OOo during the document closing
sequence and notifies the GNUmed client GUI so it can
import the closed document into the database.
"""
def __init__(self, document=None):
self.document = document
def queryClosing(self, evt, owner):
# owner is True/False whether I am the owner of the doc
pass
def notifyClosing(self, evt):
pass
def disposing(self, evt):
self.document.on_disposed_by_ooo()
self.document = None
#----------------------------------
global cOOoDocumentCloseListener
cOOoDocumentCloseListener = _cOOoDocumentCloseListener
_log.debug('python UNO bridge successfully initialized')
#------------------------------------------------------------
class gmOOoConnector(gmBorg.cBorg):
"""This class handles the connection to OOo.
Its Singleton instance stays around once initialized.
"""
# FIXME: need to detect closure of OOo !
def __init__(self):
init_ooo()
self.ooo_start_cmd = 'oowriter -invisible -accept="socket,host=localhost,port=2002;urp;"'
self.resolver_uri = "com.sun.star.bridge.UnoUrlResolver"
self.remote_context_uri = "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext"
self.desktop_uri = "com.sun.star.frame.Desktop"
self.local_context = uno.getComponentContext()
self.uri_resolver = self.local_context.ServiceManager.createInstanceWithContext(self.resolver_uri, self.local_context)
self.__desktop = None
#--------------------------------------------------------
def open_document(self, filename=None):
""" must be absolute"""
# make sure we have a desktop
if self.desktop is None:
return None
document_uri = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename)))
doc = self.desktop.loadComponentFromURL(document_uri, "_blank", 0, ())
return doc
#--------------------------------------------------------
# properties
#--------------------------------------------------------
def _get_desktop(self):
opt_name = 'external.ooo.startup_settle_time'
if self.__desktop is None:
try:
self.remote_context = self.uri_resolver.resolve(self.remote_context_uri)
except oooNoConnectException:
_log.exception('Cannot connect to OOo server.')
_log.error('Trying to start OOo server with: [%s]' % self.ooo_start_cmd)
os.system(self.ooo_start_cmd)
dbcfg = gmCfg.cCfgSQL()
ooo_wait_time = dbcfg.get2 (
option = opt_name,
workplace = gmSurgery.gmCurrentPractice().active_workplace,
bias = 'workplace',
default = 2.0
)
_log.debug('waiting %s seconds for OOo to start up' % ooo_wait_time)
time.sleep(ooo_wait_time) # OOo sometimes needs a bit
try:
self.remote_context = self.uri_resolver.resolve(self.remote_context_uri)
except oooNoConnectException:
_log.exception('Cannot start (or connect to started) OOo server. You may need to increase <%s>.' % opt_name)
return None
self.__desktop = self.remote_context.ServiceManager.createInstanceWithContext(self.desktop_uri, self.remote_context)
return self.__desktop
def _set_desktop(self, desktop):
pass
desktop = property(_get_desktop, _set_desktop)
#------------------------------------------------------------
class cOOoLetter(object):
def __init__(self, template_file=None, instance_type=None):
self.template_file = template_file
self.instance_type = instance_type
self.ooo_doc = None
#--------------------------------------------------------
# external API
#--------------------------------------------------------
def open_in_ooo(self):
# connect to OOo
ooo_srv = gmOOoConnector()
# open doc in OOo
self.ooo_doc = ooo_srv.open_document(filename = self.template_file)
if self.ooo_doc is None:
return False
# listen for close events
pat = gmPerson.gmCurrentPatient()
pat.locked = True
listener = cOOoDocumentCloseListener(document = self)
self.ooo_doc.addCloseListener(listener)
return True
#--------------------------------------------------------
def show(self, visible=True):
self.ooo_doc.CurrentController.Frame.ContainerWindow.setVisible(visible)
#--------------------------------------------------------
def replace_placeholders(self, handler=None, old_style_too = True):
# new style embedded, implicit placeholders
searcher = self.ooo_doc.createSearchDescriptor()
searcher.SearchCaseSensitive = False
searcher.SearchRegularExpression = True
searcher.SearchString = handler.placeholder_regex
placeholder_instance = self.ooo_doc.findFirst(searcher)
while placeholder_instance is not None:
placeholder_instance.String = handler[placeholder_instance.String]
placeholder_instance = self.ooo_doc.findNext(placeholder_instance.End, searcher)
if not old_style_too:
return
# old style "explicit" placeholders
text_fields = self.ooo_doc.getTextFields().createEnumeration()
while text_fields.hasMoreElements():
text_field = text_fields.nextElement()
# placeholder ?
if not text_field.supportsService('com.sun.star.text.TextField.JumpEdit'):
continue
# placeholder of type text ?
if text_field.PlaceHolderType != 0:
continue
replacement = handler[text_field.PlaceHolder]
if replacement is None:
continue
text_field.Anchor.setString(replacement)
#--------------------------------------------------------
def save_in_ooo(self, filename=None):
if filename is not None:
target_url = uno.systemPathToFileUrl(os.path.abspath(os.path.expanduser(filename)))
save_args = (
oooPropertyValue('Overwrite', 0, True, 0),
oooPropertyValue('FormatFilter', 0, 'swriter: StarOffice XML (Writer)', 0)
)
# "store AS url" stores the doc, marks it unmodified and updates
# the internal media descriptor - as opposed to "store TO url"
self.ooo_doc.storeAsURL(target_url, save_args)
else:
self.ooo_doc.store()
#--------------------------------------------------------
def close_in_ooo(self):
self.ooo_doc.dispose()
pat = gmPerson.gmCurrentPatient()
pat.locked = False
self.ooo_doc = None
#--------------------------------------------------------
def on_disposed_by_ooo(self):
# get current file name from OOo, user may have used Save As
filename = uno.fileUrlToSystemPath(self.ooo_doc.URL)
# tell UI to import the file
gmDispatcher.send (
signal = u'import_document_from_file',
filename = filename,
document_type = self.instance_type,
unlock_patient = True
)
self.ooo_doc = None
#--------------------------------------------------------
# internal helpers
#--------------------------------------------------------
#============================================================
class cFormEngine:
"""Ancestor for forms.
No real functionality as yet
Descendants should override class variables country, type, electronic,
date as neccessary
"""
def __init__ (self, pk_def=None, template=None):
self.template = template
self.patient = gmPerson.gmCurrentPatient()
self.workplace = gmSurgery.gmCurrentPractice().active_workplace
def process (self):
"""Merge values into the form template.
Accept a template [format specific to the engine] and
dictionary of parameters [specific to the template] for processing into a form.
Returns a Python file or file-like object representing the
transmittable form of the form.
For paper forms, this should be PostScript data or similar.
"""
pass
def cleanup (self):
"""
A sop to TeX which can't act as a true filter: to delete temporary files
"""
pass
def exe (self, command):
"""
Executes the provided command.
If command cotains %F. it is substituted with the filename
Otherwise, the file is fed in on stdin
"""
pass
#--------------------------------------------------------
def store(self, params=None):
"""Stores the parameters in the backend.
- link_obj can be a cursor, a connection or a service name
- assigning a cursor to link_obj allows the calling code to
group the call to store() into an enclosing transaction
(for an example see gmReferral.send_referral()...)
"""
# some forms may not have values ...
if params is None:
params = {}
patient_clinical = self.patient.get_emr()
encounter = patient_clinical.active_encounter['pk_encounter']
# FIXME: get_active_episode is no more
#episode = patient_clinical.get_active_episode()['pk_episode']
# generate "forever unique" name
cmd = "select name_short || ': <' || name_long || '::' || external_version || '>' from paperwork_templates where pk=%s";
rows = gmPG.run_ro_query('reference', cmd, None, self.pk_def)
form_name = None
if rows is None:
_log.error('error retrieving form def for [%s]' % self.pk_def)
elif len(rows) == 0:
_log.error('no form def for [%s]' % self.pk_def)
else:
form_name = rows[0][0]
# we didn't get a name but want to store the form anyhow
if form_name is None:
form_name=time.time() # hopefully unique enough
# in one transaction
queries = []
# - store form instance in form_instance
cmd = "insert into form_instances(fk_form_def, form_name, fk_episode, fk_encounter) values (%s, %s, %s, %s)"
queries.append((cmd, [self.pk_def, form_name, episode, encounter]))
# - store params in form_data
for key in params.keys():
cmd = """
insert into form_data(fk_instance, place_holder, value)
values ((select currval('form_instances_pk_seq')), %s, %s::text)
"""
queries.append((cmd, [key, params[key]]))
# - get inserted PK
queries.append(("select currval ('form_instances_pk_seq')", []))
status, err = gmPG.run_commit('historica', queries, True)
if status is None:
_log.error('failed to store form [%s] (%s): %s' % (self.pk_def, form_name, err))
return None
return status
#================================================================
# define a class for HTML forms (for printing)
#================================================================
class cXSLTFormEngine(cFormEngine):
"""This class can create XML document from requested data,
then process it with XSLT template and display results
"""
# FIXME: make this configurable ?
_preview_program = u'oowriter ' #this program must be in the system PATH
def __init__ (self, template=None):
if template is None:
raise ValueError(u'%s: cannot create form instance without a template' % __name__)
cFormEngine.__init__(self, template = template)
self._FormData = None
# here we know/can assume that the template was stored as a utf-8 encoded
# string so use that conversion to create unicode:
#self._XSLTData = unicode(str(template.template_data), 'UTF-8')
# but in fact, unicode() knows how to handle buffers, so simply:
self._XSLTData = unicode(self.template.template_data, 'UTF-8', 'strict')
# we must still devise a method of extracting the SQL query:
# - either by retrieving it from a particular tag in the XSLT or
# - by making the stored template actually be a dict which, unpickled,
# has the keys "xslt" and "sql"
self._SQL_query = self.template['sql_query'] #this sql query must output valid xml
#--------------------------------------------------------
# external API
#--------------------------------------------------------
def process(self, sql_parameters):
"""get data from backend and process it with XSLT template to produce readable output"""
rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': self._SQL_query, 'args': sql_parameters}], get_col_idx=True)
__header = '\n'
__body = rows[0][0]
# removed explicit encoding, the __body seems to be already in utf-8, and while encoding it was treated as ascii
#self._XMLData =__header.encode('utf-8') + __body.encode('utf-8')
self._XMLData =__header + __body
# process XML data according to supplied XSLT, producing HTML
styledoc = libxml2.parseDoc(self._XSLTData)
style = libxslt.parseStylesheetDoc(styledoc)
doc = libxml2.parseDoc(self._XMLData)
html = style.applyStylesheet(doc, None)
self._FormData = html.serialize()
style.freeStylesheet()
doc.freeDoc()
html.freeDoc()
#--------------------------------------------------------
def preview(self):
if self._FormData is None:
raise ValueError, u'Preview request for empty form. Make sure the form is properly initialized and process() was performed'
fname = gmTools.get_unique_filename(prefix = u'gm_XSLT_form-', suffix = u'.html')
html_file = os.open(fname, 'wb')
html_file.write(self._FormData.encode('UTF-8'))
html_file.close()
cmd = u'%s %s' % (self.__class__._preview_program, fname)
if not gmShellAPI.run_command_in_shell(command = cmd, blocking = False):
_log.error('%s: cannot launch report preview program' % __name__)
return False
#os.unlink(self.filename) #delete file
#FIXME: under Windows the temp file is deleted before preview program gets it (under Linux it works OK)
return True
#--------------------------------------------------------
def print_directly(self):
#not so fast, look at it first
self.preview()
#=====================================================
#class LaTeXFilter(Cheetah.Filters.Filter):
class LaTeXFilter:
def filter (self, item, table_sep= " \\\\\n", **kwds):
"""
Convience function to escape ISO-Latin-1 strings for TeX output
WARNING: not all ISO-Latin-1 characters are expressible in TeX
FIXME: nevertheless, there are a few more we could support
Also intelligently convert lists and tuples into TeX-style table lines
"""
if type (item) is types.UnicodeType or type (item) is types.StringType:
item = item.replace ("\\", "\\backslash") # I wonder about this, do we want users to be able to use raw TeX?
item = item.replace ("&", "\\&")
item = item.replace ("$", "\\$")
item = item.replace ('"', "") # okay, that's not right, but easiest solution for now
item = item.replace ("\n", "\\\\ ")
if len (item.strip ()) == 0:
item = "\\relax " # sometimes TeX really hates empty strings, this seems to mollify it
# FIXME: cover all of ISO-Latin-1 which can be expressed in TeX
if type (item) is types.UnicodeType:
item = item.encode ('latin-1', 'replace')
trans = {'ß':'\\ss{}', 'ä': '\\"{a}', 'Ä' :'\\"{A}', 'ö': '\\"{o}', 'Ö': '\\"{O}', 'ü': '\\"{u}', 'Ü': '\\"{U}',
'\x8a':'\\v{S}', '\x8a':'\\OE{}', '\x9a':'\\v{s}', '\x9c': '\\oe{}', '\a9f':'\\"{Y}', #Microsloth extensions
'\x86': '{\\dag}', '\x87': '{\\ddag}', '\xa7':'{\\S}', '\xb6': '{\\P}', '\xa9': '{\\copyright}', '\xbf': '?`',
'\xc0':'\\`{A}', '\xa1': "\\'{A}", '\xa2': '\\^{A}', '\xa3':'\\~{A}', '\\xc5': '{\AA}',
'\xc7':'\\c{C}', '\xc8':'\\`{E}',
'\xa1': '!`',
'\xb5':'$\mu$', '\xa3': '\pounds{}', '\xa2':'cent'}
for k, i in trans.items ():
item = item.replace (k, i)
elif type (item) is types.ListType or type (item) is types.TupleType:
item = string.join ([self.filter (i, ' & ') for i in item], table_sep)
elif item is None:
item = '\\relax % Python None\n'
elif type (item) is types.IntType or type (item) is types.FloatType:
item = str (item)
else:
item = str (item)
_log.warning("unknown type %s, string %s" % (type (item), item))
return item
#=====================================================
class cLaTeXForm (cFormEngine):
"""A forms engine wrapping LaTeX.
"""
def __init__ (self, id, template):
self.id = id
self.template = template
def process (self,params={}):
try:
latex = Cheetah.Template.Template (self.template, filter=LaTeXFilter, searchList=[params])
# create a 'sandbox' directory for LaTeX to play in
self.tmp = tempfile.mktemp ()
os.makedirs (self.tmp)
self.oldcwd = os.getcwd ()
os.chdir (self.tmp)
stdin = os.popen ("latex", "w", 2048)
stdin.write (str (latex)) #send text. LaTeX spits it's output into stdout
# FIXME: send LaTeX output to the logger
stdin.close ()
if not gmShellAPI.run_command_in_shell("dvips texput.dvi -o texput.ps", blocking=True):
raise FormError ('DVIPS returned error')
except EnvironmentError, e:
_log.error(e.strerror)
raise FormError (e.strerror)
return file ("texput.ps")
def xdvi (self):
"""
For testing purposes, runs Xdvi on the intermediate TeX output
WARNING: don't try this on Windows
"""
gmShellAPI.run_command_in_shell("xdvi texput.dvi", blocking=True)
def exe (self, command):
if "%F" in command:
command.replace ("%F", "texput.ps")
else:
command = "%s < texput.ps" % command
try:
if not gmShellAPI.run_command_in_shell(command, blocking=True):
_log.error("external command %s returned non-zero" % command)
raise FormError ('external command %s returned error' % command)
except EnvironmentError, e:
_log.error(e.strerror)
raise FormError (e.strerror)
return True
def printout (self):
command, set1 = gmCfg.getDBParam (workplace = self.workplace, option = 'main.comms.print')
self.exe (command)
def cleanup (self):
"""
Delete all the LaTeX output iles
"""
for i in os.listdir ('.'):
os.unlink (i)
os.chdir (self.oldcwd)
os.rmdir (self.tmp)
#===========================================================
class cHL7Form (cFormEngine):
pass
#============================================================
# convenience functions
#------------------------------------------------------------
def get_form(id):
"""
Instantiates a FormEngine based on the form ID or name from the backend
"""
try:
# it's a number: match to form ID
id = int (id)
cmd = 'select template, engine, pk from paperwork_templates where pk = %s'
except ValueError:
# it's a string, match to the form's name
# FIXME: can we somehow OR like this: where name_short=%s OR name_long=%s ?
cmd = 'select template, engine, flags, pk from paperwork_templates where name_short = %s'
result = gmPG.run_ro_query ('reference', cmd, None, id)
if result is None:
_log.error('error getting form [%s]' % id)
raise gmExceptions.FormError ('error getting form [%s]' % id)
if len(result) == 0:
_log.error('no form [%s] found' % id)
raise gmExceptions.FormError ('no such form found [%s]' % id)
if result[0][1] == 'L':
return LaTeXForm (result[0][2], result[0][0])
elif result[0][1] == 'T':
return TextForm (result[0][2], result[0][0])
else:
_log.error('no form engine [%s] for form [%s]' % (result[0][1], id))
raise FormError ('no engine [%s] for form [%s]' % (result[0][1], id))
#-------------------------------------------------------------
class FormError (Exception):
def __init__ (self, value):
self.value = value
def __str__ (self):
return repr (self.value)
#-------------------------------------------------------------
test_letter = """
\\documentclass{letter}
\\address{ $DOCTOR \\\\
$DOCTORADDRESS}
\\signature{$DOCTOR}
\\begin{document}
\\begin{letter}{$RECIPIENTNAME \\\\
$RECIPIENTADDRESS}
\\opening{Dear $RECIPIENTNAME}
\\textbf{Re:} $PATIENTNAME, DOB: $DOB, $PATIENTADDRESS \\\\
$TEXT
\\ifnum$INCLUDEMEDS>0
\\textbf{Medications List}
\\begin{tabular}{lll}
$MEDSLIST
\\end{tabular}
\\fi
\\ifnum$INCLUDEDISEASES>0
\\textbf{Disease List}
\\begin{tabular}{l}
$DISEASELIST
\\end{tabular}
\\fi
\\closing{$CLOSING}
\\end{letter}
\\end{document}
"""
def test_au():
f = open('../../test-area/ian/terry-form.tex')
params = {
'RECIPIENT': "Dr. R. Terry\n1 Main St\nNewcastle",
'DOCTORSNAME': 'Ian Haywood',
'DOCTORSADDRESS': '1 Smith St\nMelbourne',
'PATIENTNAME':'Joe Bloggs',
'PATIENTADDRESS':'18 Fred St\nMelbourne',
'REQUEST':'echocardiogram',
'THERAPY':'on warfarin',
'CLINICALNOTES':"""heard new murmur
Here's some
crap to demonstrate how it can cover multiple lines.""",
'COPYADDRESS':'Karsten Hilbert\nLeipzig, Germany',
'ROUTINE':1,
'URGENT':0,
'FAX':1,
'PHONE':1,
'PENSIONER':1,
'VETERAN':0,
'PADS':0,
'INSTRUCTIONS':u'Take the blue pill, Neo'
}
form = LaTeXForm (1, f.read())
form.process (params)
form.xdvi ()
form.cleanup ()
def test_au2 ():
form = LaTeXForm (2, test_letter)
params = {'RECIPIENTNAME':'Dr. Richard Terry',
'RECIPIENTADDRESS':'1 Main St\nNewcastle',
'DOCTOR':'Dr. Ian Haywood',
'DOCTORADDRESS':'1 Smith St\nMelbourne',
'PATIENTNAME':'Joe Bloggs',
'PATIENTADDRESS':'18 Fred St, Melbourne',
'TEXT':"""This is the main text of the referral letter""",
'DOB':'12/3/65',
'INCLUDEMEDS':1,
'MEDSLIST':[["Amoxycillin", "500mg", "TDS"], ["Perindopril", "4mg", "OD"]],
'INCLUDEDISEASES':0, 'DISEASELIST':'',
'CLOSING':'Yours sincerely,'
}
form.process (params)
print os.getcwd ()
form.xdvi ()
form.cleanup ()
#------------------------------------------------------------
def test_de():
template = open('../../test-area/ian/Formularkopf-DE.tex')
form = LaTeXForm(template=template.read())
params = {
'PATIENT LASTNAME': 'Kirk',
'PATIENT FIRSTNAME': 'James T.',
'PATIENT STREET': 'Hauptstrasse',
'PATIENT ZIP': '02999',
'PATIENT TOWN': 'Gross Saerchen',
'PATIENT DOB': '22.03.1931'
}
form.process(params)
form.xdvi()
form.cleanup()
#============================================================
# main
#------------------------------------------------------------
if __name__ == '__main__':
from Gnumed.pycommon import gmI18N, gmDateTime
gmI18N.activate_locale()
gmI18N.install_domain(domain='gnumed')
gmDateTime.init()
#--------------------------------------------------------
def play_with_ooo():
try:
doc = open_uri_in_ooo(filename=sys.argv[1])
except:
_log.exception('cannot open [%s] in OOo' % sys.argv[1])
raise
class myCloseListener(unohelper.Base, oooXCloseListener):
def disposing(self, evt):
print "disposing:"
def notifyClosing(self, evt):
print "notifyClosing:"
def queryClosing(self, evt, owner):
# owner is True/False whether I am the owner of the doc
print "queryClosing:"
l = myCloseListener()
doc.addCloseListener(l)
tfs = doc.getTextFields().createEnumeration()
print tfs
print dir(tfs)
while tfs.hasMoreElements():
tf = tfs.nextElement()
if tf.supportsService('com.sun.star.text.TextField.JumpEdit'):
print tf.getPropertyValue('PlaceHolder')
print " ", tf.getPropertyValue('Hint')
# doc.close(True) # closes but leaves open the dedicated OOo window
doc.dispose() # closes and disposes of the OOo window
#--------------------------------------------------------
def test_cOOoLetter():
pat = gmPerson.ask_for_patient()
if pat is None:
return
gmPerson.set_active_patient(patient = pat)
doc = cOOoLetter(template_file = sys.argv[1])
doc.open_in_ooo()
doc.replace_placeholders()
doc.save_in_ooo('~/test_cOOoLetter.odt')
doc = None
# doc.close_in_ooo()
raw_input('press to continue')
#--------------------------------------------------------
def test_cFormTemplate():
template = cFormTemplate(aPK_obj = sys.argv[2])
print template
print template.export_to_file()
#--------------------------------------------------------
def set_template_from_file():
template = cFormTemplate(aPK_obj = sys.argv[2])
template.update_template_from_file(filename = sys.argv[3])
#--------------------------------------------------------
if len(sys.argv) > 1 and sys.argv[1] == 'test':
# now run the tests
#test_au()
#test_de()
#play_with_ooo()
#test_cOOoLetter()
test_cFormTemplate()
#set_template_from_file()
#============================================================
# $Log: gmForms.py,v $
# Revision 1.63 2009/09/13 18:25:54 ncq
# - no more get-active-encounter()
#
# Revision 1.62 2009/03/10 14:18:11 ncq
# - support new-style simpler placeholders in OOo docs
#
# Revision 1.61 2009/02/18 13:43:37 ncq
# - get_unique_filename API change
#
# Revision 1.60 2008/09/02 18:59:01 ncq
# - add "invisible" to ooo startup command as suggested by Jerzy
#
# Revision 1.59 2008/08/29 20:54:28 ncq
# - cleanup
#
# Revision 1.58 2008/04/29 18:27:44 ncq
# - cOOoConnector -> gmOOoConnector
#
# Revision 1.57 2008/02/25 17:31:41 ncq
# - logging cleanup
#
# Revision 1.56 2008/01/30 13:34:50 ncq
# - switch to std lib logging
#
# Revision 1.55 2007/11/10 20:49:22 ncq
# - handle failing to connect to OOo much more gracefully
#
# Revision 1.54 2007/10/21 20:12:42 ncq
# - make OOo startup settle time configurable
#
# Revision 1.53 2007/10/07 12:27:08 ncq
# - workplace property now on gmSurgery.gmCurrentPractice() borg
#
# Revision 1.52 2007/09/01 23:31:36 ncq
# - fix form template type phrasewheel query
# - settable of external_version
# - delete_form_template()
#
# Revision 1.51 2007/08/31 23:03:45 ncq
# - improved docs
#
# Revision 1.50 2007/08/31 14:29:52 ncq
# - optionalized UNO import
# - create_form_template()
#
# Revision 1.49 2007/08/29 14:32:25 ncq
# - remove data_modified property
# - adjust to external_version
#
# Revision 1.48 2007/08/20 14:19:48 ncq
# - engine_names
# - match providers
# - fix active_only logic in get_form_templates() and sort properly
# - adjust to renamed database fields
# - cleanup
#
# Revision 1.47 2007/08/15 09:18:07 ncq
# - cleanup
# - cOOoLetter.show()
#
# Revision 1.46 2007/08/13 22:04:32 ncq
# - factor out placeholder handler
# - use view in get_form_templates()
# - add cFormTemplate() and test
# - move export_form_template() to cFormTemplate.export_to_file()
#
# Revision 1.45 2007/08/11 23:44:01 ncq
# - improve document close listener, get_form_templates(), cOOoLetter()
# - better test suite
#
# Revision 1.44 2007/07/22 08:59:19 ncq
# - get_form_templates()
# - export_form_template()
# - absolutize -> os.path.abspath
#
# Revision 1.43 2007/07/13 21:00:55 ncq
# - apply uno.absolutize()
#
# Revision 1.42 2007/07/13 12:08:38 ncq
# - do not touch unknown placeholders unless debugging is on, user might
# want to use them elsewise
# - use close listener
#
# Revision 1.41 2007/07/13 09:15:52 ncq
# - fix faulty imports
#
# Revision 1.40 2007/07/11 21:12:50 ncq
# - gmPlaceholderHandler()
# - OOo API with test suite
#
# Revision 1.39 2007/02/17 14:08:52 ncq
# - gmPerson.gmCurrentProvider.workplace now a property
#
# Revision 1.38 2006/12/23 15:23:11 ncq
# - use gmShellAPI
#
# Revision 1.37 2006/10/25 07:17:40 ncq
# - no more gmPG
# - no more cClinItem
#
# Revision 1.36 2006/05/14 21:44:22 ncq
# - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof
# - remove use of gmWhoAmI.py
#
# Revision 1.35 2006/05/12 12:03:01 ncq
# - whoami -> whereami
#
# Revision 1.34 2006/05/04 09:49:20 ncq
# - get_clinical_record() -> get_emr()
# - adjust to changes in set_active_patient()
# - need explicit set_active_patient() after ask_for_patient() if wanted
#
# Revision 1.33 2005/12/31 18:01:54 ncq
# - spelling of GNUmed
# - clean up imports
#
# Revision 1.32 2005/11/06 12:31:30 ihaywood
# I've discovered that most of what I'm trying to do with forms involves
# re-implementing Cheetah (www.cheetahtemplate.org), so switch to using this.
#
# If this new dependency annoys you, don't import the module: it's not yet
# used for any end-user functionality.
#
# Revision 1.31 2005/04/03 20:06:51 ncq
# - comment on emr.get_active_episode being no more
#
# Revision 1.30 2005/03/06 08:17:02 ihaywood
# forms: back to the old way, with support for LaTeX tables
#
# business objects now support generic linked tables, demographics
# uses them to the same functionality as before (loading, no saving)
# They may have no use outside of demographics, but saves much code already.
#
# Revision 1.29 2005/02/03 20:17:18 ncq
# - get_demographic_record() -> get_identity()
#
# Revision 1.28 2005/02/01 10:16:07 ihaywood
# refactoring of gmDemographicRecord and follow-on changes as discussed.
#
# gmTopPanel moves to gmHorstSpace
# gmRichardSpace added -- example code at present, haven't even run it myself
# (waiting on some icon .pngs from Richard)
#
# Revision 1.27 2005/01/31 10:37:26 ncq
# - gmPatient.py -> gmPerson.py
#
# Revision 1.26 2004/08/20 13:19:06 ncq
# - use getDBParam()
#
# Revision 1.25 2004/07/19 11:50:42 ncq
# - cfg: what used to be called "machine" really is "workplace", so fix
#
# Revision 1.24 2004/06/28 12:18:52 ncq
# - more id_* -> fk_*
#
# Revision 1.23 2004/06/26 07:33:55 ncq
# - id_episode -> fk/pk_episode
#
# Revision 1.22 2004/06/18 13:32:37 ncq
# - just some whitespace cleanup
#
# Revision 1.21 2004/06/17 11:36:13 ihaywood
# Changes to the forms layer.
# Now forms can have arbitrary Python expressions embedded in @..@ markup.
# A proper forms HOWTO will appear in the wiki soon
#
# Revision 1.20 2004/06/08 00:56:39 ncq
# - even if we don't need parameters we need to pass an
# empty param list to gmPG.run_commit()
#
# Revision 1.19 2004/06/05 12:41:39 ihaywood
# some more comments for gmForms.py
# minor change to gmReferral.py: print last so bugs don't waste toner ;-)
#
# Revision 1.18 2004/05/28 13:13:15 ncq
# - move currval() inside transaction in gmForm.store()
#
# Revision 1.17 2004/05/27 13:40:21 ihaywood
# more work on referrals, still not there yet
#
# Revision 1.16 2004/04/21 22:26:48 ncq
# - it is form_data.place_holder, not placeholder
#
# Revision 1.15 2004/04/21 22:05:28 ncq
# - better error reporting
#
# Revision 1.14 2004/04/21 22:01:15 ncq
# - generic store() for storing instance in form_data/form_instances
#
# Revision 1.13 2004/04/18 08:39:57 ihaywood
# new config options
#
# Revision 1.12 2004/04/11 10:15:56 ncq
# - load title in get_names() and use it superceding getFullName
#
# Revision 1.11 2004/04/10 01:48:31 ihaywood
# can generate referral letters, output to xdvi at present
#
# Revision 1.10 2004/03/12 15:23:36 ncq
# - cleanup, test_de
#
# Revision 1.9 2004/03/12 13:20:29 ncq
# - remove unneeded import
# - log keyword
#