1 var STRIP_TAB_NEW_LINE = /\n|\t|\r/g,
  2 	SINGLE_ARG_READ = /^outer$|^inner$|^text$/,
  3 	IMMUTABLE_ATTR = /(button|input)/i,
  4 	SPECIAL_URL = /href|src|style/,
  5 	VALID_ELEMENTS = /^<([A-Z][A-Z0-9]*)([^>]*)>(.*)<\/\1>/i, 
  6 	SPLIT_ATTRIBUTE = /([A-Z]*\s*=\s*['|"]?[A-Z0-9:;#\s]*['|"]?)/i,
  7 	TAG_LIST = {'UL':'LI','DL':'DT','TR':'TD'},
  8 	QUOTE_MATCHER = /(["']?)/g,
  9 	/**
 10 	 * @private - Borrowed from XUI project
 11 	 * Wraps the HTML in a TAG, Tag is optional. If the html starts with a Tag, it will wrap the context in that tag.
 12 	 */
 13 	wrapHelper = function(xhtml, el) {
 14 		// insert into documentFragment to ensure insert occurs without messing up order
 15 		if( xhtml.toString === UNDEF || xhtml.toString().indexOf("[object ") > -1 || ( xhtml && !!xhtml.nodeType ) ){
 16 			if( xhtml && xhtml.length !== UNDEF ){
 17 				var docFrag = DOC.createDocumentFragment();
 18 				xhtml = Simples.makeArray( xhtml, 0 );
 19 				for(var p=0,r=xhtml.length;p<r;p++){
 20 					docFrag.appendChild( xhtml[p] );
 21 				}
 22 				xhtml = docFrag;
 23 			}
 24 		
 25 			return xhtml;
 26 		}
 27 	    var attributes = {}, element, x, i = 0, attr, node, attrList, result, tag;
 28 	    xhtml = "" + xhtml;
 29 	    if ( VALID_ELEMENTS.test(xhtml) ) {
 30 	        result = VALID_ELEMENTS.exec(xhtml);
 31 			tag = result[1];
 32 
 33 	        // if the node has any attributes, convert to object
 34 	        if (result[2] !== "") {
 35 	            attrList = result[2].split( SPLIT_ATTRIBUTE );
 36 
 37 	            for (var l=attrList.length; i < l; i++) {
 38 	                attr = Simples.trim( attrList[i] );
 39 	                if (attr !== "" && attr !== " ") {
 40 	                    node = attr.split('=');
 41 	                    attributes[ node[0] ] = node[1].replace( QUOTE_MATCHER, "");
 42 	                }
 43 	            }
 44 	        }
 45 	        xhtml = result[3];
 46 	    } else {
 47 			tag = (el.firstChild === null) ? TAG_LIST[el.tagName] || el.tagName : el.firstChild.tagName;
 48 		}
 49 
 50 	    element = DOC.createElement(tag);
 51 
 52 	    for( x in attributes ){
 53 			Simples.attr( element, x, attributes[x] );
 54 		}
 55 
 56 	    element.innerHTML = xhtml;
 57 	    return element;
 58 	};
 59 
 60 Simples.merge( /** @lends Simples */ {
 61 	/**
 62 	 * @description to read the html from a elem
 63 	 * @param {Element} elem the element to read the dom html from
 64 	 * @param {String} location to specify how to return the dom options are [ outer, text, inner/undefined ] use outer for outerHTML, text to read all the textNodes and inner or no argument for innerHTML
 65 	 */
 66 	domRead : function( elem, location ){
 67 		if( elem && elem.nodeType ){
 68 			switch( location ){
 69 				case "outer" :
 70 					html = elem.outerHTML;
 71 
 72 					if ( !html ) {
 73 						var div = elem.ownerDocument.createElement("div");
 74 						div.appendChild( elem.cloneNode(true) );
 75 						html = div.innerHTML;
 76 					}
 77 
 78 					return html;
 79 				case "text" :
 80 					var str = "", elems = elem.childNodes;
 81 					for ( var i = 0; elems[i]; i++ ) {
 82 						elem = elems[i];
 83 
 84 						// Get the text from text nodes and CDATA nodes
 85 						if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
 86 							str += elem.nodeValue;
 87 						// Traverse everything else, except comment nodes
 88 						} else if ( elem.nodeType !== 8 ) {
 89 							str += Simples.domRead( elem, "text" );
 90 						}
 91 					}
 92 					return str;
 93 				default :
 94 					return elem.innerHTML;
 95 			}
 96 		}
 97 	},
 98 	/**
 99 	 * @description to write the dom new html string or dom elements
100 	 * @param {Element} elem the element to read the dom html from
101 	 * @param {String} location to specify how to return the dom options are desctructive: [remove, empty, outer, text, inner/undefined ], non-destructive: [top, bottom, unwrap, before, after, wrap ]
102 	 * @param {String|Elements} html the string or Elements to put into the dom
103 	 */	
104 	domManip : function( elem, location, html ){
105 		var el, parent = elem.parentNode;
106 		if( !elem || !elem.nodeType ){ return; }
107 		
108 		switch( location ){
109 			case 'text' :
110 				Simples.cleanData( elem );
111 				while ( elem.firstChild ) {
112 					elem.removeChild( elem.firstChild );
113 				}
114 				elem.appendChild( (elem && elem.ownerDocument || DOC).createTextNode( html.toString() ) );
115 				break;
116 			case 'remove' :
117 				if( parent ){
118 					Simples.cleanData( elem );     
119 					parent.removeChild(elem);
120 				}
121 				break;
122 			default :  
123 				if( elem.nodeType === 3 || elem.nodeType === 8 ){
124 					return;
125 				}
126 				switch( location ){
127 					case 'outer' :
128 						if( parent ){ 
129 							el = wrapHelper(html, elem);
130 							Simples.cleanData( elem );
131 					        parent.replaceChild( el, elem );						
132 						}
133 						break;
134 					case 'top' :
135 						elem.insertBefore( wrapHelper(html, elem), elem.firstChild);
136 						break;
137 					case 'bottom' : 
138 						elem.insertBefore( wrapHelper(html, elem), null);
139 						break;
140 					case 'unwrap' :
141 						if( parent ){
142 							var docFrag = wrapHelper( elem.childNodes, elem );
143 							Simples.cleanData( elem );
144 							el = docFrag.childNodes;
145 							parent.insertBefore( docFrag, elem );
146 							parent.removeChild( elem );
147 						}
148 						break;
149 					case 'empty' :
150 						Simples.cleanData( elem, false ); 
151 						while ( elem.firstChild ) {
152 							elem.removeChild( elem.firstChild );
153 						}
154 						break;
155 					case 'before' :
156 						if( parent ){
157 							parent.insertBefore( wrapHelper(html, parent), elem);
158 						}
159 						break;
160 					case 'after' :
161 						if( parent ){ 
162 							parent.insertBefore( wrapHelper(html, parent), elem.nextSibling);
163 						}
164 						break;
165 					case 'wrap' :
166 						if( parent ){ 
167 							var elems = wrapHelper( html, parent );           
168 							var wrap = ( elems.nodeType === 11 ? elems.firstChild : elems );
169 							parent.insertBefore( elems, elem );
170 							wrap.appendChild( elem );						
171 						}
172 						break;
173 					default :  
174 						Simples.cleanData( this, false );
175 						html = html != null ? html : location;
176 						var list, len, i = 0, testStringIndex = html.toString().indexOf("[object");
177 						if ( testStringIndex === -1 ) {
178 							elem.innerHTML = ""+html;
179 							list = elem.getElementsByTagName('SCRIPT');
180 							len = list.length;
181 							for (; i < len; i++) {
182 								eval(list[i].text);
183 							}
184 						} else if( testStringIndex > -1 ) {
185 							elem.innerHTML = "";
186 							elem.appendChild( wrapHelper( html, elem ) );
187 						}					
188 				}
189 		}
190 		return el;
191 	},
192 	/**
193 	 * @description to either check for a className, add or remove a className
194 	 * @param {Element} elem the element to manipulate the className on
195 	 * @param {String} className the class to work with
196 	 * @param {String} action to perform the step [ add, remove, has/undefined ]
197 	 */
198 	className : function( elem, className, action ){
199 		if( elem && elem.nodeType && elem.nodeType != ( 3 || 8 ) ){
200 			className = " "+className+" ";
201 			var hasClassName = (" " + elem.className + " ").replace( STRIP_TAB_NEW_LINE, " ").indexOf( className ) > -1;
202 			if( action === "add" ){
203 				if( !hasClassName ){
204 					elem.className = Simples.trim( Simples.trim( elem.className.replace( STRIP_TAB_NEW_LINE, " ") ) + className );
205 				}
206 			} else if( action === "remove" ){
207 				if( hasClassName ){
208 					elem.className = Simples.trim( (' ' + elem.className.replace( STRIP_TAB_NEW_LINE, " ") +' ').replace( className, ' ' ) );
209 				}
210 			} else {
211 				return hasClassName;
212 			}
213 		}
214 	},
215 	/**
216 	 * @description read / write the attribute on an element
217 	 * @param {Element} elem the element to manipulate the attribute
218 	 * @param {String} name the name of the attribute
219 	 * @param {String} value the value to specify, if undefined will read the attribute, if null will remove the attribute, else will add the value as a string
220 	 */
221 	attr : function( elem, name, value ){
222 		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
223 			return UNDEF;
224 		}
225 
226 		if( value === UNDEF ){
227 			if ( elem.nodeName.toUpperCase() === "FORM" && elem.getAttributeNode(name) ) {
228 				// browsers index elements by id/name on forms, give priority to attributes.				
229 				return elem.getAttributeNode( name ).nodeValue;
230 			} else if ( name === "style" && !Simples.support.style ){
231 				// get style correctly
232 				return elem.style.cssText;				
233 			} else if( elem.nodeType === 1 && !SPECIAL_URL.test( name ) && name in elem ){
234 				// These attributes don't require special treatment
235 				return elem[ name ];
236 			} else {
237 				// it must be this
238 				return elem.getAttribute( name );
239 			}
240 			return null;  
241 		} else if( value === null ){
242 			if ( elem.nodeType === 1 ) {
243 				elem.removeAttribute( name );
244 			}
245 		} else { 
246 			if ( ( typeof value == ( "function" || 'object' ) ) || ( name === "type" && IMMUTABLE_ATTR.test( elem.nodeName ) ) ) {
247 				return UNDEF;
248 			}
249 
250 			if( name === "style" && !Simples.support.style ){
251 				// get style correctly
252 				elem.style.cssText = "" + value;
253 			} else if ( elem.nodeType === 1 && !SPECIAL_URL.test( name ) && name in elem ) { 
254 				// These attributes don't require special treatment 
255 				elem[ name ] = ""+value;
256 			} else { 
257 				// it must be this
258 				elem.setAttribute(name, ""+value);
259 			}
260 		}
261 	}
262 });
263 
264 Simples.extend( /** @lends Simples.fn */ {
265 	/**
266 	 * @description to read or write to the dom basd on the elements on the Simples object
267 	 * @param {String} location to specify how to return the dom options are desctructive: [remove, empty, outer, text, inner/undefined ], non-destructive: [top, bottom, unwrap, before, after, wrap ]
268 	 * @param {String|Elements} html the string or Elements to put into the dom, if not specfied where location is [ outer, text, inner/undefined ] will read
269 	 * @returns {Simples|String} if writing to the dom will return this, else will return string of dom
270 	 */
271 	html : function( location, html ){
272 
273 		if ( arguments.length === 0 || ( arguments.length === 1 && SINGLE_ARG_READ.test( location ) ) ) {
274 			return Simples.domRead( this[0], location );
275 		}
276 		location = location != null ? location : "";
277 
278 		var c=0,i=0,l=this.length, results;
279 		while(i<l){
280 			Simples.domManip( this[i++], location, html );
281 		}
282 
283 		return this;
284 	},
285 	/**
286 	 * @description to determine whether any of the elements on the Simples object has the specified className
287 	 * @params {String} className the exact className to test for
288 	 * @returns {Boolean} indicating whether className is on elements of Simples object
289 	 */
290 	hasClass : function( className ){
291 		for ( var i = 0, l = this.length; i < l; i++ ) {
292 			if ( Simples.className( this[i], className ) ) {
293 				return true;
294 			}
295 		}
296 		return false;
297 	},
298 	/**
299 	 * @description to add the specified className to the elements on the Simples object with the specified className
300 	 * @params {String} className the className to add to the elements
301 	 */
302 	addClass : function( className ){
303 		var l = this.length;
304 		while ( l ) {
305 			Simples.className( this[ --l ], className, "add" );
306 		}
307 		return this;
308 	},
309 	/**
310 	 * @description to remove the specified className to the elements on the Simples object with the specified className
311 	 * @params {String} className the className to remove to the elements
312 	 */
313 	removeClass : function( className ){
314 		var l = this.length;
315 		while ( l ) {
316 			Simples.className( this[ --l ], className, "remove" );
317 		}
318 		return this;		
319 	},
320 	/**
321 	 * @description to read / write the given attribute to the elements on the Simples object
322 	 * @param {String} name the name of the attribute
323 	 * @param {String} value the value to specify, if undefined will read the attribute, if null will remove the attribute, else will add the value as a string
324 	 */
325 	attr : function(name, value){
326 		var klass = Simples.getConstructor( name );
327 			
328 		if( klass === "Object" ){
329 			for( var key in name ){
330 				var i=0,l=this.length,val = name[key];
331 				while(i<l){
332 					Simples.attr( this[i++], key, val );
333 				}
334 			}
335 		} else if( klass === "String" ){
336 			if( value === UNDEF ){
337 				return Simples.attr( this[0], name );
338 			} else { 
339 				for(var m=0,n=this.length;m<n;m++){
340 					Simples.attr( this[m], name, value );
341 				}
342 			}
343 		}
344 		return this;
345 	},
346 	/* TODO: Rename me as I don't indicate functionality */
347 	/**
348 	 * @description to select a new set of elements off of the elements in the Simples object
349 	 * @params {String|Function} name the string to specify the traversing, i.e. childNodes, parentNode, etc or a function to walk 
350 	 */
351 	traverse : function( name ){
352 		var isWhat = Simples.getConstructor( name ), results = new Simples(), i=0,l = this.length;
353 		while( i<l ){
354 			var current = this[i++], elem = ( isWhat === "String" ) ? current[ name ] : ( isWhat === "Function" ) ? name.call( current, current ) : null;
355 			if( elem ){
356 				results.push.apply( results, ( elem.item || elem.length ) ? Simples.makeArray( elem ) : [ elem ] );
357 			}
358 		}
359 		
360 		return results;
361 	},
362 	/**
363 	 * @description to return a subset of the selected elements
364 	 * @params {Number} i the first element to start slicing
365 	 * @params {Number} len the last element to finish slicing this is optional if not specified then the slice is to the last element
366 	 */	
367 	slice : function( i, len ){
368 		len = ( 0 < len ) ? len : 1 ;
369 		return Simples( slice.apply( this, i < 0 ? [ i ] : [+i, i+len]  ), true );
370 	}
371 });