docs: function (elem)
      {
          var result;
  
          if (!elem) { return null; }
  
          if (typeof(elem.tree) === "function") {
              elem = elem.tree();
          }
  
          var nodeName = elem.nodeName;
          var i, child;
  
          if (elem.getAttribute("_realname")) {
              nodeName = elem.getAttribute("_realname");
          }
  
          result = "<" + nodeName;
          for (i = 0; i < elem.attributes.length; i++) {
                 if(elem.attributes[i].nodeName != "_realname") {
                   result += " " + elem.attributes[i].nodeName +
                  "='" + elem.attributes[i].value
                      .replace(/&/g, "&amp;")
                         .replace(/\'/g, "&apos;")
                         .replace(/>/g, "&gt;")
                         .replace(/</g, "&lt;") + "'";
                 }
          }
  
          if (elem.childNodes.length > 0) {
              result += ">";
              for (i = 0; i < elem.childNodes.length; i++) {
                  child = elem.childNodes[i];
                  switch( child.nodeType ){
                    case Strophe.ElementType.NORMAL:
                      // normal element, so recurse
                      result += Strophe.serialize(child);
                      break;
                    case Strophe.ElementType.TEXT:
                      // text element to escape values
                      result += Strophe.xmlescape(child.nodeValue);
                      break;
                    case Strophe.ElementType.CDATA:
                      // cdata section so don't escape values
                      result += "<![CDATA["+child.nodeValue+"]]>";
                  }
              }
              result += "</" + nodeName + ">";
          } else {
              result += "/>";
          }
  
          return result;
      }