Package common :: Package xmpp :: Module protocol
[hide private]
[frames] | no frames]

Source Code for Module common.xmpp.protocol

   1  ##   protocol.py 
   2  ## 
   3  ##   Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov 
   4  ## 
   5  ##   This program is free software; you can redistribute it and/or modify 
   6  ##   it under the terms of the GNU General Public License as published by 
   7  ##   the Free Software Foundation; either version 2, or (at your option) 
   8  ##   any later version. 
   9  ## 
  10  ##   This program is distributed in the hope that it will be useful, 
  11  ##   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12  ##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  13  ##   GNU General Public License for more details. 
  14   
  15  # $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $ 
  16   
  17  """ 
  18  Protocol module contains tools that are needed for processing of xmpp-related 
  19  data structures, including jabber-objects like JID or different stanzas and 
  20  sub- stanzas) handling routines 
  21  """ 
  22   
  23  from simplexml import Node, NodeBuilder 
  24  import time 
  25   
  26  NS_ACTIVITY      ='http://jabber.org/protocol/activity'                           # XEP-0108 
  27  NS_ADDRESS        ='http://jabber.org/protocol/address'                            # XEP-0033 
  28  NS_AGENTS          ='jabber:iq:agents' 
  29  NS_AMP            ='http://jabber.org/protocol/amp' 
  30  NS_AMP_ERRORS   =NS_AMP+'#errors' 
  31  NS_ATOM          ='http://www.w3.org/2005/Atom' 
  32  NS_AUTH          ='jabber:iq:auth' 
  33  NS_AVATAR          ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' 
  34  NS_BIND          ='urn:ietf:params:xml:ns:xmpp-bind' 
  35  NS_BOB          ='urn:xmpp:bob'                                                 #XEP-0231 
  36  NS_BROWSE          ='jabber:iq:browse' 
  37  NS_BROWSING      ='http://jabber.org/protocol/browsing'                           # XEP-0195 
  38  NS_BYTESTREAM   ='http://jabber.org/protocol/bytestreams'                          # JEP-0065 
  39  NS_CAPS          ='http://jabber.org/protocol/caps'                                       # JEP-0115 
  40  NS_CAPTCHA      ='urn:xmpp:captcha'                             # XEP-0158 
  41  NS_CHATSTATES   ='http://jabber.org/protocol/chatstates'                                # JEP-0085 
  42  NS_CHATTING      ='http://jabber.org/protocol/chatting'                           # XEP-0194 
  43  NS_CLIENT          ='jabber:client' 
  44  NS_COMMANDS      ='http://jabber.org/protocol/commands' 
  45  NS_COMPONENT_ACCEPT='jabber:component:accept' 
  46  NS_COMPONENT_1  ='http://jabberd.jabberstudio.org/ns/component/1.0' 
  47  NS_COMPRESS      ='http://jabber.org/protocol/compress'                           # XEP-0138 
  48  NS_CONFERENCE   ='jabber:x:conference' 
  49  NS_DATA          ='jabber:x:data'                                                                               # XEP-0004 
  50  NS_DATA_MEDIA   ='urn:xmpp:media-element'                                                                       # XEP-0221 
  51  NS_DELAY                ='jabber:x:delay' 
  52  NS_DELAY2          ='urn:xmpp:delay' 
  53  NS_DIALBACK      ='jabber:server:dialback' 
  54  NS_DISCO                ='http://jabber.org/protocol/disco' 
  55  NS_DISCO_INFO   =NS_DISCO+'#info' 
  56  NS_DISCO_ITEMS  =NS_DISCO+'#items' 
  57  NS_ENCRYPTED    ='jabber:x:encrypted'                                                              # XEP-0027 
  58  NS_ESESSION      ='http://www.xmpp.org/extensions/xep-0116.html#ns' 
  59  NS_ESESSION_INIT='http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116 
  60  NS_EVENT                ='jabber:x:event'                                                                          # XEP-0022 
  61  NS_FEATURE        ='http://jabber.org/protocol/feature-neg' 
  62  NS_FILE          ='http://jabber.org/protocol/si/profile/file-transfer'  # JEP-0096 
  63  NS_GAMING          ='http://jabber.org/protocol/gaming'                                 # XEP-0196 
  64  NS_GEOLOC          ='http://jabber.org/protocol/geoloc'                                 # JEP-0080 
  65  NS_GROUPCHAT    ='gc-1.0' 
  66  NS_HTTP_AUTH    ='http://jabber.org/protocol/http-auth'                          # XEP-0070 
  67  NS_HTTP_BIND    ='http://jabber.org/protocol/httpbind'                            # XEP-0124 
  68  NS_IBB            ='http://jabber.org/protocol/ibb' 
  69  NS_INVISIBLE    ='presence-invisible'                                                              # Jabberd2 
  70  NS_IQ              ='iq'                                                                                                   # Jabberd2 
  71  NS_JINGLE       ='urn:xmpp:jingle:1'                                    # XEP-0166 
  72  NS_JINGLE_ERRORS='urn:xmpp:jingle:errors:1'                             # XEP-0166 
  73  NS_JINGLE_RTP   ='urn:xmpp:jingle:apps:rtp:1'                           # XEP-0167 
  74  NS_JINGLE_RTP_AUDIO='urn:xmpp:jingle:apps:rtp:audio'                    # XEP-0167 
  75  NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video'                    # XEP-0167 
  76  NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1'                # XEP-0177 
  77  NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1'                # XEP-0176 
  78  NS_LAST          ='jabber:iq:last' 
  79  NS_LOCATION  ='http://jabber.org/protocol/geoloc'                                                               # XEP-0080 
  80  NS_MESSAGE        ='message'                                                                                      # Jabberd2 
  81  NS_MOOD          ='http://jabber.org/protocol/mood'                                       # XEP-0107 
  82  NS_MUC            ='http://jabber.org/protocol/muc' 
  83  NS_MUC_USER      =NS_MUC+'#user' 
  84  NS_MUC_ADMIN    =NS_MUC+'#admin' 
  85  NS_MUC_OWNER    =NS_MUC+'#owner' 
  86  NS_MUC_UNIQUE   =NS_MUC+'#unique' 
  87  NS_MUC_CONFIG   =NS_MUC+'#roomconfig' 
  88  NS_NICK          ='http://jabber.org/protocol/nick'                                       # XEP-0172 
  89  NS_OFFLINE        ='http://www.jabber.org/jeps/jep-0030.html'                    # XEP-0013 
  90  NS_PHYSLOC        ='http://jabber.org/protocol/physloc'                            # XEP-0112 
  91  NS_PING          ='urn:xmpp:ping'                                                                               # SEP-0199 
  92  NS_PRESENCE      ='presence'                                                                                     # Jabberd2 
  93  NS_PRIVACY        ='jabber:iq:privacy' 
  94  NS_PRIVATE        ='jabber:iq:private' 
  95  NS_PROFILE        ='http://jabber.org/protocol/profile'                            # XEP-0154 
  96  NS_PUBSUB          ='http://jabber.org/protocol/pubsub'                                 # XEP-0060 
  97  NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event' 
  98  NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options'                      # XEP-0060 
  99  NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner'                        # JEP-0060 
 100  NS_REGISTER      ='jabber:iq:register' 
 101  NS_ROSTER          ='jabber:iq:roster' 
 102  NS_ROSTERX        ='http://jabber.org/protocol/rosterx'                            # XEP-0144 
 103  NS_ROSTER_VER    ='urn:xmpp:features:rosterver'               # XEP-0273 
 104  NS_RPC            ='jabber:iq:rpc'                                                                              # XEP-0009 
 105  NS_SASL          ='urn:ietf:params:xml:ns:xmpp-sasl' 
 106  NS_SECLABEL         ='urn:xmpp:sec-label:0' 
 107  NS_SECLABEL_CATALOG ='urn:xmpp:sec-label:catalog:0' 
 108  NS_SEARCH          ='jabber:iq:search' 
 109  NS_SERVER          ='jabber:server' 
 110  NS_SESSION        ='urn:ietf:params:xml:ns:xmpp-session' 
 111  NS_SI              ='http://jabber.org/protocol/si'                                             # XEP-0096 
 112  NS_SI_PUB          ='http://jabber.org/protocol/sipub'                                   # XEP-0137 
 113  NS_SIGNED          ='jabber:x:signed'                                                                     # XEP-0027 
 114  NS_SSN            ='urn:xmpp:ssn'                                                                                # XEP-0155 
 115  NS_STANZA_CRYPTO='http://www.xmpp.org/extensions/xep-0200.html#ns'        # XEP-0200 
 116  NS_STANZAS        ='urn:ietf:params:xml:ns:xmpp-stanzas' 
 117  NS_STREAM          ='http://affinix.com/jabber/stream' 
 118  NS_STREAMS        ='http://etherx.jabber.org/streams' 
 119  NS_TIME          ='jabber:iq:time'                                                                         # XEP-0900 
 120  NS_TIME_REVISED ='urn:xmpp:time'                                                                                # XEP-0202 
 121  NS_TLS            ='urn:ietf:params:xml:ns:xmpp-tls' 
 122  NS_TUNE          ='http://jabber.org/protocol/tune'                                       # XEP-0118 
 123  NS_VACATION      ='http://jabber.org/protocol/vacation' 
 124  NS_VCARD                ='vcard-temp' 
 125  NS_GMAILNOTIFY  ='google:mail:notify' 
 126  NS_GTALKSETTING ='google:setting' 
 127  NS_VCARD_UPDATE =NS_VCARD+':x:update' 
 128  NS_VERSION        ='jabber:iq:version' 
 129  NS_VIEWING        ='http://jabber.org/protocol/viewing'                            # XEP--197 
 130  NS_WAITINGLIST  ='http://jabber.org/protocol/waitinglist'                          # XEP-0130 
 131  NS_XHTML_IM      ='http://jabber.org/protocol/xhtml-im'                           # XEP-0071 
 132  NS_XHTML                = 'http://www.w3.org/1999/xhtml'                                                #  " 
 133  NS_DATA_LAYOUT  ='http://jabber.org/protocol/xdata-layout'                        # XEP-0141 
 134  NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate'                    # XEP-0122 
 135  NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' 
 136  NS_RECEIPTS     ='urn:xmpp:receipts' 
 137   
 138  xmpp_stream_error_conditions = ''' 
 139  bad-format --  --  -- The entity has sent XML that cannot be processed. 
 140  bad-namespace-prefix --  --  -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix. 
 141  conflict --  --  -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream. 
 142  connection-timeout --  --  -- The entity has not generated any traffic over the stream for some period of time. 
 143  host-gone --  --  -- The value of the 'to' attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server. 
 144  host-unknown --  --  -- The value of the 'to' attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server. 
 145  improper-addressing --  --  -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value). 
 146  internal-server-error --  --  -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream. 
 147  invalid-from -- cancel --  -- The JID or hostname provided in a 'from' address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization. 
 148  invalid-id --  --  -- The stream ID or dialback ID is invalid or does not match an ID previously provided. 
 149  invalid-namespace --  --  -- The streams namespace name is something other than "http://etherx.jabber.org/streams" or the dialback namespace name is something other than "jabber:server:dialback". 
 150  invalid-xml --  --  -- The entity has sent invalid XML over the stream to a server that performs validation. 
 151  not-authorized --  --  -- The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation. 
 152  policy-violation --  --  -- The entity has violated some local service policy. 
 153  remote-connection-failed --  --  -- The server is unable to properly connect to a remote resource that is required for authentication or authorization. 
 154  resource-constraint --  --  -- The server lacks the system resources necessary to service the stream. 
 155  restricted-xml --  --  -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character. 
 156  see-other-host --  --  -- The server will not provide service to the initiating entity but is redirecting traffic to another host. 
 157  system-shutdown --  --  -- The server is being shut down and all active streams are being closed. 
 158  undefined-condition --  --  -- The error condition is not one of those defined by the other conditions in this list. 
 159  unsupported-encoding --  --  -- The initiating entity has encoded the stream in an encoding that is not supported by the server. 
 160  unsupported-stanza-type --  --  -- The initiating entity has sent a first-level child of the stream that is not supported by the server. 
 161  unsupported-version --  --  -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server. 
 162  xml-not-well-formed --  --  -- The initiating entity has sent XML that is not well-formed.''' 
 163   
 164  xmpp_stanza_error_conditions = ''' 
 165  bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed. 
 166  conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address. 
 167  feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed. 
 168  forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action. 
 169  gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address. 
 170  internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error. 
 171  item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found. 
 172  jid-malformed -- 400 -- modify -- The value of the 'to' attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme. 
 173  not-acceptable -- 406 -- cancel -- The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server. 
 174  not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action. 
 175  not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials. 
 176  payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required. 
 177  recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable. 
 178  redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity. 
 179  registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required. 
 180  remote-server-not-found -- 404 -- cancel -- A remote server or service specified as part or all of the JID of the intended recipient does not exist. 
 181  remote-server-timeout -- 504 -- wait -- A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time. 
 182  resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request. 
 183  service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service. 
 184  subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required. 
 185  undefined-condition -- 500 --  -- Undefined Condition 
 186  unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).''' 
 187   
 188  sasl_error_conditions = ''' 
 189  aborted --  --  -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element. 
 190  incorrect-encoding --  --  -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data. 
 191  invalid-authzid --  --  -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data. 
 192  invalid-mechanism --  --  -- The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element. 
 193  mechanism-too-weak --  --  -- The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data. 
 194  not-authorized --  --  -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data. 
 195  temporary-auth-failure --  --  -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element.''' 
 196   
 197  ERRORS, _errorcodes = {}, {} 
 198  for ns, errname, errpool in ((NS_XMPP_STREAMS, 'STREAM', xmpp_stream_error_conditions), 
 199                                                     (NS_STANZAS, 'ERR', xmpp_stanza_error_conditions), 
 200                                                     (NS_SASL, 'SASL', sasl_error_conditions)): 
 201      for err in errpool.split('\n')[1:]: 
 202          cond, code, typ, text = err.split(' -- ') 
 203          name = errname + '_' + cond.upper().replace('-', '_') 
 204          locals()[name] = ns + ' ' + cond 
 205          ERRORS[ns + ' ' + cond] = [code, typ, text] 
 206          if code: 
 207              _errorcodes[code] = cond 
 208  del ns, errname, errpool, err, cond, code, typ, text 
 209   
210 -def isResultNode(node):
211 """ 212 Return true if the node is a positive reply 213 """ 214 return node and node.getType() == 'result'
215
216 -def isErrorNode(node):
217 """ 218 Return true if the node is a negative reply 219 """ 220 return node and node.getType() == 'error'
221
222 -class NodeProcessed(Exception):
223 """ 224 Exception that should be raised by handler when the handling should be 225 stopped 226 """ 227 pass
228
229 -class StreamError(Exception):
230 """ 231 Base exception class for stream errors 232 """ 233 pass
234
235 -class BadFormat(StreamError):
236 pass
237
238 -class BadNamespacePrefix(StreamError):
239 pass
240
241 -class Conflict(StreamError):
242 pass
243
244 -class ConnectionTimeout(StreamError):
245 pass
246
247 -class HostGone(StreamError):
248 pass
249
250 -class HostUnknown(StreamError):
251 pass
252
253 -class ImproperAddressing(StreamError):
254 pass
255
256 -class InternalServerError(StreamError):
257 pass
258
259 -class InvalidFrom(StreamError):
260 pass
261
262 -class InvalidID(StreamError):
263 pass
264
265 -class InvalidNamespace(StreamError):
266 pass
267
268 -class InvalidXML(StreamError):
269 pass
270
271 -class NotAuthorized(StreamError):
272 pass
273
274 -class PolicyViolation(StreamError):
275 pass
276
277 -class RemoteConnectionFailed(StreamError):
278 pass
279
280 -class ResourceConstraint(StreamError):
281 pass
282
283 -class RestrictedXML(StreamError):
284 pass
285
286 -class SeeOtherHost(StreamError):
287 pass
288
289 -class SystemShutdown(StreamError):
290 pass
291
292 -class UndefinedCondition(StreamError):
293 pass
294
295 -class UnsupportedEncoding(StreamError):
296 pass
297
298 -class UnsupportedStanzaType(StreamError):
299 pass
300
301 -class UnsupportedVersion(StreamError):
302 pass
303
304 -class XMLNotWellFormed(StreamError):
305 pass
306 307 stream_exceptions = {'bad-format': BadFormat, 308 'bad-namespace-prefix': BadNamespacePrefix, 309 'conflict': Conflict, 310 'connection-timeout': ConnectionTimeout, 311 'host-gone': HostGone, 312 'host-unknown': HostUnknown, 313 'improper-addressing': ImproperAddressing, 314 'internal-server-error': InternalServerError, 315 'invalid-from': InvalidFrom, 316 'invalid-id': InvalidID, 317 'invalid-namespace': InvalidNamespace, 318 'invalid-xml': InvalidXML, 319 'not-authorized': NotAuthorized, 320 'policy-violation': PolicyViolation, 321 'remote-connection-failed': RemoteConnectionFailed, 322 'resource-constraint': ResourceConstraint, 323 'restricted-xml': RestrictedXML, 324 'see-other-host': SeeOtherHost, 325 'system-shutdown': SystemShutdown, 326 'undefined-condition': UndefinedCondition, 327 'unsupported-encoding': UnsupportedEncoding, 328 'unsupported-stanza-type': UnsupportedStanzaType, 329 'unsupported-version': UnsupportedVersion, 330 'xml-not-well-formed': XMLNotWellFormed} 331
332 -class JID:
333 """ 334 JID can be built from string, modified, compared, serialised into string 335 """ 336
337 - def __init__(self, jid=None, node='', domain='', resource=''):
338 """ 339 JID can be specified as string (jid argument) or as separate parts 340 341 Examples: 342 JID('node@domain/resource') 343 JID(node='node',domain='domain.org') 344 """ 345 if not jid and not domain: 346 raise ValueError('JID must contain at least domain name') 347 elif type(jid) == type(self): 348 self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource 349 elif domain: 350 self.node, self.domain, self.resource = node, domain, resource 351 else: 352 if jid.find('@') + 1: 353 self.node, jid = jid.split('@', 1) 354 else: 355 self.node = '' 356 if jid.find('/')+1: 357 self.domain, self.resource = jid.split('/', 1) 358 else: 359 self.domain, self.resource = jid, ''
360
361 - def getNode(self):
362 """ 363 Return the node part of the JID 364 """ 365 return self.node
366
367 - def setNode(self, node):
368 """ 369 Set the node part of the JID to new value. Specify None to remove the node part 370 """ 371 self.node = node.lower()
372
373 - def getDomain(self):
374 """ 375 Return the domain part of the JID 376 """ 377 return self.domain
378
379 - def setDomain(self, domain):
380 """ 381 Set the domain part of the JID to new value 382 """ 383 self.domain = domain.lower()
384
385 - def getResource(self):
386 """ 387 Return the resource part of the JID 388 """ 389 return self.resource
390
391 - def setResource(self, resource):
392 """ 393 Set the resource part of the JID to new value. Specify None to remove the 394 resource part 395 """ 396 self.resource = resource
397
398 - def getStripped(self):
399 """ 400 Return the bare representation of JID. I.e. string value w/o resource 401 """ 402 return self.__str__(0)
403
404 - def __eq__(self, other):
405 """ 406 Compare the JID to another instance or to string for equality 407 """ 408 try: 409 other = JID(other) 410 except ValueError: 411 return 0 412 return self.resource == other.resource and self.__str__(0) == other.__str__(0)
413
414 - def __ne__(self, other):
415 """ 416 Compare the JID to another instance or to string for non-equality 417 """ 418 return not self.__eq__(other)
419
420 - def bareMatch(self, other):
421 """ 422 Compare the node and domain parts of the JID's for equality 423 """ 424 return self.__str__(0) == JID(other).__str__(0)
425
426 - def __str__(self, wresource=1):
427 """ 428 Serialise JID into string 429 """ 430 if self.node: 431 jid = self.node + '@' + self.domain 432 else: 433 jid = self.domain 434 if wresource and self.resource: 435 return jid + '/' + self.resource 436 return jid
437
438 - def __hash__(self):
439 """ 440 Produce hash of the JID, Allows to use JID objects as keys of the dictionary 441 """ 442 return hash(str(self))
443
444 -class BOSHBody(Node):
445 """ 446 <body> tag that wraps usual XMPP stanzas in XMPP over BOSH 447 """ 448
449 - def __init__(self, attrs={}, payload=[], node=None):
450 Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) 451 self.setNamespace(NS_HTTP_BIND)
452 453
454 -class Protocol(Node):
455 """ 456 A "stanza" object class. Contains methods that are common for presences, iqs 457 and messages 458 """ 459
460 - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, 461 payload=[], timestamp=None, xmlns=None, node=None):
462 """ 463 Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq' 464 465 to is the value of 'to' attribure, 'typ' - 'type' attribute 466 frn - from attribure, attrs - other attributes mapping, 467 payload - same meaning as for simplexml payload definition 468 timestamp - the time value that needs to be stamped over stanza 469 xmlns - namespace of top stanza node 470 node - parsed or unparsed stana to be taken as prototype. 471 """ 472 if not attrs: 473 attrs = {} 474 if to: 475 attrs['to'] = to 476 if frm: 477 attrs['from'] = frm 478 if typ: 479 attrs['type'] = typ 480 Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) 481 if not node and xmlns: 482 self.setNamespace(xmlns) 483 if self['to']: 484 self.setTo(self['to']) 485 if self['from']: 486 self.setFrom(self['from']) 487 if node and type(self) == type(node) and self.__class__ == node.__class__ and self.attrs.has_key('id'): 488 del self.attrs['id'] 489 self.timestamp = None 490 for d in self.getTags('delay', namespace=NS_DELAY2): 491 try: 492 if d.getAttr('stamp') < self.getTimestamp2(): 493 self.setTimestamp(d.getAttr('stamp')) 494 except Exception: 495 pass 496 if not self.timestamp: 497 for x in self.getTags('x', namespace=NS_DELAY): 498 try: 499 if x.getAttr('stamp') < self.getTimestamp(): 500 self.setTimestamp(x.getAttr('stamp')) 501 except Exception: 502 pass 503 if timestamp is not None: 504 self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
505
506 - def getTo(self):
507 """ 508 Return value of the 'to' attribute 509 """ 510 try: 511 return self['to'] 512 except: 513 return None
514
515 - def getFrom(self):
516 """ 517 Return value of the 'from' attribute 518 """ 519 try: 520 return self['from'] 521 except: 522 return None
523
524 - def getTimestamp(self):
525 """ 526 Return the timestamp in the 'yyyymmddThhmmss' format 527 """ 528 if self.timestamp: 529 return self.timestamp 530 return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
531
532 - def getTimestamp2(self):
533 """ 534 Return the timestamp in the 'yyyymmddThhmmss' format 535 """ 536 if self.timestamp: 537 return self.timestamp 538 return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
539
540 - def getID(self):
541 """ 542 Return the value of the 'id' attribute 543 """ 544 return self.getAttr('id')
545
546 - def setTo(self, val):
547 """ 548 Set the value of the 'to' attribute 549 """ 550 self.setAttr('to', JID(val))
551
552 - def getType(self):
553 """ 554 Return the value of the 'type' attribute 555 """ 556 return self.getAttr('type')
557
558 - def setFrom(self, val):
559 """ 560 Set the value of the 'from' attribute 561 """ 562 self.setAttr('from', JID(val))
563
564 - def setType(self, val):
565 """ 566 Set the value of the 'type' attribute 567 """ 568 self.setAttr('type', val)
569
570 - def setID(self, val):
571 """ 572 Set the value of the 'id' attribute 573 """ 574 self.setAttr('id', val)
575
576 - def getError(self):
577 """ 578 Return the error-condition (if present) or the textual description of the 579 error (otherwise) 580 """ 581 errtag = self.getTag('error') 582 if errtag: 583 for tag in errtag.getChildren(): 584 if tag.getName() <> 'text': 585 return tag.getName() 586 return errtag.getData()
587
588 - def getErrorMsg(self):
589 """ 590 Return the textual description of the error (if present) or the error condition 591 """ 592 errtag = self.getTag('error') 593 if errtag: 594 for tag in errtag.getChildren(): 595 if tag.getName() == 'text': 596 return tag.getData() 597 return self.getError()
598
599 - def getErrorCode(self):
600 """ 601 Return the error code. Obsolete. 602 """ 603 return self.getTagAttr('error', 'code')
604
605 - def setError(self,error,code=None):
606 """ 607 Set the error code. Obsolete. Use error-conditions instead 608 """ 609 if code: 610 if str(code) in _errorcodes.keys(): 611 error = ErrorNode(_errorcodes[str(code)], text=error) 612 else: 613 error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ='cancel', text=error) 614 elif type(error) in [type(''), type(u'')]: 615 error=ErrorNode(error) 616 self.setType('error') 617 self.addChild(node=error)
618
619 - def setTimestamp(self, val=None):
620 """ 621 Set the timestamp. timestamp should be the yyyymmddThhmmss string 622 """ 623 if not val: 624 val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) 625 self.timestamp=val 626 self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY)
627
628 - def getProperties(self):
629 """ 630 Return the list of namespaces to which belongs the direct childs of element 631 """ 632 props = [] 633 for child in self.getChildren(): 634 prop = child.getNamespace() 635 if prop not in props: 636 props.append(prop) 637 return props
638
639 - def __setitem__(self, item, val):
640 """ 641 Set the item 'item' to the value 'val' 642 """ 643 if item in ['to', 'from']: 644 val = JID(val) 645 return self.setAttr(item, val)
646 647
648 -class Message(Protocol):
649 """ 650 XMPP Message stanza - "push" mechanism 651 """ 652
653 - def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, 654 attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, 655 node=None):
656 """ 657 You can specify recipient, text of message, type of message any 658 additional attributes, sender of the message, any additional payload 659 (f.e. jabber:x:delay element) and namespace in one go. 660 661 Alternatively you can pass in the other XML object as the 'node' 662 parameted to replicate it as message 663 """ 664 Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, 665 payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) 666 if body: 667 self.setBody(body) 668 if xhtml: 669 self.setXHTML(xhtml) 670 if subject is not None: 671 self.setSubject(subject)
672
673 - def getBody(self):
674 """ 675 Return text of the message 676 """ 677 return self.getTagData('body')
678
679 - def getXHTML(self, xmllang=None):
680 """ 681 Return serialized xhtml-im element text of the message 682 683 TODO: Returning a DOM could make rendering faster. 684 """ 685 xhtml = self.getTag('html') 686 if xhtml: 687 if xmllang: 688 body = xhtml.getTag('body', attrs={'xml:lang': xmllang}) 689 else: 690 body = xhtml.getTag('body') 691 return str(body) 692 return None
693
694 - def getSubject(self):
695 """ 696 Return subject of the message 697 """ 698 return self.getTagData('subject')
699
700 - def getThread(self):
701 """ 702 Return thread of the message 703 """ 704 return self.getTagData('thread')
705
706 - def setBody(self, val):
707 """ 708 Set the text of the message""" 709 self.setTagData('body', val)
710
711 - def setXHTML(self, val, xmllang=None):
712 """ 713 Sets the xhtml text of the message (XEP-0071). The parameter is the 714 "inner html" to the body. 715 """ 716 try: 717 if xmllang: 718 dom = NodeBuilder('<body xmlns="%s" xml:lang="%s">%s</body>' % (NS_XHTML, xmllang, val)).getDom() 719 else: 720 dom = NodeBuilder('<body xmlns="%s">%s</body>, 0' % (NS_XHTML, val)).getDom() 721 if self.getTag('html'): 722 self.getTag('html').addChild(node=dom) 723 else: 724 self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom) 725 except Exception, e: 726 print "Error", e
727 # FIXME: log. we could not set xhtml (parse error, whatever) 728
729 - def setSubject(self, val):
730 """ 731 Set the subject of the message 732 """ 733 self.setTagData('subject', val)
734
735 - def setThread(self, val):
736 """ 737 Set the thread of the message 738 """ 739 self.setTagData('thread', val)
740
741 - def buildReply(self, text=None):
742 """ 743 Builds and returns another message object with specified text. The to, 744 from and thread properties of new message are pre-set as reply to this 745 message 746 """ 747 m = Message(to=self.getFrom(), frm=self.getTo(), body=text) 748 th = self.getThread() 749 if th: 750 m.setThread(th) 751 return m
752
753 - def getStatusCode(self):
754 """ 755 Return the status code of the message (for groupchat config change) 756 """ 757 attrs = [] 758 for xtag in self.getTags('x'): 759 for child in xtag.getTags('status'): 760 attrs.append(child.getAttr('code')) 761 return attrs
762
763 -class Presence(Protocol):
764
765 - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, 766 attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, 767 node=None):
768 """ 769 You can specify recipient, type of message, priority, show and status 770 values any additional attributes, sender of the presence, timestamp, any 771 additional payload (f.e. jabber:x:delay element) and namespace in one go. 772 Alternatively you can pass in the other XML object as the 'node' 773 parameted to replicate it as presence 774 """ 775 Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, 776 payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) 777 if priority: 778 self.setPriority(priority) 779 if show: 780 self.setShow(show) 781 if status: 782 self.setStatus(status)
783
784 - def getPriority(self):
785 """ 786 Return the priority of the message 787 """ 788 return self.getTagData('priority')
789
790 - def getShow(self):
791 """ 792 Return the show value of the message 793 """ 794 return self.getTagData('show')
795
796 - def getStatus(self):
797 """ 798 Return the status string of the message 799 """ 800 return self.getTagData('status')
801
802 - def setPriority(self, val):
803 """ 804 Set the priority of the message 805 """ 806 self.setTagData('priority', val)
807
808 - def setShow(self, val):
809 """ 810 Set the show value of the message 811 """ 812 self.setTagData('show', val)
813
814 - def setStatus(self, val):
815 """ 816 Set the status string of the message 817 """ 818 self.setTagData('status', val)
819
820 - def _muc_getItemAttr(self, tag, attr):
821 for xtag in self.getTags('x'): 822 if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): 823 continue 824 for child in xtag.getTags(tag): 825 return child.getAttr(attr)
826
827 - def _muc_getSubTagDataAttr(self, tag, attr):
828 for xtag in self.getTags('x'): 829 if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): 830 continue 831 for child in xtag.getTags('item'): 832 for cchild in child.getTags(tag): 833 return cchild.getData(), cchild.getAttr(attr) 834 return None, None
835
836 - def getRole(self):
837 """ 838 Return the presence role (for groupchat) 839 """ 840 return self._muc_getItemAttr('item', 'role')
841 - def getAffiliation(self):
842 """ 843 Return the presence affiliation (for groupchat) 844 """ 845 return self._muc_getItemAttr('item', 'affiliation')
846
847 - def getNewNick(self):
848 """ 849 Return the status code of the presence (for groupchat) 850 """ 851 return self._muc_getItemAttr('item', 'nick')
852
853 - def getJid(self):
854 """ 855 Return the presence jid (for groupchat) 856 """ 857 return self._muc_getItemAttr('item', 'jid')
858
859 - def getReason(self):
860 """ 861 Returns the reason of the presence (for groupchat) 862 """ 863 return self._muc_getSubTagDataAttr('reason', '')[0]
864
865 - def getActor(self):
866 """ 867 Return the reason of the presence (for groupchat) 868 """ 869 return self._muc_getSubTagDataAttr('actor', 'jid')[1]
870
871 - def getStatusCode(self):
872 """ 873 Return the status code of the presence (for groupchat) 874 """ 875 attrs = [] 876 for xtag in self.getTags('x'): 877 for child in xtag.getTags('status'): 878 attrs.append(child.getAttr('code')) 879 return attrs
880
881 -class Iq(Protocol):
882 """ 883 XMPP Iq object - get/set dialog mechanism 884 """ 885
886 - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, 887 payload=[], xmlns=NS_CLIENT, node=None):
888 """ 889 You can specify type, query namespace any additional attributes, 890 recipient of the iq, sender of the iq, any additional payload (f.e. 891 jabber:x:data node) and namespace in one go. 892 893 Alternatively you can pass in the other XML object as the 'node' 894 parameted to replicate it as an iq 895 """ 896 Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) 897 if payload: 898 self.setQueryPayload(payload) 899 if queryNS: 900 self.setQueryNS(queryNS)
901
902 - def getQueryNS(self):
903 """ 904 Return the namespace of the 'query' child element 905 """ 906 tag = self.getTag('query') 907 if tag: 908 return tag.getNamespace()
909
910 - def getQuerynode(self):
911 """ 912 Return the 'node' attribute value of the 'query' child element 913 """ 914 return self.getTagAttr('query', 'node')
915
916 - def getQueryPayload(self):
917 """ 918 Return the 'query' child element payload 919 """ 920 tag = self.getTag('query') 921 if tag: 922 return tag.getPayload()
923
924 - def getQueryChildren(self):
925 """ 926 Return the 'query' child element child nodes 927 """ 928 tag = self.getTag('query') 929 if tag: 930 return tag.getChildren()
931
932 - def setQueryNS(self, namespace):
933 """ 934 Set the namespace of the 'query' child element 935 """ 936 self.setTag('query').setNamespace(namespace)
937
938 - def setQueryPayload(self, payload):
939 """ 940 Set the 'query' child element payload 941 """ 942 self.setTag('query').setPayload(payload)
943
944 - def setQuerynode(self, node):
945 """ 946 Set the 'node' attribute value of the 'query' child element 947 """ 948 self.setTagAttr('query', 'node', node)
949
950 - def buildReply(self, typ):
951 """ 952 Build and return another Iq object of specified type. The to, from and 953 query child node of new Iq are pre-set as reply to this Iq. 954 """ 955 iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id': self.getID()}) 956 if self.getTag('query'): 957 iq.setQueryNS(self.getQueryNS()) 958 return iq
959
960 -class ErrorNode(Node):
961 """ 962 XMPP-style error element 963 964 In the case of stanza error should be attached to XMPP stanza. 965 In the case of stream-level errors should be used separately. 966 """ 967
968 - def __init__(self, name, code=None, typ=None, text=None):
969 """ 970 Mandatory parameter: name - name of error condition. 971 Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol. 972 """ 973 if name in ERRORS: 974 cod, type_, txt = ERRORS[name] 975 ns = name.split()[0] 976 else: 977 cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', '' 978 if typ: 979 type_ = typ 980 if code: 981 cod = code 982 if text: 983 txt = text 984 Node.__init__(self, 'error', {}, [Node(name)]) 985 if type_: 986 self.setAttr('type', type_) 987 if not cod: 988 self.setName('stream:error') 989 if txt: 990 self.addChild(node=Node(ns + ' text', {}, [txt])) 991 if cod: 992 self.setAttr('code', cod)
993
994 -class Error(Protocol):
995 """ 996 Used to quickly transform received stanza into error reply 997 """ 998
999 - def __init__(self, node, error, reply=1):
1000 """ 1001 Create error reply basing on the received 'node' stanza and the 'error' 1002 error condition 1003 1004 If the 'node' is not the received stanza but locally created ('to' and 1005 'from' fields needs not swapping) specify the 'reply' argument as false. 1006 """ 1007 if reply: 1008 Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node) 1009 else: 1010 Protocol.__init__(self, node=node) 1011 self.setError(error) 1012 if node.getType() == 'error': 1013 self.__str__ = self.__dupstr__
1014
1015 - def __dupstr__(self, dup1=None, dup2=None):
1016 """ 1017 Dummy function used as preventor of creating error node in reply to error 1018 node. I.e. you will not be able to serialise "double" error into string. 1019 """ 1020 return ''
1021
1022 -class DataField(Node):
1023 """ 1024 This class is used in the DataForm class to describe the single data item 1025 1026 If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then 1027 you will need to work with instances of this class. 1028 """ 1029
1030 - def __init__(self, name=None, value=None, typ=None, required=0, desc=None, 1031 options=[], node=None):
1032 """ 1033 Create new data field of specified name,value and type 1034 1035 Also 'required','desc' and 'options' fields can be set. Alternatively 1036 other XML object can be passed in as the 'node' parameted to replicate it 1037 as a new datafiled. 1038 """ 1039 Node.__init__(self, 'field', node=node) 1040 if name: 1041 self.setVar(name) 1042 if isinstance(value, (list, tuple)): 1043 self.setValues(value) 1044 elif value: 1045 self.setValue(value) 1046 if typ: 1047 self.setType(typ) 1048 elif not typ and not node: 1049 self.setType('text-single') 1050 if required: 1051 self.setRequired(required) 1052 if desc: 1053 self.setDesc(desc) 1054 if options: 1055 self.setOptions(options)
1056
1057 - def setRequired(self, req=1):
1058 """ 1059 Change the state of the 'required' flag 1060 """ 1061 if req: 1062 self.setTag('required') 1063 else: 1064 try: 1065 self.delChild('required') 1066 except ValueError: 1067 return
1068
1069 - def isRequired(self):
1070 """ 1071 Return in this field a required one 1072 """ 1073 return self.getTag('required')
1074
1075 - def setDesc(self, desc):
1076 """ 1077 Set the description of this field 1078 """ 1079 self.setTagData('desc', desc)
1080
1081 - def getDesc(self):
1082 """ 1083 Return the description of this field 1084 """ 1085 return self.getTagData('desc')
1086
1087 - def setValue(self, val):
1088 """ 1089 Set the value of this field 1090 """ 1091 self.setTagData('value', val)
1092
1093 - def getValue(self):
1094 return self.getTagData('value')
1095
1096 - def setValues(self, lst):
1097 """ 1098 Set the values of this field as values-list. Replaces all previous filed 1099 values! If you need to just add a value - use addValue method 1100 """ 1101 while self.getTag('value'): 1102 self.delChild('value') 1103 for val in lst: 1104 self.addValue(val)
1105
1106 - def addValue(self, val):
1107 """ 1108 Add one more value to this field. Used in 'get' iq's or such 1109 """ 1110 self.addChild('value', {}, [val])
1111
1112 - def getValues(self):
1113 """ 1114 Return the list of values associated with this field 1115 """ 1116 ret = [] 1117 for tag in self.getTags('value'): 1118 ret.append(tag.getData()) 1119 return ret
1120
1121 - def getOptions(self):
1122 """ 1123 Return label-option pairs list associated with this field 1124 """ 1125 ret = [] 1126 for tag in self.getTags('option'): 1127 ret.append([tag.getAttr('label'), tag.getTagData('value')]) 1128 return ret
1129
1130 - def setOptions(self, lst):
1131 """ 1132 Set label-option pairs list associated with this field 1133 """ 1134 while self.getTag('option'): 1135 self.delChild('option') 1136 for opt in lst: 1137 self.addOption(opt)
1138
1139 - def addOption(self, opt):
1140 """ 1141 Add one more label-option pair to this field 1142 """ 1143 if isinstance(opt, basestring): 1144 self.addChild('option').setTagData('value', opt) 1145 else: 1146 self.addChild('option', {'label': opt[0]}).setTagData('value', opt[1])
1147
1148 - def getType(self):
1149 """ 1150 Get type of this field 1151 """ 1152 return self.getAttr('type')
1153
1154 - def setType(self, val):
1155 """ 1156 Set type of this field 1157 """ 1158 return self.setAttr('type', val)
1159
1160 - def getVar(self):
1161 """ 1162 Get 'var' attribute value of this field 1163 """ 1164 return self.getAttr('var')
1165
1166 - def setVar(self, val):
1167 """ 1168 Set 'var' attribute value of this field 1169 """ 1170 return self.setAttr('var', val)
1171
1172 -class DataForm(Node):
1173 """ 1174 Used for manipulating dataforms in XMPP 1175 1176 Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many 1177 other applications. 1178 """
1179 - def __init__(self, typ=None, data=[], title=None, node=None):
1180 """ 1181 Create new dataform of type 'typ'. 'data' is the list of DataField 1182 instances that this dataform contains, 'title' - the title string. You 1183 can specify the 'node' argument as the other node to be used as base for 1184 constructing this dataform 1185 1186 title and instructions is optional and SHOULD NOT contain newlines. 1187 Several instructions MAY be present. 1188 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) 1189 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. 1190 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. 1191 'title' MAY be included in forms of type "form" and "result" 1192 """ 1193 Node.__init__(self, 'x', node=node) 1194 if node: 1195 newkids = [] 1196 for n in self.getChildren(): 1197 if n.getName() == 'field': 1198 newkids.append(DataField(node=n)) 1199 else: 1200 newkids.append(n) 1201 self.kids = newkids 1202 if typ: 1203 self.setType(typ) 1204 self.setNamespace(NS_DATA) 1205 if title: 1206 self.setTitle(title) 1207 if isinstance(data, dict): 1208 newdata = [] 1209 for name in data.keys(): 1210 newdata.append(DataField(name, data[name])) 1211 data = newdata 1212 for child in data: 1213 if isinstance(child, basestring): 1214 self.addInstructions(child) 1215 elif child.__class__.__name__ == 'DataField': 1216 self.kids.append(child) 1217 else: 1218 self.kids.append(DataField(node=child))
1219
1220 - def getType(self):
1221 """ 1222 Return the type of dataform 1223 """ 1224 return self.getAttr('type')
1225
1226 - def setType(self, typ):
1227 """ 1228 Set the type of dataform 1229 """ 1230 self.setAttr('type', typ)
1231
1232 - def getTitle(self):
1233 """ 1234 Return the title of dataform 1235 """ 1236 return self.getTagData('title')
1237
1238 - def setTitle(self, text):
1239 """ 1240 Set the title of dataform 1241 """ 1242 self.setTagData('title', text)
1243
1244 - def getInstructions(self):
1245 """ 1246 Return the instructions of dataform 1247 """ 1248 return self.getTagData('instructions')
1249
1250 - def setInstructions(self, text):
1251 """ 1252 Set the instructions of dataform 1253 """ 1254 self.setTagData('instructions', text)
1255
1256 - def addInstructions(self, text):
1257 """ 1258 Add one more instruction to the dataform 1259 """ 1260 self.addChild('instructions', {}, [text])
1261
1262 - def getField(self, name):
1263 """ 1264 Return the datafield object with name 'name' (if exists) 1265 """ 1266 return self.getTag('field', attrs={'var': name})
1267
1268 - def setField(self, name):
1269 """ 1270 Create if nessessary or get the existing datafield object with name 1271 'name' and return it 1272 """ 1273 f = self.getField(name) 1274 if f: 1275 return f 1276 return self.addChild(node=DataField(name))
1277
1278 - def asDict(self):
1279 """ 1280 Represent dataform as simple dictionary mapping of datafield names to 1281 their values 1282 """ 1283 ret = {} 1284 for field in self.getTags('field'): 1285 name = field.getAttr('var') 1286 typ = field.getType() 1287 if isinstance(typ, basestring) and typ.endswith('-multi'): 1288 val = [] 1289 for i in field.getTags('value'): 1290 val.append(i.getData()) 1291 else: 1292 val = field.getTagData('value') 1293 ret[name] = val 1294 if self.getTag('instructions'): 1295 ret['instructions'] = self.getInstructions() 1296 return ret
1297
1298 - def __getitem__(self, name):
1299 """ 1300 Simple dictionary interface for getting datafields values by their names 1301 """ 1302 item = self.getField(name) 1303 if item: 1304 return item.getValue() 1305 raise IndexError('No such field')
1306
1307 - def __setitem__(self, name, val):
1308 """ 1309 Simple dictionary interface for setting datafields values by their names 1310 """ 1311 return self.setField(name).setValue(val)
1312