""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2007 Nathanael C. Fritz This file is part of SleekXMPP. SleekXMPP is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. SleekXMPP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SleekXMPP; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ from . import base import logging from xml.etree import cElementTree as ET import copy #TODO support item groups and results class xep_0004(base.base_plugin): def plugin_init(self): self.xep = '0004' self.description = 'Data Forms' self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform) def post_init(self): self.xmpp['xep_0030'].add_feature('jabber:x:data') def handler_message_xform(self, xml): object = self.handle_form(xml) self.xmpp.event("message_form", object) def handler_presence_xform(self, xml): object = self.handle_form(xml) self.xmpp.event("presence_form", object) def handle_form(self, xml): xmlform = xml.find('{jabber:x:data}x') object = self.buildForm(xmlform) self.xmpp.event("message_xform", object) return object def buildForm(self, xml): form = Form(ftype=xml.attrib['type']) form.fromXML(xml) return form def makeForm(self, ftype='form', title='', instructions=''): return Form(self.xmpp, ftype, title, instructions) class FieldContainer(object): def __init__(self, stanza = 'form'): self.fields = [] self.field = {} self.stanza = stanza def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): self.field[var] = FormField(var, ftype, label, desc, required, value) self.fields.append(self.field[var]) return self.field[var] def buildField(self, xml): self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) self.fields.append(self.field[xml.get('var', '__unnamed__')]) self.field[xml.get('var', '__unnamed__')].buildField(xml) def buildContainer(self, xml): self.stanza = xml.tag for field in xml.findall('{jabber:x:data}field'): self.buildField(field) def getXML(self, ftype): container = ET.Element(self.stanza) for field in self.fields: container.append(field.getXML(ftype)) return container class Form(FieldContainer): types = ('form', 'submit', 'cancel', 'result') def __init__(self, xmpp=None, ftype='form', title='', instructions=''): if not ftype in self.types: raise ValueError("Invalid Form Type") FieldContainer.__init__(self) self.xmpp = xmpp self.type = ftype self.title = title self.instructions = instructions self.reported = [] self.items = [] def merge(self, form2): form1 = Form(ftype=self.type) form1.fromXML(self.getXML(self.type)) for field in form2.fields: if not field.var in form1.field: form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) else: form1.field[field.var].value = field.value for option, label in field.options: if (option, label) not in form1.field[field.var].options: form1.fields[field.var].addOption(option, label) return form1 def copy(self): newform = Form(ftype=self.type) newform.fromXML(self.getXML(self.type)) return newform def update(self, form): values = form.getValues() for var in values: if var in self.fields: self.fields[var].setValue(self.fields[var]) def getValues(self): result = {} for field in self.fields: value = field.value if len(value) == 1: value = value[0] result[field.var] = value return result def setValues(self, values={}): for field in values: if field in self.field: if isinstance(values[field], list) or isinstance(values[field], tuple): for value in values[field]: self.field[field].setValue(value) else: self.field[field].setValue(values[field]) def fromXML(self, xml): self.buildForm(xml) def addItem(self): newitem = FieldContainer('item') self.items.append(newitem) return newitem def buildItem(self, xml): newitem = self.addItem() newitem.buildContainer(xml) def addReported(self): reported = FieldContainer('reported') self.reported.append(reported) return reported def buildReported(self, xml): reported = self.addReported() reported.buildContainer(xml) def setTitle(self, title): self.title = title def setInstructions(self, instructions): self.instructions = instructions def setType(self, ftype): self.type = ftype def getXMLMessage(self, to): msg = self.xmpp.makeMessage(to) msg.append(self.getXML()) return msg def buildForm(self, xml): self.type = xml.get('type', 'form') if xml.find('{jabber:x:data}title') is not None: self.setTitle(xml.find('{jabber:x:data}title').text) if xml.find('{jabber:x:data}instructions') is not None: self.setInstructions(xml.find('{jabber:x:data}instructions').text) for field in xml.findall('{jabber:x:data}field'): self.buildField(field) for reported in xml.findall('{jabber:x:data}reported'): self.buildReported(reported) for item in xml.findall('{jabber:x:data}item'): self.buildItem(item) #def getXML(self, tostring = False): def getXML(self, ftype=None): logging.debug("creating form as %s" % ftype) if ftype: self.type = ftype form = ET.Element('{jabber:x:data}x') form.attrib['type'] = self.type if self.title and self.type in ('form', 'result'): title = ET.Element('{jabber:x:data}title') title.text = self.title form.append(title) if self.instructions and self.type == 'form': instructions = ET.Element('{jabber:x:data}instructions') instructions.text = self.instructions form.append(instructions) for field in self.fields: form.append(field.getXML(self.type)) for reported in self.reported: form.append(reported.getXML('{jabber:x:data}reported')) for item in self.items: form.append(item.getXML(self.type)) #if tostring: # form = self.xmpp.tostring(form) return form def getXHTML(self): form = ET.Element('{http://www.w3.org/1999/xhtml}form') if self.title: title = ET.Element('h2') title.text = self.title form.append(title) if self.instructions: instructions = ET.Element('p') instructions.text = self.instructions form.append(instructions) for field in self.fields: form.append(field.getXHTML()) for field in self.reported: form.append(field.getXHTML()) for field in self.items: form.append(field.getXHTML()) return form def makeSubmit(self): self.setType('submit') class FormField(object): types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single') listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single') lbtypes = ('fixed', 'text-multi') def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None): if not ftype in self.types: raise ValueError("Invalid Field Type") self.type = ftype self.var = var self.label = label self.desc = desc self.options = [] self.required = False self.value = [] if self.type in self.listtypes: self.islist = True else: self.islist = False if self.type in self.lbtypes: self.islinebreak = True else: self.islinebreak = False if value: self.setValue(value) def addOption(self, value, label): if self.islist: self.options.append((value, label)) else: raise ValueError("Cannot add options to non-list type field.") def setTrue(self): if self.type == 'boolean': self.value = [True] def setFalse(self): if self.type == 'boolean': self.value = [False] def require(self): self.required = True def setDescription(self, desc): self.desc = desc def setValue(self, value): if self.type == 'boolean': if value in ('1', 1, True, 'true', 'True', 'yes'): value = True else: value = False if self.islinebreak and value is not None: self.value += value.split('\n') else: if len(self.value) and (not self.islist or self.type == 'list-single'): self.value = [value] else: self.value.append(value) def delValue(self, value): if type(self.value) == type([]): try: idx = self.value.index(value) if idx != -1: self.value.pop(idx) except ValueError: pass else: self.value = '' def setAnswer(self, value): self.setValue(value) def buildField(self, xml): self.type = xml.get('type', 'text-single') self.label = xml.get('label', '') for option in xml.findall('{jabber:x:data}option'): self.addOption(option.find('{jabber:x:data}value').text, option.get('label', '')) for value in xml.findall('{jabber:x:data}value'): self.setValue(value.text) if xml.find('{jabber:x:data}required') is not None: self.require() if xml.find('{jabber:x:data}desc') is not None: self.setDescription(xml.find('{jabber:x:data}desc').text) def getXML(self, ftype): field = ET.Element('{jabber:x:data}field') if ftype != 'result': field.attrib['type'] = self.type if self.type != 'fixed': if self.var: field.attrib['var'] = self.var if self.label: field.attrib['label'] = self.label if ftype == 'form': for option in self.options: optionxml = ET.Element('{jabber:x:data}option') optionxml.attrib['label'] = option[1] optionval = ET.Element('{jabber:x:data}value') optionval.text = option[0] optionxml.append(optionval) field.append(optionxml) if self.required: required = ET.Element('{jabber:x:data}required') field.append(required) if self.desc: desc = ET.Element('{jabber:x:data}desc') desc.text = self.desc field.append(desc) for value in self.value: valuexml = ET.Element('{jabber:x:data}value') if value is True or value is False: if value: valuexml.text = '1' else: valuexml.text = '0' else: valuexml.text = value field.append(valuexml) return field def getXHTML(self): field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) if self.label: label = ET.Element('p') label.text = "%s: " % self.label else: label = ET.Element('p') label.text = "%s: " % self.var field.append(label) if self.type == 'boolean': formf = ET.Element('input', {'type': 'checkbox', 'name': self.var}) if len(self.value) and self.value[0] in (True, 'true', '1'): formf.attrib['checked'] = 'checked' elif self.type == 'fixed': formf = ET.Element('p') try: formf.text = ', '.join(self.value) except: pass field.append(formf) formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) try: formf.text = ', '.join(self.value) except: pass elif self.type == 'hidden': formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) try: formf.text = ', '.join(self.value) except: pass elif self.type in ('jid-multi', 'list-multi'): formf = ET.Element('select', {'name': self.var}) for option in self.options: optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'}) optf.text = option[1] if option[1] in self.value: optf.attrib['selected'] = 'selected' formf.append(option) elif self.type in ('jid-single', 'text-single'): formf = ET.Element('input', {'type': 'text', 'name': self.var}) try: formf.attrib['value'] = ', '.join(self.value) except: pass elif self.type == 'list-single': formf = ET.Element('select', {'name': self.var}) for option in self.options: optf = ET.Element('option', {'value': option[0]}) optf.text = option[1] if not optf.text: optf.text = option[0] if option[1] in self.value: optf.attrib['selected'] = 'selected' formf.append(optf) elif self.type == 'text-multi': formf = ET.Element('textarea', {'name': self.var}) try: formf.text = ', '.join(self.value) except: pass if not formf.text: formf.text = ' ' elif self.type == 'text-private': formf = ET.Element('input', {'type': 'password', 'name': self.var}) try: formf.attrib['value'] = ', '.join(self.value) except: pass label.append(formf) return field