text: /*
  This file is part of Ext JS 4.2
  
  Copyright (c) 2011-2013 Sencha Inc
  
  Contact:  http://www.sencha.com/contact
  
  Commercial Usage
  Licensees holding valid commercial licenses may use this file in accordance with the Commercial
  Software License Agreement provided with the Software or, alternatively, in accordance with the
  terms contained in a written agreement between you and Sencha.
  
  If you are unsure which license is appropriate for your use, please contact the sales department
  at http://www.sencha.com/contact.
  
  Build date: 2013-09-18 17:18:59 (940c324ac822b840618a3a8b2b4b873f83a1a9b1)
  */
  // @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.Error.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.Error.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.Error.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.Error.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.Error.raise("Array has more items than declared length: " + i + " > " + len);
                  }
                  //</debug>
              }
          }
          //<debug>
          if (i < len) {
              Ext.Error.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.Error.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);
          }
      }
  });
  