Mercurial > libervia-backend
diff src/tools/xml_tools.py @ 802:9007bb133009
core, frontends: XMLUI refactoring:
- XMLUI now use objects with 2 main classes: widgets (button, label, etc), and container which contain widgets according to a layout
- widgets and containers classes are found through introspection, thereby it's really easy to add a new one
- there is still the AddWidgetName helper, for example AddText('jid', 'test@example.net') will add a StringWidget with name "jid" and default value "test@example.net"
- container can be inside other containers. changeContainer change the first parent container
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 04 Feb 2014 18:19:00 +0100 |
parents | e0770d977d58 |
children | f100fd8d279f |
line wrap: on
line diff
--- a/src/tools/xml_tools.py Tue Feb 04 18:06:12 2014 +0100 +++ b/src/tools/xml_tools.py Tue Feb 04 18:19:00 2014 +0100 @@ -29,9 +29,47 @@ SAT_FORM_PREFIX = "SAT_FORM_" SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names + +# Helper functions + +def _dataFormField2XMLUIData(field): + """ Get data needed to create an XMLUI's Widget from Wokkel's data_form's Field + @param field: data_form.Field (it uses field.value, field.fieldType, field.label and field.var) + @return: widget_type, widget_args, widget_kwargs + + """ + widget_args = [field.value] + widget_kwargs = {} + if field.fieldType == 'fixed' or field.fieldType is None: + widget_type = 'text' + elif field.fieldType == 'text-single': + widget_type = "string" + elif field.fieldType == 'text-private': + widget_type = "password" + elif field.fieldType == 'boolean': + widget_type = "bool" + if widget_args[0] is None: + widget_args[0] = 'false' + elif field.fieldType == 'list-single': + widget_type = "list" + del widget_args[0] + widget_kwargs["options"] = [option.value for option in field.options] + else: + error(u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType) + widget_type = "string" + + if field.var: + widget_kwargs["name"] = field.var + + return widget_type, widget_args, widget_kwargs + + def dataForm2XMLUI(form, submit_id, session_id=None): - """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml""" + """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT XML + @param submit_id: callback id to call when submitting form + @param session_id: id to return with the data + """ form_ui = XMLUI("form", "vertical", submit_id=submit_id, session_id=session_id) if form.instructions: @@ -40,32 +78,18 @@ labels = [field for field in form.fieldList if field.label] if labels: # if there is no label, we don't need to use pairs - form_ui.changeLayout("pairs") + form_ui.changeContainer("pairs") for field in form.fieldList: - if field.fieldType == 'fixed': - __field_type = 'text' - elif field.fieldType == 'text-single': - __field_type = "string" - elif field.fieldType == 'text-private': - __field_type = "password" - elif field.fieldType == 'boolean': - __field_type = "bool" - if field.value is None: - field.value = "false" - elif field.fieldType == 'list-single': - __field_type = "list" - else: - error(u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType) - __field_type = "string" - + widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(field) if labels: if field.label: form_ui.addLabel(field.label) else: form_ui.addEmpty() - form_ui.addElement(__field_type, field.var, field.value, [option.value for option in field.options]) + form_ui.addWidget(widget_type, *widget_args, **widget_kwargs) + return form_ui def dataFormResult2AdvancedList(form_ui, form_xml): @@ -75,8 +99,7 @@ @param form_xml: domish.Element of the data form @return: AdvancedList element """ - headers = [] - items = [] + headers = {} try: reported_elt = form_xml.elements('jabber:x:data', 'reported').next() except StopIteration: @@ -87,30 +110,28 @@ raise exceptions.DataError("Unexpected tag") name = elt["var"] label = elt.attributes.get('label','') - type_ = elt.attributes.get('type','') # TODO - headers.append(Header(name, label)) + type_ = elt.attributes.get('type') + headers[name] = (label, type_) if not headers: raise exceptions.DataError("No reported fields (see XEP-0004 §3.4)") + adv_list = AdvancedListContainer(form_ui, headers=headers, columns=len(headers), parent=form_ui.current_container) + form_ui.changeContainer(adv_list) + item_elts = form_xml.elements('jabber:x:data', 'item') for item_elt in item_elts: - fields = [] for elt in item_elt.elements(): if elt.name != 'field': warning("Unexpected tag (%s)" % elt.name) continue - name = elt['var'] - child_elt = elt.firstChildElement() - if child_elt.name != "value": - raise exceptions.DataError('Was expecting <value> tag') - value = unicode(child_elt) - fields.append(Field(name, value)) - items.append(Item(' | '.join((field.value for field in fields if field)), fields)) + field = data_form.Field.fromElement(elt) - return form_ui.addAdvancedList(None, headers, items) + widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(field) + form_ui.addWidget(widget_type, *widget_args, **widget_kwargs) + return form_ui def dataFormResult2XMLUI(form_xml, session_id=None): """Take a raw data form (not parsed by XEP-0004) and convert it to a SàT XMLUI @@ -147,41 +168,57 @@ return form -def paramsXml2xmlUI(xml): +def paramsXML2XMLUI(xml): """Convert the xml for parameter to a SàT XML User Interface""" params_doc = minidom.parseString(xml.encode('utf-8')) top = params_doc.documentElement if top.nodeName != 'params': - error(_('INTERNAL ERROR: parameters xml not valid')) - assert(False) + raise exceptions.DataError(_('INTERNAL ERROR: parameters xml not valid')) + param_ui = XMLUI("param", "tabs") + tabs_cont = param_ui.current_container + for category in top.getElementsByTagName("category"): category_name = category.getAttribute('name') label = category.getAttribute('label') if not category_name: - error(_('INTERNAL ERROR: params categories must have a name')) - assert(False) - param_ui.addCategory(category_name, 'pairs', label=label) + raise exceptions.DataError(_('INTERNAL ERROR: params categories must have a name')) + tabs_cont.addTab(category_name, label=label, container=PairsContainer) for param in category.getElementsByTagName("param"): + widget_kwargs = {} + param_name = param.getAttribute('name') - label = param.getAttribute('label') + param_label = param.getAttribute('label') if not param_name: - error(_('INTERNAL ERROR: params must have a name')) - assert(False) + raise exceptions.DataError(_('INTERNAL ERROR: params must have a name')) + type_ = param.getAttribute('type') value = param.getAttribute('value') or None - options = getOptions(param) callback_id = param.getAttribute('callback_id') or None + + if type_ == 'list': + options = _getParamListOptions(param) + widget_kwargs['options'] = options + if type_ == "button": param_ui.addEmpty() else: - param_ui.addLabel(label or param_name) - param_ui.addElement(name="%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name), type_=type_, value=value, options=options, callback_id=callback_id) + param_ui.addLabel(param_label or param_name) + + if value: + widget_kwargs["value"] = value + + if callback_id: + widget_kwargs['callback_id'] = callback_id + + widget_kwargs['name'] = "%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name) + + param_ui.addWidget(type_, **widget_kwargs) return param_ui.toXml() -def getOptions(param): +def _getParamListOptions(param): """Retrieve the options for list element. Allow listing the <option/> tags directly in <param/> or in an intermediate <options/> tag.""" elems = param.getElementsByTagName("options") @@ -194,60 +231,349 @@ return [elem.getAttribute("value") for elem in elems] -class Header(object): - """AdvandeList's header""" +## XMLUI Elements + + +class Element(object): + """ Base XMLUI element """ + type = None + + def __init__(self, xmlui, parent=None): + """Create a container element + @param xmlui: XMLUI instance + @parent: parent element + """ + assert(self.type) is not None + if not hasattr(self, 'elem'): + self.elem = parent.xmlui.doc.createElement(self.type) + self.xmlui = xmlui + if parent is not None: + parent.append(self) + else: + self.parent = parent + + def append(self, child): + self.elem.appendChild(child.elem) + child.parent = self + + +class TopElement(Element): + """ Main XML Element """ + type = 'top' + + def __init__(self, xmlui): + self.elem = xmlui.doc.documentElement + super(TopElement, self).__init__(xmlui) + + +class TabElement(Element): + """ Used by TabsContainer to give name and label to tabs """ + type = 'tab' - def __init__(self, field_name, label, description=None, type_=None): + def __init__(self, parent, name, label): + if not isinstance(parent, TabsContainer): + raise exceptions.DataError(_("TabElement must be a child of TabsContainer")) + super(TabElement, self).__init__(parent.xmlui, parent) + self.elem.setAttribute('name', name) + self.elem.setAttribute('label', label) + + +class FieldBackElement(Element): + """ Used by ButtonWidget to indicate which field have to be sent back """ + type = 'field_back' + + def __init__(self, parent, name): + assert(isinstance(parent, ButtonWidget)) + super(FieldBackElement, self).__init__(parent.xmlui, parent) + self.elem.setAttribute('name', name) + + +class OptionElement(Element): + """" Used by ListWidget to specify options """ + type = 'option' + + def __init__(self, parent, option): + assert(isinstance(parent, ListWidget)) + super(OptionElement, self).__init__(parent.xmlui, parent) + if isinstance(option, basestring): + value, label = option, option + elif isinstance(option, tuple): + value, label = option + self.elem.setAttribute('value', value) + self.elem.setAttribute('label', label) + + +class RowElement(Element): + """" Used by AdvancedListContainer """ + type = 'row' + + def __init__(self, parent): + assert(isinstance(parent, AdvancedListContainer)) + super(RowElement, self).__init__(parent.xmlui, parent) + + +class HeaderElement(Element): + """" Used by AdvancedListContainer """ + type = 'header' + + def __init__(self, parent, name=None, Label=None, description=None): """ - @param field_name: name of the field referenced + @param parent: AdvancedListContainer instance + @param name: name of the container @param label: label to be displayed in columns @param description: long descriptive text - @param type_: TODO + + """ + assert(isinstance(parent, AdvancedListContainer)) + super(HeaderElement, self).__init__(parent.xmlui, parent) + if name: + field_elt.setAttribute('name', name) + if label: + field_elt.setAttribute('label', label) + if description: + field_elt.setAttribute('description', description) + + +class Container(Element): + """ And Element which contains other ones and has a layout """ + type = None + + def __init__(self, xmlui, parent=None): + """Create a container element + @param xmlui: XMLUI instance + @parent: parent element or None + """ + self.elem = xmlui.doc.createElement('container') + super(Container, self).__init__(xmlui, parent) + self.elem.setAttribute('type', self.type) + + def getParentContainer(self): + """ Return first parent container + @return: parent container or None """ - self.field_name = field_name - self.label = label - self.description = description - self.type = type_ - if type_ is not None: - raise NotImplementedError # TODO: + current = self.parent + while(not isinstance(current, (Container)) and + current is not None): + current = current.parent + return current + +class VerticalContainer(Container): + type = "vertical" + + +class HorizontalContainer(Container): + type = "horizontal" + + +class PairsContainer(Container): + type = "pairs" + + +class TabsContainer(Container): + type = "tabs" + + def addTab(self, name, label=None, container=VerticalContainer): + """Add a tab""" + if not label: + label = name + tab_elt = TabElement(self, name, label) + new_container = container(self.xmlui, tab_elt) + self.xmlui.changeContainer(new_container) + + def end(self): + """ Called when we have finished tabs + change current container to first container parent + + """ + parent_container = self.getParentContainer() + self.xmlui.changeContainer(parent_container) -class Item(object): - """Item used in AdvancedList""" +class AdvancedListContainer(Container): + type = "advanced_list" - def __init__(self, text=None, fields=None): + def __init__(self, xmlui, name=None, headers=None, items=None, columns=None, parent=None): + """Create an advanced list + @param headers: optional headers informations + @param items: list of Item instances + @return: created element """ - @param text: Optional textual representation, when fields are not showed individually - @param fields: list of Field instances² + if not items and columns is None: + raise DataError(_("either items or columns need do be filled")) + if headers is None: + headers = [] + if items is None: + items = [] + super(AdvancedListContainer, self).__init__(xmlui, parent) + if columns is None: + columns = len(items[0]) + self._columns = columns + self._current_column = 0 + self.current_row = None + if headers: + if len(headers) != self._columns: + raise exceptions.DataError(_("Headers lenght doesn't correspond to columns")) + self.addHeaders(headers) + if items: + self.addItems(items) + + def addHeaders(self, headers): + for header in headers: + self.addHeader(header) + + def addHeader(self, header): + pass # TODO + + def addItems(self, items): + for item in items: + self.addItem(item) + + def addItem(self, item): + if self._current_column % self._columns == 0: + self.current_row = RowElement(self) + self.current_row.append(item) + + def end(self): + """ Called when we have finished list + change current container to first container parent + """ - self.text = text - self.fields = fields if fields is not None else [] + if self._current_colum % self._columns != 0: + raise exceptions.DataError(_("Incorrect number of items in list")) + parent_container = self.getParentContainer() + self.xmlui.changeContainer(parent_container) + + +class Widget(Element): + type = None + + def __init__(self, xmlui, name=None, parent=None): + """Create an element + @param xmlui: XMLUI instance + @param name: name of the element or None + @param parent: parent element or None + """ + self.elem = xmlui.doc.createElement('widget') + super(Widget, self).__init__(xmlui, parent) + if name: + self.elem.setAttribute('name', name) + self.elem.setAttribute('type', self.type) + + +class InputWidget(Widget): + pass + + +class EmptyWidget(Widget): + type = 'empty' -class Field(object): - """Field used in AdvancedList (in items)""" +class TextWidget(Widget): + type = 'text' + + def __init__(self, xmlui, text, name=None, parent=None): + super(TextWidget, self).__init__(xmlui, name, parent) + text = self.xmlui.doc.createTextNode(text) + self.elem.appendChild(text) + + +class LabelWidget(Widget): + type='label' + + def __init__(self, xmlui, label, name=None, parent=None): + super(LabelWidget, self).__init__(xmlui, name, parent) + self.elem.setAttribute('value', label) + + +class StringWidget(InputWidget): + type = 'string' + + def __init__(self, xmlui, value=None, name=None, parent=None): + super(StringWidget, self).__init__(xmlui, name, parent) + if value: + self.elem.setAttribute('value', value) + + +class PasswordWidget(StringWidget): + type = 'password' - def __init__(self, name, value): + +class TextBoxWidget(StringWidget): + type = 'textbox' + + +class BoolWidget(InputWidget): + type = 'bool' + + def __init__(self, xmlui, value='false', name=None, parent=None): + if value == '0': + value='false' + elif value == '1': + value='true' + if not value in ('true', 'false'): + raise exceptions.DataError(_("Value must be 0, 1, false or true")) + super(BoolWidget, self).__init__(xmlui, name, parent) + self.elem.setAttribute('value', value) + + +class ButtonWidget(InputWidget): + type = 'button' + + def __init__(self, xmlui, callback_id, value=None, fields_back=None, name=None, parent=None): + """Add a button + @param callback_id: callback which will be called if button is pressed + @param value: label of the button + @fields_back: list of names of field to give back when pushing the button + @param name: name + @param parent: parent container """ - @param name: name of the field, used to identify the field in headers - @param value: actual content of the field - """ - self.name = name - self.value = value + if fields_back is None: + fields_back = [] + super(ButtonWidget, self).__init__(xmlui, name, parent) + self.elem.setAttribute('callback', callback_id) + if value: + self.elem.setAttribute('value', value) + for field in fields_back: + fback_el = FieldBackElement(self, field) + + +class ListWidget(InputWidget): + type = 'list' + def __init__(self, xmlui, options, value=None, style=None, name=None, parent=None): + if style is None: + style = set() + styles = set(style) + assert options + if not styles.issubset(['multi']): + raise exceptions.DataError(_("invalid styles")) + super(ListWidget, self).__init__(xmlui, name, parent) + self.addOptions(options) + if value: + self.elem.setAttribute('value', value) + for style in styles: + self.elem.setAttribute(style, 'yes') + + def addOptions(self, options): + """i Add options to a multi-values element (e.g. list) """ + for option in options: + OptionElement(self, option) + + +## XMLUI main class class XMLUI(object): """This class is used to create a user interface (form/window/parameters/etc) using SàT XML""" - def __init__(self, panel_type, layout="vertical", title=None, submit_id=None, session_id=None): + def __init__(self, panel_type, container="vertical", title=None, submit_id=None, session_id=None): """Init SàT XML Panel @param panel_type: one of - window (new window) - form (formulaire, depend of the frontend, usually a panel with cancel/submit buttons) - - param (parameters, presentatio depend of the frontend) - @param layout: disposition of elements, one of: + - param (parameters, presentation depend of the frontend) + @param container: disposition of elements, one of: - vertical: elements are disposed up to bottom - horizontal: elements are disposed left to right - pairs: elements come on two aligned columns @@ -256,11 +582,14 @@ @param title: title or default if None @param submit_id: callback id to call for panel_type we can submit (form, param) """ + self._introspect() if panel_type not in ['window', 'form', 'param']: raise exceptions.DataError(_("Unknown panel type [%s]") % panel_type) if panel_type == 'form' and submit_id is None: raise exceptions.DataError(_("form XMLUI need a submit_id")) - self.type_ = panel_type + if not isinstance(container, basestring): + raise exceptions.DataError(_("container argument must be a string")) + self.type = panel_type impl = minidom.getDOMImplementation() self.doc = impl.createDocument(None, "sat_xmlui", None) @@ -270,14 +599,45 @@ top_element.setAttribute("title", title) self.submit_id = submit_id self.session_id = session_id - self.parentTabsLayout = None # used only we have 'tabs' layout - self.currentCategory = None # used only we have 'tabs' layout - self.currentLayout = None - self.changeLayout(layout) + self.main_container = self._createContainer(container, TopElement(self)) + self.current_container = self.main_container + + def _introspect(self): + """ Introspect module to find Widgets and Containers """ + self._containers = {} + self._widgets = {} + for obj in globals().values(): + try: + if issubclass(obj, Widget): + if obj.__name__ == 'Widget': + continue + self._widgets[obj.type] = obj + elif issubclass(obj, Container): + if obj.__name__ == 'Container': + continue + self._containers[obj.type] = obj + except TypeError: + pass def __del__(self): self.doc.unlink() + def __getattr__(self, name): + if name.startswith("add") and name not in ('addWidget',): # addWidgetName(...) create an instance of WidgetName + class_name = name[3:]+"Widget" + if class_name in globals(): + cls = globals()[class_name] + if issubclass(cls, Widget): + def createWidget(*args, **kwargs): + if "parent" not in kwargs: + kwargs["parent"] = self.current_container + if "name" not in kwargs and issubclass(cls, InputWidget): # name can be given as first argument or in keyword arguments for InputWidgets + args = list(args) + kwargs["name"] = args.pop(0) + return cls(self, *args, **kwargs) + return createWidget + return object.__getattribute__(self, name) + @property def submit_id(self): top_element = self.doc.documentElement @@ -314,242 +674,45 @@ else: raise exceptions.DataError("session_id can't be empty") - def _createLayout(self, layout, parent=None): - """Create a layout element - @param type: layout type (cf init doc) + def _createContainer(self, container, parent=None, **kwargs): + """Create a container element + @param type: container type (cf init doc) @parent: parent element or None """ - if not layout in ['vertical', 'horizontal', 'pairs', 'tabs']: - error(_("Unknown layout type [%s]") % layout) - assert False - layout_elt = self.doc.createElement('layout') - layout_elt.setAttribute('type', layout) - if parent is not None: - parent.appendChild(layout_elt) - return layout_elt - - def _createElem(self, type_, name=None, parent=None): - """Create an element - @param type_: one of - - empty: empty element (usefull to skip something in a layout, e.g. skip first element in a PAIRS layout) - - text: text to be displayed in an multi-line area, e.g. instructions - @param name: name of the element or None - @param parent: parent element or None - @return: created element - """ - elem = self.doc.createElement('elem') - if name: - elem.setAttribute('name', name) - elem.setAttribute('type', type_) - if parent is not None: - parent.appendChild(elem) - return elem - - def changeLayout(self, layout): - """Change the current layout""" - self.currentLayout = self._createLayout(layout, self.currentCategory if self.currentCategory else self.doc.documentElement) - if layout == "tabs": - self.parentTabsLayout = self.currentLayout - - def addEmpty(self, name=None): - """Add a multi-lines text""" - return self._createElem('empty', name, self.currentLayout) - - def addText(self, text, name=None): - """Add a multi-lines text""" - elem = self._createElem('text', name, self.currentLayout) - text = self.doc.createTextNode(text) - elem.appendChild(text) - return elem - - def addLabel(self, text, name=None): - """Add a single line text, mainly useful as label before element""" - elem = self._createElem('label', name, self.currentLayout) - elem.setAttribute('value', text) - return elem - - def addString(self, name=None, value=None): - """Add a string box""" - elem = self._createElem('string', name, self.currentLayout) - if value: - elem.setAttribute('value', value) - return elem - - def addPassword(self, name=None, value=None): - """Add a password box""" - elem = self._createElem('password', name, self.currentLayout) - if value: - elem.setAttribute('value', value) - return elem - - def addTextBox(self, name=None, value=None): - """Add a string box""" - elem = self._createElem('textbox', name, self.currentLayout) - if value: - elem.setAttribute('value', value) - return elem - - def addBool(self, name=None, value="true"): - """Add a string box""" - if value=="0": - value="false" - elif value=="1": - value="true" - assert value in ["true", "false"] - elem = self._createElem('bool', name, self.currentLayout) - elem.setAttribute('value', value) - return elem - - def addList(self, options, name=None, value=None, style=None): - """Add a list of choices""" - if style is None: - style = set() - styles = set(style) - assert options - assert styles.issubset(['multi']) - elem = self._createElem('list', name, self.currentLayout) - self.addOptions(options, elem) - if value: - elem.setAttribute('value', value) - for style in styles: - elem.setAttribute(style, 'yes') - return elem - - def addAdvancedList(self, name=None, headers=None, items=None): - """Create an advanced list - @param headers: optional headers informations - @param items: list of Item instances - @return: created element - """ - elem = self._createElem('advanced_list', name, self.currentLayout) - self.addHeaders(headers, elem) - if items: - self.addItems(items, elem) - return elem + if container not in self._containers: + raise exceptions.DataError(_("Unknown container type [%s]") % container) + cls = self._containers[container] + new_container = cls(self, parent, **kwargs) + return new_container - def addButton(self, callback_id, name, value, fields_back=[]): - """Add a button - @param callback: callback which will be called if button is pressed - @param name: name - @param value: label of the button - @fields_back: list of names of field to give back when pushing the button - """ - elem = self._createElem('button', name, self.currentLayout) - elem.setAttribute('callback', callback_id) - elem.setAttribute('value', value) - for field in fields_back: - fback_el = self.doc.createElement('field_back') - fback_el.setAttribute('name', field) - elem.appendChild(fback_el) - return elem - - def addElement(self, type_, name=None, value=None, options=None, callback_id=None, headers=None, available=None): - """Convenience method to add element, the params correspond to the ones in addSomething methods""" - if type_ == 'empty': - return self.addEmpty(name) - elif type_ == 'text': - assert value is not None - return self.addText(value, name) - elif type_ == 'label': - assert(value) - return self.addLabel(value) - elif type_ == 'string': - return self.addString(name, value) - elif type_ == 'password': - return self.addPassword(name, value) - elif type_ == 'textbox': - return self.addTextBox(name, value) - elif type_ == 'bool': - if not value: - value = "true" - return self.addBool(name, value) - elif type_ == 'list': - return self.addList(options, name, value) - elif type_ == 'advancedlist': - return self.addAdvancedList(name, headers, available) - elif type_ == 'button': - assert(callback_id and value) - return self.addButton(callback_id, name, value) - - # List - - def addOptions(self, options, parent): - """Add options to a multi-values element (e.g. list) - @param parent: multi-values element""" - for option in options: - opt = self.doc.createElement('option') - if isinstance(option, basestring): - value, label = option, option - elif isinstance(option, tuple): - value, label = option - opt.setAttribute('value', value) - opt.setAttribute('label', label) - parent.appendChild(opt) + def changeContainer(self, container, **kwargs): + """Change the current container + @param container: either container type (container it then created), + or an Container instance""" + if isinstance(container, basestring): + self.current_container = self._createContainer(container, self.current_container.getParentContainer() or self.main_container, **kwargs) + else: + self.current_container = self.main_container if container is None else container + assert(isinstance(self.current_container, Container)) + return self.current_container - # Advanced list - - def addHeaders(self, headers, parent): - headers_elt = self.doc.createElement('headers') - for header in headers: - field_elt = self.doc.createElement('field') - field_elt.setAttribute('field_name', header.field_name) - field_elt.setAttribute('label', header.label) - if header.description: - field_elt.setAttribute('description', header.description) - if header.type: - field_elt.setAttribute('type', header.type) - headers_elt.appendChild(field_elt) - parent.appendChild(headers_elt) - - def addItems(self, items, parent): - """Add items to an AdvancedList - @param items: list of Item instances - @param parent: parent element (should be addAdvancedList) - - """ - items_elt = self.doc.createElement('items') - for item in items: - item_elt = self.doc.createElement('item') - if item.text is not None: - text_elt = self.doc.createElement('text') - text_elt.appendChild(self.doc.createTextNode(item.text)) - item_elt.appendChild(text_elt) - - for field in item.fields: - field_elt = self.doc.createElement('field') - field_elt.setAttribute('name', field.name) - field_elt.setAttribute('value', field.value) - item_elt.appendChild(field_elt) - - items_elt.appendChild(item_elt) - - parent.appendChild(items_elt) - - # Tabs - - def addCategory(self, name, layout, label=None): - """Add a category to current layout (must be a tabs layout)""" - assert(layout != 'tabs') - if not self.parentTabsLayout: - error(_("Trying to add a category without parent tabs layout")) - assert(False) - if self.parentTabsLayout.getAttribute('type') != 'tabs': - error(_("parent layout of a category is not tabs")) - assert(False) - - if not label: - label = name - self.currentCategory = cat = self.doc.createElement('category') - cat.setAttribute('name', name) - cat.setAttribute('label', label) - self.changeLayout(layout) - self.parentTabsLayout.appendChild(cat) + def addWidget(self, type_, *args, **kwargs): + """Convenience method to add an element""" + if type_ not in self._widgets: + raise exceptions.DataError(_("Invalid type [%s]") % type_) + if "parent" not in kwargs: + kwargs["parent"] = self.current_container + cls = self._widgets[type_] + return cls(self, *args, **kwargs) def toXml(self): """return the XML representation of the panel""" return self.doc.toxml() +# Misc other funtions + + class ElementParser(object): """callable class to parse XML string into Element Found at http://stackoverflow.com/questions/2093400/how-to-create-twisted-words-xish-domish-element-entirely-from-raw-xml/2095942#2095942