text: // @tag enterprise
  /**
   * @class Ext.data.amf.XmlDecoder
   * This class parses an XML-based AMFX message and returns the deserialized
   * objects. You should not need to use this class directly. It's mostly used by
   * the AMFX Direct implementation.
   * To decode a message, first construct a Decoder:
   *
   *      decoder = Ext.create('Ext.data.amf.XmlDecoder');
   *
   * Then ask it to read in the message :
   *
   *     resp = decoder.readAmfxMessage(str);
   *
   * For more information on working with AMF data please refer to the
   * [AMF Guide](#/guide/amf).
   */
  Ext.define('Ext.data.amf.XmlDecoder', {
  
      alias: 'data.amf.xmldecoder',
  
      statics: {
  
          /**
           * Parses an xml string and returns an xml document
           * @private
           * @param {String} xml
           */
          readXml: function(xml) {
              var doc;
  
              if (window.DOMParser) {
                  doc = (new DOMParser()).parseFromString(xml, "text/xml");
              } else {
                  doc = new ActiveXObject("Microsoft.XMLDOM");
                  doc.loadXML(xml);
              }
  
              return doc;
          },
  
          /**
           * parses a node containing a byte array in hexadecimal format, returning the reconstructed array.
           * @param {HTMLElement/XMLElement} node the node
           * @return {Array} a byte array
           */
          readByteArray: function(node) {
              var bytes = [],
                  c, i, str;
              str = node.firstChild.nodeValue;
              for (i = 0; i < str.length; i = i + 2) {
                  c = str.substr(i, 2);
                  bytes.push(parseInt(c, 16));
              }
              return bytes;
          },
  
          /**
           * Deserializes an AMF3 binary object from a byte array
           * @param {Array} bytes the byte array containing one AMF3-encoded value
           * @return {Object} the decoded value
           */
          readAMF3Value: function(bytes) {
              var packet;
              packet = Ext.create('Ext.data.amf.Packet');
              return packet.decodeValue(bytes);
          },
  
          /**
           * Accepts Flex-style UID and decodes the number in the first four bytes (8 hex digits) of data.
           * @param {String} messageId the message ID
           * @return {Number} the transaction ID
           */
          decodeTidFromFlexUID: function(messageId) {
              var str;
              str = messageId.substr(0,8);
              return parseInt(str, 16);
          }
  
      },
  
      /**
       * Creates new encoder.
       * @param {Object} config Configuration options
       */
      constructor: function(config) {
          this.initConfig(config);
          this.clear();
      },
  
      /**
       * Clears the accumulated data and reference tables
       */
      clear: function() {
          // reset reference counters
          this.objectReferences=[];
          this.traitsReferences=[];
          this.stringReferences=[];
      },
  
      /**
       * Reads and returns a decoded AMFX packet.
       * @param {String} xml the xml of the message
       * @return {Object} the response object containing the message
       */
      readAmfxMessage: function(xml) {
          var doc, amfx, body,
              i, resp={};
          this.clear(); // reset counters
          doc = Ext.data.amf.XmlDecoder.readXml(xml);
          amfx = doc.getElementsByTagName('amfx')[0];
          //<debug>
          if (!amfx) {
              Ext.warn.log("No AMFX tag in message");
          }
          if (amfx.getAttribute('ver') != "3") {
              Ext.raise("Unsupported AMFX version: " + amfx.getAttribute('ver'));
          }
          //</debug>
          body = amfx.getElementsByTagName('body')[0];
          resp.targetURI = body.getAttribute('targetURI');
          resp.responseURI = body.getAttribute('responseURI'); // most likely empty string
          for (i = 0; i < body.childNodes.length; i++) {
              if (body.childNodes.item(i).nodeType != 1) {
                  // only process element nodes, ignore white space and text nodes
                  continue;
              }
              resp.message = this.readValue(body.childNodes.item(i));
              break; // no need to keep iterating
          }
          return resp;
      },
  
      /**
       * Parses an HTML element returning the appropriate JavaScript value from the AMFX data.
       * @param {HTMLElement} node the node to parse
       * @return {Object} a JavaScript object or value
       */
      readValue: function(node) {
          var val;
          if (typeof node.normalize === 'function') {
              node.normalize();
          }
          // 2DO: handle references!
          if (node.tagName == "null") {
              return null;
          } else if (node.tagName == "true") {
              return true;
          } else if (node.tagName == "false") {
              return false;
          } else if (node.tagName == "string") {
              return this.readString(node);
          } else if (node.tagName == "int") {
              return parseInt(node.firstChild.nodeValue);
          } else if (node.tagName == "double") {
              return parseFloat(node.firstChild.nodeValue);
          } else if (node.tagName == "date") {
              val = new Date(parseFloat(node.firstChild.nodeValue));
              // record in object reference table
              this.objectReferences.push(val);
              return val;
          } else if (node.tagName == "dictionary") {
              return this.readDictionary(node);
          } else if (node.tagName == "array") {
              return this.readArray(node);
          } else if (node.tagName == "ref") {
              return this.readObjectRef(node);
          } else if (node.tagName == "object") {
              return this.readObject(node);
          } else if (node.tagName == "xml") {
              // the CDATA content of the node is a parseable XML document. parse it.
              return Ext.data.amf.XmlDecoder.readXml(node.firstChild.nodeValue);
          } else if (node.tagName == "bytearray") {
              // a byte array is usually an AMF stream. Parse it to a byte array, then pass through the AMF decoder to get the objects inside
              return Ext.data.amf.XmlDecoder.readAMF3Value(Ext.data.amf.XmlDecoder.readByteArray(node));
          }
          //<debug>
          Ext.raise("Unknown tag type: " + node.tagName);
          //</debug>
          return null;
      },
  
      /**
       * Reads a string or string reference and return the value
       * @param {HTMLElement/XMLElement} node the node containing a string object
       * @return {String} the parsed string
       */
      readString: function(node) {
          var val;
          if (node.getAttributeNode('id')) {
              return this.stringReferences[parseInt(node.getAttribute('id'))];
          }
          val = (node.firstChild ? node.firstChild.nodeValue : "") || "";
          this.stringReferences.push(val);
          return val;
      },
  
      /**
       * Parses and returns an ordered list of trait names
       * @param {HTMLElement/XMLElement} node the traits node from the XML doc
       * @return {Array} an array of ordered trait names or null if it's an externalizable object
       */
      readTraits: function(node) {
          var traits = [], i, rawtraits;
          if (node === null) {
              return null;
          }
          if (node.getAttribute('externalizable') == "true") {
              // no traits since it's an externalizable or a null object.
              return null;
          }
          if (node.getAttributeNode('id')) {
              // return traits reference
              return this.traitsReferences[parseInt(node.getAttributeNode('id').value)];
          }
          /* // empty anonymous objects still seem to get their empty traits in the reference table
           if (!node.hasChildNodes()) {
           var className = node.parentNode.getElementsByTagName('type');
           if (className.length == 0) {
           return traits; // special case of an anonymous object with no traits. Does not get reference counted
           }
           }
           */
          rawtraits = node.childNodes;
          for (i = 0; i < rawtraits.length; i++) {
              if (rawtraits.item(i).nodeType != 1) {
                  // only process element nodes, ignore white space and text nodes
                  continue;
              }
              // this will be a string, but let the readValue function handle it nonetheless
              traits.push(this.readValue(rawtraits.item(i)));
          }
  
          // register traits in ref table:
          this.traitsReferences.push(traits);
          return traits;
      },
  
      /**
       * Parses and return an object / array / dictionary / date from reference
       * @param {HTMLElement/XMLElement} node the ref node
       * @return {Object} the previously instantiated object referred to by the ref node
       */
      readObjectRef: function(node) {
          var id;
          id = parseInt(node.getAttribute('id'));
          return this.objectReferences[id];
      },
  
      /**
       * Parses and returns an AMFX object.
       * @param {HTMLElement/XMLElement} the `<object>` node to parse
       * @return {Object} the deserialized object
       */
      readObject: function(node) {
          var obj,
              traits = [],
              traitsNode,
              i, j, n,
              key, val,
              klass = null, className;
  
          className = node.getAttribute('type');
          if (className) {
              klass = Ext.ClassManager.getByAlias('amfx.' + className); // check if special case for class
          }
          obj = klass ? new klass() : (className ? {$className: className} : {}); // if there is no klass, mark the classname for easier parsing of returned results
  
          // check if we need special handling for this class
          if ((!klass) && this.converters[className]) {
              obj = this.converters[className](this,node);
              return obj; // we're done
          }
  
          traitsNode = node.getElementsByTagName('traits')[0];
          traits = this.readTraits(traitsNode);
          //<debug>
          if (traits === null) {
              Ext.raise("No support for externalizable object: " + className);
          }
          //</debug>
          // Register object if ref table, in case there's a cyclical reference coming
          this.objectReferences.push(obj);
  
  
          // Now we expect an item for each trait name we have. We assume it's an ordered list. We'll skip the first (traits) tag
          j = 0;
          for (i = 0; i < node.childNodes.length; i++) {
              n = node.childNodes.item(i);
              if (n.nodeType != 1) {
                  // Ignore text nodes and non-element nodes
                  continue;
              }
              if (n.tagName == "traits") {
                  // ignore the traits node. We've already covered it.
                  continue;
              }
              key = traits[j];
              val = this.readValue(n);
              j = j + 1;
              obj[key] = val;
              //<debug>
              if (j > traits.length) {
                  Ext.raise("Too many items for object, not enough traits: " + className);
              }
              //</debug>
          }
          return obj;
      },
  
      /**
       * Parses and returns an AMFX array.
       * @param {HTMLElement/XMLElement} node the array node
       * @return {Array} the deserialized array
       */
      readArray: function(node) {
          var arr=[],
              n,i,j,l,name, val, len, childnodes, cn;
  
          // register array in object references table before we parse, in case of circular references
          this.objectReferences.push(arr);
  
          len = parseInt(node.getAttributeNode('length').value);
          i = 0;
          // the length only accounts for the ordinal values. For the rest, we'll read them as ECMA key-value pairs
          for (l = 0; l < node.childNodes.length; l++) {
              n = node.childNodes.item(l);
              if (n.nodeType != 1) {
                  // Ignore text nodes and non-element nodes
                  continue;
              }
              if (n.tagName == "item") {
                  // parse item node
                  name = n.getAttributeNode('name').value;
                  childnodes = n.childNodes;
                  for (j = 0; j < childnodes.length; j++) {
                      cn = childnodes.item(j);
                      if (cn.nodeType != 1) {
                          // Ignore text nodes and non-element nodes
                          continue;
                      }
                      val = this.readValue(cn);
                      break; // out of loop. We've found our value
                  }
                  arr[name] = val;
              } else {
                  // ordinal node
                  arr[i] = this.readValue(n);
                  i++;
                  //<debug>
                  if (i > len) {
                      Ext.raise("Array has more items than declared length: " + i + " > " + len);
                  }
                  //</debug>
              }
          }
          //<debug>
          if (i < len) {
              Ext.raise("Array has less items than declared length: " + i + " < " + len);
          }
          //</debug>
          return arr;
      },
  
      /**
       * Parses and returns an AMFX dictionary.
       * @param {HTMLElement/XMLElement} node the `<dictionary>` node
       * @return {Object} a javascript object with the dictionary value-pair elements
       */
      readDictionary: function(node) {
          // For now, handle regular objects
          var dict = {},
              key, val,
              i, j, n, len;
  
          len = parseInt(node.getAttribute('length'));
          // Register dictionary in the ref table, in case there's a cyclical reference coming
          this.objectReferences.push(dict);
  
  
          // now find pairs of keys and values
          key = null;
          val = null;
          j = 0;
          for (i = 0; i < node.childNodes.length; i++) {
              n = node.childNodes.item(i);
              if (n.nodeType != 1) {
                  // Ignore text nodes and non-element nodes
                  continue;
              }
              if (!key) {
                  key = this.readValue(n);
                  continue; // next element is the value
              }
              val = this.readValue(n);
              j = j + 1;
              dict[key] = val;
              key = null;
              val = null;
          }
          //<debug>
          if (j != len) {
              Ext.raise("Incorrect number of dictionary values: " + j + " != " + len);
          }
          //</debug>
          return dict;
      },
  
  
      /**
       * Converts externalizable flex objects with a source array to a regular array.
       * @private
       */
      convertObjectWithSourceField: function(node) {
          var i, n, val;
          for (i = 0; i < node.childNodes.length; i++) {
              n = node.childNodes.item(i);
              if (n.tagName == "bytearray") {
                  val = this.readValue(n);
                  this.objectReferences.push(val);
                  return val;
              }
          }
          return null; // we shouldn't reach here, but just in case
      },
  
      /**
       * Converters used in converting specific typed Flex classes to JavaScript usable form.
       * @private
       */
  
      converters: {
          'flex.messaging.io.ArrayCollection': function(decoder,node) {
              return decoder.convertObjectWithSourceField(node);
          },
          'mx.collections.ArrayList':  function(decoder,node) {
              return decoder.convertObjectWithSourceField(node);
          },
          'mx.collections.ArrayCollection':  function(decoder,node) {
              return decoder.convertObjectWithSourceField(node);
          }
      }
  });
  