protocol.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  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. # $Id$
  15. """
  16. Protocol module contains tools that is needed for processing of
  17. xmpp-related data structures.
  18. """
  19. from .simplexml import Node,ustr
  20. import time
  21. NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108
  22. NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033
  23. NS_ADMIN ='http://jabber.org/protocol/admin' # XEP-0133
  24. NS_ADMIN_ADD_USER =NS_ADMIN+'#add-user' # XEP-0133
  25. NS_ADMIN_DELETE_USER =NS_ADMIN+'#delete-user' # XEP-0133
  26. NS_ADMIN_DISABLE_USER =NS_ADMIN+'#disable-user' # XEP-0133
  27. NS_ADMIN_REENABLE_USER =NS_ADMIN+'#reenable-user' # XEP-0133
  28. NS_ADMIN_END_USER_SESSION =NS_ADMIN+'#end-user-session' # XEP-0133
  29. NS_ADMIN_GET_USER_PASSWORD =NS_ADMIN+'#get-user-password' # XEP-0133
  30. NS_ADMIN_CHANGE_USER_PASSWORD =NS_ADMIN+'#change-user-password' # XEP-0133
  31. NS_ADMIN_GET_USER_ROSTER =NS_ADMIN+'#get-user-roster' # XEP-0133
  32. NS_ADMIN_GET_USER_LASTLOGIN =NS_ADMIN+'#get-user-lastlogin' # XEP-0133
  33. NS_ADMIN_USER_STATS =NS_ADMIN+'#user-stats' # XEP-0133
  34. NS_ADMIN_EDIT_BLACKLIST =NS_ADMIN+'#edit-blacklist' # XEP-0133
  35. NS_ADMIN_EDIT_WHITELIST =NS_ADMIN+'#edit-whitelist' # XEP-0133
  36. NS_ADMIN_REGISTERED_USERS_NUM =NS_ADMIN+'#get-registered-users-num' # XEP-0133
  37. NS_ADMIN_DISABLED_USERS_NUM =NS_ADMIN+'#get-disabled-users-num' # XEP-0133
  38. NS_ADMIN_ONLINE_USERS_NUM =NS_ADMIN+'#get-online-users-num' # XEP-0133
  39. NS_ADMIN_ACTIVE_USERS_NUM =NS_ADMIN+'#get-active-users-num' # XEP-0133
  40. NS_ADMIN_IDLE_USERS_NUM =NS_ADMIN+'#get-idle-users-num' # XEP-0133
  41. NS_ADMIN_REGISTERED_USERS_LIST =NS_ADMIN+'#get-registered-users-list' # XEP-0133
  42. NS_ADMIN_DISABLED_USERS_LIST =NS_ADMIN+'#get-disabled-users-list' # XEP-0133
  43. NS_ADMIN_ONLINE_USERS_LIST =NS_ADMIN+'#get-online-users-list' # XEP-0133
  44. NS_ADMIN_ACTIVE_USERS_LIST =NS_ADMIN+'#get-active-users-list' # XEP-0133
  45. NS_ADMIN_IDLE_USERS_LIST =NS_ADMIN+'#get-idle-users-list' # XEP-0133
  46. NS_ADMIN_ANNOUNCE =NS_ADMIN+'#announce' # XEP-0133
  47. NS_ADMIN_SET_MOTD =NS_ADMIN+'#set-motd' # XEP-0133
  48. NS_ADMIN_EDIT_MOTD =NS_ADMIN+'#edit-motd' # XEP-0133
  49. NS_ADMIN_DELETE_MOTD =NS_ADMIN+'#delete-motd' # XEP-0133
  50. NS_ADMIN_SET_WELCOME =NS_ADMIN+'#set-welcome' # XEP-0133
  51. NS_ADMIN_DELETE_WELCOME =NS_ADMIN+'#delete-welcome' # XEP-0133
  52. NS_ADMIN_EDIT_ADMIN =NS_ADMIN+'#edit-admin' # XEP-0133
  53. NS_ADMIN_RESTART =NS_ADMIN+'#restart' # XEP-0133
  54. NS_ADMIN_SHUTDOWN =NS_ADMIN+'#shutdown' # XEP-0133
  55. NS_AGENTS ='jabber:iq:agents' # XEP-0094 (historical)
  56. NS_AMP ='http://jabber.org/protocol/amp' # XEP-0079
  57. NS_AMP_ERRORS =NS_AMP+'#errors' # XEP-0079
  58. NS_AUTH ='jabber:iq:auth' # XEP-0078
  59. NS_AVATAR ='jabber:iq:avatar' # XEP-0008 (historical)
  60. NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' # RFC 3920
  61. NS_BROWSE ='jabber:iq:browse' # XEP-0011 (historical)
  62. NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # XEP-0065
  63. NS_CAPS ='http://jabber.org/protocol/caps' # XEP-0115
  64. NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # XEP-0085
  65. NS_CLIENT ='jabber:client' # RFC 3921
  66. NS_COMMANDS ='http://jabber.org/protocol/commands' # XEP-0050
  67. NS_COMPONENT_ACCEPT ='jabber:component:accept' # XEP-0114
  68. NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0' # Jabberd2
  69. NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138
  70. NS_DATA ='jabber:x:data' # XEP-0004
  71. NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # XEP-0141
  72. NS_DATA_VALIDATE ='http://jabber.org/protocol/xdata-validate' # XEP-0122
  73. NS_DELAY ='jabber:x:delay' # XEP-0091 (deprecated)
  74. NS_DIALBACK ='jabber:server:dialback' # RFC 3921
  75. NS_DISCO ='http://jabber.org/protocol/disco' # XEP-0030
  76. NS_DISCO_INFO =NS_DISCO+'#info' # XEP-0030
  77. NS_DISCO_ITEMS =NS_DISCO+'#items' # XEP-0030
  78. NS_ENCRYPTED ='jabber:x:encrypted' # XEP-0027
  79. NS_EVENT ='jabber:x:event' # XEP-0022 (deprecated)
  80. NS_FEATURE ='http://jabber.org/protocol/feature-neg' # XEP-0020
  81. NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # XEP-0096
  82. NS_GATEWAY ='jabber:iq:gateway' # XEP-0100
  83. NS_GEOLOC ='http://jabber.org/protocol/geoloc' # XEP-0080
  84. NS_GROUPCHAT ='gc-1.0' # XEP-0045
  85. NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124
  86. NS_IBB ='http://jabber.org/protocol/ibb' # XEP-0047
  87. NS_INVISIBLE ='presence-invisible' # Jabberd2
  88. NS_IQ ='iq' # Jabberd2
  89. NS_LAST ='jabber:iq:last' # XEP-0012
  90. NS_MESSAGE ='message' # Jabberd2
  91. NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107
  92. NS_MUC ='http://jabber.org/protocol/muc' # XEP-0045
  93. NS_MUC_ADMIN =NS_MUC+'#admin' # XEP-0045
  94. NS_MUC_OWNER =NS_MUC+'#owner' # XEP-0045
  95. NS_MUC_UNIQUE =NS_MUC+'#unique' # XEP-0045
  96. NS_MUC_USER =NS_MUC+'#user' # XEP-0045
  97. NS_MUC_REGISTER =NS_MUC+'#register' # XEP-0045
  98. NS_MUC_REQUEST =NS_MUC+'#request' # XEP-0045
  99. NS_MUC_ROOMCONFIG =NS_MUC+'#roomconfig' # XEP-0045
  100. NS_MUC_ROOMINFO =NS_MUC+'#roominfo' # XEP-0045
  101. NS_MUC_ROOMS =NS_MUC+'#rooms' # XEP-0045
  102. NS_MUC_TRAFIC =NS_MUC+'#traffic' # XEP-0045
  103. NS_NICK ='http://jabber.org/protocol/nick' # XEP-0172
  104. NS_OFFLINE ='http://jabber.org/protocol/offline' # XEP-0013
  105. NS_PHYSLOC ='http://jabber.org/protocol/physloc' # XEP-0112
  106. NS_PRESENCE ='presence' # Jabberd2
  107. NS_PRIVACY ='jabber:iq:privacy' # RFC 3921
  108. NS_PRIVATE ='jabber:iq:private' # XEP-0049
  109. NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060
  110. NS_REGISTER ='jabber:iq:register' # XEP-0077
  111. NS_RC ='http://jabber.org/protocol/rc' # XEP-0146
  112. NS_ROSTER ='jabber:iq:roster' # RFC 3921
  113. NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144
  114. NS_RPC ='jabber:iq:rpc' # XEP-0009
  115. NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl' # RFC 3920
  116. NS_SEARCH ='jabber:iq:search' # XEP-0055
  117. NS_SERVER ='jabber:server' # RFC 3921
  118. NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session' # RFC 3921
  119. NS_SI ='http://jabber.org/protocol/si' # XEP-0096
  120. NS_SI_PUB ='http://jabber.org/protocol/sipub' # XEP-0137
  121. NS_SIGNED ='jabber:x:signed' # XEP-0027
  122. NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas' # RFC 3920
  123. NS_STREAMS ='http://etherx.jabber.org/streams' # RFC 3920
  124. NS_TIME ='jabber:iq:time' # XEP-0090 (deprecated)
  125. NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' # RFC 3920
  126. NS_VACATION ='http://jabber.org/protocol/vacation' # XEP-0109
  127. NS_VCARD ='vcard-temp' # XEP-0054
  128. NS_VCARD_UPDATE ='vcard-temp:x:update' # XEP-0153
  129. NS_VERSION ='jabber:iq:version' # XEP-0092
  130. NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130
  131. NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071
  132. NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' # RFC 3920
  133. xmpp_stream_error_conditions="""
  134. bad-format -- -- -- The entity has sent XML that cannot be processed.
  135. 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.
  136. conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream.
  137. connection-timeout -- -- -- The entity has not generated any traffic over the stream for some period of time.
  138. 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.
  139. 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.
  140. improper-addressing -- -- -- A stanza sent between two servers lacks a 'to' or 'from' attribute (or the attribute has no value).
  141. internal-server-error -- -- -- The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream.
  142. 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.
  143. invalid-id -- -- -- The stream ID or dialback ID is invalid or does not match an ID previously provided.
  144. 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".
  145. invalid-xml -- -- -- The entity has sent invalid XML over the stream to a server that performs validation.
  146. 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.
  147. policy-violation -- -- -- The entity has violated some local service policy.
  148. remote-connection-failed -- -- -- The server is unable to properly connect to a remote resource that is required for authentication or authorization.
  149. resource-constraint -- -- -- The server lacks the system resources necessary to service the stream.
  150. restricted-xml -- -- -- The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character.
  151. see-other-host -- -- -- The server will not provide service to the initiating entity but is redirecting traffic to another host.
  152. system-shutdown -- -- -- The server is being shut down and all active streams are being closed.
  153. undefined-condition -- -- -- The error condition is not one of those defined by the other conditions in this list.
  154. unsupported-encoding -- -- -- The initiating entity has encoded the stream in an encoding that is not supported by the server.
  155. unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server.
  156. 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.
  157. xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed."""
  158. xmpp_stanza_error_conditions="""
  159. bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed.
  160. conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address.
  161. feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed.
  162. forbidden -- 403 -- auth -- The requesting entity does not possess the required permissions to perform the action.
  163. gone -- 302 -- modify -- The recipient or server can no longer be contacted at this address.
  164. internal-server-error -- 500 -- wait -- The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error.
  165. item-not-found -- 404 -- cancel -- The addressed JID or item requested cannot be found.
  166. 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.
  167. 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.
  168. not-allowed -- 405 -- cancel -- The recipient or server does not allow any entity to perform the action.
  169. not-authorized -- 401 -- auth -- The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials.
  170. payment-required -- 402 -- auth -- The requesting entity is not authorized to access the requested service because payment is required.
  171. recipient-unavailable -- 404 -- wait -- The intended recipient is temporarily unavailable.
  172. redirect -- 302 -- modify -- The recipient or server is redirecting requests for this information to another entity.
  173. registration-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because registration is required.
  174. 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.
  175. 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.
  176. resource-constraint -- 500 -- wait -- The server or recipient lacks the system resources necessary to service the request.
  177. service-unavailable -- 503 -- cancel -- The server or recipient does not currently provide the requested service.
  178. subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required.
  179. undefined-condition -- 500 -- --
  180. 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)."""
  181. sasl_error_conditions="""
  182. aborted -- -- -- The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element.
  183. 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.
  184. 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.
  185. 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.
  186. 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.
  187. 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.
  188. 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."""
  189. ERRORS,_errorcodes={},{}
  190. for ns,errname,errpool in [(NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions),
  191. (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions),
  192. (NS_SASL ,'SASL' ,sasl_error_conditions)]:
  193. for err in errpool.split('\n')[1:]:
  194. cond,code,typ,text=err.split(' -- ')
  195. name=errname+'_'+cond.upper().replace('-','_')
  196. locals()[name]=ns+' '+cond
  197. ERRORS[ns+' '+cond]=[code,typ,text]
  198. if code: _errorcodes[code]=cond
  199. del ns,errname,errpool,err,cond,code,typ,text
  200. def isResultNode(node):
  201. """ Returns true if the node is a positive reply. """
  202. return node and node.getType()=='result'
  203. def isErrorNode(node):
  204. """ Returns true if the node is a negative reply. """
  205. return node and node.getType()=='error'
  206. class NodeProcessed(Exception):
  207. """ Exception that should be raised by handler when the handling should be stopped. """
  208. class StreamError(Exception):
  209. """ Base exception class for stream errors."""
  210. class BadFormat(StreamError): pass
  211. class BadNamespacePrefix(StreamError): pass
  212. class Conflict(StreamError): pass
  213. class ConnectionTimeout(StreamError): pass
  214. class HostGone(StreamError): pass
  215. class HostUnknown(StreamError): pass
  216. class ImproperAddressing(StreamError): pass
  217. class InternalServerError(StreamError): pass
  218. class InvalidFrom(StreamError): pass
  219. class InvalidID(StreamError): pass
  220. class InvalidNamespace(StreamError): pass
  221. class InvalidXML(StreamError): pass
  222. class NotAuthorized(StreamError): pass
  223. class PolicyViolation(StreamError): pass
  224. class RemoteConnectionFailed(StreamError): pass
  225. class ResourceConstraint(StreamError): pass
  226. class RestrictedXML(StreamError): pass
  227. class SeeOtherHost(StreamError): pass
  228. class SystemShutdown(StreamError): pass
  229. class UndefinedCondition(StreamError): pass
  230. class UnsupportedEncoding(StreamError): pass
  231. class UnsupportedStanzaType(StreamError): pass
  232. class UnsupportedVersion(StreamError): pass
  233. class XMLNotWellFormed(StreamError): pass
  234. stream_exceptions = {'bad-format': BadFormat,
  235. 'bad-namespace-prefix': BadNamespacePrefix,
  236. 'conflict': Conflict,
  237. 'connection-timeout': ConnectionTimeout,
  238. 'host-gone': HostGone,
  239. 'host-unknown': HostUnknown,
  240. 'improper-addressing': ImproperAddressing,
  241. 'internal-server-error': InternalServerError,
  242. 'invalid-from': InvalidFrom,
  243. 'invalid-id': InvalidID,
  244. 'invalid-namespace': InvalidNamespace,
  245. 'invalid-xml': InvalidXML,
  246. 'not-authorized': NotAuthorized,
  247. 'policy-violation': PolicyViolation,
  248. 'remote-connection-failed': RemoteConnectionFailed,
  249. 'resource-constraint': ResourceConstraint,
  250. 'restricted-xml': RestrictedXML,
  251. 'see-other-host': SeeOtherHost,
  252. 'system-shutdown': SystemShutdown,
  253. 'undefined-condition': UndefinedCondition,
  254. 'unsupported-encoding': UnsupportedEncoding,
  255. 'unsupported-stanza-type': UnsupportedStanzaType,
  256. 'unsupported-version': UnsupportedVersion,
  257. 'xml-not-well-formed': XMLNotWellFormed}
  258. class JID:
  259. """ JID object. JID can be built from string, modified, compared, serialised into string. """
  260. def __init__(self, jid=None, node='', domain='', resource=''):
  261. """ Constructor. JID can be specified as string (jid argument) or as separate parts.
  262. Examples:
  263. JID('node@domain/resource')
  264. JID(node='node',domain='domain.org')
  265. """
  266. if not jid and not domain: raise ValueError('JID must contain at least domain name')
  267. elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource
  268. elif domain: self.node,self.domain,self.resource=node,domain,resource
  269. else:
  270. if jid.find('@')+1: self.node,jid=jid.split('@',1)
  271. else: self.node=''
  272. if jid.find('/')+1: self.domain,self.resource=jid.split('/',1)
  273. else: self.domain,self.resource=jid,''
  274. def getNode(self):
  275. """ Return the node part of the JID """
  276. return self.node
  277. def setNode(self,node):
  278. """ Set the node part of the JID to new value. Specify None to remove the node part."""
  279. self.node=node.lower()
  280. def getDomain(self):
  281. """ Return the domain part of the JID """
  282. return self.domain
  283. def setDomain(self,domain):
  284. """ Set the domain part of the JID to new value."""
  285. self.domain=domain.lower()
  286. def getResource(self):
  287. """ Return the resource part of the JID """
  288. return self.resource
  289. def setResource(self,resource):
  290. """ Set the resource part of the JID to new value. Specify None to remove the resource part."""
  291. self.resource=resource
  292. def getStripped(self):
  293. """ Return the bare representation of JID. I.e. string value w/o resource. """
  294. return self.__str__(0)
  295. def __eq__(self, other):
  296. """ Compare the JID to another instance or to string for equality. """
  297. try: other=JID(other)
  298. except ValueError: return 0
  299. return self.resource==other.resource and self.__str__(0) == other.__str__(0)
  300. def __ne__(self, other):
  301. """ Compare the JID to another instance or to string for non-equality. """
  302. return not self.__eq__(other)
  303. def bareMatch(self, other):
  304. """ Compare the node and domain parts of the JID's for equality. """
  305. return self.__str__(0) == JID(other).__str__(0)
  306. def __str__(self,wresource=1):
  307. """ Serialise JID into string. """
  308. if self.node: jid=self.node+'@'+self.domain
  309. else: jid=self.domain
  310. if wresource and self.resource: return jid+'/'+self.resource
  311. return jid
  312. def __hash__(self):
  313. """ Produce hash of the JID, Allows to use JID objects as keys of the dictionary. """
  314. return hash(self.__str__())
  315. class Protocol(Node):
  316. """ A "stanza" object class. Contains methods that are common for presences, iqs and messages. """
  317. def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
  318. """ Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'.
  319. to is the value of 'to' attribure, 'typ' - 'type' attribute
  320. frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition
  321. timestamp - the time value that needs to be stamped over stanza
  322. xmlns - namespace of top stanza node
  323. node - parsed or unparsed stana to be taken as prototype.
  324. """
  325. if not attrs: attrs={}
  326. if to: attrs['to']=to
  327. if frm: attrs['from']=frm
  328. if typ: attrs['type']=typ
  329. Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
  330. if not node and xmlns: self.setNamespace(xmlns)
  331. if self['to']: self.setTo(self['to'])
  332. if self['from']: self.setFrom(self['from'])
  333. if node and type(self)==type(node) and self.__class__==node.__class__ and 'id' in self.attrs: del self.attrs['id']
  334. self.timestamp=None
  335. for x in self.getTags('x',namespace=NS_DELAY):
  336. try:
  337. if not self.getTimestamp() or x.getAttr('stamp')<self.getTimestamp(): self.setTimestamp(x.getAttr('stamp'))
  338. except: pass
  339. if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=''
  340. def getTo(self):
  341. """ Return value of the 'to' attribute. """
  342. try: return self['to']
  343. except: return None
  344. def getFrom(self):
  345. """ Return value of the 'from' attribute. """
  346. try: return self['from']
  347. except: return None
  348. def getTimestamp(self):
  349. """ Return the timestamp in the 'yyyymmddThhmmss' format. """
  350. return self.timestamp
  351. def getID(self):
  352. """ Return the value of the 'id' attribute. """
  353. return self.getAttr('id')
  354. def setTo(self,val):
  355. """ Set the value of the 'to' attribute. """
  356. self.setAttr('to', JID(val))
  357. def getType(self):
  358. """ Return the value of the 'type' attribute. """
  359. return self.getAttr('type')
  360. def setFrom(self,val):
  361. """ Set the value of the 'from' attribute. """
  362. self.setAttr('from', JID(val))
  363. def setType(self,val):
  364. """ Set the value of the 'type' attribute. """
  365. self.setAttr('type', val)
  366. def setID(self,val):
  367. """ Set the value of the 'id' attribute. """
  368. self.setAttr('id', val)
  369. def getError(self):
  370. """ Return the error-condition (if present) or the textual description of the error (otherwise). """
  371. errtag=self.getTag('error')
  372. if errtag:
  373. for tag in errtag.getChildren():
  374. if tag.getName()!='text': return tag.getName()
  375. return errtag.getData()
  376. def getErrorCode(self):
  377. """ Return the error code. Obsolette. """
  378. return self.getTagAttr('error','code')
  379. def setError(self,error,code=None):
  380. """ Set the error code. Obsolette. Use error-conditions instead. """
  381. if code:
  382. if str(code) in list(_errorcodes.keys()): error=ErrorNode(_errorcodes[str(code)],text=error)
  383. else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error)
  384. elif type(error) in [type(''),type('')]: error=ErrorNode(error)
  385. self.setType('error')
  386. self.addChild(node=error)
  387. def setTimestamp(self,val=None):
  388. """Set the timestamp. timestamp should be the yyyymmddThhmmss string."""
  389. if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime())
  390. self.timestamp=val
  391. self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY)
  392. def getProperties(self):
  393. """ Return the list of namespaces to which belongs the direct childs of element"""
  394. props=[]
  395. for child in self.getChildren():
  396. prop=child.getNamespace()
  397. if prop not in props: props.append(prop)
  398. return props
  399. def __setitem__(self,item,val):
  400. """ Set the item 'item' to the value 'val'."""
  401. if item in ['to','from']: val=JID(val)
  402. return self.setAttr(item,val)
  403. class Message(Protocol):
  404. """ XMPP Message stanza - "push" mechanism."""
  405. def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
  406. """ Create message object. You can specify recipient, text of message, type of message
  407. any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
  408. Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """
  409. Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
  410. if body: self.setBody(body)
  411. if subject: self.setSubject(subject)
  412. def getBody(self):
  413. """ Returns text of the message. """
  414. return self.getTagData('body')
  415. def getSubject(self):
  416. """ Returns subject of the message. """
  417. return self.getTagData('subject')
  418. def getThread(self):
  419. """ Returns thread of the message. """
  420. return self.getTagData('thread')
  421. def setBody(self,val):
  422. """ Sets the text of the message. """
  423. self.setTagData('body',val)
  424. def setSubject(self,val):
  425. """ Sets the subject of the message. """
  426. self.setTagData('subject',val)
  427. def setThread(self,val):
  428. """ Sets the thread of the message. """
  429. self.setTagData('thread',val)
  430. def buildReply(self,text=None):
  431. """ Builds and returns another message object with specified text.
  432. The to, from, thread and type properties of new message are pre-set as reply to this message. """
  433. m=Message(to=self.getFrom(),frm=self.getTo(),body=text,typ=self.getType())
  434. th=self.getThread()
  435. if th: m.setThread(th)
  436. return m
  437. class Presence(Protocol):
  438. """ XMPP Presence object."""
  439. def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
  440. """ Create presence object. You can specify recipient, type of message, priority, show and status values
  441. any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
  442. Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. """
  443. Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
  444. if priority: self.setPriority(priority)
  445. if show: self.setShow(show)
  446. if status: self.setStatus(status)
  447. def getPriority(self):
  448. """ Returns the priority of the message. """
  449. return self.getTagData('priority')
  450. def getShow(self):
  451. """ Returns the show value of the message. """
  452. return self.getTagData('show')
  453. def getStatus(self):
  454. """ Returns the status string of the message. """
  455. return self.getTagData('status')
  456. def setPriority(self,val):
  457. """ Sets the priority of the message. """
  458. self.setTagData('priority',val)
  459. def setShow(self,val):
  460. """ Sets the show value of the message. """
  461. self.setTagData('show',val)
  462. def setStatus(self,val):
  463. """ Sets the status string of the message. """
  464. self.setTagData('status',val)
  465. def _muc_getItemAttr(self,tag,attr):
  466. for xtag in self.getTags('x'):
  467. for child in xtag.getTags(tag):
  468. return child.getAttr(attr)
  469. def _muc_getSubTagDataAttr(self,tag,attr):
  470. for xtag in self.getTags('x'):
  471. for child in xtag.getTags('item'):
  472. for cchild in child.getTags(tag):
  473. return cchild.getData(),cchild.getAttr(attr)
  474. return None,None
  475. def getRole(self):
  476. """Returns the presence role (for groupchat)"""
  477. return self._muc_getItemAttr('item','role')
  478. def getAffiliation(self):
  479. """Returns the presence affiliation (for groupchat)"""
  480. return self._muc_getItemAttr('item','affiliation')
  481. def getNick(self):
  482. """Returns the nick value (for nick change in groupchat)"""
  483. return self._muc_getItemAttr('item','nick')
  484. def getJid(self):
  485. """Returns the presence jid (for groupchat)"""
  486. return self._muc_getItemAttr('item','jid')
  487. def getReason(self):
  488. """Returns the reason of the presence (for groupchat)"""
  489. return self._muc_getSubTagDataAttr('reason','')[0]
  490. def getActor(self):
  491. """Returns the reason of the presence (for groupchat)"""
  492. return self._muc_getSubTagDataAttr('actor','jid')[1]
  493. def getStatusCode(self):
  494. """Returns the status code of the presence (for groupchat)"""
  495. return self._muc_getItemAttr('status','code')
  496. class Iq(Protocol):
  497. """ XMPP Iq object - get/set dialog mechanism. """
  498. def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
  499. """ Create Iq object. You can specify type, query namespace
  500. any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
  501. Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. """
  502. Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
  503. if payload: self.setQueryPayload(payload)
  504. if queryNS: self.setQueryNS(queryNS)
  505. def getQuery(self):
  506. """ Return the IQ's child element if it exists, None otherwise."""
  507. typ = self.getType()
  508. query=None
  509. for child in self.getChildren():
  510. if ('error' != typ) or ('error' != child.getName()):
  511. query=child
  512. break
  513. return query
  514. def getQueryNS(self):
  515. """ Return the namespace of the 'query' child element."""
  516. tag=self.getQuery()
  517. if tag: return tag.getNamespace()
  518. def getQuerynode(self):
  519. """ Return the 'node' attribute value of the 'query' child element."""
  520. return self.getQuery().getAttr('node')
  521. def getQueryPayload(self):
  522. """ Return the 'query' child element payload."""
  523. tag=self.getQuery()
  524. if tag: return tag.getPayload()
  525. def getQueryChildren(self):
  526. """ Return the 'query' child element child nodes."""
  527. tag=self.getQuery()
  528. if tag: return tag.getChildren()
  529. def setQuery(self, name=None):
  530. """ Change the name of the query node, creating it if needed. Keep the existing name if none is given (use 'query' if it's a creation). Return the query node."""
  531. query=self.getQuery()
  532. if query is None:
  533. query=self.addChild('query')
  534. if name is not None:
  535. query.setName(name)
  536. return query
  537. def setQueryNS(self,namespace):
  538. """ Set the namespace of the 'query' child element."""
  539. self.setQuery().setNamespace(namespace)
  540. def setQueryPayload(self,payload):
  541. """ Set the 'query' child element payload."""
  542. self.setQuery().setPayload(payload)
  543. def setQuerynode(self,node):
  544. """ Set the 'node' attribute value of the 'query' child element."""
  545. self.setQuery().setAttr('node',node)
  546. def buildReply(self,typ):
  547. """ Builds and returns another Iq object of specified type.
  548. The to, from and query child node of new Iq are pre-set as reply to this Iq. """
  549. iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()})
  550. iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
  551. return iq
  552. class ErrorNode(Node):
  553. """ XMPP-style error element.
  554. In the case of stanza error should be attached to XMPP stanza.
  555. In the case of stream-level errors should be used separately. """
  556. def __init__(self,name,code=None,typ=None,text=None):
  557. """ Create new error node object.
  558. Mandatory parameter: name - name of error condition.
  559. Optional parameters: code, typ, text. Used for backwards compartibility with older XMPP protocol."""
  560. if name in ERRORS:
  561. cod,type,txt=ERRORS[name]
  562. ns=name.split()[0]
  563. else: cod,ns,type,txt='500',NS_STANZAS,'cancel',''
  564. if typ: type=typ
  565. if code: cod=code
  566. if text: txt=text
  567. Node.__init__(self,'error',{},[Node(name)])
  568. if type: self.setAttr('type',type)
  569. if not cod: self.setName('stream:error')
  570. if txt: self.addChild(node=Node(ns+' text',{},[txt]))
  571. if cod: self.setAttr('code',cod)
  572. class Error(Protocol):
  573. """ Used to quickly transform received stanza into error reply."""
  574. def __init__(self,node,error,reply=1):
  575. """ Create error reply basing on the received 'node' stanza and the 'error' error condition.
  576. If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping)
  577. specify the 'reply' argument as false."""
  578. if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node)
  579. else: Protocol.__init__(self,node=node)
  580. self.setError(error)
  581. if node.getType()=='error': self.__str__=self.__dupstr__
  582. def __dupstr__(self,dup1=None,dup2=None):
  583. """ Dummy function used as preventor of creating error node in reply to error node.
  584. I.e. you will not be able to serialise "double" error into string.
  585. """
  586. return ''
  587. class DataField(Node):
  588. """ This class is used in the DataForm class to describe the single data item.
  589. If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122)
  590. then you will need to work with instances of this class. """
  591. def __init__(self,name=None,value=None,typ=None,required=0,label=None,desc=None,options=[],node=None):
  592. """ Create new data field of specified name,value and type.
  593. Also 'required','desc' and 'options' fields can be set.
  594. Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled.
  595. """
  596. Node.__init__(self,'field',node=node)
  597. if name: self.setVar(name)
  598. if type(value) in [list,tuple]: self.setValues(value)
  599. elif value: self.setValue(value)
  600. if typ: self.setType(typ)
  601. elif not typ and not node: self.setType('text-single')
  602. if required: self.setRequired(required)
  603. if label: self.setLabel(label)
  604. if desc: self.setDesc(desc)
  605. if options: self.setOptions(options)
  606. def setRequired(self,req=1):
  607. """ Change the state of the 'required' flag. """
  608. if req: self.setTag('required')
  609. else:
  610. try: self.delChild('required')
  611. except ValueError: return
  612. def isRequired(self):
  613. """ Returns in this field a required one. """
  614. return self.getTag('required')
  615. def setLabel(self,label):
  616. """ Set the label of this field. """
  617. self.setAttr('label',label)
  618. def getLabel(self):
  619. """ Return the label of this field. """
  620. return self.getAttr('label')
  621. def setDesc(self,desc):
  622. """ Set the description of this field. """
  623. self.setTagData('desc',desc)
  624. def getDesc(self):
  625. """ Return the description of this field. """
  626. return self.getTagData('desc')
  627. def setValue(self,val):
  628. """ Set the value of this field. """
  629. self.setTagData('value',val)
  630. def getValue(self):
  631. return self.getTagData('value')
  632. def setValues(self,lst):
  633. """ Set the values of this field as values-list.
  634. Replaces all previous filed values! If you need to just add a value - use addValue method."""
  635. while self.getTag('value'): self.delChild('value')
  636. for val in lst: self.addValue(val)
  637. def addValue(self,val):
  638. """ Add one more value to this field. Used in 'get' iq's or such."""
  639. self.addChild('value',{},[val])
  640. def getValues(self):
  641. """ Return the list of values associated with this field."""
  642. ret=[]
  643. for tag in self.getTags('value'): ret.append(tag.getData())
  644. return ret
  645. def getOptions(self):
  646. """ Return label-option pairs list associated with this field."""
  647. ret=[]
  648. for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')])
  649. return ret
  650. def setOptions(self,lst):
  651. """ Set label-option pairs list associated with this field."""
  652. while self.getTag('option'): self.delChild('option')
  653. for opt in lst: self.addOption(opt)
  654. def addOption(self,opt):
  655. """ Add one more label-option pair to this field."""
  656. if type(opt) in [str,str]: self.addChild('option').setTagData('value',opt)
  657. else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1])
  658. def getType(self):
  659. """ Get type of this field. """
  660. return self.getAttr('type')
  661. def setType(self,val):
  662. """ Set type of this field. """
  663. return self.setAttr('type',val)
  664. def getVar(self):
  665. """ Get 'var' attribute value of this field. """
  666. return self.getAttr('var')
  667. def setVar(self,val):
  668. """ Set 'var' attribute value of this field. """
  669. return self.setAttr('var',val)
  670. class DataReported(Node):
  671. """ This class is used in the DataForm class to describe the 'reported data field' data items which are used in
  672. 'multiple item form results' (as described in XEP-0004).
  673. Represents the fields that will be returned from a search. This information is useful when
  674. you try to use the jabber:iq:search namespace to return dynamic form information.
  675. """
  676. def __init__(self,node=None):
  677. """ Create new empty 'reported data' field. However, note that, according XEP-0004:
  678. * It MUST contain one or more DataFields.
  679. * Contained DataFields SHOULD possess a 'type' and 'label' attribute in addition to 'var' attribute
  680. * Contained DataFields SHOULD NOT contain a <value/> element.
  681. Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new
  682. dataitem.
  683. """
  684. Node.__init__(self,'reported',node=node)
  685. if node:
  686. newkids=[]
  687. for n in self.getChildren():
  688. if n.getName()=='field': newkids.append(DataField(node=n))
  689. else: newkids.append(n)
  690. self.kids=newkids
  691. def getField(self,name):
  692. """ Return the datafield object with name 'name' (if exists). """
  693. return self.getTag('field',attrs={'var':name})
  694. def setField(self,name,typ=None,label=None):
  695. """ Create if nessessary or get the existing datafield object with name 'name' and return it.
  696. If created, attributes 'type' and 'label' are applied to new datafield."""
  697. f=self.getField(name)
  698. if f: return f
  699. return self.addChild(node=DataField(name,None,typ,0,label))
  700. def asDict(self):
  701. """ Represent dataitem as simple dictionary mapping of datafield names to their values."""
  702. ret={}
  703. for field in self.getTags('field'):
  704. name=field.getAttr('var')
  705. typ=field.getType()
  706. if isinstance(typ,str) and typ[-6:]=='-multi':
  707. val=[]
  708. for i in field.getTags('value'): val.append(i.getData())
  709. else: val=field.getTagData('value')
  710. ret[name]=val
  711. if self.getTag('instructions'): ret['instructions']=self.getInstructions()
  712. return ret
  713. def __getitem__(self,name):
  714. """ Simple dictionary interface for getting datafields values by their names."""
  715. item=self.getField(name)
  716. if item: return item.getValue()
  717. raise IndexError('No such field')
  718. def __setitem__(self,name,val):
  719. """ Simple dictionary interface for setting datafields values by their names."""
  720. return self.setField(name).setValue(val)
  721. class DataItem(Node):
  722. """ This class is used in the DataForm class to describe data items which are used in 'multiple
  723. item form results' (as described in XEP-0004).
  724. """
  725. def __init__(self,node=None):
  726. """ Create new empty data item. However, note that, according XEP-0004, DataItem MUST contain ALL
  727. DataFields described in DataReported.
  728. Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new
  729. dataitem.
  730. """
  731. Node.__init__(self,'item',node=node)
  732. if node:
  733. newkids=[]
  734. for n in self.getChildren():
  735. if n.getName()=='field': newkids.append(DataField(node=n))
  736. else: newkids.append(n)
  737. self.kids=newkids
  738. def getField(self,name):
  739. """ Return the datafield object with name 'name' (if exists). """
  740. return self.getTag('field',attrs={'var':name})
  741. def setField(self,name):
  742. """ Create if nessessary or get the existing datafield object with name 'name' and return it. """
  743. f=self.getField(name)
  744. if f: return f
  745. return self.addChild(node=DataField(name))
  746. def asDict(self):
  747. """ Represent dataitem as simple dictionary mapping of datafield names to their values."""
  748. ret={}
  749. for field in self.getTags('field'):
  750. name=field.getAttr('var')
  751. typ=field.getType()
  752. if isinstance(typ,str) and typ[-6:]=='-multi':
  753. val=[]
  754. for i in field.getTags('value'): val.append(i.getData())
  755. else: val=field.getTagData('value')
  756. ret[name]=val
  757. if self.getTag('instructions'): ret['instructions']=self.getInstructions()
  758. return ret
  759. def __getitem__(self,name):
  760. """ Simple dictionary interface for getting datafields values by their names."""
  761. item=self.getField(name)
  762. if item: return item.getValue()
  763. raise IndexError('No such field')
  764. def __setitem__(self,name,val):
  765. """ Simple dictionary interface for setting datafields values by their names."""
  766. return self.setField(name).setValue(val)
  767. class DataForm(Node):
  768. """ DataForm class. Used for manipulating dataforms in XMPP.
  769. Relevant XEPs: 0004, 0068, 0122.
  770. Can be used in disco, pub-sub and many other applications."""
  771. def __init__(self, typ=None, data=[], title=None, node=None):
  772. """
  773. Create new dataform of type 'typ'; 'data' is the list of DataReported,
  774. DataItem and DataField instances that this dataform contains; 'title'
  775. is the title string.
  776. You can specify the 'node' argument as the other node to be used as
  777. base for constructing this dataform.
  778. title and instructions is optional and SHOULD NOT contain newlines.
  779. Several instructions MAY be present.
  780. 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' )
  781. 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively.
  782. 'cancel' form can not contain any fields. All other forms contains AT LEAST one field.
  783. 'title' MAY be included in forms of type "form" and "result"
  784. """
  785. Node.__init__(self,'x',node=node)
  786. if node:
  787. newkids=[]
  788. for n in self.getChildren():
  789. if n.getName()=='field': newkids.append(DataField(node=n))
  790. elif n.getName()=='item': newkids.append(DataItem(node=n))
  791. elif n.getName()=='reported': newkids.append(DataReported(node=n))
  792. else: newkids.append(n)
  793. self.kids=newkids
  794. if typ: self.setType(typ)
  795. self.setNamespace(NS_DATA)
  796. if title: self.setTitle(title)
  797. if type(data)==type({}):
  798. newdata=[]
  799. for name in list(data.keys()): newdata.append(DataField(name,data[name]))
  800. data=newdata
  801. for child in data:
  802. if type(child) in [type(''),type('')]: self.addInstructions(child)
  803. elif child.__class__.__name__=='DataField': self.kids.append(child)
  804. elif child.__class__.__name__=='DataItem': self.kids.append(child)
  805. elif child.__class__.__name__=='DataReported': self.kids.append(child)
  806. else: self.kids.append(DataField(node=child))
  807. def getType(self):
  808. """ Return the type of dataform. """
  809. return self.getAttr('type')
  810. def setType(self,typ):
  811. """ Set the type of dataform. """
  812. self.setAttr('type',typ)
  813. def getTitle(self):
  814. """ Return the title of dataform. """
  815. return self.getTagData('title')
  816. def setTitle(self,text):
  817. """ Set the title of dataform. """
  818. self.setTagData('title',text)
  819. def getInstructions(self):
  820. """ Return the instructions of dataform. """
  821. return self.getTagData('instructions')
  822. def setInstructions(self,text):
  823. """ Set the instructions of dataform. """
  824. self.setTagData('instructions',text)
  825. def addInstructions(self,text):
  826. """ Add one more instruction to the dataform. """
  827. self.addChild('instructions',{},[text])
  828. def getField(self,name):
  829. """ Return the datafield object with name 'name' (if exists). """
  830. return self.getTag('field',attrs={'var':name})
  831. def setField(self,name):
  832. """ Create if nessessary or get the existing datafield object with name 'name' and return it. """
  833. f=self.getField(name)
  834. if f: return f
  835. return self.addChild(node=DataField(name))
  836. def asDict(self):
  837. """ Represent dataform as simple dictionary mapping of datafield names to their values."""
  838. ret={}
  839. for field in self.getTags('field'):
  840. name=field.getAttr('var')
  841. typ=field.getType()
  842. if isinstance(typ,str) and typ[-6:]=='-multi':
  843. val=[]
  844. for i in field.getTags('value'): val.append(i.getData())
  845. else: val=field.getTagData('value')
  846. ret[name]=val
  847. if self.getTag('instructions'): ret['instructions']=self.getInstructions()
  848. return ret
  849. def __getitem__(self,name):
  850. """ Simple dictionary interface for getting datafields values by their names."""
  851. item=self.getField(name)
  852. if item: return item.getValue()
  853. raise IndexError('No such field')
  854. def __setitem__(self,name,val):
  855. """ Simple dictionary interface for setting datafields values by their names."""
  856. return self.setField(name).setValue(val)