/**
 * Copyright (c) 2009 Yakabod, Inc.
 * File: jquery.xml.js
 * Author: Jim Auldridge
 *
 * $Revision: 1.3 $
 * $Date: 2009/10/29 17:10:00 $
 *
 * Last edited by: $Author: jauldrid $
 *
 * Error free JSLint: 20091029 1252
 */

/*
 *  This jQuery plugin provides two new jQuery object methods, and two new
 *  jQuery prototype methods.
 *
 *  OBJECT
 *    $.loadXMLFromString() - receive a string and attempt to parse it into a
 *                            DOM Document
 *                              var doc = $.loadXMLFromString( '<root></root>' );
 *
 *    $.createXMLDocument() - create a new and empty DOM Document
 *                              var doc = $.createXMLDocument();
 *
 *  PROTOTYPE
 *    $.fn.toXMLDocument() - Retreve the value of inputs, selects, and textareas
 *                           OR the innerHTML of other elements and parse them
 *                           into an DOM Document
 *                             var doc = $( 'nodeSelector' ).toXMLDocument();
 *
 *    $.fn.xml() - a sister method to $.fn.html(), this will return the innerXML
 *                 of a DOM (XML) Document
 *                   var serializedXML = $( xmlDocument ).xml();
 */
( function()
{
  if( window.jQuery )
  {
    ( function( $ )
    {
      $.extend( ( function()
      {
        var getAXOXMLVersion = ( function()
        {
          var AXOXMLVersion;

          return function()
          {
            if( typeof AXOXMLVersion === 'undefined' )
            {
              if( typeof window.ActiveXObject !== 'undefined' )
              {
                var i, AXOXMLversions = ['MSXML3', 'MSXML2', 'MSXML', 'Microsoft'], AXOXMLTest;
                for( i = 0; i< AXOXMLversions.length; i++ )
                {
                  try
                  {
                    AXOXMLTest = new window.ActiveXObject( AXOXMLversions[i] + '.DomDocument' );
                    AXOXMLVersion = AXOXMLversions[i];
                    AXOXMLTest = null;
                    break;
                  }
                  catch( e ){}
                }
              }
            }
            return AXOXMLVersion;
          };
        } )();

        return {
          /**
           * $.loadXMLFromString
           *
           * @public
           * @static
           * @throws STRING
           * @param sXMLContent STRING - string to parse into DOM DOcument
           * @param options OBJECT - optional object of options. Only one option
           *                         currently has any effect, and that only in Mozilla
           *                         based browsers. It is options.sMIMEType and defaults
           *                         to 'text/xml'.  You probably have no need to change it
           * @return OBJECT (DOM Document)
           */
          loadXMLFromString: function( sXMLContent, options )
          {
            if( typeof sXMLContent !== 'string' )
            {
              throw '$.loadXMLFromString requires string as first paramater, sXMLContent.  You gave ' + typeof sXMLContent;
            }

            sXMLContent = $.trim( sXMLContent );
            if( ! sXMLContent.match( /^<\?/ ) )
            {
              sXMLContent = '<?xml version="1.0"?>' + sXMLContent;
            }

            if( typeof options !== 'object' || options === null )
            {
              options = {};
            }

            options = $.extend( {
              sMIMEType: 'text/xml'
            }, options );

            /* In Firefox, DOMParser is a function, in Safari it's an object */
            if( typeof window.DOMParser === 'undefined')
            {
              var parseFromString = function(){ return null; }, AXOXMLVersion;

              if( typeof window.ActiveXObject !== 'undefined' )
              {
                AXOXMLVersion = getAXOXMLVersion();
                if( typeof AXOXMLVersion === 'string' )
                {
                  parseFromString = function( sXMLContent )
                  {
                    var AXODOMDocument = new window.ActiveXObject( AXOXMLVersion + '.DomDocument' );
                    AXODOMDocument.async = false;
                    AXODOMDocument.loadXML( sXMLContent );
                    return AXODOMDocument;
                  };
                }
              }
              else if( typeof window.XMLHttpRequest !== 'undefined' )
              {
                parseFromString = function( sXMLContent, sMIMEType )
                {
                  var oXHR = new window.XMLHttpRequest();
                  oXHR.open(
                    'GET',
                    'data:' + sMIMEType  + ';charset=utf-8,' + encodeURIComponent( sXMLContent ),
                    false
                  );
                  oXHR.send( null );
                  return oXHR.responseXML;
                };
              }

              window.DOMParser = function(){};
              window.DOMParser.prototype.parseFromString = parseFromString;
            }

            return ( new window.DOMParser() ).parseFromString( sXMLContent, options.sMIMEType );
          },
          /**
           * $.createXMLDocument
           *
           * @public
           * @static
           * @todo Enhance to take options which Mozilla based browsers allow
           * @return OBJECT (Empty DOM Document)
           */
          createXMLDocument: ( function()
          {
            var AXOXMLVersion, createDocument = function(){ return null; };

            if( typeof window.document.implementation === 'object' &&
                typeof window.document.implementation.createDocument === 'function'
              )
            {
              createDocument = function()
              {
                return window.document.implementation.createDocument( null, null, null );
              };
            }
            else if( typeof window.ActiveXObject !== 'undefined' )
            {
              AXOXMLVersion = getAXOXMLVersion();
              if( typeof AXOXMLVersion === 'string' )
              {
                createDocument = function()
                {
                  return new window.ActiveXObject( AXOXMLVersion + '.DomDocument' );
                };
              }
            }

            return createDocument;
          } )()
        };
      } )() );

      $.fn.extend( {
        /**
         * $.fn.toXMLDocument
         * 
         * @public
         * @param options OBJECT - optional object of options. Only one option
         *                         currently has any effect, and that only in Mozilla
         *                         based browsers. It is options.sMIMEType and defaults
         *                         to 'text/xml'.  You probably have no need to change it
         * @return ARRAY (of DOM Document objects)
         */
        toXMLDocument: function( options )
        {
          var xmlDocuments = [];

          if( typeof options !== 'object' || options === null )
          {
            options = {};
          }

          /*
           * TODO could probably do this next section more efficiently, but
           * buttons are putting a wrench in the works when trying .is(':input')
           */
          this.filter( 'input, select, textarea' ).each( function()
          {
            xmlDocuments.push( $.loadXMLFromString( $( this ).val(), options ) );
          } );
          this.not( 'input, select, textarea' ).each( function()
          {
            xmlDocuments.push( $.loadXMLFromString( $( this ).html(), options ) );
          } );

          return xmlDocuments;
        },
        /**
         * $.fn.xml
         *
         * @public
         * @param options OBJECT - optional object of options. Only one option
         *                         currently has any effect. It is options.appendMultiple
         *                         and defaults to false. Change it to true if
         *                         your jQuery object holds several items and you
         *                         want their innerXML joined together in the
         *                         return.  A jQuery object with more than one
         *                         item while this option is false will result in
         *                         the return of an empty string
         * @return STRING
         */
        xml: function( options )
        {
          var returnValue = '', node, i;

          if( typeof options !== 'object' || options === null )
          {
            options = {};
          }

          options = $.extend( {
            appendMultiple: false
          }, options );

          if( this.length > 0 )
          {
            if( this.length === 1 )
            {
              node = this[0];
              if( node.innerXML )
              {
                returnValue = node.innerXML;
              }
              else if( node.xml )
              {
                returnValue = node.xml;
              }
              else if( typeof window.XMLSerializer !== 'undefined' )
              {
                returnValue = ( new window.XMLSerializer() ).serializeToString( node );
              }
            }
            else if( options.appendMultiple === true )
            {
              for( i = 0; i < this.length; i++ )
              {
                returnValue += $( this[i] ).xml( options );
              }
            }
          }

          return returnValue;
        }
      } );
    } )( window.jQuery );
  }
} )();
