class Presence(Stanza):
    """Wraper object for <presence /> stanzas."""
    stanza_type="presence"
    def __init__(self, xmlnode = None, from_jid = None, to_jid = None, stanza_type = None, 
            stanza_id = None, show = None, status = None, priority = 0,
            error = None, error_cond = None, stream = None):
        """Initialize a `Presence` object.

        :Parameters:
            - `xmlnode`: XML node to_jid be wrapped into the `Presence` object
              or other Presence object to be copied. If not given then new
              presence stanza is created using following parameters.
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: None, "available", "unavailable",
              "subscribe", "subscribed", "unsubscribe", "unsubscribed" or
              "error". "available" is automaticaly changed to_jid None.
            - `stanza_id`: stanza id -- value of stanza's "id" attribute
            - `show`: "show" field of presence stanza. One of: None, "away",
              "xa", "dnd", "chat".
            - `status`: descriptive text for the presence stanza.
            - `priority`: presence priority.
            - `error_cond`: error condition name. Ignored if `stanza_type` is not "error"
        :Types:
            - `xmlnode`: `unicode` or `libxml2.xmlNode` or `Stanza`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `show`: `unicode`
            - `status`: `unicode`
            - `priority`: `unicode`
            - `error_cond`: `unicode`"""
        self.xmlnode=None
        if isinstance(xmlnode,Presence):
            pass
        elif isinstance(xmlnode,Stanza):
            raise TypeError,"Couldn't make Presence from other Stanza"
        elif isinstance(xmlnode,libxml2.xmlNode):
            pass
        elif xmlnode is not None:
            raise TypeError,"Couldn't make Presence from %r" % (type(xmlnode),)

        if stanza_type and stanza_type not in presence_types:
            raise ValueError, "Invalid presence type: %r" % (type,)

        if stanza_type=="available":
            stanza_type=None

        if xmlnode is None:
            xmlnode="presence"

        Stanza.__init__(self, xmlnode, from_jid = from_jid, to_jid = to_jid, stanza_type = stanza_type,
                stanza_id = stanza_id, error = error, error_cond = error_cond, stream = stream)

        if show:
            self.xmlnode.newTextChild(common_ns,"show",to_utf8(show))
        if status:
            self.xmlnode.newTextChild(common_ns,"status",to_utf8(status))
        if priority and priority!=0:
            self.xmlnode.newTextChild(common_ns,"priority",to_utf8(unicode(priority)))

    def copy(self):
        """Create a deep copy of the presence stanza.

        :returntype: `Presence`"""
        return Presence(self)

    def set_status(self,status):
        """Change presence status description.

        :Parameters:
            - `status`: descriptive text for the presence stanza.
        :Types:
            - `status`: `unicode`"""
        n=self.xpath_eval("ns:status")
        if not status:
            if n:
                n[0].unlinkNode()
                n[0].freeNode()
            else:
                return
        if n:
            n[0].setContent(to_utf8(status))
        else:
            self.xmlnode.newTextChild(common_ns,"status",to_utf8(status))

    def get_status(self):
        """Get presence status description.

        :return: value of stanza's <status/> field.
        :returntype: `unicode`"""
        n=self.xpath_eval("ns:status")
        if n:
            return from_utf8(n[0].getContent())
        else:
            return None

    def get_show(self):
        """Get presence "show" field.

        :return: value of stanza's <show/> field.
        :returntype: `unicode`"""
        n=self.xpath_eval("ns:show")
        if n:
            return from_utf8(n[0].getContent())
        else:
            return None

    def set_show(self,show):
        """Change presence "show" field.

        :Parameters:
            - `show`: new value for the "show" field of presence stanza. One
              of: None, "away", "xa", "dnd", "chat".
        :Types:
            - `show`: `unicode`"""
        n=self.xpath_eval("ns:show")
        if not show:
            if n:
                n[0].unlinkNode()
                n[0].freeNode()
            else:
                return
        if n:
            n[0].setContent(to_utf8(show))
        else:
            self.xmlnode.newTextChild(common_ns,"show",to_utf8(show))

    def get_priority(self):
        """Get presence priority.

        :return: value of stanza's priority. 0 if the stanza doesn't contain
            <priority/> element.
        :returntype: `int`"""
        n=self.xpath_eval("ns:priority")
        if not n:
            return 0
        try:
            prio=int(n[0].getContent())
        except ValueError:
            return 0
        return prio

    def set_priority(self,priority):
        """Change presence priority.

        :Parameters:
            - `priority`: new presence priority.
        :Types:
            - `priority`: `int`"""
        n=self.xpath_eval("ns:priority")
        if not priority:
            if n:
                n[0].unlinkNode()
                n[0].freeNode()
            else:
                return
        priority=int(priority)
        if priority<-128 or priority>127:
            raise ValueError, "Bad priority value"
        priority=str(priority)
        if n:
            n[0].setContent(priority)
        else:
            self.xmlnode.newTextChild(common_ns,"priority",priority)

    def make_accept_response(self):
        """Create "accept" response for the "subscribe"/"subscribed"/"unsubscribe"/"unsubscribed"
        presence stanza.

        :return: new stanza.
        :returntype: `Presence`"""

        if self.get_type() not in ("subscribe","subscribed","unsubscribe","unsubscribed"):
            raise ValueError, ("Results may only be generated for 'subscribe',"
                "'subscribed','unsubscribe' or 'unsubscribed' presence")

        pr=Presence(stanza_type=accept_responses[self.get_type()],
            from_jid=self.get_to(),to_jid=self.get_from(),stanza_id=self.get_id())
        return pr

    def make_deny_response(self):
        """Create "deny" response for the "subscribe"/"subscribed"/"unsubscribe"/"unsubscribed"
        presence stanza.

        :return: new presence stanza.
        :returntype: `Presence`"""
        if self.get_type() not in ("subscribe","subscribed","unsubscribe","unsubscribed"):
            raise ValueError, ("Results may only be generated for 'subscribe',"
                "'subscribed','unsubscribe' or 'unsubscribed' presence")

        pr=Presence(stanza_type=deny_responses[self.get_type()],
            from_jid=self.get_to(),to_jid=self.get_from(),stanza_id=self.get_id())
        return pr

    def make_error_response(self,cond):
        """Create error response for the any non-error presence stanza.

        :Parameters:
            - `cond`: error condition name, as defined in XMPP specification.
        :Types:
            - `cond`: `unicode`

        :return: new presence stanza.
        :returntype: `Presence`"""

        if self.get_type() == "error":
            raise ValueError, "Errors may not be generated in response to errors"

        p=Presence(stanza_type="error",from_jid=self.get_to(),to_jid=self.get_from(),
            stanza_id=self.get_id(),error_cond=cond)

        if self.xmlnode.children:
            n=self.xmlnode.children
            while n:
                p.xmlnode.children.addPrevSibling(n.copyNode(1))
                n=n.next
        return p class Message(Stanza):
    """Wraper object for <message /> stanzas."""
    stanza_type="message"
    def __init__(self, xmlnode = None, from_jid = None, to_jid = None, stanza_type = None, stanza_id = None,
            subject = None, body = None, thread = None, error = None, error_cond = None, stream = None):
        """Initialize a `Message` object.

        :Parameters:
            - `xmlnode`: XML node to_jid be wrapped into the `Message` object
              or other Message object to be copied. If not given then new
              presence stanza is created using following parameters.
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: "get", "set", "result" or "error".
            - `stanza_id`: stanza id -- value of stanza's "id" attribute. If not
              given, then unique for the session value is generated.
            - `subject`: message subject,
            - `body`: message body.
            - `thread`: message thread id.
            - `error_cond`: error condition name. Ignored if `stanza_type` is not "error".
        :Types:
            - `xmlnode`: `unicode` or `libxml2.xmlNode` or `Stanza`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `subject`: `unicode`
            - `body`: `unicode`
            - `thread`: `unicode`
            - `error_cond`: `unicode`"""

        self.xmlnode=None
        if isinstance(xmlnode,Message):
            pass
        elif isinstance(xmlnode,Stanza):
            raise TypeError, "Couldn't make Message from other Stanza"
        elif isinstance(xmlnode,libxml2.xmlNode):
            pass
        elif xmlnode is not None:
            raise TypeError, "Couldn't make Message from %r" % (type(xmlnode),)

        if xmlnode is None:
            xmlnode="message"

        Stanza.__init__(self, xmlnode, from_jid = from_jid, to_jid = to_jid, stanza_type = stanza_type,
                stanza_id = stanza_id, error = error, error_cond = error_cond, stream = stream)

        if subject is not None:
            self.xmlnode.newTextChild(common_ns,"subject",to_utf8(subject))
        if body is not None:
            self.xmlnode.newTextChild(common_ns,"body",to_utf8(body))
        if thread is not None:
            self.xmlnode.newTextChild(common_ns,"thread",to_utf8(thread))

    def get_subject(self):
        """Get the message subject.

        :return: the message subject or `None` if there is no subject.
        :returntype: `unicode`"""
        n=self.xpath_eval("ns:subject")
        if n:
            return from_utf8(n[0].getContent())
        else:
            return None

    def get_thread(self):
        """Get the thread-id subject.

        :return: the thread-id or `None` if there is no thread-id.
        :returntype: `unicode`"""
        n=self.xpath_eval("ns:thread")
        if n:
            return from_utf8(n[0].getContent())
        else:
            return None

    def copy(self):
        """Create a deep copy of the message stanza.

        :returntype: `Message`"""
        return Message(self)

    def get_body(self):
        """Get the body of the message.

        :return: the body of the message or `None` if there is no body.
        :returntype: `unicode`"""
        n=self.xpath_eval("ns:body")
        if n:
            return from_utf8(n[0].getContent())
        else:
            return None

    def make_error_response(self,cond):
        """Create error response for any non-error message stanza.

        :Parameters:
            - `cond`: error condition name, as defined in XMPP specification.

        :return: new message stanza with the same "id" as self, "from" and
            "to" attributes swapped, type="error" and containing <error />
            element plus payload of `self`.
        :returntype: `unicode`"""

        if self.get_type() == "error":
            raise ValueError, "Errors may not be generated in response to errors"

        m=Message(stanza_type="error",from_jid=self.get_to(),to_jid=self.get_from(),
            stanza_id=self.get_id(),error_cond=cond)

        if self.xmlnode.children:
            n=self.xmlnode.children
            while n:
                m.xmlnode.children.addPrevSibling(n.copyNode(1))
                n=n.next
        return m class Iq(Stanza):
    """Wraper object for <iq /> stanzas."""
    stanza_type="iq"
    def __init__(self, xmlnode = None, from_jid = None, to_jid = None, stanza_type = None,
            stanza_id = None, error = None, error_cond=None, stream = None):
        """Initialize an `Iq` object.

        :Parameters:
            - `xmlnode`: XML node to_jid be wrapped into the `Iq` object
              or other Iq object to be copied. If not given then new
              presence stanza is created using following parameters.
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: "get", "set", "result" or "error".
            - `stanza_id`: stanza id -- value of stanza's "id" attribute. If not
              given, then unique for the session value is generated.
            - `error_cond`: error condition name. Ignored if `stanza_type` is not "error".
        :Types:
            - `xmlnode`: `unicode` or `libxml2.xmlNode` or `Iq`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `error_cond`: `unicode`"""
        self.xmlnode=None
        if isinstance(xmlnode,Iq):
            pass
        elif isinstance(xmlnode,Stanza):
            raise TypeError,"Couldn't make Iq from other Stanza"
        elif isinstance(xmlnode,libxml2.xmlNode):
            pass
        elif xmlnode is not None:
            raise TypeError,"Couldn't make Iq from %r" % (type(xmlnode),)
        elif not stanza_type:
            raise ValueError, "type is required for Iq"
        else:
            if not stanza_id and stanza_type in ("get", "set"):
                stanza_id=gen_id()

        if not xmlnode and stanza_type not in ("get","set","result","error"):
            raise ValueError, "Invalid Iq type: %r" % (stanza_type,)

        if xmlnode is None:
            xmlnode="iq"

        Stanza.__init__(self, xmlnode, from_jid = from_jid, to_jid = to_jid,
            stanza_type = stanza_type, stanza_id = stanza_id, error = error,
            error_cond = error_cond, stream = stream)

    def copy(self):
        """Create a deep copy of the iq stanza.

        :returntype: `Iq`"""
        return Iq(self)

    def make_error_response(self,cond):
        """Create error response for the a "get" or "set" iq stanza.

        :Parameters:
            - `cond`: error condition name, as defined in XMPP specification.

        :return: new `Iq` object with the same "id" as self, "from" and "to"
            attributes swapped, type="error" and containing <error /> element
            plus payload of `self`.
        :returntype: `Iq`"""

        if self.get_type() in ("result", "error"):
            raise ValueError, "Errors may not be generated for 'result' and 'error' iq"

        iq=Iq(stanza_type="error",from_jid=self.get_to(),to_jid=self.get_from(),
            stanza_id=self.get_id(),error_cond=cond)
        n=self.get_query()
        if n:
            n=n.copyNode(1)
            iq.xmlnode.children.addPrevSibling(n)
        return iq

    def make_result_response(self):
        """Create result response for the a "get" or "set" iq stanza.

        :return: new `Iq` object with the same "id" as self, "from" and "to"
            attributes replaced and type="result".
        :returntype: `Iq`"""

        if self.get_type() not in ("set","get"):
            raise ValueError, "Results may only be generated for 'set' or 'get' iq"

        iq=Iq(stanza_type="result", from_jid=self.get_to(),
                to_jid=self.get_from(), stanza_id=self.get_id())

        return iq

    def new_query(self,ns_uri,name="query"):
        """Create new payload element for the stanza.

        :Parameters:
            - `ns_uri`: namespace URI of the element.
            - `name`: element name.
        :Types:
            - `ns_uri`: `str`
            - `name`: `unicode`

        :return: the new payload node.
        :returntype: `libxml2.xmlNode`"""
        return self.set_new_content(ns_uri,name)

    def get_query(self):
        """Get the payload element of the stanza.

        :return: the payload element or None if there is no payload.
        :returntype: `libxml2.xmlNode`"""
        c = self.xmlnode.children
        while c:
            try:
                if c.ns():
                    return c
            except libxml2.treeError:
                pass
            c = c.next
        return None

    def get_query_ns(self):
        """Get a namespace of the stanza payload.

        :return: XML namespace URI of the payload or None if there is no
            payload.
        :returntype: `str`"""
        q=self.get_query()
        if q:
            return get_node_ns_uri(q)
        else:
            return None class JID(object):
    """JID.

    :Ivariables:
        - `node`: node part of the JID
        - `domain`: domain part of the JID
        - `resource`: resource part of the JID

    JID objects are immutable. They are also cached for better performance.
    """
    cache=weakref.WeakValueDictionary()
    __slots__=["node","domain","resource","__weakref__"]
    def __new__(cls,node_or_jid=None,domain=None,resource=None,check=True):
        """Create a new JID object or take one from the cache.

        :Parameters:
            - `node_or_jid`: node part of the JID, JID object to copy or
              Unicode representation of the JID.
            - `domain`: domain part of the JID
            - `resource`: resource part of the JID
            - `check`: if `False` then JID is not checked for specifiaction
              compliance.
        """

        if isinstance(node_or_jid,JID):
            return node_or_jid

        if domain is None and resource is None:
            obj=cls.cache.get(node_or_jid)
            if obj:
                return obj
        else:
            obj=None
        if obj is None:
            obj=object.__new__(cls)

        if node_or_jid:
            node_or_jid = unicode(node_or_jid)
        if (node_or_jid and
                ((u"@" in node_or_jid) or (u"/" in node_or_jid))):
            obj.__from_unicode(node_or_jid)
            cls.cache[node_or_jid]=obj
        else:
            if domain is None and resource is None:
                if node_or_jid is None:
                    raise JIDError,"At least domain must be given"
                domain=node_or_jid
                node_or_jid=None
            if check:
                obj.__set_node(node_or_jid)
                obj.__set_domain(domain)
                obj.__set_resource(resource)
            else:
                object.__setattr__(obj,"node",node_or_jid)
                object.__setattr__(obj,"domain",domain)
                object.__setattr__(obj,"resource",resource)
        return obj

    def __setattr__(self,name,value):
        raise RuntimeError,"JID objects are immutable!"

    def __from_unicode(self,s,check=True):
        """Initialize JID object from Unicode string.

        :Parameters:
            - `s`: the JID string
            - `check`: when `False` then the JID is not checked for
              specification compliance."""
        s1=s.split(u"/",1)
        s2=s1[0].split(u"@",1)
        if len(s2)==2:
            if check:
                self.__set_node(s2[0])
                self.__set_domain(s2[1])
            else:
                object.__setattr__(self,"node",s2[0])
                object.__setattr__(self,"domain",s2[1])
        else:
            if check:
                self.__set_domain(s2[0])
            else:
                object.__setattr__(self,"domain",s2[0])
            object.__setattr__(self,"node",None)
        if len(s1)==2:
            if check:
                self.__set_resource(s1[1])
            else:
                object.__setattr__(self,"resource",s1[1])
        else:
            object.__setattr__(self,"resource",None)
        if not self.domain:
            raise JIDError,"Domain is required in JID."

    def __set_node(self,s):
        """Initialize `self.node`

        :Parameters:
            - `s`: Node part of the JID
        :Types:
            - `s`: unicode

        :raise JIDError: if the node name is too long.
        :raise pyxmpp.xmppstringprep.StringprepError: if the
            node name fails Nodeprep preparation."""
        if s:
            s = unicode(s)
            s=nodeprep.prepare(s)
            if len(s.encode("utf-8"))>1023:
                raise JIDError,"Node name too long"
        else:
            s=None
        object.__setattr__(self,"node",s)

    def __set_domain(self,s):
        """Initialize `self.domain`

        :Parameters:
            - `s`: Unicode or UTF-8 domain part of the JID

        :raise JIDError: if the domain name is too long."""

        if s is None:
            raise JIDError,"Domain must be given"
        if s:
            s = unicode(s)
        s=idna.nameprep(s)
        if len(s.encode("utf-8"))>1023:
            raise JIDError,"Domain name too long"
        object.__setattr__(self,"domain",s)

    def __set_resource(self,s):
        """Initialize `self.resource`

        :Parameters:
            - `s`: Unicode or UTF-8 resource part of the JID

        :raise JIDError: if the resource name is too long.
        :raise pyxmpp.xmppstringprep.StringprepError: if the
            node name fails Resourceprep preparation."""
        if s:
            s = unicode(s)
            s=resourceprep.prepare(s)
            if len(s.encode("utf-8"))>1023:
                raise JIDError,"Resource name too long"
        else:
            s=None
        object.__setattr__(self,"resource",s)

    def __str__(self):
        warnings.warn("JIDs should not be used as strings", DeprecationWarning, stacklevel=2)
        return self.as_utf8()

    def __unicode__(self):
        return self.as_unicode()

    def __repr__(self):
        return "<JID: %r>" % (self.as_unicode())

    def as_utf8(self):
        """UTF-8 encoded JID representation.

        :return: UTF-8 encoded JID."""
        return self.as_unicode().encode("utf-8")

    def as_string(self):
        """UTF-8 encoded JID representation.

        *Deprecated* Always use Unicode objects, or `as_utf8` if you really want.

        :return: UTF-8 encoded JID."""
        warnings.warn("JID.as_string() is deprecated. Use unicode() or `as_utf8` instead.",
                 DeprecationWarning, stacklevel=1)
        return self.as_utf8()

    def as_unicode(self):
        """Unicode string JID representation.

        :return: JID as Unicode string."""
        r=self.domain
        if self.node:
            r=self.node+u'@'+r
        if self.resource:
            r=r+u'/'+self.resource
        if not JID.cache.has_key(r):
            JID.cache[r]=self
        return r

    def bare(self):
        """Make bare JID made by removing resource from current `self`.

        :return: new JID object without resource part."""
        return JID(self.node,self.domain,check=False)

    def __eq__(self,other):
        if other is None:
            return False
        elif type(other) in (str, unicode):
            try:
                other=JID(other)
            except:
                return False
        elif not isinstance(other,JID):
            return False

        return (self.node==other.node
            and are_domains_equal(self.domain,other.domain)
            and self.resource==other.resource)

    def __ne__(self,other):
        return not self.__eq__(other)

    def __cmp__(self,other):
        a=self.as_unicode()
        return cmp(a,other)

    def __hash__(self):
        return hash(self.node)^hash(self.domain)^hash(self.resource) def xml_element_ns_iter(nodelist, ns_uri):
    """Iterate over sibling XML elements. Only elements in the given namespace will be returned.

    Usually used to iterade over node's children like this::

        xml_node_iter(node.children)

    :Parameters:
        - `nodelist`: start node of the list.
    :Types:
        - `nodelist`: `libxml2.xmlNode`
    """
    node = nodelist
    while node:
        if node.type == "element" and get_node_ns_uri(node)==ns_uri:
            yield node
        node = node.next class MucPresence(Presence,MucStanzaExt):
    """
    Extend `Presence` with MUC related interface.
    """
    def __init__(self, xmlnode=None,from_jid=None,to_jid=None,stanza_type=None,stanza_id=None,
            show=None,status=None,priority=0,error=None,error_cond=None):
        """Initialize a `MucPresence` object.

        :Parameters:
            - `xmlnode`: XML node to_jid be wrapped into the `MucPresence` object
              or other Presence object to be copied. If not given then new
              presence stanza is created using following parameters.
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: None, "available", "unavailable",
              "subscribe", "subscribed", "unsubscribe", "unsubscribed" or
              "error". "available" is automaticaly changed to_jid None.
            - `stanza_id`: stanza id -- value of stanza's "id" attribute
            - `show`: "show" field of presence stanza. One of: None, "away",
              "xa", "dnd", "chat".
            - `status`: descriptive text for the presence stanza.
            - `priority`: presence priority.
            - `error_cond`: error condition name. Ignored if `stanza_type` is not "error"
        :Types:
            - `xmlnode`: `unicode` or `libxml2.xmlNode` or `pyxmpp.stanza.Stanza`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `show`: `unicode`
            - `status`: `unicode`
            - `priority`: `unicode`
            - `error_cond`: `unicode`"""
        MucStanzaExt.__init__(self)
        Presence.__init__(self,xmlnode,from_jid=from_jid,to_jid=to_jid,
                stanza_type=stanza_type,stanza_id=stanza_id,
                show=show,status=status,priority=priority,
                error=error,error_cond=error_cond)

    def copy(self):
        """
        Return a copy of `self`.
        """
        return MucPresence(self)

    def make_join_request(self, password = None, history_maxchars = None,
            history_maxstanzas = None, history_seconds = None,
            history_since = None):
        """
        Make the presence stanza a MUC room join request.

        :Parameters:
            - `password`: password to the room.
            - `history_maxchars`: limit of the total number of characters in
              history.
            - `history_maxstanzas`: limit of the total number of messages in
              history.
            - `history_seconds`: send only messages received in the last
              `seconds` seconds.
            - `history_since`: Send only the messages received since the
              dateTime specified (UTC).
        :Types:
            - `password`: `unicode`
            - `history_maxchars`: `int`
            - `history_maxstanzas`: `int`
            - `history_seconds`: `int`
            - `history_since`: `datetime.datetime`
        """
        self.clear_muc_child()
        self.muc_child=MucX(parent=self.xmlnode)
        if (history_maxchars is not None or history_maxstanzas is not None
                or history_seconds is not None or history_since is not None):
            history = HistoryParameters(history_maxchars, history_maxstanzas,
                    history_seconds, history_since)
            self.muc_child.set_history(history)
        if password is not None:
            self.muc_child.set_password(password)

    def get_join_info(self):
        """If `self` is a MUC room join request return the information contained.

        :return: the join request details or `None`.
        :returntype: `MucX`
        """
        x=self.get_muc_child()
        if not x:
            return None
        if not isinstance(x,MucX):
            return None
        return x

    def free(self):
        """Free the data associated with this `MucPresence` object."""
        self.muc_free()
        Presence.free(self) class MucUserX(MucXBase):
    """
    Wrapper for http://www.jabber.org/protocol/muc#user namespaced
    stanza payload "x" elements and usually containing information
    about a room user.

    :Ivariables:
        - `xmlnode`: wrapped XML node
    :Types:
        - `xmlnode`: `libxml2.xmlNode`
    """
    ns=MUC_USER_NS
    def get_items(self):
        """Get a list of objects describing the content of `self`.

        :return: the list of objects.
        :returntype: `list` of `MucItemBase` (`MucItem` and/or `MucStatus`)
        """
        if not self.xmlnode.children:
            return []
        ret=[]
        n=self.xmlnode.children
        while n:
            ns=n.ns()
            if ns and ns.getContent()!=self.ns:
                pass
            elif n.name=="item":
                ret.append(MucItem(n))
            elif n.name=="status":
                ret.append(MucStatus(n))
            # FIXME: alt,decline,invite,password
            n=n.next
        return ret
    def clear(self):
        """
        Clear the content of `self.xmlnode` removing all <item/>, <status/>, etc.
        """
        if not self.xmlnode.children:
            return
        n=self.xmlnode.children
        while n:
            ns=n.ns()
            if ns and ns.getContent()!=MUC_USER_NS:
                pass
            else:
                n.unlinkNode()
                n.freeNode()
            n=n.next
    def add_item(self,item):
        """Add an item to `self`.

        :Parameters:
            - `item`: the item to add.
        :Types:
            - `item`: `MucItemBase`
        """
        if not isinstance(item,MucItemBase):
            raise TypeError,"Bad item type for muc#user"
        item.as_xml(self.xmlnode) class MucItem(MucItemBase):
    """
    MUC <item/> element -- describes a room occupant.

    :Ivariables:
        - `affiliation`: affiliation of the user.
        - `role`: role of the user.
        - `jid`: JID of the user.
        - `nick`: nickname of the user.
        - `actor`: actor modyfying the user data.
        - `reason`: reason of change of the user data.
    :Types:
        - `affiliation`: `str`
        - `role`: `str`
        - `jid`: `JID`
        - `nick`: `unicode`
        - `actor`: `JID`
        - `reason`: `unicode`
    """
    def __init__(self,xmlnode_or_affiliation,role=None,jid=None,nick=None,actor=None,reason=None):
        """
        Initialize a `MucItem` object.

        :Parameters:
            - `xmlnode_or_affiliation`: XML node to be pased or the affiliation of
              the user being described.
            - `role`: role of the user.
            - `jid`: JID of the user.
            - `nick`: nickname of the user.
            - `actor`: actor modyfying the user data.
            - `reason`: reason of change of the user data.
        :Types:
            - `xmlnode_or_affiliation`: `libxml2.xmlNode` or `str`
            - `role`: `str`
            - `jid`: `JID`
            - `nick`: `unicode`
            - `actor`: `JID`
            - `reason`: `unicode`
        """
        self.jid,self.nick,self.actor,self.affiliation,self.reason,self.role=(None,)*6
        MucItemBase.__init__(self)
        if isinstance(xmlnode_or_affiliation,libxml2.xmlNode):
            self.__from_xmlnode(xmlnode_or_affiliation)
        else:
            self.__init(xmlnode_or_affiliation,role,jid,nick,actor,reason)

    def __init(self,affiliation,role,jid=None,nick=None,actor=None,reason=None):
        """Initialize a `MucItem` object from a set of attributes.

        :Parameters:
            - `affiliation`: affiliation of the user.
            - `role`: role of the user.
            - `jid`: JID of the user.
            - `nick`: nickname of the user.
            - `actor`: actor modyfying the user data.
            - `reason`: reason of change of the user data.
        :Types:
            - `affiliation`: `str`
            - `role`: `str`
            - `jid`: `JID`
            - `nick`: `unicode`
            - `actor`: `JID`
            - `reason`: `unicode`
        """
        if not affiliation:
            affiliation=None
        elif affiliation not in affiliations:
            raise ValueError,"Bad affiliation"
        self.affiliation=affiliation
        if not role:
            role=None
        elif role not in roles:
            raise ValueError,"Bad role"
        self.role=role
        if jid:
            self.jid=JID(jid)
        else:
            self.jid=None
        if actor:
            self.actor=JID(actor)
        else:
            self.actor=None
        self.nick=nick
        self.reason=reason

    def __from_xmlnode(self, xmlnode):
        """Initialize a `MucItem` object from an XML node.

        :Parameters:
            - `xmlnode`: the XML node.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`
        """
        actor=None
        reason=None
        n=xmlnode.children
        while n:
            ns=n.ns()
            if ns and ns.getContent()!=MUC_USER_NS:
                continue
            if n.name=="actor":
                actor=n.getContent()
            if n.name=="reason":
                reason=n.getContent()
            n=n.next
        self.__init(
            from_utf8(xmlnode.prop("affiliation")),
            from_utf8(xmlnode.prop("role")),
            from_utf8(xmlnode.prop("jid")),
            from_utf8(xmlnode.prop("nick")),
            from_utf8(actor),
            from_utf8(reason),
            );

    def as_xml(self,parent):
        """
        Create XML representation of `self`.

        :Parameters:
            - `parent`: the element to which the created node should be linked to.
        :Types:
            - `parent`: `libxml2.xmlNode`

        :return: an XML node.
        :returntype: `libxml2.xmlNode`
        """
        n=parent.newChild(None,"item",None)
        if self.actor:
            n.newTextChild(None,"actor",to_utf8(self.actor))
        if self.reason:
            n.newTextChild(None,"reason",to_utf8(self.reason))
        n.setProp("affiliation",to_utf8(self.affiliation))
        if self.role:
            n.setProp("role",to_utf8(self.role))
        if self.jid:
            n.setProp("jid",to_utf8(self.jid.as_unicode()))
        if self.nick:
            n.setProp("nick",to_utf8(self.nick))
        return n class MucStatus(MucItemBase):
    """
    MUC <item/> element - describes special meaning of a stanza

    :Ivariables:
        - `code`: staus code, as defined in JEP 45
    :Types:
        - `code`: `int`
    """
    def __init__(self,xmlnode_or_code):
        """Initialize a `MucStatus` element.

        :Parameters:
            - `xmlnode_or_code`: XML node to parse or a status code.
        :Types:
            - `xmlnode_or_code`: `libxml2.xmlNode` or `int`
        """
        self.code=None
        MucItemBase.__init__(self)
        if isinstance(xmlnode_or_code,libxml2.xmlNode):
            self.__from_xmlnode(xmlnode_or_code)
        else:
            self.__init(xmlnode_or_code)

    def __init(self,code):
        """Initialize a `MucStatus` element from a status code.

        :Parameters:
            - `code`: the status code.
        :Types:
            - `code`: `int`
        """
        code=int(code)
        if code<0 or code>999:
            raise ValueError,"Bad status code"
        self.code=code

    def __from_xmlnode(self, xmlnode):
        """Initialize a `MucStatus` element from an XML node.

        :Parameters:
            - `xmlnode`: XML node to parse.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`
        """
        self.code=int(xmlnode.prop("code"))

    def as_xml(self,parent):
        """
        Create XML representation of `self`.

        :Parameters:
            - `parent`: the element to which the created node should be linked to.
        :Types:
            - `parent`: `libxml2.xmlNode`

        :return: an XML node.
        :returntype: `libxml2.xmlNode`
        """
        n=parent.newChild(None,"status",None)
        n.setProp("code","%03i" % (self.code,))
        return n MUC_OWNER_NS=MUC_NS+"#owner" DATAFORM_NS = "jabber:x:data" class Form(StanzaPayloadObject):
    """A JEP-0004 compliant data form.

    Additionally to the direct access to the contained fields via the `fields` attribute,
    `Form` object provides an iterator and mapping interface for field access. E.g.::

        for field in form:
            ...

    or::

        field = form['field_name']

    :Ivariables:
        - `type`: form type ("form", "submit", "cancel" or "result").
        - `title`: form title.
        - `instructions`: instructions for a form user.
        - `fields`: the fields in the form.
        - `reported_fields`: list of fields returned in a multi-item data form.
        - `items`: items in a multi-item data form.
    :Types:
        - `title`: `unicode`
        - `instructions`: `unicode`
        - `fields`: `list` of `Field`
        - `reported_fields`: `list` of `Field`
        - `items`: `list` of `Item`
    """
    allowed_types = ("form", "submit", "cancel", "result")
    xml_element_name = "x"
    xml_element_namespace = DATAFORM_NS

    def __init__(self, xmlnode_or_type = "form", title = None, instructions = None,
            fields = None, reported_fields = None, items = None):
        """Initialize a `Form` object.

        :Parameters:
            - `xmlnode_or_type`: XML element to parse or a form title.
            - `title`: form title.
            - `instructions`: instructions for the form.
            - `fields`: form fields.
            - `reported_fields`: fields reported in multi-item data.
            - `items`: items of multi-item data.
        :Types:
            - `xmlnode_or_type`: `libxml2.xmlNode` or `str`
            - `title`: `unicode`
            - `instructions`: `unicode`
            - `fields`: `list` of `Field`
            - `reported_fields`: `list` of `Field`
            - `items`: `list` of `Item`
        """
        if isinstance(xmlnode_or_type, libxml2.xmlNode):
            self.__from_xml(xmlnode_or_type)
        elif xmlnode_or_type not in self.allowed_types:
            raise ValueError, "Form type %r not allowed." % (xmlnode_or_type,)
        else:
            self.type = xmlnode_or_type
            self.title = title
            self.instructions = instructions
            if fields:
                self.fields = list(fields)
            else:
                self.fields = []
            if reported_fields:
                self.reported_fields = list(reported_fields)
            else:
                self.reported_fields = []
            if items:
                self.items = list(items)
            else:
                self.items = []

    def __getitem__(self, name_or_index):
        if isinstance(name_or_index, int):
            return self.fields[name_or_index]
        for f in self.fields:
            if f.name == name_or_index:
                return f
        raise KeyError, name_or_index

    def __contains__(self, name):
        for f in self.fields:
            if f.name == name:
                return True
        return False

    def __iter__(self):
        for field in self.fields:
            yield field

    def add_field(self, name = None, values = None, field_type = None,
            label = None, options = None, required = False, desc = None, value = None):
        """Add a field to the form.

        :Parameters:
            - `name`: field name.
            - `values`: raw field values. Not to be used together with `value`.
            - `field_type`: field type.
            - `label`: field label.
            - `options`: optional values for the field.
            - `required`: `True` if the field is required.
            - `desc`: natural-language description of the field.
            - `value`: field value or values in a field_type-specific type. May be used only
              if `values` parameter is not provided.
        :Types:
            - `name`: `unicode`
            - `values`: `list` of `unicode`
            - `field_type`: `str`
            - `label`: `unicode`
            - `options`: `list` of `Option`
            - `required`: `bool`
            - `desc`: `unicode`
            - `value`: `bool` for "boolean" field, `JID` for "jid-single", `list` of `JID`
              for "jid-multi", `list` of `unicode` for "list-multi" and "text-multi"
              and `unicode` for other field types.

        :return: the field added.
        :returntype: `Field`
        """
        field = Field(name, values, field_type, label, options, required, desc, value)
        self.fields.append(field)
        return field

    def add_item(self, fields = None):
        """Add and item to the form.

        :Parameters:
            - `fields`: fields of the item (they may be added later).
        :Types:
            - `fields`: `list` of `Field`

        :return: the item added.
        :returntype: `Item`
        """
        item = Item(fields)
        self.items.append(item)
        return item

    def make_submit(self, keep_types = False):
        """Make a "submit" form using data in `self`.

        Remove uneeded information from the form. The information removed
        includes: title, instructions, field labels, fixed fields etc.

        :raise ValueError: when any required field has no value.

        :Parameters:
            - `keep_types`: when `True` field type information will be included
              in the result form. That is usually not needed.
        :Types:
            - `keep_types`: `bool`

        :return: the form created.
        :returntype: `Form`"""
        result = Form("submit")
        for field in self.fields:
            if field.type == "fixed":
                continue
            if not field.values:
                if field.required:
                    raise ValueError, "Required field with no value!"
                continue
            if keep_types:
                result.add_field(field.name, field.values, field.type)
            else:
                result.add_field(field.name, field.values)
        return result

    def copy(self):
        """Get a deep copy of `self`.

        :return: a deep copy of `self`.
        :returntype: `Form`"""
        return copy.deepcopy(self)

    def complete_xml_element(self, xmlnode, doc):
        """Complete the XML node with `self` content.

        :Parameters:
            - `xmlnode`: XML node with the element being built. It has already
              right name and namespace, but no attributes or content.
            - `doc`: document to which the element belongs.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`
            - `doc`: `libxml2.xmlDoc`"""
        if self.type not in self.allowed_types:
            raise ValueError, "Form type %r not allowed." % (self.type,)
        xmlnode.setProp("type", self.type)
        if self.type == "cancel":
            return
        ns = xmlnode.ns()
        if self.title is not None:
            xmlnode.newTextChild(ns, "title", to_utf8(self.title))
        if self.instructions is not None:
            xmlnode.newTextChild(ns, "instructions", to_utf8(self.instructions))
        for field in self.fields:
            field.as_xml(xmlnode, doc)
        if self.type != "result":
            return
        if self.reported_fields:
            reported = xmlnode.newChild(ns, "reported", None)
            for field in self.reported_fields:
                field.as_xml(reported, doc)
        for item in self.items:
            item.as_xml(xmlnode, doc)

    def __from_xml(self, xmlnode):
        """Initialize a `Form` object from an XML element.

        :Parameters:
            - `xmlnode`: the XML element.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`
        """
        self.fields = []
        self.reported_fields = []
        self.items = []
        self.title = None
        self.instructions = None
        if (xmlnode.type != "element" or xmlnode.name != "x"
                or xmlnode.ns().content != DATAFORM_NS):
            raise ValueError, "Not a form: " + xmlnode.serialize() 
        self.type = xmlnode.prop("type")
        if not self.type in self.allowed_types:
            raise BadRequestProtocolError, "Bad form type: %r" % (self.type,)
        child = xmlnode.children
        while child:
            if child.type != "element" or child.ns().content != DATAFORM_NS:
                pass
            elif child.name == "title":
                self.title = from_utf8(child.getContent())
            elif child.name == "instructions":
                self.instructions = from_utf8(child.getContent())
            elif child.name == "field":
                self.fields.append(Field._new_from_xml(child))
            elif child.name == "item":
                self.items.append(Item._new_from_xml(child))
            elif child.name == "reported":
                self.__get_reported(child)
            child = child.next

    def __get_reported(self, xmlnode):
        """Parse the <reported/> element of the form.

        :Parameters:
            - `xmlnode`: the element to parse.
        :Types:
            - `xmlnode`: `libxml2.xmlNode`"""
        child = xmlnode.children
        while child:
            if child.type != "element" or child.ns().content != DATAFORM_NS:
                pass
            elif child.name == "field":
                self.reported_fields.append(Field._new_from_xml(child))
            child = child.next
import logging
import weakref
from pyxmpp.presence import Presence
from pyxmpp.message import Message
from pyxmpp.iq import Iq
from pyxmpp.jid import JID
from pyxmpp.xmlextra import xml_element_ns_iter
from pyxmpp.jabber.muccore import MucPresence,MucUserX,MucItem,MucStatus
from pyxmpp.jabber.muccore import MUC_OWNER_NS
from pyxmpp.jabber.dataforms import DATAFORM_NS, Form
            self.subject=s
            self.handler.subject_changed(user,stanza)
        else:
            self.handler.message_received(user,stanza)

    def process_error_message(self,stanza):
        """
        Process <message type="error"/> received from the room.

        :Parameters:
            - `stanza`: the stanza received.
        :Types:
            - `stanza`: `Message`
        """
        self.handler.error(stanza)

    def process_error_presence(self,stanza):
        """
        Process <presence type="error"/> received from the room.

        :Parameters:
            - `stanza`: the stanza received.
        :Types:
            - `stanza`: `Presence`
        """
        self.handler.error(stanza)

    def process_configuration_form_success(self, stanza):
        """
        Process successful result of a room configuration form request.

        :Parameters:
            - `stanza`: the stanza received.
        :Types:
            - `stanza`: `Presence`
        """
        if stanza.get_query_ns() != MUC_OWNER_NS:
            raise ValueError, "Bad result namespace" # TODO: ProtocolError
        query = stanza.get_query()
        form = None
        for el in xml_element_ns_iter(query.children, DATAFORM_NS):
            form = Form(el)
            break
        if not form:
            raise ValueError, "No form received" # TODO: ProtocolError
        self.configuration_form = form
        self.handler.configuration_form_received(form)

    def process_configuration_form_error(self, stanza):
        """
        Process error response for a room configuration form request.

        :Parameters:
            - `stanza`: the stanza received.
        :Types:
            - `stanza`: `Presence`
        """
        self.handler.error(stanza)

    def request_configuration_form(self):
        """
        Request a configuration form for the room.

        When the form is received `self.handler.configuration_form_received` will be called.
        When an error response is received then `self.handler.error` will be called.

        :return: id of the request stanza.
        :returntype: `unicode`
        """
        iq = Iq(to_jid = self.room_jid.bare(), stanza_type = "get")
        iq.new_query(MUC_OWNER_NS, "query")
        self.manager.stream.set_response_handlers(
                iq, self.process_configuration_form_success, self.process_configuration_form_error)
        self.manager.stream.send(iq)
        return iq.get_id()

    def process_configuration_success(self, stanza):
        """
        Process success response for a room configuration request.

        :Parameters:
            - `stanza`: the stanza received.
        :Types:
            - `stanza`: `Presence`
        """
        _unused = stanza
        self.configured = True
        self.handler.room_configured()

    def process_configuration_error(self, stanza):
        """
        Process error response for a room configuration request.

        :Parameters:
            - `stanza`: the stanza received.
        :Types:
            - `stanza`: `Presence`
        """
        self.handler.room_configuration_error(stanza)

    def configure_room(self, form):
        """
        Configure the room using the provided data.
        Do nothing if the provided form is of type 'cancel'.

        :Parameters:
            - `form`: the configuration parameters. Should be a 'submit' form made by filling-in
              the configuration form retireved using `self.request_configuration_form` or
              a 'cancel' form.
        :Types:
            - `form`: `Form`

        :return: id of the request stanza or `None` if a 'cancel' form was provieded.
        :returntype: `unicode`
        """

        if form.type == "cancel":
            return None
        elif form.type != "submit":
            raise ValueError, "A 'submit' form required to configure a room"

        iq = Iq(to_jid = self.room_jid.bare(), stanza_type = "set")