Mercurial > libervia-backend
comparison src/tools/xml_tools.py @ 630:0b914394e74f
core: added advanced list to XMLUI (need improvment, very basic so far)
- the advanced list use headers which can be used as columns
- in the future, should provide the ability to highly customize list disposition, in a way similar to Amarok 2's playlist.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 08 Sep 2013 18:05:19 +0200 |
parents | 6f4c31192c7c |
children | 17bd09cd1001 |
comparison
equal
deleted
inserted
replaced
629:204930b870e1 | 630:0b914394e74f |
---|---|
19 | 19 |
20 from logging import debug, info, error | 20 from logging import debug, info, error |
21 from xml.dom import minidom | 21 from xml.dom import minidom |
22 from wokkel import data_form | 22 from wokkel import data_form |
23 from twisted.words.xish import domish | 23 from twisted.words.xish import domish |
24 from core import exceptions | |
24 | 25 |
25 """This library help manage XML used in SàT (parameters, registration, etc) """ | 26 """This library help manage XML used in SàT (parameters, registration, etc) """ |
26 | 27 |
27 | 28 |
28 def dataForm2xml(form): | 29 def dataForm2XML(form): |
29 """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml""" | 30 """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml""" |
30 | 31 |
31 form_ui = XMLUI("form", "vertical") | 32 form_ui = XMLUI("form", "vertical") |
32 | 33 |
33 if form.instructions: | 34 if form.instructions: |
58 form_ui.addEmpty() | 59 form_ui.addEmpty() |
59 | 60 |
60 form_ui.addElement(__field_type, field.var, field.value, [option.value for option in field.options]) | 61 form_ui.addElement(__field_type, field.var, field.value, [option.value for option in field.options]) |
61 return form_ui.toXml() | 62 return form_ui.toXml() |
62 | 63 |
64 def dataFormResult2AdvancedList(form_ui, form_xml): | |
65 """Take a raw data form (not parsed by XEP-0004) and convert it to an advanced list | |
66 raw data form is used because Wokkel doesn't manage result items parsing yet | |
67 @param form_ui: the XMLUI where the AdvancedList will be added² | |
68 @param form_xml: domish.Element of the data form | |
69 @return: AdvancedList element | |
70 """ | |
71 headers = [] | |
72 items = [] | |
73 try: | |
74 reported_elt = form_xml.elements('jabber:x:data', 'reported').next() | |
75 except StopIteration: | |
76 raise exceptions.DataError("Couldn't find expected <reported> tag") | |
77 | |
78 for elt in reported_elt.children: | |
79 if elt.name != "field": | |
80 raise exceptions.DataError("Unexpected tag") | |
81 name = elt["var"] | |
82 label = elt.attributes.get('label','') | |
83 type_ = elt.attributes.get('type','') # TODO | |
84 headers.append(Header(name, label)) | |
85 | |
86 if not headers: | |
87 raise DataError("No reported fields (see XEP-0004 §3.4)") | |
88 | |
89 item_elts = form_xml.elements('jabber:x:data', 'item') | |
90 | |
91 for item_elt in item_elts: | |
92 fields = [] | |
93 for elt in item_elt.children: | |
94 if elt.name != 'field': | |
95 warning("Unexpected tag (%s)" % elt.name) | |
96 continue | |
97 name = elt['var'] | |
98 child_elt = elt.firstChildElement() | |
99 if child_elt.name != "value": | |
100 raise DataError('Was expecting <value> tag') | |
101 value = unicode(child_elt) | |
102 fields.append(Field(name, value)) | |
103 items.append(Item(' | '.join((field.value for field in fields if field)), fields)) | |
104 | |
105 return form_ui.addAdvancedList(None, headers, items) | |
106 | |
107 | |
108 def dataFormResult2XML(form_xml): | |
109 """Take a raw data form (not parsed by XEP-0004) and convert it to a SàT XMLUI | |
110 raw data form is used because Wokkel doesn't manage result items parsing yet | |
111 @param form_xml: domish.Element of the data form | |
112 @return: XMLUI interface | |
113 """ | |
114 | |
115 form_ui = XMLUI("window", "vertical") | |
116 dataFormResult2AdvancedList(form_ui, form_xml) | |
117 return form_ui.toXml() | |
63 | 118 |
64 def tupleList2dataForm(values): | 119 def tupleList2dataForm(values): |
65 """convert a list of tuples (name,value) to a wokkel submit data form""" | 120 """convert a list of tuples (name,value) to a wokkel submit data form""" |
66 form = data_form.Form('submit') | 121 form = data_form.Form('submit') |
67 for value in values: | 122 for value in values: |
68 field = data_form.Field(var=value[0], value=value[1]) | 123 field = data_form.Field(var=value[0], value=value[1]) |
69 form.addField(field) | 124 form.addField(field) |
70 | 125 |
71 return form | 126 return form |
72 | |
73 | 127 |
74 def paramsXml2xmlUI(xml): | 128 def paramsXml2xmlUI(xml): |
75 """Convert the xml for parameter to a SàT XML User Interface""" | 129 """Convert the xml for parameter to a SàT XML User Interface""" |
76 params_doc = minidom.parseString(xml.encode('utf-8')) | 130 params_doc = minidom.parseString(xml.encode('utf-8')) |
77 top = params_doc.documentElement | 131 top = params_doc.documentElement |
102 param_ui.addElement(name=name, type_=type_, value=value, callback_id=callback_id) | 156 param_ui.addElement(name=name, type_=type_, value=value, callback_id=callback_id) |
103 | 157 |
104 return param_ui.toXml() | 158 return param_ui.toXml() |
105 | 159 |
106 | 160 |
161 class Header(object): | |
162 """AdvandeList's header""" | |
163 | |
164 def __init__(self, field_name, label, description=None, type_=None): | |
165 """ | |
166 @param field_name: name of the field referenced | |
167 @param label: label to be displayed in columns | |
168 @param description: long descriptive text | |
169 @param type_: TODO | |
170 | |
171 """ | |
172 self.field_name = field_name | |
173 self.label = label | |
174 self.description = description | |
175 self.type = type_ | |
176 if type_ is not None: | |
177 raise NotImplementedError # TODO: | |
178 | |
179 | |
180 class Item(object): | |
181 """Item used in AdvancedList""" | |
182 | |
183 def __init__(self, text=None, fields=None): | |
184 """ | |
185 @param text: Optional textual representation, when fields are not showed individually | |
186 @param fields: list of Field instances² | |
187 """ | |
188 self.text = text | |
189 self.fields = fields if fields is not None else [] | |
190 | |
191 | |
192 class Field(object): | |
193 """Field used in AdvancedList (in items)""" | |
194 | |
195 def __init__(self, name, value): | |
196 """ | |
197 @param name: name of the field, used to identify the field in headers | |
198 @param value: actual content of the field | |
199 """ | |
200 self.name = name | |
201 self.value = value | |
202 | |
203 | |
204 | |
107 class XMLUI(object): | 205 class XMLUI(object): |
108 """This class is used to create a user interface (form/window/parameters/etc) using SàT XML""" | 206 """This class is used to create a user interface (form/window/parameters/etc) using SàT XML""" |
109 | 207 |
110 def __init__(self, panel_type, layout="vertical", title=None): | 208 def __init__(self, panel_type, layout="vertical", title=None): |
111 """Init SàT XML Panel | 209 """Init SàT XML Panel |
138 self.changeLayout(layout) | 236 self.changeLayout(layout) |
139 | 237 |
140 def __del__(self): | 238 def __del__(self): |
141 self.doc.unlink() | 239 self.doc.unlink() |
142 | 240 |
143 def __createLayout(self, layout, parent=None): | 241 def _createLayout(self, layout, parent=None): |
144 """Create a layout element | 242 """Create a layout element |
145 @param type: layout type (cf init doc) | 243 @param type: layout type (cf init doc) |
146 @parent: parent element or None | 244 @parent: parent element or None |
147 """ | 245 """ |
148 if not layout in ['vertical', 'horizontal', 'pairs', 'tabs']: | 246 if not layout in ['vertical', 'horizontal', 'pairs', 'tabs']: |
152 layout_elt.setAttribute('type', layout) | 250 layout_elt.setAttribute('type', layout) |
153 if parent is not None: | 251 if parent is not None: |
154 parent.appendChild(layout_elt) | 252 parent.appendChild(layout_elt) |
155 return layout_elt | 253 return layout_elt |
156 | 254 |
157 def __createElem(self, type_, name=None, parent=None): | 255 def _createElem(self, type_, name=None, parent=None): |
158 """Create an element | 256 """Create an element |
159 @param type_: one of | 257 @param type_: one of |
160 - empty: empty element (usefull to skip something in a layout, e.g. skip first element in a PAIRS layout) | 258 - empty: empty element (usefull to skip something in a layout, e.g. skip first element in a PAIRS layout) |
161 - text: text to be displayed in an multi-line area, e.g. instructions | 259 - text: text to be displayed in an multi-line area, e.g. instructions |
162 @param name: name of the element or None | 260 @param name: name of the element or None |
163 @param parent: parent element or None | 261 @param parent: parent element or None |
262 @return: created element | |
164 """ | 263 """ |
165 elem = self.doc.createElement('elem') | 264 elem = self.doc.createElement('elem') |
166 if name: | 265 if name: |
167 elem.setAttribute('name', name) | 266 elem.setAttribute('name', name) |
168 elem.setAttribute('type', type_) | 267 elem.setAttribute('type', type_) |
170 parent.appendChild(elem) | 269 parent.appendChild(elem) |
171 return elem | 270 return elem |
172 | 271 |
173 def changeLayout(self, layout): | 272 def changeLayout(self, layout): |
174 """Change the current layout""" | 273 """Change the current layout""" |
175 self.currentLayout = self.__createLayout(layout, self.currentCategory if self.currentCategory else self.doc.documentElement) | 274 self.currentLayout = self._createLayout(layout, self.currentCategory if self.currentCategory else self.doc.documentElement) |
176 if layout == "tabs": | 275 if layout == "tabs": |
177 self.parentTabsLayout = self.currentLayout | 276 self.parentTabsLayout = self.currentLayout |
178 | 277 |
179 def addEmpty(self, name=None): | 278 def addEmpty(self, name=None): |
180 """Add a multi-lines text""" | 279 """Add a multi-lines text""" |
181 self.__createElem('empty', name, self.currentLayout) | 280 return self._createElem('empty', name, self.currentLayout) |
182 | 281 |
183 def addText(self, text, name=None): | 282 def addText(self, text, name=None): |
184 """Add a multi-lines text""" | 283 """Add a multi-lines text""" |
185 elem = self.__createElem('text', name, self.currentLayout) | 284 elem = self._createElem('text', name, self.currentLayout) |
186 text = self.doc.createTextNode(text) | 285 text = self.doc.createTextNode(text) |
187 elem.appendChild(text) | 286 elem.appendChild(text) |
287 return elem | |
188 | 288 |
189 def addLabel(self, text, name=None): | 289 def addLabel(self, text, name=None): |
190 """Add a single line text, mainly useful as label before element""" | 290 """Add a single line text, mainly useful as label before element""" |
191 elem = self.__createElem('label', name, self.currentLayout) | 291 elem = self._createElem('label', name, self.currentLayout) |
192 elem.setAttribute('value', text) | 292 elem.setAttribute('value', text) |
293 return elem | |
193 | 294 |
194 def addString(self, name=None, value=None): | 295 def addString(self, name=None, value=None): |
195 """Add a string box""" | 296 """Add a string box""" |
196 elem = self.__createElem('string', name, self.currentLayout) | 297 elem = self._createElem('string', name, self.currentLayout) |
197 if value: | 298 if value: |
198 elem.setAttribute('value', value) | 299 elem.setAttribute('value', value) |
300 return elem | |
199 | 301 |
200 def addPassword(self, name=None, value=None): | 302 def addPassword(self, name=None, value=None): |
201 """Add a password box""" | 303 """Add a password box""" |
202 elem = self.__createElem('password', name, self.currentLayout) | 304 elem = self._createElem('password', name, self.currentLayout) |
203 if value: | 305 if value: |
204 elem.setAttribute('value', value) | 306 elem.setAttribute('value', value) |
307 return elem | |
205 | 308 |
206 def addTextBox(self, name=None, value=None): | 309 def addTextBox(self, name=None, value=None): |
207 """Add a string box""" | 310 """Add a string box""" |
208 elem = self.__createElem('textbox', name, self.currentLayout) | 311 elem = self._createElem('textbox', name, self.currentLayout) |
209 if value: | 312 if value: |
210 elem.setAttribute('value', value) | 313 elem.setAttribute('value', value) |
314 return elem | |
211 | 315 |
212 def addBool(self, name=None, value="true"): | 316 def addBool(self, name=None, value="true"): |
213 """Add a string box""" | 317 """Add a string box""" |
214 assert value in ["true", "false"] | 318 assert value in ["true", "false"] |
215 elem = self.__createElem('bool', name, self.currentLayout) | 319 elem = self._createElem('bool', name, self.currentLayout) |
216 elem.setAttribute('value', value) | 320 elem.setAttribute('value', value) |
321 return elem | |
217 | 322 |
218 def addList(self, options, name=None, value=None, style=set()): | 323 def addList(self, options, name=None, value=None, style=set()): |
219 """Add a list of choices""" | 324 """Add a list of choices""" |
220 styles = set(style) | 325 styles = set(style) |
221 assert options | 326 assert options |
222 assert styles.issubset(['multi']) | 327 assert styles.issubset(['multi']) |
223 elem = self.__createElem('list', name, self.currentLayout) | 328 elem = self._createElem('list', name, self.currentLayout) |
224 self.addOptions(options, elem) | 329 self.addOptions(options, elem) |
225 if value: | 330 if value: |
226 elem.setAttribute('value', value) | 331 elem.setAttribute('value', value) |
227 for style in styles: | 332 for style in styles: |
228 elem.setAttribute(style, 'yes') | 333 elem.setAttribute(style, 'yes') |
334 return elem | |
335 | |
336 def addAdvancedList(self, name=None, headers=None, items=None): | |
337 """Create an advanced list | |
338 @param headers: optional headers informations | |
339 @param items: list of Item instances | |
340 @return: created element | |
341 """ | |
342 elem = self._createElem('advanced_list', name, self.currentLayout) | |
343 self.addHeaders(headers, elem) | |
344 if items: | |
345 self.addItems(items, elem) | |
346 return elem | |
229 | 347 |
230 def addButton(self, callback_id, name, value, fields_back=[]): | 348 def addButton(self, callback_id, name, value, fields_back=[]): |
231 """Add a button | 349 """Add a button |
232 @param callback: callback which will be called if button is pressed | 350 @param callback: callback which will be called if button is pressed |
233 @param name: name | 351 @param name: name |
234 @param value: label of the button | 352 @param value: label of the button |
235 @fields_back: list of names of field to give back when pushing the button""" | 353 @fields_back: list of names of field to give back when pushing the button |
236 elem = self.__createElem('button', name, self.currentLayout) | 354 """ |
355 elem = self._createElem('button', name, self.currentLayout) | |
237 elem.setAttribute('callback_id', callback_id) | 356 elem.setAttribute('callback_id', callback_id) |
238 elem.setAttribute('value', value) | 357 elem.setAttribute('value', value) |
239 for field in fields_back: | 358 for field in fields_back: |
240 fback_el = self.doc.createElement('field_back') | 359 fback_el = self.doc.createElement('field_back') |
241 fback_el.setAttribute('name', field) | 360 fback_el.setAttribute('name', field) |
242 elem.appendChild(fback_el) | 361 elem.appendChild(fback_el) |
243 | 362 return elem |
244 def addElement(self, type_, name=None, value=None, options=None, callback_id=None): | 363 |
364 def addElement(self, type_, name=None, value=None, options=None, callback_id=None, headers=None, available=None): | |
245 """Convenience method to add element, the params correspond to the ones in addSomething methods""" | 365 """Convenience method to add element, the params correspond to the ones in addSomething methods""" |
246 if type_ == 'empty': | 366 if type_ == 'empty': |
247 self.addEmpty(name) | 367 return self.addEmpty(name) |
248 elif type_ == 'text': | 368 elif type_ == 'text': |
249 assert value is not None | 369 assert value is not None |
250 self.addText(value, name) | 370 return self.addText(value, name) |
251 elif type_ == 'label': | 371 elif type_ == 'label': |
252 assert(value) | 372 assert(value) |
253 self.addLabel(value) | 373 return self.addLabel(value) |
254 elif type_ == 'string': | 374 elif type_ == 'string': |
255 self.addString(name, value) | 375 return self.addString(name, value) |
256 elif type_ == 'password': | 376 elif type_ == 'password': |
257 self.addPassword(name, value) | 377 return self.addPassword(name, value) |
258 elif type_ == 'textbox': | 378 elif type_ == 'textbox': |
259 self.addTextBox(name, value) | 379 return self.addTextBox(name, value) |
260 elif type_ == 'bool': | 380 elif type_ == 'bool': |
261 if not value: | 381 if not value: |
262 value = "true" | 382 value = "true" |
263 self.addBool(name, value) | 383 return self.addBool(name, value) |
264 elif type_ == 'list': | 384 elif type_ == 'list': |
265 self.addList(options, name, value) | 385 return self.addList(options, name, value) |
386 elif type_ == 'advancedlist': | |
387 return self.addAdvancedList(name, headers, available) | |
266 elif type_ == 'button': | 388 elif type_ == 'button': |
267 assert(callback_id and value) | 389 assert(callback_id and value) |
268 self.addButton(callback_id, name, value) | 390 return self.addButton(callback_id, name, value) |
391 | |
392 # List | |
269 | 393 |
270 def addOptions(self, options, parent): | 394 def addOptions(self, options, parent): |
271 """Add options to a multi-values element (e.g. list) | 395 """Add options to a multi-values element (e.g. list) |
272 @param parent: multi-values element""" | 396 @param parent: multi-values element""" |
273 for option in options: | 397 for option in options: |
274 opt = self.doc.createElement('option') | 398 opt = self.doc.createElement('option') |
275 opt.setAttribute('value', option) | 399 opt.setAttribute('value', option) |
276 parent.appendChild(opt) | 400 parent.appendChild(opt) |
401 | |
402 # Advanced list | |
403 | |
404 def addHeaders(self, headers, parent): | |
405 headers_elt = self.doc.createElement('headers') | |
406 for header in headers: | |
407 field_elt = self.doc.createElement('field') | |
408 field_elt.setAttribute('field_name', header.field_name) | |
409 field_elt.setAttribute('label', header.label) | |
410 if header.description: | |
411 field_elt.setAttribute('description', header.description) | |
412 if header.type: | |
413 field_elt.setAttribute('type', header.type) | |
414 headers_elt.appendChild(field_elt) | |
415 parent.appendChild(headers_elt) | |
416 | |
417 def addItems(self, items, parent): | |
418 """Add items to an AdvancedList | |
419 @param items: list of Item instances | |
420 @param parent: parent element (should be addAdvancedList) | |
421 | |
422 """ | |
423 items_elt = self.doc.createElement('items') | |
424 for item in items: | |
425 item_elt = self.doc.createElement('item') | |
426 if item.text is not None: | |
427 text_elt = self.doc.createElement('text') | |
428 text_elt.appendChild(self.doc.createTextNode(item.text)) | |
429 item_elt.appendChild(text_elt) | |
430 | |
431 for field in item.fields: | |
432 field_elt = self.doc.createElement('field') | |
433 field_elt.setAttribute('name', field.name) | |
434 field_elt.setAttribute('value', field.value) | |
435 item_elt.appendChild(field_elt) | |
436 | |
437 items_elt.appendChild(item_elt) | |
438 | |
439 parent.appendChild(items_elt) | |
440 | |
441 # Tabs | |
277 | 442 |
278 def addCategory(self, name, layout, label=None): | 443 def addCategory(self, name, layout, label=None): |
279 """Add a category to current layout (must be a tabs layout)""" | 444 """Add a category to current layout (must be a tabs layout)""" |
280 assert(layout != 'tabs') | 445 assert(layout != 'tabs') |
281 if not self.parentTabsLayout: | 446 if not self.parentTabsLayout: |