comparison sat/tools/xml_tools.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents e2cb04b381bb
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT: a jabber client 4 # SAT: a jabber client
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
28 from twisted.words.xish import domish 28 from twisted.words.xish import domish
29 from twisted.words.protocols.jabber import jid 29 from twisted.words.protocols.jabber import jid
30 from twisted.internet import defer 30 from twisted.internet import defer
31 from sat.core import exceptions 31 from sat.core import exceptions
32 from collections import OrderedDict 32 from collections import OrderedDict
33 from copy import deepcopy 33 import html.entities
34 import htmlentitydefs
35 import re 34 import re
36 35
37 """This library help manage XML used in SàT (parameters, registration, etc)""" 36 """This library help manage XML used in SàT (parameters, registration, etc)"""
38 37
39 SAT_FORM_PREFIX = "SAT_FORM_" 38 SAT_FORM_PREFIX = "SAT_FORM_"
62 """ 61 """
63 widget_args = [field.value] 62 widget_args = [field.value]
64 widget_kwargs = {} 63 widget_kwargs = {}
65 if field.fieldType is None and field.ext_type is not None: 64 if field.fieldType is None and field.ext_type is not None:
66 # we have an extended field 65 # we have an extended field
67 if field.ext_type == u"xml": 66 if field.ext_type == "xml":
68 element = field.value 67 element = field.value
69 if element.uri == C.NS_XHTML: 68 if element.uri == C.NS_XHTML:
70 widget_type = "xhtmlbox" 69 widget_type = "xhtmlbox"
71 widget_args[0] = element.toXml() 70 widget_args[0] = element.toXml()
72 widget_kwargs["read_only"] = read_only 71 widget_kwargs["read_only"] = read_only
73 else: 72 else:
74 log.warning(u"unknown XML element, falling back to textbox") 73 log.warning("unknown XML element, falling back to textbox")
75 widget_type = "textbox" 74 widget_type = "textbox"
76 widget_args[0] = element.toXml() 75 widget_args[0] = element.toXml()
77 widget_kwargs["read_only"] = read_only 76 widget_kwargs["read_only"] = read_only
78 else: 77 else:
79 raise exceptions.DataError(u"unknown extended type {ext_type}".format( 78 raise exceptions.DataError("unknown extended type {ext_type}".format(
80 ext_type = field.ext_type)) 79 ext_type = field.ext_type))
81 80
82 elif field.fieldType == "fixed" or field.fieldType is None: 81 elif field.fieldType == "fixed" or field.fieldType is None:
83 widget_type = "text" 82 widget_type = "text"
84 if field.value is None: 83 if field.value is None:
85 if field.label is None: 84 if field.label is None:
86 log.warning(_(u"Fixed field has neither value nor label, ignoring it")) 85 log.warning(_("Fixed field has neither value nor label, ignoring it"))
87 field.value = "" 86 field.value = ""
88 else: 87 else:
89 field.value = field.label 88 field.value = field.label
90 field.label = None 89 field.label = None
91 widget_args[0] = field.value 90 widget_args[0] = field.value
95 elif field.fieldType == "jid-single": 94 elif field.fieldType == "jid-single":
96 widget_type = "jid_input" 95 widget_type = "jid_input"
97 widget_kwargs["read_only"] = read_only 96 widget_kwargs["read_only"] = read_only
98 elif field.fieldType == "text-multi": 97 elif field.fieldType == "text-multi":
99 widget_type = "textbox" 98 widget_type = "textbox"
100 widget_args[0] = u"\n".join(field.values) 99 widget_args[0] = "\n".join(field.values)
101 widget_kwargs["read_only"] = read_only 100 widget_kwargs["read_only"] = read_only
102 elif field.fieldType == "hidden": 101 elif field.fieldType == "hidden":
103 widget_type = "hidden" 102 widget_type = "hidden"
104 elif field.fieldType == "text-private": 103 elif field.fieldType == "text-private":
105 widget_type = "password" 104 widget_type = "password"
119 ] 118 ]
120 widget_kwargs["selected"] = widget_args 119 widget_kwargs["selected"] = widget_args
121 widget_args = [] 120 widget_args = []
122 else: 121 else:
123 log.error( 122 log.error(
124 u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType 123 "FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType
125 ) 124 )
126 widget_type = "string" 125 widget_type = "string"
127 widget_kwargs["read_only"] = read_only 126 widget_kwargs["read_only"] = read_only
128 127
129 if field.var: 128 if field.var:
204 - headers (dict{unicode: unicode}): form headers (field labels and types) 203 - headers (dict{unicode: unicode}): form headers (field labels and types)
205 - xmlui_data (list[tuple]): list of (widget_type, widget_args, widget_kwargs) 204 - xmlui_data (list[tuple]): list of (widget_type, widget_args, widget_kwargs)
206 """ 205 """
207 headers = OrderedDict() 206 headers = OrderedDict()
208 try: 207 try:
209 reported_elt = form_xml.elements("jabber:x:data", "reported").next() 208 reported_elt = next(form_xml.elements("jabber:x:data", "reported"))
210 except StopIteration: 209 except StopIteration:
211 raise exceptions.DataError( 210 raise exceptions.DataError(
212 "Couldn't find expected <reported> tag in %s" % form_xml.toXml() 211 "Couldn't find expected <reported> tag in %s" % form_xml.toXml()
213 ) 212 )
214 213
227 item_elts = form_xml.elements("jabber:x:data", "item") 226 item_elts = form_xml.elements("jabber:x:data", "item")
228 227
229 for item_elt in item_elts: 228 for item_elt in item_elts:
230 for elt in item_elt.elements(): 229 for elt in item_elt.elements():
231 if elt.name != "field": 230 if elt.name != "field":
232 log.warning(u"Unexpected tag (%s)" % elt.name) 231 log.warning("Unexpected tag (%s)" % elt.name)
233 continue 232 continue
234 field = data_form.Field.fromElement(elt) 233 field = data_form.Field.fromElement(elt)
235 234
236 xmlui_data.append(_dataFormField2XMLUIData(field)) 235 xmlui_data.append(_dataFormField2XMLUIData(field))
237 236
301 @return: XMLUI instance 300 @return: XMLUI instance
302 """ 301 """
303 # we deepcopy the form because _dataFormField2XMLUIData can modify the value 302 # we deepcopy the form because _dataFormField2XMLUIData can modify the value
304 # FIXME: check if it's really important, the only modified value seems to be 303 # FIXME: check if it's really important, the only modified value seems to be
305 # the replacement of None by "" on fixed fields 304 # the replacement of None by "" on fixed fields
306 form = deepcopy(result_form) 305 # form = deepcopy(result_form)
307 form = result_form 306 form = result_form
308 for name, field in form.fields.iteritems(): 307 for name, field in form.fields.items():
309 try: 308 try:
310 base_field = base_form.fields[name] 309 base_field = base_form.fields[name]
311 except KeyError: 310 except KeyError:
312 continue 311 continue
313 field.options = base_field.options[:] 312 field.options = base_field.options[:]
322 @param value: value to clean 321 @param value: value to clean
323 @return: value in a non DBus type (only clean string yet) 322 @return: value in a non DBus type (only clean string yet)
324 """ 323 """
325 # XXX: must be removed when DBus types will no cause problems anymore 324 # XXX: must be removed when DBus types will no cause problems anymore
326 # FIXME: should be cleaned inside D-Bus bridge itself 325 # FIXME: should be cleaned inside D-Bus bridge itself
327 if isinstance(value, basestring): 326 if isinstance(value, str):
328 return unicode(value) 327 return str(value)
329 return value 328 return value
330 329
331 330
332 def XMLUIResult2DataFormResult(xmlui_data): 331 def XMLUIResult2DataFormResult(xmlui_data):
333 """ Extract form data from a XMLUI return. 332 """ Extract form data from a XMLUI return.
334 333
335 @param xmlui_data (dict): data returned by frontends for XMLUI form 334 @param xmlui_data (dict): data returned by frontends for XMLUI form
336 @return: dict of data usable by Wokkel's data form 335 @return: dict of data usable by Wokkel's data form
337 """ 336 """
338 ret = {} 337 ret = {}
339 for key, value in xmlui_data.iteritems(): 338 for key, value in xmlui_data.items():
340 if not key.startswith(SAT_FORM_PREFIX): 339 if not key.startswith(SAT_FORM_PREFIX):
341 continue 340 continue
342 if isinstance(value, basestring) and u'\n' in value: 341 if isinstance(value, str) and '\n' in value:
343 # data form expects multi-lines text to be in separated values 342 # data form expects multi-lines text to be in separated values
344 value = value.split(u'\n') 343 value = value.split('\n')
345 ret[key[len(SAT_FORM_PREFIX) :]] = _cleanValue(value) 344 ret[key[len(SAT_FORM_PREFIX) :]] = _cleanValue(value)
346 return ret 345 return ret
347 346
348 347
349 def formEscape(name): 348 def formEscape(name):
350 """Return escaped name for forms. 349 """Return escaped name for forms.
351 350
352 @param name (unicode): form name 351 @param name (unicode): form name
353 @return: unicode 352 @return: unicode
354 """ 353 """
355 return u"%s%s" % (SAT_FORM_PREFIX, name) 354 return "%s%s" % (SAT_FORM_PREFIX, name)
356 355
357 356
358 def isXMLUICancelled(raw_xmlui): 357 def isXMLUICancelled(raw_xmlui):
359 """Tell if an XMLUI has been cancelled by checking raw XML""" 358 """Tell if an XMLUI has been cancelled by checking raw XML"""
360 return C.bool(raw_xmlui.get(u'cancelled', C.BOOL_FALSE)) 359 return C.bool(raw_xmlui.get('cancelled', C.BOOL_FALSE))
361 360
362 361
363 def XMLUIResultToElt(xmlui_data): 362 def XMLUIResultToElt(xmlui_data):
364 """Construct result domish.Element from XMLUI result. 363 """Construct result domish.Element from XMLUI result.
365 364
616 @param option (string, tuple) 615 @param option (string, tuple)
617 @param selected (boolean) 616 @param selected (boolean)
618 """ 617 """
619 assert isinstance(parent, ListWidget) 618 assert isinstance(parent, ListWidget)
620 super(OptionElement, self).__init__(parent.xmlui, parent) 619 super(OptionElement, self).__init__(parent.xmlui, parent)
621 if isinstance(option, basestring): 620 if isinstance(option, str):
622 value, label = option, option 621 value, label = option, option
623 elif isinstance(option, tuple): 622 elif isinstance(option, tuple):
624 value, label = option 623 value, label = option
625 else: 624 else:
626 raise NotImplementedError 625 raise NotImplementedError
641 """ 640 """
642 assert isinstance(parent, JidsListWidget) 641 assert isinstance(parent, JidsListWidget)
643 super(JidElement, self).__init__(parent.xmlui, parent) 642 super(JidElement, self).__init__(parent.xmlui, parent)
644 if isinstance(jid_, jid.JID): 643 if isinstance(jid_, jid.JID):
645 value = jid_.full() 644 value = jid_.full()
646 elif isinstance(jid_, basestring): 645 elif isinstance(jid_, str):
647 value = unicode(jid_) 646 value = str(jid_)
648 else: 647 else:
649 raise NotImplementedError 648 raise NotImplementedError
650 jid_txt = self.xmlui.doc.createTextNode(value) 649 jid_txt = self.xmlui.doc.createTextNode(value)
651 self.elem.appendChild(jid_txt) 650 self.elem.appendChild(jid_txt)
652 651
877 super(Widget, self).__init__(xmlui, parent) 876 super(Widget, self).__init__(xmlui, parent)
878 if name: 877 if name:
879 self.elem.setAttribute("name", name) 878 self.elem.setAttribute("name", name)
880 if name in xmlui.named_widgets: 879 if name in xmlui.named_widgets:
881 raise exceptions.ConflictError( 880 raise exceptions.ConflictError(
882 _(u'A widget with the name "{name}" already exists.').format( 881 _('A widget with the name "{name}" already exists.').format(
883 name=name 882 name=name
884 ) 883 )
885 ) 884 )
886 xmlui.named_widgets[name] = self 885 xmlui.named_widgets[name] = self
887 self.elem.setAttribute("type", self.type) 886 self.elem.setAttribute("type", self.type)
971 def __init__(self, xmlui, jid, name=None, parent=None): 970 def __init__(self, xmlui, jid, name=None, parent=None):
972 super(JidWidget, self).__init__(xmlui, name, parent) 971 super(JidWidget, self).__init__(xmlui, name, parent)
973 try: 972 try:
974 self.elem.setAttribute("value", jid.full()) 973 self.elem.setAttribute("value", jid.full())
975 except AttributeError: 974 except AttributeError:
976 self.elem.setAttribute("value", unicode(jid)) 975 self.elem.setAttribute("value", str(jid))
977 976
978 977
979 class DividerWidget(Widget): 978 class DividerWidget(Widget):
980 type = "divider" 979 type = "divider"
981 980
1047 word, set to False only if you made the XHTML yourself) 1046 word, set to False only if you made the XHTML yourself)
1048 """ 1047 """
1049 if clean: 1048 if clean:
1050 if cleanXHTML is None: 1049 if cleanXHTML is None:
1051 raise exceptions.NotFound( 1050 raise exceptions.NotFound(
1052 u"No cleaning method set, can't clean the XHTML") 1051 "No cleaning method set, can't clean the XHTML")
1053 value = cleanXHTML(value) 1052 value = cleanXHTML(value)
1054 1053
1055 super(XHTMLBoxWidget, self).__init__( 1054 super(XHTMLBoxWidget, self).__init__(
1056 xmlui, value=value, name=name, parent=parent, read_only=read_only) 1055 xmlui, value=value, name=name, parent=parent, read_only=read_only)
1057 1056
1112 FieldBackElement(self, field) 1111 FieldBackElement(self, field)
1113 1112
1114 1113
1115 class ListWidget(InputWidget): 1114 class ListWidget(InputWidget):
1116 type = "list" 1115 type = "list"
1117 STYLES = (u"multi", u"noselect", u"extensible", u"reducible", u"inline") 1116 STYLES = ("multi", "noselect", "extensible", "reducible", "inline")
1118 1117
1119 def __init__( 1118 def __init__(
1120 self, xmlui, options, selected=None, styles=None, name=None, parent=None 1119 self, xmlui, options, selected=None, styles=None, name=None, parent=None
1121 ): 1120 ):
1122 """ 1121 """
1142 styles = set() if styles is None else set(styles) 1141 styles = set() if styles is None else set(styles)
1143 if styles is None: 1142 if styles is None:
1144 styles = set() 1143 styles = set()
1145 else: 1144 else:
1146 styles = set(styles) 1145 styles = set(styles)
1147 if u"noselect" in styles and (u"multi" in styles or selected): 1146 if "noselect" in styles and ("multi" in styles or selected):
1148 raise exceptions.DataError( 1147 raise exceptions.DataError(
1149 _( 1148 _(
1150 u'"multi" flag and "selected" option are not compatible with ' 1149 '"multi" flag and "selected" option are not compatible with '
1151 u'"noselect" flag' 1150 '"noselect" flag'
1152 ) 1151 )
1153 ) 1152 )
1154 if not options: 1153 if not options:
1155 # we can have no options if we get a submitted data form 1154 # we can have no options if we get a submitted data form
1156 # but we can't use submitted values directly, 1155 # but we can't use submitted values directly,
1161 self.setStyles(styles) 1160 self.setStyles(styles)
1162 1161
1163 def addOptions(self, options, selected=None): 1162 def addOptions(self, options, selected=None):
1164 """Add options to a multi-values element (e.g. list) """ 1163 """Add options to a multi-values element (e.g. list) """
1165 if selected: 1164 if selected:
1166 if isinstance(selected, basestring): 1165 if isinstance(selected, str):
1167 selected = [selected] 1166 selected = [selected]
1168 else: 1167 else:
1169 selected = [] 1168 selected = []
1170 for option in options: 1169 for option in options:
1171 assert isinstance(option, basestring) or isinstance(option, tuple) 1170 assert isinstance(option, str) or isinstance(option, tuple)
1172 value = option if isinstance(option, basestring) else option[0] 1171 value = option if isinstance(option, str) else option[0]
1173 OptionElement(self, option, value in selected) 1172 OptionElement(self, option, value in selected)
1174 1173
1175 def setStyles(self, styles): 1174 def setStyles(self, styles):
1176 if not styles.issubset(self.STYLES): 1175 if not styles.issubset(self.STYLES):
1177 raise exceptions.DataError(_(u"invalid styles")) 1176 raise exceptions.DataError(_("invalid styles"))
1178 for style in styles: 1177 for style in styles:
1179 self.elem.setAttribute(style, "yes") 1178 self.elem.setAttribute(style, "yes")
1180 # TODO: check flags incompatibily (noselect and multi) like in __init__ 1179 # TODO: check flags incompatibily (noselect and multi) like in __init__
1181 1180
1182 def setStyle(self, style): 1181 def setStyle(self, style):
1184 1183
1185 @property 1184 @property
1186 def value(self): 1185 def value(self):
1187 """Return the value of first selected option""" 1186 """Return the value of first selected option"""
1188 for child in self.elem.childNodes: 1187 for child in self.elem.childNodes:
1189 if child.tagName == u"option" and child.getAttribute(u"selected") == u"true": 1188 if child.tagName == "option" and child.getAttribute("selected") == "true":
1190 return child.getAttribute(u"value") 1189 return child.getAttribute("value")
1191 return u"" 1190 return ""
1192 1191
1193 1192
1194 class JidsListWidget(InputWidget): 1193 class JidsListWidget(InputWidget):
1195 """A list of text or jids where elements can be added/removed or modified""" 1194 """A list of text or jids where elements can be added/removed or modified"""
1196 1195
1356 C.XMLUI_DIALOG, 1355 C.XMLUI_DIALOG,
1357 ]: 1356 ]:
1358 raise exceptions.DataError(_("Unknown panel type [%s]") % panel_type) 1357 raise exceptions.DataError(_("Unknown panel type [%s]") % panel_type)
1359 if panel_type == C.XMLUI_FORM and submit_id is None: 1358 if panel_type == C.XMLUI_FORM and submit_id is None:
1360 raise exceptions.DataError(_("form XMLUI need a submit_id")) 1359 raise exceptions.DataError(_("form XMLUI need a submit_id"))
1361 if not isinstance(container, basestring): 1360 if not isinstance(container, str):
1362 raise exceptions.DataError(_("container argument must be a string")) 1361 raise exceptions.DataError(_("container argument must be a string"))
1363 if dialog_opt is not None and panel_type != C.XMLUI_DIALOG: 1362 if dialog_opt is not None and panel_type != C.XMLUI_DIALOG:
1364 raise exceptions.DataError( 1363 raise exceptions.DataError(
1365 _("dialog_opt can only be used with dialog panels") 1364 _("dialog_opt can only be used with dialog panels")
1366 ) 1365 )
1408 # in bin/sat script then evaluated 1407 # in bin/sat script then evaluated
1409 # bin/sat should be refactored 1408 # bin/sat should be refactored
1410 # log.debug(u'introspecting XMLUI widgets and containers') 1409 # log.debug(u'introspecting XMLUI widgets and containers')
1411 cls._containers = {} 1410 cls._containers = {}
1412 cls._widgets = {} 1411 cls._widgets = {}
1413 for obj in globals().values(): 1412 for obj in list(globals().values()):
1414 try: 1413 try:
1415 if issubclass(obj, Widget): 1414 if issubclass(obj, Widget):
1416 if obj.__name__ == "Widget": 1415 if obj.__name__ == "Widget":
1417 continue 1416 continue
1418 cls._widgets[obj.type] = obj 1417 cls._widgets[obj.type] = obj
1419 creator_name = u"add" + obj.__name__ 1418 creator_name = "add" + obj.__name__
1420 if creator_name.endswith('Widget'): 1419 if creator_name.endswith('Widget'):
1421 creator_name = creator_name[:-6] 1420 creator_name = creator_name[:-6]
1422 is_input = issubclass(obj, InputWidget) 1421 is_input = issubclass(obj, InputWidget)
1423 # FIXME: cf. above comment 1422 # FIXME: cf. above comment
1424 # log.debug(u"Adding {creator_name} creator (is_input={is_input}))" 1423 # log.debug(u"Adding {creator_name} creator (is_input={is_input}))"
1523 def changeContainer(self, container, **kwargs): 1522 def changeContainer(self, container, **kwargs):
1524 """Change the current container 1523 """Change the current container
1525 1524
1526 @param container: either container type (container it then created), 1525 @param container: either container type (container it then created),
1527 or an Container instance""" 1526 or an Container instance"""
1528 if isinstance(container, basestring): 1527 if isinstance(container, str):
1529 self.current_container = self._createContainer( 1528 self.current_container = self._createContainer(
1530 container, 1529 container,
1531 self.current_container.getParentContainer() or self.main_container, 1530 self.current_container.getParentContainer() or self.main_container,
1532 **kwargs 1531 **kwargs
1533 ) 1532 )
1631 profile=profile, 1630 profile=profile,
1632 ) 1631 )
1633 return xmlui_d 1632 return xmlui_d
1634 1633
1635 1634
1636 def deferDialog(host, message, title=u"Please confirm", type_=C.XMLUI_DIALOG_CONFIRM, 1635 def deferDialog(host, message, title="Please confirm", type_=C.XMLUI_DIALOG_CONFIRM,
1637 options=None, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False, 1636 options=None, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False,
1638 profile=C.PROF_KEY_NONE): 1637 profile=C.PROF_KEY_NONE):
1639 """Create a submitable dialog and manage it with a deferred 1638 """Create a submitable dialog and manage it with a deferred
1640 1639
1641 @param message(unicode): message to display 1640 @param message(unicode): message to display
1687 return new_elt 1686 return new_elt
1688 1687
1689 1688
1690 def isXHTMLField(field): 1689 def isXHTMLField(field):
1691 """Check if a data_form.Field is an XHTML one""" 1690 """Check if a data_form.Field is an XHTML one"""
1692 return (field.fieldType is None and field.ext_type == u"xml" and 1691 return (field.fieldType is None and field.ext_type == "xml" and
1693 field.value.uri == C.NS_XHTML) 1692 field.value.uri == C.NS_XHTML)
1694 1693
1695 1694
1696 class ElementParser(object): 1695 class ElementParser(object):
1697 """callable class to parse XML string into Element""" 1696 """callable class to parse XML string into Element"""
1703 if entity in XML_ENTITIES: 1702 if entity in XML_ENTITIES:
1704 # we don't escape XML entities 1703 # we don't escape XML entities
1705 return matchobj.group(0) 1704 return matchobj.group(0)
1706 else: 1705 else:
1707 try: 1706 try:
1708 return unichr(htmlentitydefs.name2codepoint[entity]) 1707 return chr(html.entities.name2codepoint[entity])
1709 except KeyError: 1708 except KeyError:
1710 log.warning(u"removing unknown entity {}".format(entity)) 1709 log.warning("removing unknown entity {}".format(entity))
1711 return u"" 1710 return ""
1712 1711
1713 def __call__(self, raw_xml, force_spaces=False, namespace=None): 1712 def __call__(self, raw_xml, force_spaces=False, namespace=None):
1714 """ 1713 """
1715 @param raw_xml(unicode): the raw XML 1714 @param raw_xml(unicode): the raw XML
1716 @param force_spaces (bool): if True, replace occurrences of '\n' and '\t' 1715 @param force_spaces (bool): if True, replace occurrences of '\n' and '\t'
1719 element 1718 element
1720 """ 1719 """
1721 # we need to wrap element in case 1720 # we need to wrap element in case
1722 # there is not a unique one on the top 1721 # there is not a unique one on the top
1723 if namespace is not None: 1722 if namespace is not None:
1724 raw_xml = u"<div xmlns='{}'>{}</div>".format(namespace, raw_xml) 1723 raw_xml = "<div xmlns='{}'>{}</div>".format(namespace, raw_xml)
1725 else: 1724 else:
1726 raw_xml = u"<div>{}</div>".format(raw_xml) 1725 raw_xml = "<div>{}</div>".format(raw_xml)
1727 1726
1728 # avoid ParserError on HTML escaped chars 1727 # avoid ParserError on HTML escaped chars
1729 raw_xml = html_entity_re.sub(self._escapeHTML, raw_xml) 1728 raw_xml = html_entity_re.sub(self._escapeHTML, raw_xml)
1730 1729
1731 self.result = None 1730 self.result = None
1765 """ 1764 """
1766 data = [] 1765 data = []
1767 for child in node.childNodes: 1766 for child in node.childNodes:
1768 if child.nodeType == child.TEXT_NODE: 1767 if child.nodeType == child.TEXT_NODE:
1769 data.append(child.wholeText) 1768 data.append(child.wholeText)
1770 return u"".join(data) 1769 return "".join(data)
1771 1770
1772 1771
1773 def findAll(elt, namespaces=None, names=None): 1772 def findAll(elt, namespaces=None, names=None):
1774 """Find child element at any depth matching criteria 1773 """Find child element at any depth matching criteria
1775 1774
1778 None to accept every names 1777 None to accept every names
1779 @param namespace(iterable[unicode], basestring, None): URIs to match 1778 @param namespace(iterable[unicode], basestring, None): URIs to match
1780 None to accept every namespaces 1779 None to accept every namespaces
1781 @return ((G)domish.Element): found elements 1780 @return ((G)domish.Element): found elements
1782 """ 1781 """
1783 if isinstance(namespaces, basestring): 1782 if isinstance(namespaces, str):
1784 namespaces = tuple((namespaces,)) 1783 namespaces = tuple((namespaces,))
1785 if isinstance(names, basestring): 1784 if isinstance(names, str):
1786 names = tuple((names,)) 1785 names = tuple((names,))
1787 1786
1788 for child in elt.elements(): 1787 for child in elt.elements():
1789 if ( 1788 if (
1790 domish.IElement.providedBy(child) 1789 domish.IElement.providedBy(child)